Chris@133: /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ Chris@133: Chris@133: /* Chris@133: Sonic Visualiser Chris@133: An audio file viewer and annotation editor. Chris@133: Centre for Digital Music, Queen Mary, University of London. Chris@195: This file copyright 2006-2007 QMUL. Chris@133: Chris@133: This program is free software; you can redistribute it and/or Chris@133: modify it under the terms of the GNU General Public License as Chris@133: published by the Free Software Foundation; either version 2 of the Chris@133: License, or (at your option) any later version. See the file Chris@133: COPYING included with this distribution for more information. Chris@133: */ Chris@133: Chris@193: #include "SliceLayer.h" Chris@133: Chris@133: #include "view/View.h" Chris@153: #include "base/AudioLevel.h" Chris@167: #include "base/RangeMapper.h" Chris@195: #include "base/RealTime.h" Chris@376: #include "ColourMapper.h" Chris@376: #include "ColourDatabase.h" Chris@195: Chris@195: #include "PaintAssistant.h" Chris@133: Chris@1383: #include "base/Profiler.h" Chris@1383: Chris@133: #include Chris@133: #include Chris@316: #include Chris@316: Chris@133: Chris@193: SliceLayer::SliceLayer() : Chris@1400: m_binAlignment(BinsSpanScalePoints), Chris@1281: m_colourMap(int(ColourMapper::Ice)), Chris@1362: m_colourInverted(false), Chris@153: m_energyScale(dBScale), Chris@198: m_samplingMode(SampleMean), Chris@1233: m_plotStyle(PlotLines), Chris@193: m_binScale(LinearBins), Chris@153: m_normalize(false), Chris@284: m_threshold(0.0), Chris@284: m_initialThreshold(0.0), Chris@198: m_gain(1.0), Chris@1238: m_minbin(0), Chris@1238: m_maxbin(0), Chris@198: m_currentf0(0), Chris@198: m_currentf1(0) Chris@133: { Chris@133: } Chris@133: Chris@193: SliceLayer::~SliceLayer() Chris@133: { Chris@193: Chris@133: } Chris@133: Chris@133: void Chris@1472: SliceLayer::setSliceableModel(ModelId modelId) Chris@133: { Chris@1472: auto newModel = ModelById::getAs(modelId); Chris@1472: Chris@1472: if (!modelId.isNone() && !newModel) { Chris@1472: throw std::logic_error("Not a DenseThreeDimensionalModel"); Chris@193: } Chris@193: Chris@1472: if (m_sliceableModel == modelId) return; Chris@1472: m_sliceableModel = modelId; Chris@193: Chris@1472: if (newModel) { Chris@1472: connectSignals(m_sliceableModel); Chris@193: Chris@1472: if (m_minbin == 0 && m_maxbin == 0) { Chris@1472: m_minbin = 0; Chris@1472: m_maxbin = newModel->getHeight(); Chris@1472: } Chris@1389: } Chris@1238: Chris@193: emit modelReplaced(); Chris@1238: emit layerParametersChanged(); Chris@153: } Chris@153: Chris@153: void Chris@1472: SliceLayer::sliceableModelReplaced(ModelId orig, ModelId replacement) Chris@153: { Chris@587: SVDEBUG << "SliceLayer::sliceableModelReplaced(" << orig << ", " << replacement << ")" << endl; Chris@153: Chris@193: if (orig == m_sliceableModel) { Chris@1472: setSliceableModel(replacement); Chris@153: } Chris@153: } Chris@153: Chris@198: QString Chris@918: SliceLayer::getFeatureDescription(LayerGeometryProvider *v, QPoint &p) const Chris@198: { Chris@199: int minbin, maxbin, range; Chris@805: return getFeatureDescriptionAux(v, p, true, minbin, maxbin, range); Chris@199: } Chris@199: Chris@199: QString Chris@918: SliceLayer::getFeatureDescriptionAux(LayerGeometryProvider *v, QPoint &p, Chris@805: bool includeBinDescription, Chris@805: int &minbin, int &maxbin, int &range) const Chris@199: { Chris@199: minbin = 0; Chris@199: maxbin = 0; Chris@1472: Chris@1472: auto sliceableModel = Chris@1472: ModelById::getAs(m_sliceableModel); Chris@1472: if (!sliceableModel) return ""; Chris@198: Chris@1400: if (m_binAlignment == BinsSpanScalePoints) { Chris@1400: minbin = int(getBinForX(v, p.x())); Chris@1400: maxbin = int(getBinForX(v, p.x() + 1)); Chris@1400: } else { Chris@1400: minbin = int(getBinForX(v, p.x()) + 0.5); Chris@1400: maxbin = int(getBinForX(v, p.x() + 1) + 0.5); Chris@1400: } Chris@1238: Chris@1472: int mh = sliceableModel->getHeight(); Chris@199: if (minbin >= mh) minbin = mh - 1; Chris@199: if (maxbin >= mh) maxbin = mh - 1; Chris@199: if (minbin < 0) minbin = 0; Chris@199: if (maxbin < 0) maxbin = 0; Chris@198: Chris@1472: sv_samplerate_t sampleRate = sliceableModel->getSampleRate(); Chris@198: Chris@906: sv_frame_t f0 = m_currentf0; Chris@906: sv_frame_t f1 = m_currentf1; Chris@198: Chris@198: RealTime rt0 = RealTime::frame2RealTime(f0, sampleRate); Chris@198: RealTime rt1 = RealTime::frame2RealTime(f1, sampleRate); Chris@198: Chris@906: range = int(f1 - f0 + 1); Chris@198: Chris@280: QString rtrangestr = QString("%1 s").arg((rt1 - rt0).toText().c_str()); Chris@280: Chris@199: if (includeBinDescription) { Chris@198: Chris@1238: int i0 = minbin - m_minbin; Chris@1238: int i1 = maxbin - m_minbin; Chris@1238: Chris@906: float minvalue = 0.0; Chris@1238: if (in_range_for(m_values, i0)) minvalue = m_values[i0]; Chris@198: Chris@199: float maxvalue = minvalue; Chris@1238: if (in_range_for(m_values, i1)) maxvalue = m_values[i1]; Chris@1238: Chris@199: if (minvalue > maxvalue) std::swap(minvalue, maxvalue); Chris@199: Chris@199: QString binstr; Chris@199: if (maxbin != minbin) { Chris@199: binstr = tr("%1 - %2").arg(minbin+1).arg(maxbin+1); Chris@199: } else { Chris@199: binstr = QString("%1").arg(minbin+1); Chris@199: } Chris@199: Chris@199: QString valuestr; Chris@199: if (maxvalue != minvalue) { Chris@199: valuestr = tr("%1 - %2").arg(minvalue).arg(maxvalue); Chris@199: } else { Chris@199: valuestr = QString("%1").arg(minvalue); Chris@199: } Chris@199: Chris@280: QString description = tr("Time:\t%1 - %2\nRange:\t%3 samples (%4)\nBin:\t%5\n%6 value:\t%7") Chris@199: .arg(QString::fromStdString(rt0.toText(true))) Chris@199: .arg(QString::fromStdString(rt1.toText(true))) Chris@199: .arg(range) Chris@280: .arg(rtrangestr) Chris@199: .arg(binstr) Chris@199: .arg(m_samplingMode == NearestSample ? tr("First") : Chris@199: m_samplingMode == SampleMean ? tr("Mean") : tr("Peak")) Chris@199: .arg(valuestr); Chris@199: Chris@199: return description; Chris@199: Chris@199: } else { Chris@199: Chris@280: QString description = tr("Time:\t%1 - %2\nRange:\t%3 samples (%4)") Chris@199: .arg(QString::fromStdString(rt0.toText(true))) Chris@199: .arg(QString::fromStdString(rt1.toText(true))) Chris@280: .arg(range) Chris@280: .arg(rtrangestr); Chris@199: Chris@199: return description; Chris@199: } Chris@198: } Chris@198: Chris@906: double Chris@1238: SliceLayer::getXForBin(const LayerGeometryProvider *v, double bin) const Chris@198: { Chris@1386: return getXForScalePoint(v, bin, m_minbin, m_maxbin); Chris@1386: } Chris@1386: Chris@1386: double Chris@1386: SliceLayer::getXForScalePoint(const LayerGeometryProvider *v, Chris@1386: double p, double pmin, double pmax) const Chris@1386: { Chris@906: double x = 0; Chris@198: Chris@1238: int pw = v->getPaintWidth(); Chris@1238: int origin = m_xorigins[v->getId()]; Chris@1238: int w = pw - origin; Chris@1238: if (w < 1) w = 1; Chris@1238: Chris@1399: if (pmax <= pmin) { Chris@1399: pmax = pmin + 1.0; Chris@1399: } Chris@1399: Chris@1394: if (p < pmin) p = pmin; Chris@1394: if (p > pmax) p = pmax; Chris@1399: Chris@1394: if (m_binScale == LinearBins) { Chris@1394: x = (w * (p - pmin)) / (pmax - pmin); Chris@1394: } else { Chris@198: Chris@1394: if (m_binScale == InvertedLogBins) { Chris@1394: // stoopid Chris@1394: p = pmax - p; Chris@1394: } Chris@198: Chris@1281: // The 0.8 here is an awkward compromise. Our x-coord is Chris@1281: // proportional to log of bin number, with the x-coord "of a Chris@1281: // bin" being that of the left edge of the bin range. We can't Chris@1281: // start counting bins from 0, as that would give us x = -Inf Chris@1281: // and hide the first bin entirely. But if we start from 1, we Chris@1281: // are giving a lot of space to the first bin, which in most Chris@1281: // display modes won't be used because the "point" location Chris@1281: // for that bin is in the middle of it. Yet in some modes Chris@1281: // we'll still want it. A compromise is to count our first bin Chris@1281: // as "a bit less than 1", so that most of it is visible but a Chris@1281: // bit is tactfully cropped at the left edge so it doesn't Chris@1281: // take up so much space. Chris@1394: const double origin = 0.8; Chris@198: Chris@1394: // sometimes we are called with a pmin/pmax range that begins Chris@1394: // before 0: in that situation, we shift everything along by Chris@1394: // the difference between 0 and pmin before doing any other Chris@1394: // calculations Chris@1394: double reqdshift = 0.0; Chris@1394: if (pmin < 0) reqdshift = -pmin; Chris@1394: Chris@1394: double pminlog = log10(pmin + reqdshift + origin); Chris@1394: double pmaxlog = log10(pmax + reqdshift + origin); Chris@1394: double plog = log10(p + reqdshift + origin); Chris@1394: x = (w * (plog - pminlog)) / (pmaxlog - pminlog); Chris@1399: /* Chris@1394: cerr << "getXForScalePoint(" << p << "): pmin = " << pmin Chris@1394: << ", pmax = " << pmax << ", w = " << w Chris@1394: << ", reqdshift = " << reqdshift Chris@1394: << ", pminlog = " << pminlog << ", pmaxlog = " << pmaxlog Chris@1394: << ", plog = " << plog Chris@1394: << " -> x = " << x << endl; Chris@1399: */ Chris@1394: if (m_binScale == InvertedLogBins) { Chris@1394: // still stoopid Chris@1394: x = w - x; Chris@1394: } Chris@198: } Chris@1238: Chris@1238: return x + origin; Chris@198: } Chris@198: Chris@1238: double Chris@1238: SliceLayer::getBinForX(const LayerGeometryProvider *v, double x) const Chris@198: { Chris@1386: return getScalePointForX(v, x, m_minbin, m_maxbin); Chris@1386: } Chris@198: Chris@1386: double Chris@1386: SliceLayer::getScalePointForX(const LayerGeometryProvider *v, Chris@1386: double x, double pmin, double pmax) const Chris@1386: { Chris@1386: double p = 0; Chris@1386: Chris@1238: int pw = v->getPaintWidth(); Chris@1238: int origin = m_xorigins[v->getId()]; Chris@1238: Chris@1238: int w = pw - origin; Chris@1238: if (w < 1) w = 1; Chris@1238: Chris@1238: x = x - origin; Chris@1238: if (x < 0) x = 0; Chris@1256: Chris@1256: double eps = 1e-10; Chris@198: Chris@1399: if (pmax <= pmin) { Chris@1399: pmax = pmin + 1.0; Chris@1399: } Chris@1399: Chris@1394: if (m_binScale == LinearBins) { Chris@1394: p = pmin + eps + (x * (pmax - pmin)) / w; Chris@1394: } else { Chris@198: Chris@1394: if (m_binScale == InvertedLogBins) { Chris@1394: x = w - x; Chris@1394: } Chris@1394: Chris@1394: // See comments in getXForScalePoint Chris@1394: Chris@1394: const double origin = 0.8; Chris@1394: double reqdshift = 0.0; Chris@1394: if (pmin < 0) reqdshift = -pmin; Chris@1394: Chris@1394: double pminlog = log10(pmin + reqdshift + origin); Chris@1394: double pmaxlog = log10(pmax + reqdshift + origin); Chris@1394: Chris@1394: double plog = pminlog + eps + (x * (pmaxlog - pminlog)) / w; Chris@1394: p = pow(10.0, plog) - reqdshift - origin; Chris@1394: Chris@1394: if (m_binScale == InvertedLogBins) { Chris@1394: p = pmax - p; Chris@1394: } Chris@198: } Chris@198: Chris@1394: return p; Chris@198: } Chris@198: Chris@906: double Chris@1238: SliceLayer::getYForValue(const LayerGeometryProvider *v, double value, double &norm) const Chris@274: { Chris@906: norm = 0.0; Chris@274: Chris@1238: if (m_yorigins.find(v->getId()) == m_yorigins.end()) return 0; Chris@274: Chris@274: value *= m_gain; Chris@274: Chris@1238: int yorigin = m_yorigins[v->getId()]; Chris@1238: int h = m_heights[v->getId()]; Chris@906: double thresh = getThresholdDb(); Chris@274: Chris@906: double y = 0.0; Chris@274: Chris@274: if (h <= 0) return y; Chris@274: Chris@274: switch (m_energyScale) { Chris@274: Chris@274: case dBScale: Chris@274: { Chris@906: double db = thresh; Chris@906: if (value > 0.0) db = 10.0 * log10(fabs(value)); Chris@274: if (db < thresh) db = thresh; Chris@274: norm = (db - thresh) / -thresh; Chris@906: y = yorigin - (double(h) * norm); Chris@274: break; Chris@274: } Chris@274: Chris@274: case MeterScale: Chris@274: y = AudioLevel::multiplier_to_preview(value, h); Chris@906: norm = double(y) / double(h); Chris@274: y = yorigin - y; Chris@274: break; Chris@274: Chris@538: case AbsoluteScale: Chris@906: value = fabs(value); Chris@1264: #if (__GNUC__ >= 7) Chris@1263: __attribute__ ((fallthrough)); Chris@1263: #endif Chris@538: Chris@805: case LinearScale: Chris@274: default: Chris@538: norm = (value - m_threshold); Chris@284: if (norm < 0) norm = 0; Chris@906: y = yorigin - (double(h) * norm); Chris@274: break; Chris@274: } Chris@274: Chris@274: return y; Chris@274: } Chris@274: Chris@906: double Chris@1238: SliceLayer::getValueForY(const LayerGeometryProvider *v, double y) const Chris@274: { Chris@906: double value = 0.0; Chris@274: Chris@1238: if (m_yorigins.find(v->getId()) == m_yorigins.end()) return value; Chris@274: Chris@1238: int yorigin = m_yorigins[v->getId()]; Chris@1238: int h = m_heights[v->getId()]; Chris@906: double thresh = getThresholdDb(); Chris@274: Chris@274: if (h <= 0) return value; Chris@274: Chris@274: y = yorigin - y; Chris@274: Chris@274: switch (m_energyScale) { Chris@274: Chris@274: case dBScale: Chris@274: { Chris@906: double db = ((y / h) * -thresh) + thresh; Chris@906: value = pow(10.0, db/10.0); Chris@274: break; Chris@274: } Chris@274: Chris@274: case MeterScale: Chris@906: value = AudioLevel::preview_to_multiplier(int(lrint(y)), h); Chris@274: break; Chris@805: Chris@805: case LinearScale: Chris@805: case AbsoluteScale: Chris@274: default: Chris@284: value = y / h + m_threshold; Chris@274: } Chris@274: Chris@274: return value / m_gain; Chris@274: } Chris@274: Chris@133: void Chris@916: SliceLayer::paint(LayerGeometryProvider *v, QPainter &paint, QRect rect) const Chris@133: { Chris@1472: auto sliceableModel = Chris@1472: ModelById::getAs(m_sliceableModel); Chris@1472: if (!sliceableModel || Chris@1472: !sliceableModel->isOK() || Chris@1472: !sliceableModel->isReady()) return; Chris@133: Chris@1383: Profiler profiler("SliceLayer::paint()"); Chris@1383: Chris@195: paint.save(); Chris@1383: paint.setRenderHint(QPainter::Antialiasing, true); Chris@538: paint.setBrush(Qt::NoBrush); Chris@195: Chris@195: if (v->getViewManager() && v->getViewManager()->shouldShowScaleGuides()) { Chris@195: if (!m_scalePoints.empty()) { Chris@195: paint.setPen(QColor(240, 240, 240)); //!!! and dark background? Chris@1284: int ratio = int(round(double(v->getPaintHeight()) / Chris@1284: m_scalePaintHeight)); Chris@805: for (int i = 0; i < (int)m_scalePoints.size(); ++i) { Chris@1284: paint.drawLine(0, m_scalePoints[i] * ratio, Chris@1284: rect.width(), m_scalePoints[i] * ratio); Chris@195: } Chris@195: } Chris@195: } Chris@195: Chris@1472: int mh = sliceableModel->getHeight(); Chris@1389: int bin0 = 0; Chris@1389: if (m_maxbin > m_minbin) { Chris@1389: mh = m_maxbin - m_minbin; Chris@1389: bin0 = m_minbin; Chris@1389: } Chris@1387: Chris@1233: if (m_plotStyle == PlotBlocks) { Chris@1233: // Must use actual zero-width pen, too slow otherwise Chris@1233: paint.setPen(QPen(getBaseQColor(), 0)); Chris@1233: } else { Chris@1389: // Similarly, if there are very many bins here, we use a Chris@1389: // thinner pen Chris@1400: QPen pen; Chris@1389: if (mh < 10000) { Chris@1401: pen = v->scalePen(QPen(getBaseQColor(), 0.8)); Chris@1400: } else { Chris@1400: pen = QPen(getBaseQColor(), 1); Chris@1387: } Chris@1389: paint.setPen(pen); Chris@1233: } Chris@195: Chris@607: int xorigin = getVerticalScaleWidth(v, true, paint) + 1; Chris@1238: m_xorigins[v->getId()] = xorigin; // for use in getFeatureDescription Chris@133: Chris@1383: int yorigin = v->getPaintHeight() - getHorizontalScaleHeight(v, paint) - Chris@1383: paint.fontMetrics().height(); Chris@195: int h = yorigin - paint.fontMetrics().height() - 8; Chris@133: Chris@1238: m_yorigins[v->getId()] = yorigin; // for getYForValue etc Chris@1238: m_heights[v->getId()] = h; Chris@274: Chris@274: if (h <= 0) return; Chris@274: Chris@133: QPainterPath path; Chris@1238: Chris@193: int divisor = 0; Chris@193: Chris@198: m_values.clear(); Chris@805: for (int bin = 0; bin < mh; ++bin) { Chris@906: m_values.push_back(0.0); Chris@193: } Chris@193: Chris@906: sv_frame_t f0 = v->getCentreFrame(); Chris@193: int f0x = v->getXForFrame(f0); Chris@195: f0 = v->getFrameForX(f0x); Chris@906: sv_frame_t f1 = v->getFrameForX(f0x + 1); Chris@195: if (f1 > f0) --f1; Chris@193: Chris@682: // cerr << "centre frame " << v->getCentreFrame() << ", x " << f0x << ", f0 " << f0 << ", f1 " << f1 << endl; Chris@274: Chris@1472: int res = sliceableModel->getResolution(); Chris@906: int col0 = int(f0 / res); Chris@805: int col1 = col0; Chris@906: if (m_samplingMode != NearestSample) col1 = int(f1 / res); Chris@274: f0 = col0 * res; Chris@274: f1 = (col1 + 1) * res - 1; Chris@274: Chris@682: // cerr << "resolution " << res << ", col0 " << col0 << ", col1 " << col1 << ", f0 " << f0 << ", f1 " << f1 << endl; Chris@1383: // cerr << "mh = " << mh << endl; Chris@193: Chris@198: m_currentf0 = f0; Chris@198: m_currentf1 = f1; Chris@198: Chris@254: BiasCurve curve; Chris@254: getBiasCurve(curve); Chris@906: int cs = int(curve.size()); Chris@254: Chris@805: for (int col = col0; col <= col1; ++col) { Chris@1383: DenseThreeDimensionalModel::Column column = Chris@1472: sliceableModel->getColumn(col); Chris@805: for (int bin = 0; bin < mh; ++bin) { Chris@1383: float value = column[bin0 + bin]; Chris@254: if (bin < cs) value *= curve[bin]; Chris@193: if (m_samplingMode == SamplePeak) { Chris@198: if (value > m_values[bin]) m_values[bin] = value; Chris@193: } else { Chris@198: m_values[bin] += value; Chris@193: } Chris@153: } Chris@193: ++divisor; Chris@193: } Chris@193: Chris@906: float max = 0.0; Chris@805: for (int bin = 0; bin < mh; ++bin) { Chris@847: if (m_samplingMode == SampleMean && divisor > 0) { Chris@906: m_values[bin] /= float(divisor); Chris@847: } Chris@198: if (m_values[bin] > max) max = m_values[bin]; Chris@193: } Chris@906: if (max != 0.0 && m_normalize) { Chris@805: for (int bin = 0; bin < mh; ++bin) { Chris@198: m_values[bin] /= max; Chris@193: } Chris@193: } Chris@193: Chris@1362: ColourMapper mapper(m_colourMap, m_colourInverted, 0, 1); Chris@197: Chris@1383: double ytop = 0, ybottom = 0; Chris@1383: bool firstBinOfPixel = true; Chris@1384: Chris@1384: QColor prevColour = v->getBackground(); Chris@1384: double prevYtop = 0; Chris@1383: Chris@1400: double xleft = -1, xmiddle = -1, xright = -1; Chris@1400: double prevXmiddle = 0; Chris@1400: Chris@805: for (int bin = 0; bin < mh; ++bin) { Chris@193: Chris@1400: if (m_binAlignment == BinsSpanScalePoints) { Chris@1400: if (xright >= 0) xleft = xright; // previous value of Chris@1400: else xleft = getXForBin(v, bin0 + bin); Chris@1400: xmiddle = getXForBin(v, bin0 + bin + 0.5); Chris@1400: xright = getXForBin(v, bin0 + bin + 1); Chris@1400: } else { Chris@1400: if (xright >= 0) xleft = xright; // previous value of Chris@1400: else xleft = getXForBin(v, bin0 + bin - 0.5); Chris@1400: xmiddle = getXForBin(v, bin0 + bin); Chris@1400: xright = getXForBin(v, bin0 + bin + 0.5); Chris@1400: } Chris@193: Chris@906: double value = m_values[bin]; Chris@906: double norm = 0.0; Chris@1238: double y = getYForValue(v, value, norm); Chris@133: Chris@1383: if (y < ytop || firstBinOfPixel) { Chris@1383: ytop = y; Chris@1383: } Chris@1383: if (y > ybottom || firstBinOfPixel) { Chris@1383: ybottom = y; Chris@1383: } Chris@193: Chris@1400: if (int(xright) != int(xleft) || bin+1 == mh) { Chris@1383: Chris@1383: if (m_plotStyle == PlotLines) { Chris@1383: Chris@1383: if (bin == 0) { Chris@1400: path.moveTo(xmiddle, y); Chris@1383: } else { Chris@1383: if (ytop != ybottom) { Chris@1400: path.lineTo(xmiddle, ybottom); Chris@1400: path.lineTo(xmiddle, ytop); Chris@1400: path.moveTo(xmiddle, ybottom); Chris@1383: } else { Chris@1400: path.lineTo(xmiddle, ytop); Chris@1383: } Chris@1383: } Chris@1383: Chris@1383: } else if (m_plotStyle == PlotSteps) { Chris@1383: Chris@1383: if (bin == 0) { Chris@1400: path.moveTo(xleft, y); Chris@1383: } else { Chris@1400: path.lineTo(xleft, ytop); Chris@1383: } Chris@1400: path.lineTo(xright, ytop); Chris@1383: Chris@1383: } else if (m_plotStyle == PlotBlocks) { Chris@1383: Chris@1384: // work in pixel coords here, as we don't want the Chris@1384: // vertical edges to be antialiased Chris@1384: Chris@1400: path.moveTo(QPoint(int(xleft), int(yorigin))); Chris@1400: path.lineTo(QPoint(int(xleft), int(ytop))); Chris@1400: path.lineTo(QPoint(int(xright), int(ytop))); Chris@1400: path.lineTo(QPoint(int(xright), int(yorigin))); Chris@1400: path.lineTo(QPoint(int(xleft), int(yorigin))); Chris@1383: Chris@1383: } else if (m_plotStyle == PlotFilledBlocks) { Chris@1383: Chris@1384: QColor c = mapper.map(norm); Chris@1384: paint.setPen(Qt::NoPen); Chris@1384: Chris@1384: // work in pixel coords here, as we don't want the Chris@1384: // vertical edges to be antialiased Chris@1384: Chris@1400: if (xright > xleft + 1) { Chris@1384: Chris@1384: QVector pp; Chris@1384: Chris@1384: if (bin > 0) { Chris@1384: paint.setBrush(prevColour); Chris@1384: pp.clear(); Chris@1400: pp << QPoint(int(prevXmiddle), int(yorigin)); Chris@1400: pp << QPoint(int(prevXmiddle), int(prevYtop)); Chris@1400: pp << QPoint(int((xmiddle + prevXmiddle) / 2), Chris@1384: int((ytop + prevYtop) / 2)); Chris@1400: pp << QPoint(int((xmiddle + prevXmiddle) / 2), Chris@1384: int(yorigin)); Chris@1384: paint.drawConvexPolygon(QPolygon(pp)); Chris@1384: Chris@1384: paint.setBrush(c); Chris@1384: pp.clear(); Chris@1400: pp << QPoint(int((xmiddle + prevXmiddle) / 2), Chris@1384: int(yorigin)); Chris@1400: pp << QPoint(int((xmiddle + prevXmiddle) / 2), Chris@1384: int((ytop + prevYtop) / 2)); Chris@1400: pp << QPoint(int(xmiddle), int(ytop)); Chris@1400: pp << QPoint(int(xmiddle), int(yorigin)); Chris@1384: paint.drawConvexPolygon(QPolygon(pp)); Chris@1384: } Chris@1384: Chris@1384: prevColour = c; Chris@1384: prevYtop = ytop; Chris@1384: Chris@1384: } else { Chris@1384: Chris@1400: paint.fillRect(QRect(int(xleft), int(ytop), Chris@1400: int(xright) - int(xleft), Chris@1384: int(yorigin) - int(ytop)), Chris@1384: c); Chris@1384: } Chris@1400: Chris@1400: prevXmiddle = xmiddle; Chris@193: } Chris@193: Chris@1383: firstBinOfPixel = true; Chris@193: Chris@1383: } else { Chris@1383: firstBinOfPixel = false; Chris@133: } Chris@133: } Chris@133: Chris@197: if (m_plotStyle != PlotFilledBlocks) { Chris@197: paint.drawPath(path); Chris@197: } Chris@133: paint.restore(); Chris@195: } Chris@195: Chris@195: int Chris@918: SliceLayer::getVerticalScaleWidth(LayerGeometryProvider *, bool, QPainter &paint) const Chris@195: { Chris@1472: // Qt 5.13 deprecates QFontMetrics::width(), but its suggested Chris@1472: // replacement (horizontalAdvance) was only added in Qt 5.11 Chris@1472: // which is too new for us Chris@1472: #pragma GCC diagnostic ignored "-Wdeprecated-declarations" Chris@1472: Chris@1238: int width; Chris@538: if (m_energyScale == LinearScale || m_energyScale == AbsoluteScale) { Chris@1266: width = std::max(paint.fontMetrics().width("0.0") + 13, Chris@1238: paint.fontMetrics().width("x10-10")); Chris@195: } else { Chris@1238: width = std::max(paint.fontMetrics().width(tr("0dB")), Chris@1238: paint.fontMetrics().width(tr("-Inf"))) + 13; Chris@195: } Chris@1238: return width; Chris@195: } Chris@195: Chris@195: void Chris@918: SliceLayer::paintVerticalScale(LayerGeometryProvider *v, bool, QPainter &paint, QRect rect) const Chris@195: { Chris@906: double thresh = m_threshold; Chris@538: if (m_energyScale != LinearScale && m_energyScale != AbsoluteScale) { Chris@284: thresh = AudioLevel::dB_to_multiplier(getThresholdDb()); Chris@195: } Chris@195: Chris@195: // int h = (rect.height() * 3) / 4; Chris@195: // int y = (rect.height() / 2) - (h / 2); Chris@195: Chris@1383: int yorigin = v->getPaintHeight() - getHorizontalScaleHeight(v, paint) - Chris@1383: paint.fontMetrics().height(); Chris@195: int h = yorigin - paint.fontMetrics().height() - 8; Chris@195: if (h < 0) return; Chris@195: Chris@195: QRect actual(rect.x(), rect.y() + yorigin - h, rect.width(), h); Chris@195: Chris@220: int mult = 1; Chris@220: Chris@195: PaintAssistant::paintVerticalLevelScale Chris@195: (paint, actual, thresh, 1.0 / m_gain, Chris@195: PaintAssistant::Scale(m_energyScale), Chris@220: mult, Chris@195: const_cast *>(&m_scalePoints)); Chris@220: Chris@1284: // Ugly hack (but then everything about this scale drawing is a Chris@1284: // bit ugly). In pixel-doubling hi-dpi scenarios, the scale is Chris@1284: // painted at pixel-doubled resolution but we do explicit Chris@1284: // pixel-doubling ourselves when painting the layer content. We Chris@1284: // make a note of this here so that we can compare with the Chris@1284: // equivalent dimension in the paint method when deciding where to Chris@1284: // place scale continuation lines. Chris@1284: m_scalePaintHeight = v->getPaintHeight(); Chris@1284: Chris@220: if (mult != 1 && mult != 0) { Chris@906: int log = int(lrint(log10(mult))); Chris@220: QString a = tr("x10"); Chris@220: QString b = QString("%1").arg(-log); Chris@220: paint.drawText(3, 8 + paint.fontMetrics().ascent(), a); Chris@220: paint.drawText(3 + paint.fontMetrics().width(a), Chris@220: 3 + paint.fontMetrics().ascent(), b); Chris@220: } Chris@133: } Chris@133: Chris@1281: bool Chris@1281: SliceLayer::hasLightBackground() const Chris@1281: { Chris@1281: if (usesSolidColour()) { Chris@1362: ColourMapper mapper(m_colourMap, m_colourInverted, 0, 1); Chris@1281: return mapper.hasLightBackground(); Chris@1281: } else { Chris@1281: return SingleColourLayer::hasLightBackground(); Chris@1281: } Chris@1281: } Chris@1281: Chris@153: Layer::PropertyList Chris@193: SliceLayer::getProperties() const Chris@153: { Chris@287: PropertyList list = SingleColourLayer::getProperties(); Chris@538: list.push_back("Bin Scale"); Chris@193: list.push_back("Plot Type"); Chris@153: list.push_back("Scale"); Chris@153: list.push_back("Normalize"); Chris@284: list.push_back("Threshold"); Chris@153: list.push_back("Gain"); Chris@153: Chris@153: return list; Chris@153: } Chris@153: Chris@153: QString Chris@193: SliceLayer::getPropertyLabel(const PropertyName &name) const Chris@153: { Chris@193: if (name == "Plot Type") return tr("Plot Type"); Chris@290: if (name == "Scale") return tr("Scale"); Chris@153: if (name == "Normalize") return tr("Normalize"); Chris@284: if (name == "Threshold") return tr("Threshold"); Chris@153: if (name == "Gain") return tr("Gain"); Chris@193: if (name == "Sampling Mode") return tr("Sampling Mode"); Chris@538: if (name == "Bin Scale") return tr("Bin Scale"); Chris@287: return SingleColourLayer::getPropertyLabel(name); Chris@153: } Chris@153: Chris@335: QString Chris@335: SliceLayer::getPropertyIconName(const PropertyName &name) const Chris@335: { Chris@335: if (name == "Normalize") return "normalise"; Chris@335: return ""; Chris@335: } Chris@335: Chris@153: Layer::PropertyType Chris@193: SliceLayer::getPropertyType(const PropertyName &name) const Chris@153: { Chris@153: if (name == "Gain") return RangeProperty; Chris@153: if (name == "Normalize") return ToggleProperty; Chris@284: if (name == "Threshold") return RangeProperty; Chris@287: if (name == "Plot Type") return ValueProperty; Chris@290: if (name == "Scale") return ValueProperty; Chris@287: if (name == "Sampling Mode") return ValueProperty; Chris@287: if (name == "Bin Scale") return ValueProperty; Chris@1281: if (name == "Colour" && usesSolidColour()) return ColourMapProperty; Chris@287: return SingleColourLayer::getPropertyType(name); Chris@153: } Chris@153: Chris@153: QString Chris@193: SliceLayer::getPropertyGroupName(const PropertyName &name) const Chris@153: { Chris@153: if (name == "Scale" || Chris@153: name == "Normalize" || Chris@193: name == "Sampling Mode" || Chris@284: name == "Threshold" || Chris@193: name == "Gain") return tr("Scale"); Chris@193: if (name == "Plot Type" || Chris@538: name == "Bin Scale") return tr("Bins"); Chris@287: return SingleColourLayer::getPropertyGroupName(name); Chris@153: } Chris@153: Chris@153: int Chris@193: SliceLayer::getPropertyRangeAndValue(const PropertyName &name, Chris@216: int *min, int *max, int *deflt) const Chris@153: { Chris@216: int val = 0; Chris@153: Chris@216: int garbage0, garbage1, garbage2; Chris@153: if (!min) min = &garbage0; Chris@153: if (!max) max = &garbage1; Chris@248: if (!deflt) deflt = &garbage2; Chris@153: Chris@153: if (name == "Gain") { Chris@153: Chris@1266: *min = -50; Chris@1266: *max = 50; Chris@216: *deflt = 0; Chris@153: Chris@1238: // cerr << "gain is " << m_gain << ", mode is " << m_samplingMode << endl; Chris@193: Chris@1266: val = int(lrint(log10(m_gain) * 20.0)); Chris@1266: if (val < *min) val = *min; Chris@1266: if (val > *max) val = *max; Chris@153: Chris@284: } else if (name == "Threshold") { Chris@284: Chris@1266: *min = -80; Chris@1266: *max = 0; Chris@284: Chris@906: *deflt = int(lrint(AudioLevel::multiplier_to_dB(m_initialThreshold))); Chris@1266: if (*deflt < *min) *deflt = *min; Chris@1266: if (*deflt > *max) *deflt = *max; Chris@284: Chris@1266: val = int(lrint(AudioLevel::multiplier_to_dB(m_threshold))); Chris@1266: if (val < *min) val = *min; Chris@1266: if (val > *max) val = *max; Chris@284: Chris@153: } else if (name == "Normalize") { Chris@1266: Chris@1266: val = (m_normalize ? 1 : 0); Chris@216: *deflt = 0; Chris@153: Chris@1281: } else if (name == "Colour" && usesSolidColour()) { Chris@197: Chris@287: *min = 0; Chris@287: *max = ColourMapper::getColourMapCount() - 1; Chris@1281: *deflt = int(ColourMapper::Ice); Chris@287: Chris@287: val = m_colourMap; Chris@153: Chris@153: } else if (name == "Scale") { Chris@153: Chris@1266: *min = 0; Chris@1266: *max = 3; Chris@216: *deflt = (int)dBScale; Chris@153: Chris@1266: val = (int)m_energyScale; Chris@153: Chris@193: } else if (name == "Sampling Mode") { Chris@153: Chris@1266: *min = 0; Chris@1266: *max = 2; Chris@216: *deflt = (int)SampleMean; Chris@193: Chris@1266: val = (int)m_samplingMode; Chris@153: Chris@193: } else if (name == "Plot Type") { Chris@193: Chris@193: *min = 0; Chris@197: *max = 3; Chris@216: *deflt = (int)PlotSteps; Chris@193: Chris@216: val = (int)m_plotStyle; Chris@193: Chris@193: } else if (name == "Bin Scale") { Chris@193: Chris@193: *min = 0; Chris@198: *max = 2; Chris@216: *deflt = (int)LinearBins; Chris@198: // *max = 1; // I don't think we really do want to offer inverted log Chris@193: Chris@216: val = (int)m_binScale; Chris@193: Chris@153: } else { Chris@1266: val = SingleColourLayer::getPropertyRangeAndValue(name, min, max, deflt); Chris@153: } Chris@153: Chris@216: return val; Chris@153: } Chris@153: Chris@153: QString Chris@193: SliceLayer::getPropertyValueLabel(const PropertyName &name, Chris@1281: int value) const Chris@153: { Chris@1281: if (name == "Colour" && usesSolidColour()) { Chris@1362: return ColourMapper::getColourMapLabel(value); Chris@153: } Chris@153: if (name == "Scale") { Chris@1266: switch (value) { Chris@1266: default: Chris@1266: case 0: return tr("Linear"); Chris@1266: case 1: return tr("Meter"); Chris@1266: case 2: return tr("Log"); Chris@1266: case 3: return tr("Absolute"); Chris@1266: } Chris@153: } Chris@193: if (name == "Sampling Mode") { Chris@1266: switch (value) { Chris@1266: default: Chris@1266: case 0: return tr("Any"); Chris@1266: case 1: return tr("Mean"); Chris@1266: case 2: return tr("Peak"); Chris@1266: } Chris@193: } Chris@193: if (name == "Plot Type") { Chris@1266: switch (value) { Chris@1266: default: Chris@1266: case 0: return tr("Lines"); Chris@1266: case 1: return tr("Steps"); Chris@1266: case 2: return tr("Blocks"); Chris@1266: case 3: return tr("Colours"); Chris@1266: } Chris@193: } Chris@193: if (name == "Bin Scale") { Chris@1266: switch (value) { Chris@1266: default: Chris@1266: case 0: return tr("Linear"); Chris@1266: case 1: return tr("Log"); Chris@1266: case 2: return tr("Rev Log"); Chris@1266: } Chris@153: } Chris@287: return SingleColourLayer::getPropertyValueLabel(name, value); Chris@153: } Chris@153: Chris@167: RangeMapper * Chris@193: SliceLayer::getNewPropertyRangeMapper(const PropertyName &name) const Chris@167: { Chris@167: if (name == "Gain") { Chris@167: return new LinearRangeMapper(-50, 50, -25, 25, tr("dB")); Chris@167: } Chris@284: if (name == "Threshold") { Chris@284: return new LinearRangeMapper(-80, 0, -80, 0, tr("dB")); Chris@284: } Chris@287: return SingleColourLayer::getNewPropertyRangeMapper(name); Chris@167: } Chris@167: Chris@133: void Chris@193: SliceLayer::setProperty(const PropertyName &name, int value) Chris@133: { Chris@153: if (name == "Gain") { Chris@1266: setGain(powf(10, float(value)/20.0f)); Chris@284: } else if (name == "Threshold") { Chris@1266: if (value == -80) setThreshold(0.0f); Chris@1266: else setThreshold(float(AudioLevel::dB_to_multiplier(value))); Chris@1281: } else if (name == "Colour" && usesSolidColour()) { Chris@287: setFillColourMap(value); Chris@153: } else if (name == "Scale") { Chris@1266: switch (value) { Chris@1266: default: Chris@1266: case 0: setEnergyScale(LinearScale); break; Chris@1266: case 1: setEnergyScale(MeterScale); break; Chris@1266: case 2: setEnergyScale(dBScale); break; Chris@1266: case 3: setEnergyScale(AbsoluteScale); break; Chris@1266: } Chris@193: } else if (name == "Plot Type") { Chris@1266: setPlotStyle(PlotStyle(value)); Chris@193: } else if (name == "Sampling Mode") { Chris@1266: switch (value) { Chris@1266: default: Chris@1266: case 0: setSamplingMode(NearestSample); break; Chris@1266: case 1: setSamplingMode(SampleMean); break; Chris@1266: case 2: setSamplingMode(SamplePeak); break; Chris@1266: } Chris@193: } else if (name == "Bin Scale") { Chris@1266: switch (value) { Chris@1266: default: Chris@1266: case 0: setBinScale(LinearBins); break; Chris@1266: case 1: setBinScale(LogBins); break; Chris@1266: case 2: setBinScale(InvertedLogBins); break; Chris@1266: } Chris@153: } else if (name == "Normalize") { Chris@1266: setNormalize(value ? true : false); Chris@287: } else { Chris@287: SingleColourLayer::setProperty(name, value); Chris@153: } Chris@153: } Chris@153: Chris@153: void Chris@197: SliceLayer::setFillColourMap(int map) Chris@197: { Chris@197: if (m_colourMap == map) return; Chris@197: m_colourMap = map; Chris@197: emit layerParametersChanged(); Chris@197: } Chris@197: Chris@197: void Chris@193: SliceLayer::setEnergyScale(EnergyScale scale) Chris@153: { Chris@153: if (m_energyScale == scale) return; Chris@153: m_energyScale = scale; Chris@153: emit layerParametersChanged(); Chris@153: } Chris@153: Chris@153: void Chris@193: SliceLayer::setSamplingMode(SamplingMode mode) Chris@153: { Chris@193: if (m_samplingMode == mode) return; Chris@193: m_samplingMode = mode; Chris@153: emit layerParametersChanged(); Chris@153: } Chris@153: Chris@153: void Chris@193: SliceLayer::setPlotStyle(PlotStyle style) Chris@153: { Chris@193: if (m_plotStyle == style) return; Chris@197: bool colourTypeChanged = (style == PlotFilledBlocks || Chris@197: m_plotStyle == PlotFilledBlocks); Chris@193: m_plotStyle = style; Chris@197: if (colourTypeChanged) { Chris@197: emit layerParameterRangesChanged(); Chris@197: } Chris@153: emit layerParametersChanged(); Chris@153: } Chris@153: Chris@153: void Chris@193: SliceLayer::setBinScale(BinScale scale) Chris@153: { Chris@193: if (m_binScale == scale) return; Chris@193: m_binScale = scale; Chris@153: emit layerParametersChanged(); Chris@153: } Chris@153: Chris@153: void Chris@193: SliceLayer::setNormalize(bool n) Chris@153: { Chris@153: if (m_normalize == n) return; Chris@153: m_normalize = n; Chris@153: emit layerParametersChanged(); Chris@153: } Chris@153: Chris@153: void Chris@284: SliceLayer::setThreshold(float thresh) Chris@284: { Chris@284: if (m_threshold == thresh) return; Chris@284: m_threshold = thresh; Chris@284: emit layerParametersChanged(); Chris@284: } Chris@284: Chris@284: void Chris@193: SliceLayer::setGain(float gain) Chris@153: { Chris@153: if (m_gain == gain) return; Chris@153: m_gain = gain; Chris@153: emit layerParametersChanged(); Chris@153: } Chris@153: Chris@284: float Chris@284: SliceLayer::getThresholdDb() const Chris@284: { Chris@284: if (m_threshold == 0.0) return -80.f; Chris@906: float db = float(AudioLevel::multiplier_to_dB(m_threshold)); Chris@284: return db; Chris@284: } Chris@284: Chris@287: int Chris@287: SliceLayer::getDefaultColourHint(bool darkbg, bool &impose) Chris@287: { Chris@287: impose = false; Chris@287: return ColourDatabase::getInstance()->getColourIndex Chris@287: (QString(darkbg ? "Bright Blue" : "Blue")); Chris@287: } Chris@287: Chris@316: void Chris@316: SliceLayer::toXml(QTextStream &stream, Chris@316: QString indent, QString extraAttributes) const Chris@153: { Chris@153: QString s; Chris@153: Chris@1362: s += QString("energyScale=\"%1\" " Chris@1362: "samplingMode=\"%2\" " Chris@1362: "plotStyle=\"%3\" " Chris@1362: "binScale=\"%4\" " Chris@1362: "gain=\"%5\" " Chris@1362: "threshold=\"%6\" " Chris@1362: "normalize=\"%7\" %8 ") Chris@1266: .arg(m_energyScale) Chris@193: .arg(m_samplingMode) Chris@598: .arg(m_plotStyle) Chris@598: .arg(m_binScale) Chris@153: .arg(m_gain) Chris@598: .arg(m_threshold) Chris@1238: .arg(m_normalize ? "true" : "false") Chris@1238: .arg(QString("minbin=\"%1\" " Chris@1238: "maxbin=\"%2\"") Chris@1238: .arg(m_minbin) Chris@1238: .arg(m_maxbin)); Chris@153: Chris@1362: // New-style colour map attribute, by string id rather than by Chris@1362: // number Chris@1362: Chris@1362: s += QString("fillColourMap=\"%1\" ") Chris@1362: .arg(ColourMapper::getColourMapId(m_colourMap)); Chris@1362: Chris@1362: // Old-style colour map attribute Chris@1362: Chris@1362: s += QString("colourScheme=\"%1\" ") Chris@1362: .arg(ColourMapper::getBackwardCompatibilityColourMap(m_colourMap)); Chris@1362: Chris@316: SingleColourLayer::toXml(stream, indent, extraAttributes + " " + s); Chris@153: } Chris@153: Chris@153: void Chris@193: SliceLayer::setProperties(const QXmlAttributes &attributes) Chris@153: { Chris@153: bool ok = false; Chris@153: Chris@287: SingleColourLayer::setProperties(attributes); Chris@153: Chris@153: EnergyScale scale = (EnergyScale) Chris@1266: attributes.value("energyScale").toInt(&ok); Chris@153: if (ok) setEnergyScale(scale); Chris@153: Chris@193: SamplingMode mode = (SamplingMode) Chris@1266: attributes.value("samplingMode").toInt(&ok); Chris@193: if (ok) setSamplingMode(mode); Chris@153: Chris@1362: QString colourMapId = attributes.value("fillColourMap"); Chris@1362: int colourMap = ColourMapper::getColourMapById(colourMapId); Chris@1362: if (colourMap >= 0) { Chris@1362: setFillColourMap(colourMap); Chris@1362: } else { Chris@1362: colourMap = attributes.value("colourScheme").toInt(&ok); Chris@1362: if (ok && colourMap < ColourMapper::getColourMapCount()) { Chris@1362: setFillColourMap(colourMap); Chris@1362: } Chris@1362: } Chris@197: Chris@598: PlotStyle s = (PlotStyle) Chris@1266: attributes.value("plotStyle").toInt(&ok); Chris@598: if (ok) setPlotStyle(s); Chris@598: Chris@598: BinScale b = (BinScale) Chris@1266: attributes.value("binScale").toInt(&ok); Chris@598: if (ok) setBinScale(b); Chris@598: Chris@153: float gain = attributes.value("gain").toFloat(&ok); Chris@153: if (ok) setGain(gain); Chris@153: Chris@598: float threshold = attributes.value("threshold").toFloat(&ok); Chris@598: if (ok) setThreshold(threshold); Chris@598: Chris@153: bool normalize = (attributes.value("normalize").trimmed() == "true"); Chris@153: setNormalize(normalize); Chris@1238: Chris@1238: bool alsoOk = false; Chris@1238: Chris@1238: float min = attributes.value("minbin").toFloat(&ok); Chris@1238: float max = attributes.value("maxbin").toFloat(&alsoOk); Chris@1238: if (ok && alsoOk) setDisplayExtents(min, max); Chris@133: } Chris@133: Chris@133: bool Chris@1238: SliceLayer::getValueExtents(double &min, double &max, bool &logarithmic, Chris@1238: QString &unit) const Chris@133: { Chris@1472: auto sliceableModel = Chris@1472: ModelById::getAs(m_sliceableModel); Chris@1472: if (!sliceableModel) return false; Chris@1238: Chris@1238: min = 0; Chris@1472: max = double(sliceableModel->getHeight()); Chris@1238: Chris@1238: logarithmic = (m_binScale == BinScale::LogBins); Chris@1238: unit = ""; Chris@1238: Chris@1238: return true; Chris@133: } Chris@133: Chris@1238: bool Chris@1238: SliceLayer::getDisplayExtents(double &min, double &max) const Chris@1238: { Chris@1472: auto sliceableModel = Chris@1472: ModelById::getAs(m_sliceableModel); Chris@1472: if (!sliceableModel) return false; Chris@1238: Chris@1472: double hmax = double(sliceableModel->getHeight()); Chris@1238: Chris@1238: min = m_minbin; Chris@1238: max = m_maxbin; Chris@1238: if (max <= min) { Chris@1238: min = 0; Chris@1238: max = hmax; Chris@1238: } Chris@1238: if (min < 0) min = 0; Chris@1238: if (max > hmax) max = hmax; Chris@1238: Chris@1238: return true; Chris@1238: } Chris@1238: Chris@1238: bool Chris@1238: SliceLayer::setDisplayExtents(double min, double max) Chris@1238: { Chris@1472: auto sliceableModel = Chris@1472: ModelById::getAs(m_sliceableModel); Chris@1472: if (!sliceableModel) return false; Chris@1238: Chris@1238: m_minbin = int(lrint(min)); Chris@1238: m_maxbin = int(lrint(max)); Chris@1403: Chris@1403: if (m_minbin < 0) { Chris@1403: m_minbin = 0; Chris@1403: } Chris@1403: if (m_maxbin < 0) { Chris@1403: m_maxbin = 0; Chris@1403: } Chris@1472: if (m_minbin > sliceableModel->getHeight()) { Chris@1472: m_minbin = sliceableModel->getHeight(); Chris@1403: } Chris@1472: if (m_maxbin > sliceableModel->getHeight()) { Chris@1472: m_maxbin = sliceableModel->getHeight(); Chris@1403: } Chris@1403: if (m_maxbin < m_minbin) { Chris@1403: m_maxbin = m_minbin; Chris@1403: } Chris@1403: Chris@1238: emit layerParametersChanged(); Chris@1238: return true; Chris@1238: } Chris@1238: Chris@1238: int Chris@1238: SliceLayer::getVerticalZoomSteps(int &defaultStep) const Chris@1238: { Chris@1472: auto sliceableModel = Chris@1472: ModelById::getAs(m_sliceableModel); Chris@1472: if (!sliceableModel) return 0; Chris@1238: Chris@1238: defaultStep = 0; Chris@1472: int h = sliceableModel->getHeight(); Chris@1238: return h; Chris@1238: } Chris@1238: Chris@1238: int Chris@1238: SliceLayer::getCurrentVerticalZoomStep() const Chris@1238: { Chris@1472: auto sliceableModel = Chris@1472: ModelById::getAs(m_sliceableModel); Chris@1472: if (!sliceableModel) return 0; Chris@1238: Chris@1238: double min, max; Chris@1238: getDisplayExtents(min, max); Chris@1472: return sliceableModel->getHeight() - int(lrint(max - min)); Chris@1238: } Chris@1238: Chris@1238: void Chris@1238: SliceLayer::setVerticalZoomStep(int step) Chris@1238: { Chris@1472: auto sliceableModel = Chris@1472: ModelById::getAs(m_sliceableModel); Chris@1472: if (!sliceableModel) return; Chris@1238: Chris@1238: // SVDEBUG << "SliceLayer::setVerticalZoomStep(" < " << bin1 << endl; Chris@1389: Chris@1389: setDisplayExtents(floor(bin0), ceil(bin1)); Chris@1389: } Chris@1389: