diff --git a/plugins/dockers/histogram/histogramdockerwidget.cpp b/plugins/dockers/histogram/histogramdockerwidget.cpp index c609a8f97a..e9a2b157ed 100644 --- a/plugins/dockers/histogram/histogramdockerwidget.cpp +++ b/plugins/dockers/histogram/histogramdockerwidget.cpp @@ -1,194 +1,315 @@ /* * Copyright (c) 2016 Eugene Ingerman * * This library is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; version 2 of the License, or * (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "histogramdockerwidget.h" #include #include #include #include #include #include #include #include #include "KoChannelInfo.h" #include "kis_paint_device.h" #include "KoColorSpace.h" #include "kis_iterator_ng.h" #include "kis_canvas2.h" +struct HistogramComputationStrokeStrategy::Private { + + class ProcessData : public KisStrokeJobData + { + public: + ProcessData() + : KisStrokeJobData(SEQUENTIAL) + {} + }; +}; + + +HistogramComputationStrokeStrategy::HistogramComputationStrokeStrategy(KisImageWSP image) + : KisSimpleStrokeStrategy(QLatin1String("ComputeHistogram")), + m_image(image) +{ + enableJob(KisSimpleStrokeStrategy::JOB_INIT, true, KisStrokeJobData::BARRIER, KisStrokeJobData::EXCLUSIVE); + enableJob(KisSimpleStrokeStrategy::JOB_DOSTROKE); + //enableJob(KisSimpleStrokeStrategy::JOB_FINISH); + enableJob(KisSimpleStrokeStrategy::JOB_CANCEL, true, KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::EXCLUSIVE); + + setRequestsOtherStrokesToEnd(false); + setClearsRedoOnStart(false); + setCanForgetAboutMe(true); +} + +QList HistogramComputationStrokeStrategy::createJobsData() +{ +// QSize thumbnailOversampledSize = oversample * thumbnailSize; + +// if ((thumbnailOversampledSize.width() > imageRect.width()) || (thumbnailOversampledSize.height() > imageRect.height())) { +// thumbnailOversampledSize.scale(imageRect.size(), Qt::KeepAspectRatio); +// } + +// QVector tileRects = KritaUtils::splitRectIntoPatches(QRect(QPoint(0, 0), thumbnailOversampledSize), QSize(thumbnailTileDim, thumbnailTileDim)); + QList jobsData; + jobsData << new HistogramComputationStrokeStrategy::Private::ProcessData(); + +// Q_FOREACH (const QRect &tileRectangle, tileRects) { +// jobsData << new HistogramComputationStrokeStrategy::Private::ProcessData(dev, thumbDev, thumbnailOversampledSize, tileRectangle); +// } +// jobsData << new HistogramComputationStrokeStrategy::Private::FinishProcessing(thumbDev, thumbnailSize); + + return jobsData; +} + +HistogramComputationStrokeStrategy::~HistogramComputationStrokeStrategy() +{ +} + + +void HistogramComputationStrokeStrategy::initStrokeCallback() +{ +} + +void HistogramComputationStrokeStrategy::doStrokeCallback(KisStrokeJobData *data) +{ + Private::ProcessData *d_pd = dynamic_cast(data); + KisPaintDeviceSP m_dev = m_image->projection(); + QRect m_bounds = m_image->bounds(); + + const KoColorSpace *cs = m_dev->colorSpace(); + quint32 channelCount = m_dev->channelCount(); + quint32 pixelSize = m_dev->pixelSize(); + + quint32 imageSize = m_bounds.width() * m_bounds.height(); + quint32 nSkip = 1 + (imageSize >> 20); //for speed use about 1M pixels for computing histograms + + + //KisSharedPtr bins = new HistVector(); + HistogramDataSP hisData = HistogramDataSP(new HistogramData()); + + + + //allocate space for the histogram data + hisData->bins.resize((int)channelCount); + for (auto &bin : hisData->bins) { + bin.resize(std::numeric_limits::max() + 1); + } + +// for (int i = 0; i < bins->size(); i++) { +// bins->at(i).resize(std::numeric_limits::max()); +// } + + QRect bounds = m_dev->exactBounds(); + if (bounds.isEmpty()) + return; + + quint32 toSkip = nSkip; + + KisSequentialConstIterator it(m_dev, m_dev->exactBounds()); + + int numConseqPixels = it.nConseqPixels(); + while (it.nextPixels(numConseqPixels)) { + + numConseqPixels = it.nConseqPixels(); + const quint8* pixel = it.rawDataConst(); + for (int k = 0; k < numConseqPixels; ++k) { + if (--toSkip == 0) { + for (int chan = 0; chan < (int)channelCount; ++chan) { + m_bins[chan][cs->scaleToU8(pixel, chan)]++; + } + toSkip = nSkip; + } + pixel += pixelSize; + } + } + + emit computationResultReady(hisData); +} + +void HistogramComputationStrokeStrategy::finishStrokeCallback() +{ +} + +void HistogramComputationStrokeStrategy::cancelStrokeCallback() +{ +} + + + HistogramDockerWidget::HistogramDockerWidget(QWidget *parent, const char *name, Qt::WindowFlags f) : QLabel(parent, f), m_colorSpace(0), m_smoothHistogram(true) { setObjectName(name); } HistogramDockerWidget::~HistogramDockerWidget() { } void HistogramDockerWidget::updateHistogram(KisCanvas2* canvas) { if (canvas) { KisPaintDeviceSP paintDevice = canvas->image()->projection(); QRect bounds = canvas->image()->bounds(); // remember to save the color space to paint the histogram data! m_colorSpace = paintDevice->colorSpace(); KisPaintDeviceSP m_devClone = new KisPaintDevice(paintDevice->colorSpace()); m_devClone->makeCloneFrom(paintDevice, bounds); HistogramComputationThread *workerThread = new HistogramComputationThread(m_devClone, bounds); connect(workerThread, &HistogramComputationThread::resultReady, this, &HistogramDockerWidget::receiveNewHistogram); connect(workerThread, &HistogramComputationThread::finished, workerThread, &QObject::deleteLater); workerThread->start(); } else { m_histogramData.clear(); update(); } } void HistogramDockerWidget::receiveNewHistogram(HistVector *histogramData) { m_histogramData = *histogramData; update(); } void HistogramDockerWidget::paintEvent(QPaintEvent *event) { if (m_colorSpace && !m_histogramData.empty()) { int nBins = m_histogramData.at(0).size(); const KoColorSpace* cs = m_colorSpace; QLabel::paintEvent(event); QPainter painter(this); painter.fillRect(0, 0, this->width(), this->height(), this->palette().dark().color()); painter.setPen(this->palette().light().color()); const int NGRID = 4; for (int i = 0; i <= NGRID; ++i) { painter.drawLine(this->width()*i / NGRID, 0., this->width()*i / NGRID, this->height()); painter.drawLine(0., this->height()*i / NGRID, this->width(), this->height()*i / NGRID); } unsigned int nChannels = cs->channelCount(); QList channels = cs->channels(); unsigned int highest = 0; //find the most populous bin in the histogram to scale it properly for (int chan = 0; chan < channels.size(); chan++) { if (channels.at(chan)->channelType() != KoChannelInfo::ALPHA) { std::vector histogramTemp = m_histogramData.at(chan); //use 98th percentile, rather than max for better visual appearance int nthPercentile = 2 * histogramTemp.size() / 100; //unsigned int max = *std::max_element(m_histogramData.at(chan).begin(),m_histogramData.at(chan).end()); std::nth_element(histogramTemp.begin(), histogramTemp.begin() + nthPercentile, histogramTemp.end(), std::greater()); unsigned int max = *(histogramTemp.begin() + nthPercentile); highest = std::max(max, highest); } } painter.setWindow(QRect(-1, 0, nBins + 1, highest)); painter.setCompositionMode(QPainter::CompositionMode_Plus); for (int chan = 0; chan < (int)nChannels; chan++) { if (channels.at(chan)->channelType() != KoChannelInfo::ALPHA) { QColor color = channels.at(chan)->color(); //special handling of grayscale color spaces. can't use color returned above. if(cs->colorChannelCount()==1){ color = QColor(Qt::gray); } QColor fill_color = color; fill_color.setAlphaF(.25); painter.setBrush(fill_color); QPen pen = QPen(color); pen.setWidth(0); painter.setPen(pen); if (m_smoothHistogram) { QPainterPath path; path.moveTo(QPointF(-1, highest)); for (qint32 i = 0; i < nBins; ++i) { float v = std::max((float)highest - m_histogramData[chan][i], 0.f); path.lineTo(QPointF(i, v)); } path.lineTo(QPointF(nBins + 1, highest)); path.closeSubpath(); painter.drawPath(path); } else { pen.setWidth(1); painter.setPen(pen); for (qint32 i = 0; i < nBins; ++i) { float v = std::max((float)highest - m_histogramData[chan][i], 0.f); painter.drawLine(QPointF(i, highest), QPointF(i, v)); } } } } } } void HistogramComputationThread::run() { const KoColorSpace *cs = m_dev->colorSpace(); quint32 channelCount = m_dev->channelCount(); quint32 pixelSize = m_dev->pixelSize(); quint32 imageSize = m_bounds.width() * m_bounds.height(); quint32 nSkip = 1 + (imageSize >> 20); //for speed use about 1M pixels for computing histograms //allocate space for the histogram data bins.resize((int)channelCount); for (auto &bin : bins) { bin.resize(std::numeric_limits::max() + 1); } QRect bounds = m_dev->exactBounds(); if (bounds.isEmpty()) return; quint32 toSkip = nSkip; KisSequentialConstIterator it(m_dev, m_dev->exactBounds()); int numConseqPixels = it.nConseqPixels(); while (it.nextPixels(numConseqPixels)) { numConseqPixels = it.nConseqPixels(); const quint8* pixel = it.rawDataConst(); for (int k = 0; k < numConseqPixels; ++k) { if (--toSkip == 0) { for (int chan = 0; chan < (int)channelCount; ++chan) { bins[chan][cs->scaleToU8(pixel, chan)]++; } toSkip = nSkip; } pixel += pixelSize; } } emit resultReady(&bins); } diff --git a/plugins/dockers/histogram/histogramdockerwidget.h b/plugins/dockers/histogram/histogramdockerwidget.h index bbd8eec41a..8441353f81 100644 --- a/plugins/dockers/histogram/histogramdockerwidget.h +++ b/plugins/dockers/histogram/histogramdockerwidget.h @@ -1,82 +1,124 @@ /* * Copyright (c) 2016 Eugene Ingerman * * This library is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; version 2 of the License, or * (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef HISTOGRAMDOCKERWIDGET_H #define HISTOGRAMDOCKERWIDGET_H #include #include #include #include #include "kis_types.h" #include +#include class KisCanvas2; class KoColorSpace; + typedef std::vector > HistVector; //Don't use QVector here - it's too slow for this purpose +struct HistogramData +{ + HistogramData() {} + ~HistogramData() {} + + HistVector bins; + KoColorSpace* colorSpace; +}; +typedef QSharedPointer HistogramDataSP; + + class HistogramComputationThread : public QThread { Q_OBJECT public: HistogramComputationThread(KisPaintDeviceSP _dev, const QRect& _bounds) : m_dev(_dev), m_bounds(_bounds) {} void run() override; Q_SIGNALS: void resultReady(HistVector*); private: KisPaintDeviceSP m_dev; QRect m_bounds; HistVector bins; }; +class HistogramComputationStrokeStrategy : public QObject, public KisSimpleStrokeStrategy +{ + Q_OBJECT +public: + HistogramComputationStrokeStrategy(KisImageWSP image); + ~HistogramComputationStrokeStrategy() override; + + static QList createJobsData(); + +private: + void initStrokeCallback() override; + void doStrokeCallback(KisStrokeJobData *data) override; + void finishStrokeCallback() override; + void cancelStrokeCallback() override; + +Q_SIGNALS: + //Emitted when thumbnail is updated and overviewImage is fully generated. + void computationResultReady(HistogramDataSP data); + + +private: + struct Private; + const QScopedPointer m_d; + //QMutex m_thumbnailMergeMutex; + KisImageSP m_image; + HistVector m_bins; +}; + + class HistogramDockerWidget : public QLabel { Q_OBJECT public: HistogramDockerWidget(QWidget *parent = 0, const char *name = 0, Qt::WindowFlags f = 0); ~HistogramDockerWidget() override; void paintEvent(QPaintEvent *event) override; public Q_SLOTS: /** * @brief updateHistogram starts calculation of the histogram * @param canvas canvas that the calculations must be based on * * Note: don't try to save the paint device of the projection of the image. * Paint device of the projection changes in multiple cases, for example * Isolate Mode or when opening an image with a single layer. */ void updateHistogram(KisCanvas2* canvas); void receiveNewHistogram(HistVector*); private: HistVector m_histogramData; const KoColorSpace* m_colorSpace; bool m_smoothHistogram; }; #endif // HISTOGRAMDOCKERWIDGET_H