diff --git a/core/libs/dimg/filters/levels/histogrambox.cpp b/core/libs/dimg/filters/levels/histogrambox.cpp index 1ac19cacc9..d80a31c7b1 100644 --- a/core/libs/dimg/filters/levels/histogrambox.cpp +++ b/core/libs/dimg/filters/levels/histogrambox.cpp @@ -1,400 +1,401 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2008-09-30 * Description : a widget to display an image histogram and its control widgets * * Copyright (C) 2008-2009 by Andi Clemens * Copyright (C) 2011-2020 by Gilles Caulier * * This program is free software; you can redistribute it * and/or modify it under the terms of the GNU General * Public License as published by the Free Software Foundation; * either version 2, or (at your option) * any later version. * * This program 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 General Public License for more details. * * ============================================================ */ #include "histogrambox.h" // Qt includes #include #include #include #include #include #include #include #include #include #include #include #include #include // KDE includes #include // Local includes #include "digikam_debug.h" #include "colorgradientwidget.h" #include "histogramwidget.h" #include "digikam_globals.h" namespace Digikam { class Q_DECL_HIDDEN HistogramBox::Private { public: explicit Private() + : scaleBG(nullptr), + linHistoButton(nullptr), + logHistoButton(nullptr), + histoBox(nullptr), + channelCB(nullptr), + hGradient(nullptr), + histogramWidget(nullptr) { - scaleBG = nullptr; - linHistoButton = nullptr; - logHistoButton = nullptr; - channelCB = nullptr; - hGradient = nullptr; - histogramWidget = nullptr; - histoBox = nullptr; } QButtonGroup* scaleBG; QToolButton* linHistoButton; QToolButton* logHistoButton; QWidget* histoBox; QComboBox* channelCB; ColorGradientWidget* hGradient; HistogramWidget* histogramWidget; }; HistogramBox::HistogramBox(QWidget* const parent, HistogramBoxType type, bool selectMode) : QWidget(parent), d(new Private) { d->channelCB = new QComboBox(this); QLabel* const channelLabel = new QLabel(i18n("Channel:"), this); channelLabel->setAlignment(Qt::AlignRight | Qt::AlignVCenter); QWidget* const scaleBox = new QWidget(this); QHBoxLayout* const hlay = new QHBoxLayout(scaleBox); d->scaleBG = new QButtonGroup(scaleBox); scaleBox->setWhatsThis(i18n("

Select the histogram scale.

" "

If the image's maximal counts are small, you can use the linear scale.

" "

Logarithmic scale can be used when the maximal counts are big; " "if it is used, all values (small and large) will be visible on the graph.

")); d->linHistoButton = new QToolButton(scaleBox); d->linHistoButton->setToolTip(i18nc("linear histogram scaling mode", "Linear")); d->linHistoButton->setIcon(QIcon::fromTheme(QLatin1String("view-object-histogram-linear"))); d->linHistoButton->setCheckable(true); d->scaleBG->addButton(d->linHistoButton, LinScaleHistogram); d->logHistoButton = new QToolButton(scaleBox); d->logHistoButton->setToolTip(i18nc("logarithmic histogram scaling mode", "Logarithmic")); d->logHistoButton->setIcon(QIcon::fromTheme(QLatin1String("view-object-histogram-logarithmic"))); d->logHistoButton->setCheckable(true); d->scaleBG->addButton(d->logHistoButton, LogScaleHistogram); hlay->setSpacing(0); hlay->setContentsMargins(QMargins()); hlay->addWidget(d->linHistoButton); hlay->addWidget(d->logHistoButton); d->scaleBG->setExclusive(true); d->logHistoButton->setChecked(true); d->histoBox = new QWidget; QVBoxLayout* const histoBoxLayout = new QVBoxLayout; d->histogramWidget = new HistogramWidget(256, 140, d->histoBox, selectMode, true, true); d->histogramWidget->setWhatsThis(i18n("Here you can see the target preview image histogram drawing " "of the selected image channel. This one is re-computed at any " "settings changes.")); d->hGradient = new ColorGradientWidget(Qt::Horizontal, 10, d->histoBox); d->hGradient->setColors(QColor("black"), QColor("white")); histoBoxLayout->addWidget(d->histogramWidget); histoBoxLayout->addWidget(d->hGradient); histoBoxLayout->setContentsMargins(QMargins()); histoBoxLayout->setSpacing(1); d->histoBox->setLayout(histoBoxLayout); QGridLayout* const mainLayout = new QGridLayout; mainLayout->addWidget(channelLabel, 0, 0, 1, 1); mainLayout->addWidget(d->channelCB, 0, 1, 1, 1); mainLayout->addWidget(scaleBox, 0, 3, 1, 2); mainLayout->addWidget(d->histoBox, 2, 0, 1, 5); mainLayout->setColumnStretch(2, 10); mainLayout->setContentsMargins(QMargins()); mainLayout->setSpacing(5); setLayout(mainLayout); // --------------------------------------------------------------- setHistogramType(type); // --------------------------------------------------------------- connect(d->channelCB, SIGNAL(activated(int)), this, SLOT(slotChannelChanged())); connect(d->scaleBG, SIGNAL(buttonReleased(int)), this, SLOT(slotScaleChanged())); connect(this, SIGNAL(signalChannelChanged(ChannelType)), d->histogramWidget, SLOT(setChannelType(ChannelType))); connect(this, SIGNAL(signalScaleChanged(HistogramScale)), d->histogramWidget, SLOT(setScaleType(HistogramScale))); } HistogramBox::~HistogramBox() { histogram()->stopHistogramComputation(); delete d; } void HistogramBox::setGradientVisible(bool visible) { d->hGradient->setVisible(visible); } void HistogramBox::setGradientColors(const QColor& from, const QColor& to) { d->hGradient->setColors(from, to); } void HistogramBox::setStatisticsVisible(bool b) { d->histogramWidget->setStatisticsVisible(b); } ChannelType HistogramBox::channel() const { int index = d->channelCB->currentIndex(); + return (ChannelType)(d->channelCB->itemData(index).toInt()); } void HistogramBox::setChannel(ChannelType channel) { int id = d->channelCB->findData(QVariant(channel)); d->channelCB->setCurrentIndex(id); slotChannelChanged(); } void HistogramBox::setChannelEnabled(bool enabled) { d->channelCB->setEnabled(enabled); } HistogramScale HistogramBox::scale() const { return static_cast(d->scaleBG->checkedId()); } void HistogramBox::setScale(HistogramScale scale) { d->scaleBG->button((int)scale)->setChecked(true); slotScaleChanged(); } HistogramWidget* HistogramBox::histogram() const { return d->histogramWidget; } void HistogramBox::setHistogramMargin(int margin) { d->histoBox->layout()->setContentsMargins(margin, margin, margin, margin); } void HistogramBox::slotChannelChanged() { switch (channel()) { case LuminosityChannel: setGradientColors(QColor("black"), QColor("white")); break; case RedChannel: setGradientColors(QColor("black"), QColor("red")); break; case GreenChannel: setGradientColors(QColor("black"), QColor("green")); break; case BlueChannel: setGradientColors(QColor("black"), QColor("blue")); break; case AlphaChannel: setGradientColors(QColor("black"), QColor("white")); break; case ColorChannels: setGradientColors(QColor("black"), QColor("white")); break; } emit signalChannelChanged(channel()); } void HistogramBox::slotScaleChanged() { emit signalScaleChanged(scale()); } void HistogramBox::setHistogramType(HistogramBoxType type) { // all possible channels for histogram widget are defined in this map QMap > channelDescMap; // this string will contain the WhatsThis message for the channelCB QString channelCBDescr(i18n("

Select the histogram channel to display:

")); // those pairs hold the combobox text and WhatsThis description for each channel item typedef QPair ChannelPair; ChannelPair luminosityPair(i18nc("The luminosity channel", "Luminosity"), i18n( "Luminosity: display the image's luminosity values.")); ChannelPair redPair(i18nc("The red channel", "Red"), i18n( "Red: display the red image-channel values.")); ChannelPair greenPair(i18nc("The green channel", "Green"), i18n( "Green: display the green image-channel values.")); ChannelPair bluePair(i18nc("The blue channel", "Blue"), i18n( "Blue: display the blue image-channel values.")); ChannelPair colorsPair(i18nc("The colors channel", "Colors"), i18n( "Colors: Display all color channel values at the same time.")); ChannelPair alphaPair(i18nc("The alpha channel", "Alpha"), i18n( "Alpha: display the alpha image-channel values. " "This channel corresponds to the transparency value and " "is supported by some image formats, such as PNG or TIF.")); channelDescMap.insert(LuminosityChannel, luminosityPair); channelDescMap.insert(RedChannel, redPair); channelDescMap.insert(GreenChannel, greenPair); channelDescMap.insert(BlueChannel, bluePair); channelDescMap.insert(ColorChannels, colorsPair); channelDescMap.insert(AlphaChannel, alphaPair); switch (type) { case RGB: d->channelCB->clear(); d->channelCB->addItem(channelDescMap[RedChannel].first, QVariant(RedChannel)); d->channelCB->addItem(channelDescMap[GreenChannel].first, QVariant(GreenChannel)); d->channelCB->addItem(channelDescMap[BlueChannel].first, QVariant(BlueChannel)); channelCBDescr.append(QLatin1String("

")); channelCBDescr.append(channelDescMap[RedChannel].second).append(QLatin1String("
")); channelCBDescr.append(channelDescMap[GreenChannel].second).append(QLatin1String("
")); channelCBDescr.append(channelDescMap[BlueChannel].second); channelCBDescr.append(QLatin1String("

")); break; case RGBA: d->channelCB->clear(); d->channelCB->addItem(channelDescMap[RedChannel].first, QVariant(RedChannel)); d->channelCB->addItem(channelDescMap[GreenChannel].first, QVariant(GreenChannel)); d->channelCB->addItem(channelDescMap[BlueChannel].first, QVariant(BlueChannel)); d->channelCB->addItem(channelDescMap[AlphaChannel].first, QVariant(AlphaChannel)); channelCBDescr.append(QLatin1String("

")); channelCBDescr.append(channelDescMap[RedChannel].second).append(QLatin1String("
")); channelCBDescr.append(channelDescMap[GreenChannel].second).append(QLatin1String("
")); channelCBDescr.append(channelDescMap[BlueChannel].second).append(QLatin1String("
")); channelCBDescr.append(channelDescMap[AlphaChannel].second); channelCBDescr.append(QLatin1String("

")); break; case LRGB: d->channelCB->clear(); d->channelCB->addItem(channelDescMap[LuminosityChannel].first, QVariant(LuminosityChannel)); d->channelCB->addItem(channelDescMap[RedChannel].first, QVariant(RedChannel)); d->channelCB->addItem(channelDescMap[GreenChannel].first, QVariant(GreenChannel)); d->channelCB->addItem(channelDescMap[BlueChannel].first, QVariant(BlueChannel)); channelCBDescr.append(QLatin1String("

")); channelCBDescr.append(channelDescMap[LuminosityChannel].second).append(QLatin1String("
")); channelCBDescr.append(channelDescMap[RedChannel].second).append(QLatin1String("
")); channelCBDescr.append(channelDescMap[GreenChannel].second).append(QLatin1String("
")); channelCBDescr.append(channelDescMap[BlueChannel].second); channelCBDescr.append(QLatin1String("

")); break; case LRGBA: d->channelCB->clear(); d->channelCB->addItem(channelDescMap[LuminosityChannel].first, QVariant(LuminosityChannel)); d->channelCB->addItem(channelDescMap[RedChannel].first, QVariant(RedChannel)); d->channelCB->addItem(channelDescMap[GreenChannel].first, QVariant(GreenChannel)); d->channelCB->addItem(channelDescMap[BlueChannel].first, QVariant(BlueChannel)); d->channelCB->addItem(channelDescMap[AlphaChannel].first, QVariant(AlphaChannel)); channelCBDescr.append(QLatin1String("

")); channelCBDescr.append(channelDescMap[LuminosityChannel].second).append(QLatin1String("
")); channelCBDescr.append(channelDescMap[RedChannel].second).append(QLatin1String("
")); channelCBDescr.append(channelDescMap[GreenChannel].second).append(QLatin1String("
")); channelCBDescr.append(channelDescMap[BlueChannel].second).append(QLatin1String("
")); channelCBDescr.append(channelDescMap[AlphaChannel].second); channelCBDescr.append(QLatin1String("

")); break; case LRGBC: d->channelCB->clear(); d->channelCB->addItem(channelDescMap[LuminosityChannel].first, QVariant(LuminosityChannel)); d->channelCB->addItem(channelDescMap[RedChannel].first, QVariant(RedChannel)); d->channelCB->addItem(channelDescMap[GreenChannel].first, QVariant(GreenChannel)); d->channelCB->addItem(channelDescMap[BlueChannel].first, QVariant(BlueChannel)); d->channelCB->addItem(channelDescMap[ColorChannels].first, QVariant(ColorChannels)); channelCBDescr.append(QLatin1String("

")); channelCBDescr.append(channelDescMap[LuminosityChannel].second).append(QLatin1String("
")); channelCBDescr.append(channelDescMap[RedChannel].second).append(QLatin1String("
")); channelCBDescr.append(channelDescMap[GreenChannel].second).append(QLatin1String("
")); channelCBDescr.append(channelDescMap[BlueChannel].second).append(QLatin1String("
")); channelCBDescr.append(channelDescMap[ColorChannels].second); channelCBDescr.append(QLatin1String("

")); break; case LRGBAC: d->channelCB->clear(); d->channelCB->addItem(channelDescMap[LuminosityChannel].first, QVariant(LuminosityChannel)); d->channelCB->addItem(channelDescMap[RedChannel].first, QVariant(RedChannel)); d->channelCB->addItem(channelDescMap[GreenChannel].first, QVariant(GreenChannel)); d->channelCB->addItem(channelDescMap[BlueChannel].first, QVariant(BlueChannel)); d->channelCB->addItem(channelDescMap[AlphaChannel].first, QVariant(AlphaChannel)); d->channelCB->addItem(channelDescMap[ColorChannels].first, QVariant(ColorChannels)); channelCBDescr.append(QLatin1String("

")); channelCBDescr.append(channelDescMap[LuminosityChannel].second).append(QLatin1String("
")); channelCBDescr.append(channelDescMap[RedChannel].second).append(QLatin1String("
")); channelCBDescr.append(channelDescMap[GreenChannel].second).append(QLatin1String("
")); channelCBDescr.append(channelDescMap[BlueChannel].second).append(QLatin1String("
")); channelCBDescr.append(channelDescMap[AlphaChannel].second).append(QLatin1String("
")); channelCBDescr.append(channelDescMap[ColorChannels].second); channelCBDescr.append(QLatin1String("

")); break; default: break; } d->channelCB->setWhatsThis(channelCBDescr); } } // namespace Digikam diff --git a/core/libs/dimg/filters/levels/histogrambox.h b/core/libs/dimg/filters/levels/histogrambox.h index 964bbd9388..be5a730718 100644 --- a/core/libs/dimg/filters/levels/histogrambox.h +++ b/core/libs/dimg/filters/levels/histogrambox.h @@ -1,94 +1,94 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2008-09-30 * Description : a widget to display an image histogram and its control widgets * * Copyright (C) 2008-2009 by Andi Clemens * Copyright (C) 2011-2020 by Gilles Caulier * * This program is free software; you can redistribute it * and/or modify it under the terms of the GNU General * Public License as published by the Free Software Foundation; * either version 2, or (at your option) * any later version. * * This program 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 General Public License for more details. * * ============================================================ */ #ifndef DIGIKAM_HISTOGRAM_BOX_H #define DIGIKAM_HISTOGRAM_BOX_H // Qt includes #include // Local includes #include "digikam_debug.h" #include "digikam_export.h" #include "digikam_globals.h" class QColor; namespace Digikam { class HistogramWidget; class DIGIKAM_EXPORT HistogramBox : public QWidget { Q_OBJECT public: explicit HistogramBox(QWidget* const parent = nullptr, HistogramBoxType type = Digikam::LRGB, bool selectMode = false); ~HistogramBox(); void setHistogramType(HistogramBoxType type); void setHistogramMargin(int); void setGradientColors(const QColor& from, const QColor& to); void setGradientVisible(bool visible); - ChannelType channel() const; + ChannelType channel() const; void setChannelEnabled(bool enabled); void setStatisticsVisible(bool b); - HistogramScale scale() const; + HistogramScale scale() const; - HistogramWidget* histogram() const; + HistogramWidget* histogram() const; Q_SIGNALS: void signalChannelChanged(ChannelType channel); void signalScaleChanged(HistogramScale scale); public Q_SLOTS: void setChannel(ChannelType channel); void setScale(HistogramScale scale); protected Q_SLOTS: void slotChannelChanged(); void slotScaleChanged(); private: class Private; Private* const d; }; } // namespace Digikam #endif // DIGIKAM_HISTOGRAM_BOX_H diff --git a/core/libs/dimg/filters/levels/histogrampainter.cpp b/core/libs/dimg/filters/levels/histogrampainter.cpp index 63b7c35cad..241d5fc4c8 100644 --- a/core/libs/dimg/filters/levels/histogrampainter.cpp +++ b/core/libs/dimg/filters/levels/histogrampainter.cpp @@ -1,647 +1,653 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2009-10-26 * Description : a class that manages painting histograms * * Copyright (C) 2009 by Johannes Wienke * Copyright (C) 2011-2020 by Gilles Caulier * * This program is free software; you can redistribute it * and/or modify it under the terms of the GNU General * Public License as published by the Free Software Foundation; * either version 2, or (at your option) any later version. * * This program 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 General Public License for more details. * * ============================================================ */ #include "histogrampainter.h" // C++ includes #include // Qt includes #include // KDE includes #include // Local includes #include "digikam_debug.h" #define HISTOGRAM_CALC_CUTOFF_MIN 0.1 #define HISTOGRAM_CALC_CUTOFF_MAX 0.9 #define HISTOGRAM_CALC_CUTOFF_HEIGHT 0.8 namespace Digikam { class Q_DECL_HIDDEN HistogramPainter::Private { public: explicit Private() : histogram(nullptr), widgetToInitFrom(nullptr), scale(LogScaleHistogram), channelType(LuminosityChannel), highlightSelection(false), selectionMin(0.0), selectionMax(0.0), showColorGuide(false), showXGrid(true) { } public: double calculateMax() { int segments = histogram->getHistogramSegments(); int startSeg = (int)(HISTOGRAM_CALC_CUTOFF_MIN * (segments - 1)); int endSeg = (int)(HISTOGRAM_CALC_CUTOFF_MAX * (segments - 1)); double max = 0.0; switch (scale) { case LinScaleHistogram: switch (channelType) { case GreenChannel: case BlueChannel: case RedChannel: case AlphaChannel: case LuminosityChannel: max = qMin(histogram->getMaximum(channelType, startSeg, endSeg) / HISTOGRAM_CALC_CUTOFF_HEIGHT, histogram->getMaximum(channelType, 0, segments - 1)); break; case ColorChannels: max = qMin(qMax(qMax(histogram->getMaximum(RedChannel, startSeg, endSeg), histogram->getMaximum(GreenChannel, startSeg, endSeg)), histogram->getMaximum(BlueChannel, startSeg, endSeg)) / HISTOGRAM_CALC_CUTOFF_HEIGHT, qMax(qMax(histogram->getMaximum(RedChannel, 0, segments - 1), histogram->getMaximum(GreenChannel, 0, segments - 1)), histogram->getMaximum(BlueChannel, 0, segments - 1))); break; default: qCDebug(DIGIKAM_DIMG_LOG) << "Untreated channel type " << channelType << ". Using luminosity as default."; max = qMin(histogram->getMaximum(LuminosityChannel, startSeg, endSeg) / HISTOGRAM_CALC_CUTOFF_HEIGHT, histogram->getMaximum(LuminosityChannel, 0, segments - 1)); break; } break; case LogScaleHistogram: switch (channelType) { case GreenChannel: case BlueChannel: case RedChannel: case AlphaChannel: case LuminosityChannel: max = histogram->getMaximum(channelType, 0, segments - 1); break; case ColorChannels: max = qMax(qMax(histogram->getMaximum(RedChannel, 0, segments - 1), histogram->getMaximum(GreenChannel, 0, segments - 1)), histogram->getMaximum(BlueChannel, 0, segments - 1)); break; default: qCDebug(DIGIKAM_DIMG_LOG) << "Untreated channel type " << channelType << ". Using luminosity as default."; max = histogram->getMaximum(LuminosityChannel, 0, segments - 1); break; } if (max > 0.0) { max = log(max); } else { max = 1.0; } break; default: qCDebug(DIGIKAM_DIMG_LOG) << "Untreated histogram scale " << scale << ". Using linear as default."; break; } return max; } inline int scaleToPixmapHeight(const double& value, const int& pixmapHeight, const double& max) { if (max == 0) { return 0; } switch (scale) { case LinScaleHistogram: { return qMin((int)((pixmapHeight * value) / max), pixmapHeight); } case LogScaleHistogram: { if (value == 0.0) { return 0; } if (value < 0.0) { qCWarning(DIGIKAM_DIMG_LOG) << "Scaling value < 0: " << value << ". Assuming 0."; return 0; } return qMin((int)((pixmapHeight * log(value)) / max), pixmapHeight); } default: { qCDebug(DIGIKAM_DIMG_LOG) << "Unknown scale type " << scale; return 0; } } } inline void calculateSegmentsForIndex(const int& x, const int& drawWidth, int& startSegment, int& endSegment) { if (drawWidth == 0) { startSegment = 0; endSegment = 0; return; } startSegment = (x * (histogram->getHistogramSegments()) - 1) / drawWidth; endSegment = ((x + 1) * (histogram->getHistogramSegments()) - 1) / drawWidth; } void renderSingleColorLine(QPixmap& bufferPixmap, QPainter& p1) { p1.save(); int wWidth = bufferPixmap.width(); int wHeight = bufferPixmap.height(); QImage bb(wWidth, wHeight, QImage::Format_RGB32); QPainter p2; p2.begin(&bb); initPainterFromWidget(&p2); double max = 1.05 * calculateMax(); QPainterPath curvePath; curvePath.moveTo(1, wHeight - 1); int yPrev = 0; - for (int x = 1; x < (wWidth - 1); ++x) + for (int x = 1 ; x < (wWidth - 1) ; ++x) { // calculate histogram segments included in this single pixel line int startSegment = 0; int endSegment = 0; calculateSegmentsForIndex(x - 1, wWidth - 2, startSegment, endSegment); double value = histogram->getMaximum(channelType, startSegment, endSegment); int y = scaleToPixmapHeight(value, wHeight - 2, max); if (x > 1) { (y > yPrev) ? curvePath.lineTo(x, wHeight - yPrev) : curvePath.lineTo(x - 1, wHeight - y); } curvePath.lineTo(x, wHeight - y); yPrev = y; } curvePath.lineTo(wWidth - 2, wHeight - 1); curvePath.lineTo(1, wHeight - 1); curvePath.closeSubpath(); p2.fillRect(0, 0, wWidth, wHeight, palette.color(QPalette::Active, QPalette::Window)); QColor pColor; QColor bColor; switch (channelType) { case GreenChannel: pColor = QColor(63, 255, 63); bColor = QColor(0, 192, 0); break; case BlueChannel: pColor = QColor(63, 63, 255); bColor = QColor(0, 0, 192); break; case RedChannel: pColor = QColor(255, 63, 63); bColor = QColor(192, 0, 0); break; default: pColor = palette.color(QPalette::Active, QPalette::WindowText); bColor = palette.color(QPalette::Inactive, QPalette::WindowText); break; } p2.setPen(QPen(pColor, 1, Qt::SolidLine)); p2.setBrush(QBrush(bColor, Qt::SolidPattern)); p2.drawPath(curvePath); if (highlightSelection) { p2.setClipRect((int)(selectionMin * wWidth), 0, (int)(selectionMax * wWidth - selectionMin * wWidth), wHeight); p2.fillRect((int)(selectionMin * wWidth), 0, (int)(selectionMax * wWidth - selectionMin * wWidth), wHeight, QBrush(palette.color(QPalette::Active, QPalette::WindowText), Qt::SolidPattern)); p2.fillPath(curvePath, QBrush(palette.color(QPalette::Active, QPalette::Window), Qt::SolidPattern)); } p2.end(); p1.drawImage(0, 0, bb); p1.restore(); } void renderMultiColorLine(QPixmap& bufferPixmap, QPainter& p1) { p1.save(); int wWidth = bufferPixmap.width(); int wHeight = bufferPixmap.height(); QImage bb(wWidth, wHeight, QImage::Format_RGB32); QPainter p2; p2.begin(&bb); initPainterFromWidget(&p2); double max = 1.05 * calculateMax(); QPainterPath curveRed, curveGreen, curveBlue; curveRed.moveTo(1, wHeight - 1); curveGreen.moveTo(1, wHeight - 1); curveBlue.moveTo(1, wHeight - 1); - int yrPrev = 0; - int ygPrev = 0; - int ybPrev = 0; + int yrPrev = 0; + int ygPrev = 0; + int ybPrev = 0; - for (int x = 1; x < (wWidth - 1); ++x) + for (int x = 1 ; x < (wWidth - 1) ; ++x) { // calculate histogram segments included in this single pixel line int startSegment = 0; int endSegment = 0; calculateSegmentsForIndex(x - 1, wWidth - 2, startSegment, endSegment); double valueR = histogram->getMaximum(RedChannel, startSegment, endSegment); double valueG = histogram->getMaximum(GreenChannel, startSegment, endSegment); double valueB = histogram->getMaximum(BlueChannel, startSegment, endSegment); int yr = scaleToPixmapHeight(valueR, wHeight - 1, max); int yg = scaleToPixmapHeight(valueG, wHeight - 1, max); int yb = scaleToPixmapHeight(valueB, wHeight - 1, max); if (x > 1) { (yr > yrPrev) ? curveRed.lineTo(x, wHeight - yrPrev) : curveRed.lineTo(x - 1, wHeight - yr); (yg > ygPrev) ? curveGreen.lineTo(x, wHeight - ygPrev) : curveGreen.lineTo(x - 1, wHeight - yg); (yb > ybPrev) ? curveBlue.lineTo(x, wHeight - ybPrev) : curveBlue.lineTo(x - 1, wHeight - yb); } curveRed.lineTo(x, wHeight - yr); curveGreen.lineTo(x, wHeight - yg); curveBlue.lineTo(x, wHeight - yb); yrPrev = yr; ygPrev = yg; ybPrev = yb; } curveRed.lineTo(wWidth - 2, wHeight - 1); curveRed.lineTo(1, wHeight - 1); curveRed.closeSubpath(); curveGreen.lineTo(wWidth - 2, wHeight - 1); curveGreen.lineTo(1, wHeight - 1); curveGreen.closeSubpath(); curveBlue.lineTo(wWidth - 2, wHeight - 1); curveBlue.lineTo(1, wHeight - 1); curveBlue.closeSubpath(); p2.fillRect(0, 0, wWidth, wHeight, palette.color(QPalette::Active, QPalette::Window)); p2.fillPath(curveBlue, QBrush(Qt::black, Qt::SolidPattern)); p2.fillPath(curveRed, QBrush(Qt::black, Qt::SolidPattern)); p2.fillPath(curveGreen, QBrush(Qt::black, Qt::SolidPattern)); p2.setCompositionMode(QPainter::CompositionMode_Screen); p2.setPen(QPen(QColor(63, 63, 255), 1, Qt::SolidLine)); p2.setBrush(QBrush(QColor(0, 0, 192), Qt::SolidPattern)); p2.drawPath(curveBlue); p2.setPen(QPen(QColor(255, 63, 63), 1, Qt::SolidLine)); p2.setBrush(QBrush(QColor(192, 0, 0), Qt::SolidPattern)); p2.drawPath(curveRed); p2.setPen(QPen(QColor(63, 255, 63), 1, Qt::SolidLine)); p2.setBrush(QBrush(QColor(0, 192, 0), Qt::SolidPattern)); p2.drawPath(curveGreen); // Highlight + if (highlightSelection) { p2.setClipRect((int)(selectionMin * wWidth), 0, (int)(selectionMax * wWidth - selectionMin * wWidth), wHeight); p2.setCompositionMode(QPainter::CompositionMode_Source); p2.fillRect((int)(selectionMin * wWidth), 0, (int)(selectionMax * wWidth - selectionMin * wWidth), wHeight, palette.color(QPalette::Active, QPalette::WindowText)); p2.fillPath(curveBlue, QBrush(Qt::black, Qt::SolidPattern)); p2.fillPath(curveRed, QBrush(Qt::black, Qt::SolidPattern)); p2.fillPath(curveGreen, QBrush(Qt::black, Qt::SolidPattern)); p2.setCompositionMode(QPainter::CompositionMode_Screen); p2.fillPath(curveBlue, QBrush(QColor(0, 0, 255), Qt::SolidPattern)); p2.fillPath(curveRed, QBrush(QColor(255, 0, 0), Qt::SolidPattern)); p2.fillPath(curveGreen, QBrush(QColor(0, 255, 0), Qt::SolidPattern)); p2.setClipRect(0, 0, wWidth, wHeight); } p2.end(); p1.drawImage(0, 0, bb); p1.restore(); } void renderXGrid(QPixmap& bufferPixmap, QPainter& p1) { for (int x = 0; x < bufferPixmap.width(); ++x) { - if ((x == bufferPixmap.width() / 4) || (x == bufferPixmap.width() / 2) || + if ((x == bufferPixmap.width() / 4) || + (x == bufferPixmap.width() / 2) || (x == 3 * bufferPixmap.width() / 4)) { p1.setPen(QPen(palette.color(QPalette::Active, QPalette::Base), 1, Qt::SolidLine)); p1.drawLine(x, bufferPixmap.height(), x, 0); } } } void renderColorGuide(QPixmap& bufferPixmap, QPainter& p1) { - if (histogram->isSixteenBit() && !colorGuide.sixteenBit()) + if (histogram->isSixteenBit() && !colorGuide.sixteenBit()) { colorGuide.convertToSixteenBit(); } else if (!histogram->isSixteenBit() && colorGuide.sixteenBit()) { colorGuide.convertToEightBit(); } p1.setPen(QPen(Qt::red, 1, Qt::DotLine)); int guidePos = -1; switch (channelType) { case RedChannel: { guidePos = colorGuide.red(); break; } case GreenChannel: { guidePos = colorGuide.green(); break; } case BlueChannel: { guidePos = colorGuide.blue(); break; } case LuminosityChannel: { guidePos = qMax(qMax(colorGuide.red(), colorGuide.green()), colorGuide.blue()); break; } case ColorChannels: { guidePos = qMax(qMax(colorGuide.red(), colorGuide.green()), colorGuide.blue()); break; } default: { guidePos = colorGuide.alpha(); break; } } if (guidePos != -1) { int xGuide = (int)(((double)(guidePos * bufferPixmap.width())) / ((double)histogram->getHistogramSegments())); p1.drawLine(xGuide, 0, xGuide, bufferPixmap.height()); QString string = i18n("x:%1", guidePos); QFontMetrics fontMt(string); QRect rect = fontMt.boundingRect(0, 0, bufferPixmap.width(), bufferPixmap.height(), 0, string); p1.setPen(QPen(Qt::red, 1, Qt::SolidLine)); rect.moveTop(1); if (xGuide < bufferPixmap.width() / 2) { rect.moveLeft(xGuide); p1.fillRect(rect, QBrush(QColor(250, 250, 255))); p1.drawRect(rect); rect.moveLeft(xGuide + 3); p1.drawText(rect, Qt::AlignLeft, string); } else { rect.moveRight(xGuide); p1.fillRect(rect, QBrush(QColor(250, 250, 255))); p1.drawRect(rect); rect.moveRight(xGuide - 3); p1.drawText(rect, Qt::AlignRight, string); } } } void initPainterFromWidget(QPainter* const p) { if (!p || !widgetToInitFrom) { return; } const QPalette& pal = widgetToInitFrom->palette(); p->setPen(QPen(pal.brush(widgetToInitFrom->foregroundRole()), 0)); p->setBackground(pal.brush(widgetToInitFrom->backgroundRole())); p->setFont(widgetToInitFrom->font()); } public: ImageHistogram* histogram; QPainter painter; QWidget* widgetToInitFrom; QPalette palette; // rendering settings + HistogramScale scale; ChannelType channelType; bool highlightSelection; double selectionMin; double selectionMax; bool showColorGuide; bool showXGrid; DColor colorGuide; }; HistogramPainter::HistogramPainter(QObject* const parent) : QObject(parent), d(new Private()) { } HistogramPainter::~HistogramPainter() { delete d; } void HistogramPainter::setHistogram(ImageHistogram* const histogram) { d->histogram = histogram; } void HistogramPainter::setScale(HistogramScale scale) { d->scale = scale; } void HistogramPainter::setChannelType(ChannelType channelType) { d->channelType = channelType; } void HistogramPainter::setHighlightSelection(bool highlightSelection) { d->highlightSelection = highlightSelection; } void HistogramPainter::setSelection(double selectionMin, double selectionMax) { - if (selectionMin < 0.0 || selectionMin > 1.0) + if ((selectionMin < 0.0) || (selectionMin > 1.0)) { qCWarning(DIGIKAM_DIMG_LOG) << "selectionMin out of range: " << selectionMin << ". Clamping value"; selectionMin = qMax(0.0, qMin(1.0, selectionMin)); } - if (selectionMax < 0.0 || selectionMax > 1.0) + if ((selectionMax < 0.0) || (selectionMax > 1.0)) { qCWarning(DIGIKAM_DIMG_LOG) << "selectionMax out of range: " << selectionMax << ". Clamping value"; selectionMax = qMax(0.0, qMin(1.0, selectionMax)); } d->selectionMin = selectionMin; d->selectionMax = selectionMax; } void HistogramPainter::setRenderXGrid(bool renderXGrid) { d->showXGrid = renderXGrid; } void HistogramPainter::enableHistogramGuideByColor(const DColor& color) { d->colorGuide = color; d->showColorGuide = true; } void HistogramPainter::disableHistogramGuide() { d->showColorGuide = false; } void HistogramPainter::initFrom(QWidget* const widget) { d->widgetToInitFrom = widget; } void HistogramPainter::render(QPixmap& bufferPixmap) { if (!d->histogram) { qCDebug(DIGIKAM_DIMG_LOG) << "Cannot render because the histogram is missing"; return; } int wWidth = bufferPixmap.width(); int wHeight = bufferPixmap.height(); d->painter.begin(&bufferPixmap); if (d->widgetToInitFrom) { d->initPainterFromWidget(&d->painter); d->palette = d->widgetToInitFrom->palette(); } // clear background + d->painter.fillRect(0, 0, wWidth, wHeight, d->palette.color(QPalette::Active, QPalette::Window)); // decide how to render the line + if (d->channelType == ColorChannels) { d->renderMultiColorLine(bufferPixmap, d->painter); } else { d->renderSingleColorLine(bufferPixmap, d->painter); } if (d->showXGrid) { d->renderXGrid(bufferPixmap, d->painter); } if (d->showColorGuide) { d->renderColorGuide(bufferPixmap, d->painter); } // draw a final border around everything + d->painter.setPen(QPen(d->palette.color(QPalette::Active, QPalette::WindowText), 1, Qt::SolidLine)); d->painter.drawRect(0, 0, wWidth - 1, wHeight - 1); d->painter.end(); } } // namespace Digikam diff --git a/core/libs/dimg/filters/levels/histogramwidget.cpp b/core/libs/dimg/filters/levels/histogramwidget.cpp index d07583c557..b523afe4b6 100644 --- a/core/libs/dimg/filters/levels/histogramwidget.cpp +++ b/core/libs/dimg/filters/levels/histogramwidget.cpp @@ -1,730 +1,745 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2004-07-21 * Description : a widget to display an image histogram. * * Copyright (C) 2004-2020 by Gilles Caulier * * This program is free software; you can redistribute it * and/or modify it under the terms of the GNU General * Public License as published by the Free Software Foundation; * either version 2, or (at your option) * any later version. * * This program 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 General Public License for more details. * * ============================================================ */ #include "histogramwidget.h" // Qt includes #include #include #include #include #include #include #include #include #include #include #include #include #include // KDE includes #include // Local includes #include "dimg.h" #include "ditemtooltip.h" #include "imagehistogram.h" #include "digikam_globals.h" #include "digikam_debug.h" #include "histogrampainter.h" #include "dlayoutbox.h" #include "dworkingpixmap.h" namespace Digikam { class Q_DECL_HIDDEN HistogramWidget::Private { public: enum HistogramState { HistogramNone = 0, // No current histogram values calculation. HistogramDataLoading, // The image is being loaded HistogramStarted, // Histogram values calculation started. HistogramCompleted, // Histogram values calculation completed. HistogramFailed // Histogram values calculation failed. }; public: explicit Private() : sixteenBits(false), guideVisible(false), statisticsVisible(false), inSelected(false), selectMode(false), showProgress(false), renderingType(FullImageHistogram), range(255), state(HistogramNone), channelType(LuminosityChannel), scaleType(LogScaleHistogram), imageHistogram(nullptr), selectionHistogram(nullptr), xmin(0), xminOrg(0), xmax(0), animationState(0), animation(nullptr), histogramPainter(nullptr) { progressPix = DWorkingPixmap(); } public: bool sixteenBits; - bool guideVisible; // Display color guide. - bool statisticsVisible; // Display tooltip histogram statistics. + bool guideVisible; ///< Display color guide. + bool statisticsVisible; ///< Display tooltip histogram statistics. bool inSelected; - bool selectMode; // If true, a part of the histogram can be selected ! - bool showProgress; // If true, a message will be displayed during histogram computation, - // else nothing (limit flicker effect in widget especially for small - // image/computation time). - int renderingType; // Using full image or image selection for histogram rendering. + bool selectMode; ///< If true, a part of the histogram can be selected ! + bool showProgress; ///< If true, a message will be displayed during histogram computation, + ///< else nothing (limit flicker effect in widget especially for small + ///< image/computation time). + int renderingType; ///< Using full image or image selection for histogram rendering. int range; - HistogramState state; // Clear drawing zone with message. + HistogramState state; ///< Clear drawing zone with message. - ChannelType channelType; // Channel type to draw - HistogramScale scaleType; // Scale to use for drawing - ImageHistogram* imageHistogram; // Full image - ImageHistogram* selectionHistogram; // Image selection + ChannelType channelType; ///< Channel type to draw + HistogramScale scaleType; ///< Scale to use for drawing + ImageHistogram* imageHistogram; ///< Full image + ImageHistogram* selectionHistogram; ///< Image selection // Current selection information. double xmin; double xminOrg; double xmax; int animationState; QPropertyAnimation* animation; DWorkingPixmap progressPix; DColor colorGuide; HistogramPainter* histogramPainter; }; HistogramWidget::HistogramWidget(int w, int h, QWidget* const parent, bool selectMode, bool showProgress, bool statisticsVisible) : QWidget(parent), d(new Private) { setup(w, h, selectMode, statisticsVisible); d->showProgress = showProgress; } HistogramWidget::~HistogramWidget() { d->animation->stop(); delete d->imageHistogram; delete d->selectionHistogram; delete d; } void HistogramWidget::setup(int w, int h, bool selectMode, bool statisticsVisible) { d->statisticsVisible = statisticsVisible; d->selectMode = selectMode; d->histogramPainter = new HistogramPainter(this); d->animation = new QPropertyAnimation(this, "animationState", this); d->animation->setStartValue(0); d->animation->setEndValue(d->progressPix.frameCount() - 1); d->animation->setDuration(200 * d->progressPix.frameCount()); d->animation->setLoopCount(-1); setMouseTracking(true); setAttribute(Qt::WA_DeleteOnClose); setMinimumSize(w, h); } void HistogramWidget::connectHistogram(const ImageHistogram* const histogram) { connect(histogram, SIGNAL(calculationAboutToStart()), this, SLOT(slotCalculationAboutToStart())); connect(histogram, SIGNAL(calculationFinished(bool)), this, SLOT(slotCalculationFinished(bool))); } void HistogramWidget::updateData(const DImg& img, const DImg& sel, bool showProgress) { d->showProgress = showProgress; d->sixteenBits = !img.isNull() ? img.sixteenBit() : sel.sixteenBit(); // We are deleting the histogram data, so we must not use it to draw any more. + d->state = HistogramWidget::Private::HistogramNone; // Do not using ImageHistogram::getHistogramSegments() // method here because histogram hasn't yet been computed. + d->range = d->sixteenBits ? MAX_SEGMENT_16BIT : MAX_SEGMENT_8BIT; emit signalMaximumValueChanged(d->range); if (!img.isNull() || sel.isNull()) { // do not delete main histogram if only the selection is reset + delete d->imageHistogram; d->imageHistogram = nullptr; } // Calc new histogram data + if (!img.isNull()) { d->imageHistogram = new ImageHistogram(img); connectHistogram(d->imageHistogram); } delete d->selectionHistogram; d->selectionHistogram = nullptr; if (!sel.isNull()) { d->selectionHistogram = new ImageHistogram(sel); connectHistogram(d->selectionHistogram); } else { if (d->renderingType == ImageSelectionHistogram) { setRenderingType(FullImageHistogram); } } ImageHistogram* const histo = currentHistogram(); if (histo) { histo->calculateInThread(); } else { qCWarning(DIGIKAM_DIMG_LOG) << "Current histogram is null"; } } void HistogramWidget::updateSelectionData(const DImg& sel, bool showProgress) { updateData(DImg(), sel, showProgress); } void HistogramWidget::setHistogramGuideByColor(const DColor& color) { d->guideVisible = true; d->colorGuide = color; update(); } void HistogramWidget::setStatisticsVisible(bool b) { d->statisticsVisible = b; update(); } void HistogramWidget::setRenderingType(HistogramRenderingType type) { if (type != d->renderingType) { d->renderingType = type; ImageHistogram* const nowUsedHistogram = currentHistogram(); if (!nowUsedHistogram) { qCWarning(DIGIKAM_DIMG_LOG) << "Current histogram is null"; return; } // already calculated? + if (!nowUsedHistogram->isValid()) { // still computing, or need to start it? + if (nowUsedHistogram->isCalculating()) { setState(HistogramWidget::Private::HistogramStarted); } else { nowUsedHistogram->calculateInThread(); } } else { update(); } } } HistogramRenderingType HistogramWidget::renderingType() const { return (HistogramRenderingType)d->renderingType; } ImageHistogram* HistogramWidget::currentHistogram() const { - if (d->renderingType == ImageSelectionHistogram && d->selectionHistogram) + if ((d->renderingType == ImageSelectionHistogram) && d->selectionHistogram) { return d->selectionHistogram; } else { return d->imageHistogram; } } void HistogramWidget::reset() { d->histogramPainter->disableHistogramGuide(); update(); } void HistogramWidget::startWaitingAnimation() { if (d->showProgress) { d->animation->start(); } setCursor(Qt::WaitCursor); } void HistogramWidget::stopWaitingAnimation() { d->animation->stop(); unsetCursor(); } void HistogramWidget::setState(int state) { if (d->state == state) { return; } d->state = (HistogramWidget::Private::HistogramState)state; switch (state) { case HistogramWidget::Private::HistogramNone: { break; } case HistogramWidget::Private::HistogramDataLoading: { startWaitingAnimation(); break; } case HistogramWidget::Private::HistogramStarted: { startWaitingAnimation(); break; } case HistogramWidget::Private::HistogramCompleted: { notifyValuesChanged(); emit signalHistogramComputationDone(d->sixteenBits); stopWaitingAnimation(); update(); break; } case HistogramWidget::Private::HistogramFailed: { emit signalHistogramComputationFailed(); // Remove old histogram data from memory. + delete d->imageHistogram; d->imageHistogram = nullptr; delete d->selectionHistogram; d->selectionHistogram = nullptr; stopWaitingAnimation(); update(); break; } } } void HistogramWidget::slotCalculationAboutToStart() { // only react to the histogram that the user is currently waiting for + if (QObject::sender() != currentHistogram()) { return; } setState(HistogramWidget::Private::HistogramStarted); } void HistogramWidget::slotCalculationFinished(bool success) { // only react to the histogram that the user is currently waiting for if (QObject::sender() != currentHistogram()) { return; } if (success) { setState(HistogramWidget::Private::HistogramCompleted); } else { setState(HistogramWidget::Private::HistogramFailed); } } void HistogramWidget::setDataLoading() { setState(HistogramWidget::Private::HistogramDataLoading); } void HistogramWidget::setLoadingFailed() { setState(HistogramWidget::Private::HistogramFailed); } void HistogramWidget::stopHistogramComputation() { if (d->imageHistogram) { d->imageHistogram->stopCalculation(); } if (d->selectionHistogram) { d->selectionHistogram->stopCalculation(); } stopWaitingAnimation(); } int HistogramWidget::animationState() const { return d->animationState; } void HistogramWidget::setAnimationState(int animationState) { if (d->animationState == animationState) { return; } d->animationState = animationState; update(); } void HistogramWidget::paintEvent(QPaintEvent*) { // Widget is disabled, not initialized, // or loading, but no message shall be drawn: // Drawing grayed frame. - if (!isEnabled() || - d->state == HistogramWidget::Private::HistogramNone || - (!d->showProgress && (d->state == HistogramWidget::Private::HistogramStarted || - d->state == HistogramWidget::Private::HistogramDataLoading)) + + if (!isEnabled() || + (d->state == HistogramWidget::Private::HistogramNone) || + (!d->showProgress && ((d->state == HistogramWidget::Private::HistogramStarted) || + (d->state == HistogramWidget::Private::HistogramDataLoading))) ) { QPainter p1(this); p1.fillRect(0, 0, width(), height(), palette().color(QPalette::Disabled, QPalette::Window)); p1.setPen(QPen(palette().color(QPalette::Active, QPalette::WindowText), 1, Qt::SolidLine)); p1.drawRect(0, 0, width() - 1, height() - 1); QPen pen(palette().color(QPalette::Disabled, QPalette::WindowText)); pen.setStyle(Qt::SolidLine); pen.setWidth(1); p1.setPen(pen); p1.drawRect(0, 0, width(), height()); p1.end(); return; } else if (d->showProgress && - (d->state == HistogramWidget::Private::HistogramStarted || - d->state == HistogramWidget::Private::HistogramDataLoading) + ((d->state == HistogramWidget::Private::HistogramStarted) || + (d->state == HistogramWidget::Private::HistogramDataLoading)) ) { // Image data is loading or histogram is being computed, we draw a message. // In first, we draw an animation. QPixmap anim = d->progressPix.frameAt(d->animationState); // ... and we render busy text. QPainter p1(this); p1.fillRect(0, 0, width(), height(), palette().color(QPalette::Active, QPalette::Window)); p1.setPen(QPen(palette().color(QPalette::Active, QPalette::WindowText), 1, Qt::SolidLine)); p1.drawRect(0, 0, width() - 1, height() - 1); p1.drawPixmap(width() / 2 - anim.width() / 2, anim.height(), anim); p1.setPen(palette().color(QPalette::Active, QPalette::Text)); if (d->state == HistogramWidget::Private::HistogramDataLoading) { p1.drawText(0, 0, width(), height(), Qt::AlignCenter, i18n("Loading image...")); } else { p1.drawText(0, 0, width(), height(), Qt::AlignCenter, i18n("Histogram calculation...")); } p1.end(); + return; } else if (d->state == HistogramWidget::Private::HistogramFailed) { // Histogram computation failed, we draw a message. + QPainter p1(this); p1.fillRect(0, 0, width(), height(), palette().color(QPalette::Active, QPalette::Window)); p1.setPen(QPen(palette().color(QPalette::Active, QPalette::WindowText), 1, Qt::SolidLine)); p1.drawRect(0, 0, width() - 1, height() - 1); p1.setPen(palette().color(QPalette::Active, QPalette::Text)); p1.drawText(0, 0, width(), height(), Qt::AlignCenter, i18n("Histogram\ncalculation\nfailed.")); p1.end(); return; } // render histogram in normal case + ImageHistogram* histogram = nullptr; - if (d->renderingType == ImageSelectionHistogram && d->selectionHistogram) + if ((d->renderingType == ImageSelectionHistogram) && d->selectionHistogram) { histogram = d->selectionHistogram; } else { histogram = d->imageHistogram; } if (!histogram) { return; } d->histogramPainter->setHistogram(histogram); d->histogramPainter->setChannelType(d->channelType); d->histogramPainter->setScale(d->scaleType); d->histogramPainter->setSelection(d->xmin, d->xmax); d->histogramPainter->setHighlightSelection(d->selectMode); if (d->guideVisible == true) { d->histogramPainter->enableHistogramGuideByColor(d->colorGuide); } else { d->histogramPainter->disableHistogramGuide(); } // A QPixmap is used to enable the double buffering. + QPixmap bufferPixmap(size()); d->histogramPainter->initFrom(this); d->histogramPainter->render(bufferPixmap); // render the pixmap on the widget + QPainter p2(this); p2.drawPixmap(0, 0, bufferPixmap); p2.end(); // render statistics if needed + if (d->statisticsVisible) { DToolTipStyleSheet cnt; QString tipText, value; tipText = QLatin1String(""); tipText += cnt.cellBeg + i18n("Mean:") + cnt.cellMid; double mean = histogram->getMean(d->channelType, 0, histogram->getHistogramSegments() - 1); tipText += value.setNum(mean, 'f', 1) + cnt.cellEnd; tipText += cnt.cellBeg + i18n("Pixels:") + cnt.cellMid; double pixels = histogram->getPixels(); tipText += value.setNum((float)pixels, 'f', 0) + cnt.cellEnd; tipText += cnt.cellBeg + i18n("Std dev.:") + cnt.cellMid; double stddev = histogram->getStdDev(d->channelType, 0, histogram->getHistogramSegments() - 1); tipText += value.setNum(stddev, 'f', 1) + cnt.cellEnd; tipText += cnt.cellBeg + i18n("Count:") + cnt.cellMid; double counts = histogram->getCount(d->channelType, 0, histogram->getHistogramSegments() - 1); tipText += value.setNum((float)counts, 'f', 0) + cnt.cellEnd; tipText += cnt.cellBeg + i18n("Median:") + cnt.cellMid; double median = histogram->getMedian(d->channelType, 0, histogram->getHistogramSegments() - 1); tipText += value.setNum(median, 'f', 1) + cnt.cellEnd; tipText += cnt.cellBeg + i18n("Percent:") + cnt.cellMid; double percentile = (pixels > 0 ? (100.0 * counts / pixels) : 0.0); tipText += value.setNum(percentile, 'f', 1) + cnt.cellEnd; tipText += QLatin1String("
"); setToolTip(tipText); } } void HistogramWidget::mousePressEvent(QMouseEvent* e) { - if (d->selectMode == true && d->state == HistogramWidget::Private::HistogramCompleted) + if ((d->selectMode == true) && (d->state == HistogramWidget::Private::HistogramCompleted)) { if (!d->inSelected) { d->inSelected = true; update(); } d->xmin = ((double)e->pos().x()) / ((double)width()); d->xminOrg = d->xmin; d->xmax = d->xmin; notifyValuesChanged(); } } void HistogramWidget::mouseReleaseEvent(QMouseEvent*) { - if (d->selectMode == true && d->state == HistogramWidget::Private::HistogramCompleted) + if ((d->selectMode == true) && (d->state == HistogramWidget::Private::HistogramCompleted)) { d->inSelected = false; // Only single click without mouse move? Remove selection. if (d->xmax == d->xmin) { d->xmin = 0.0; d->xmax = 0.0; notifyValuesChanged(); update(); } } } void HistogramWidget::mouseMoveEvent(QMouseEvent* e) { - if (d->selectMode == true && d->state == HistogramWidget::Private::HistogramCompleted) + if ((d->selectMode == true) && (d->state == HistogramWidget::Private::HistogramCompleted)) { setCursor(Qt::CrossCursor); if (d->inSelected) { double max = ((double)e->pos().x()) / ((double)width()); if (max < d->xminOrg) { d->xmax = d->xminOrg; d->xmin = max; } else { d->xmin = d->xminOrg; d->xmax = max; } notifyValuesChanged(); update(); } } } void HistogramWidget::notifyValuesChanged() { emit signalIntervalChanged((int)(d->xmin * d->range), d->xmax == 0.0 ? d->range : (int)(d->xmax * d->range)); } void HistogramWidget::slotMinValueChanged(int min) { - if (d->selectMode == true && d->state == HistogramWidget::Private::HistogramCompleted) + if ((d->selectMode == true) && (d->state == HistogramWidget::Private::HistogramCompleted)) { - if (min == 0 && d->xmax == 1.0) + if ((min == 0) && (d->xmax == 1.0)) { // everything is selected means no selection d->xmin = 0.0; d->xmax = 0.0; } - if (min >= 0 && min < d->range) + if ((min >= 0) && (min < d->range)) { d->xmin = ((double)min) / d->range; } update(); } } void HistogramWidget::slotMaxValueChanged(int max) { - if (d->selectMode == true && d->state == HistogramWidget::Private::HistogramCompleted) + if ((d->selectMode == true) && (d->state == HistogramWidget::Private::HistogramCompleted)) { - if (d->xmin == 0.0 && max == d->range) + if ((d->xmin == 0.0) && (max == d->range)) { // everything is selected means no selection d->xmin = 0.0; d->xmax = 0.0; } - else if (max > 0 && max <= d->range) + else if ((max > 0) && (max <= d->range)) { d->xmax = ((double)max) / d->range; } update(); } } void HistogramWidget::setChannelType(ChannelType channel) { d->channelType = channel; update(); } ChannelType HistogramWidget::channelType() const { return d->channelType; } void HistogramWidget::setScaleType(HistogramScale scale) { d->scaleType = scale; update(); } HistogramScale HistogramWidget::scaleType() const { return d->scaleType; } } // namespace Digikam diff --git a/core/libs/dimg/filters/levels/histogramwidget.h b/core/libs/dimg/filters/levels/histogramwidget.h index 49d182ba90..ef825800f4 100644 --- a/core/libs/dimg/filters/levels/histogramwidget.h +++ b/core/libs/dimg/filters/levels/histogramwidget.h @@ -1,142 +1,146 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2004-07-21 * Description : a widget to display an image histogram. * * Copyright (C) 2004-2020 by Gilles Caulier * * This program is free software; you can redistribute it * and/or modify it under the terms of the GNU General * Public License as published by the Free Software Foundation; * either version 2, or (at your option) * any later version. * * This program 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 General Public License for more details. * * ============================================================ */ #ifndef DIGIKAM_HISTOGRAM_WIDGET_H #define DIGIKAM_HISTOGRAM_WIDGET_H // Qt includes #include #include #include #include // Local includes #include "dimg.h" #include "dcolor.h" #include "digikam_debug.h" #include "digikam_export.h" #include "digikam_globals.h" namespace Digikam { class ImageHistogram; class DIGIKAM_EXPORT HistogramWidget : public QWidget { Q_OBJECT Q_PROPERTY(int animationState READ animationState WRITE setAnimationState) public: - /** Standard constructor. Needed to use updateData() methods after to create valid instance. + /** + * Standard constructor. Needed to use updateData() methods after to create valid instance. */ - HistogramWidget(int w, int h, // Widget size. + HistogramWidget(int w, int h, // Widget size. QWidget* const parent=nullptr, bool selectMode=true, bool showProgress=true, bool statisticsVisible=false); ~HistogramWidget(); - /** Stop current histogram computations. + /** + * Stop current histogram computations. */ void stopHistogramComputation(); - /** Update full image histogram data methods. + /** + * Update full image histogram data methods. */ void updateData(const DImg& img, // full image data. const DImg& sel=DImg(), // selection image data bool showProgress=true); - /** Update image selection histogram data methods. + /** + * Update image selection histogram data methods. */ void updateSelectionData(const DImg& sel, bool showProgress=true); void setDataLoading(); void setLoadingFailed(); void setHistogramGuideByColor(const DColor& color); void setStatisticsVisible(bool b); void reset(); - HistogramScale scaleType() const; - ChannelType channelType() const; + HistogramScale scaleType() const; + ChannelType channelType() const; - int animationState() const; + int animationState() const; void setAnimationState(int animationState); void setRenderingType(HistogramRenderingType type); - HistogramRenderingType renderingType() const; + HistogramRenderingType renderingType() const; /** Currently rendered histogram, depending on current rendering type. */ - ImageHistogram* currentHistogram() const; + ImageHistogram* currentHistogram() const; Q_SIGNALS: void signalIntervalChanged(int min, int max); void signalMaximumValueChanged(int); void signalHistogramComputationDone(bool); void signalHistogramComputationFailed(); public Q_SLOTS: void slotMinValueChanged(int min); void slotMaxValueChanged(int max); void setChannelType(ChannelType channel); void setScaleType(HistogramScale scale); protected Q_SLOTS: void slotCalculationAboutToStart(); void slotCalculationFinished(bool success); protected: - void paintEvent(QPaintEvent*) override; - void mousePressEvent(QMouseEvent*) override; - void mouseReleaseEvent(QMouseEvent*) override; - void mouseMoveEvent(QMouseEvent*) override; + void paintEvent(QPaintEvent*) override; + void mousePressEvent(QMouseEvent*) override; + void mouseReleaseEvent(QMouseEvent*) override; + void mouseMoveEvent(QMouseEvent*) override; private: void notifyValuesChanged(); void connectHistogram(const ImageHistogram* const histogram); void setup(int w, int h, bool selectMode, bool statisticsVisible); void setState(int state); void startWaitingAnimation(); void stopWaitingAnimation(); private: class Private; Private* const d; }; } // namespace Digikam #endif // DIGIKAM_HISTOGRAM_WIDGET_H diff --git a/core/libs/dimg/filters/levels/imagehistogram.cpp b/core/libs/dimg/filters/levels/imagehistogram.cpp index 80c125422d..2a5bef2f44 100644 --- a/core/libs/dimg/filters/levels/imagehistogram.cpp +++ b/core/libs/dimg/filters/levels/imagehistogram.cpp @@ -1,696 +1,709 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2004-07-21 * Description : image histogram manipulation methods. * * Copyright (C) 2004-2020 by Gilles Caulier * * This program is free software; you can redistribute it * and/or modify it under the terms of the GNU General * Public License as published by the Free Software Foundation; * either version 2, or (at your option) * any later version. * * This program 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 General Public License for more details. * * ============================================================ */ #include "imagehistogram.h" // C++ includes #include #include #include // Qt includes #include // Local includes #include "dimg.h" #include "digikam_debug.h" #include "digikam_globals.h" namespace Digikam { class Q_DECL_HIDDEN ImageHistogram::Private { public: - // NOTE : Using a structure instead a class is more fast + // NOTE : Using a structure instead a class is more faster // (access with memset() and bytes manipulation). struct double_packet { double value; double red; double green; double blue; double alpha; }; public: explicit Private() + : histogram(nullptr), + valid(false), + histoSegments(0) { - histogram = nullptr; - histoSegments = 0; - valid = false; } public: - /** The histogram data.*/ + /** + * The histogram data. + */ struct double_packet* histogram; bool valid; - /** Image information.*/ + /** + * Image information. + */ DImg img; - /** Numbers of histogram segments depending of image bytes depth*/ + /** + * Numbers of histogram segments depending of image bytes depth + */ int histoSegments; }; ImageHistogram::ImageHistogram(const DImg& img, QObject* const parent) : DynamicThread(parent), d(new Private) { // A simple copy of reference must be enough instead a deep copy. See this BKO comment for details: // https://bugs.kde.org/show_bug.cgi?id=274555#c40 - //d->img = img.copy(); +/* + d->img = img.copy(); +*/ d->img = img; d->histoSegments = d->img.sixteenBit() ? NUM_SEGMENTS_16BIT : NUM_SEGMENTS_8BIT; } ImageHistogram::~ImageHistogram() { stopCalculation(); if (d->histogram) { delete [] d->histogram; } delete d; } bool ImageHistogram::isSixteenBit() const { return d->img.sixteenBit(); } int ImageHistogram::getHistogramSegments() const { return d->histoSegments; } int ImageHistogram::getMaxSegmentIndex() const { return (d->histoSegments - 1); } void ImageHistogram::calculateInThread() { // this is done in an extra method and not in the constructor // to allow to connect to the signals, which is only possible after construction + if (!d->img.isNull()) { emit calculationAboutToStart(); start(); } else { emit calculationFinished(false); } } void ImageHistogram::stopCalculation() { stop(); wait(); } bool ImageHistogram::isValid() const { return d->valid; } bool ImageHistogram::isCalculating() const { return isRunning(); } // List of threaded operations. void ImageHistogram::run() { calculate(); } void ImageHistogram::calculate() { // TODO this gets even called with null img + if (d->img.isNull()) { emit calculationFinished(false); return; } // check if the calculation has been done before + if (d->histogram && d->valid) { emit calculationFinished(true); return; } uint i; int max; emit calculationStarted(); if (!d->histogram) { d->histogram = new Private::double_packet[d->histoSegments]; } if (!d->histogram) { qCWarning(DIGIKAM_DIMG_LOG) << ("HistogramWidget::calcHistogramValues: Unable to allocate memory!"); emit calculationFinished(false); return; } memset(d->histogram, 0, d->histoSegments * sizeof(struct Private::double_packet)); if (isSixteenBit()) // 16 bits image. { unsigned short blue, green, red, alpha; unsigned short* const data = reinterpret_cast(d->img.bits()); // count here instead of inside the loop, because d is not optimized because it's not defined in the header + const uint count = d->img.width() * d->img.height() * 4; for (i = 0 ; runningFlag() && (i < count) ; i += 4) { blue = data[i ]; green = data[i + 1]; red = data[i + 2]; alpha = data[i + 3]; d->histogram[blue].blue++; d->histogram[green].green++; d->histogram[red].red++; d->histogram[alpha].alpha++; max = (blue > green) ? blue : green; if (red > max) { d->histogram[red].value++; } else { d->histogram[max].value++; } } } else // 8 bits images. { uchar blue, green, red, alpha; const uchar* const data = d->img.bits(); // count here instead of inside the loop, because d is not optimized because it's not defined in the header + const uint count = d->img.width() * d->img.height() * 4; for (i = 0 ; runningFlag() && (i < count) ; i += 4) { blue = data[i ]; green = data[i + 1]; red = data[i + 2]; alpha = data[i + 3]; d->histogram[blue].blue++; d->histogram[green].green++; d->histogram[red].red++; d->histogram[alpha].alpha++; max = (blue > green) ? blue : green; if (red > max) { d->histogram[red].value++; } else { d->histogram[max].value++; } } } if (runningFlag()) { d->valid = true; emit calculationFinished(true); } } double ImageHistogram::getCount(int channel, int start, int end) const { int i; double count = 0.0; - if (!d->histogram || start < 0 || - end > d->histoSegments - 1 || start > end) + if (!d->histogram || (start < 0) || + (end > d->histoSegments - 1) || (start > end)) { return 0.0; } switch (channel) { case LuminosityChannel: for (i = start ; i <= end ; ++i) { count += d->histogram[i].value; } break; case RedChannel: for (i = start ; i <= end ; ++i) { count += d->histogram[i].red; } break; case GreenChannel: for (i = start ; i <= end ; ++i) { count += d->histogram[i].green; } break; case BlueChannel: for (i = start ; i <= end ; ++i) { count += d->histogram[i].blue; } break; case AlphaChannel: for (i = start ; i <= end ; ++i) { count += d->histogram[i].alpha; } break; } return count; } double ImageHistogram::getPixels() const { if (!d->histogram) { return 0.0; } return (double)(d->img.numPixels()); } double ImageHistogram::getMean(int channel, int start, int end) const { int i; double mean = 0.0; double count; - if (!d->histogram || start < 0 || - end > d->histoSegments - 1 || start > end) + if (!d->histogram || (start < 0) || + (end > d->histoSegments - 1) || (start > end)) { return 0.0; } switch (channel) { case LuminosityChannel: for (i = start ; i <= end ; ++i) { mean += i * d->histogram[i].value; } break; case RedChannel: for (i = start ; i <= end ; ++i) { mean += i * d->histogram[i].red; } break; case GreenChannel: for (i = start ; i <= end ; ++i) { mean += i * d->histogram[i].green; } break; case BlueChannel: for (i = start ; i <= end ; ++i) { mean += i * d->histogram[i].blue; } break; case AlphaChannel: for (i = start ; i <= end ; ++i) { mean += i * d->histogram[i].alpha; } break; default: return 0.0; } count = getCount(channel, start, end); if (count > 0.0) { return (mean / count); } return mean; } int ImageHistogram::getMedian(int channel, int start, int end) const { int i; double sum = 0.0; double count; - if (!d->histogram || start < 0 || - end > d->histoSegments - 1 || start > end) + if (!d->histogram || (start < 0) || + (end > d->histoSegments - 1) || (start > end)) { return 0; } count = getCount(channel, start, end); switch (channel) { case LuminosityChannel: for (i = start ; i <= end ; ++i) { sum += d->histogram[i].value; if (sum * 2 > count) { return i; } } break; case RedChannel: for (i = start ; i <= end ; ++i) { sum += d->histogram[i].red; if (sum * 2 > count) { return i; } } break; case GreenChannel: for (i = start ; i <= end ; ++i) { sum += d->histogram[i].green; if (sum * 2 > count) { return i; } } break; case BlueChannel: for (i = start ; i <= end ; ++i) { sum += d->histogram[i].blue; if (sum * 2 > count) { return i; } } break; case AlphaChannel: for (i = start ; i <= end ; ++i) { sum += d->histogram[i].alpha; if (sum * 2 > count) { return i; } } break; default: return 0; } return 0; } double ImageHistogram::getStdDev(int channel, int start, int end) const { int i; double dev = 0.0; double count; double mean; - if (!d->histogram || start < 0 || - end > d->histoSegments - 1 || start > end) + if (!d->histogram || (start < 0) || + (end > d->histoSegments - 1) || (start > end)) { return 0.0; } mean = getMean(channel, start, end); count = getCount(channel, start, end); if (count == 0.0) { count = 1.0; } switch (channel) { case LuminosityChannel: for (i = start ; i <= end ; ++i) { dev += (i - mean) * (i - mean) * d->histogram[i].value; } break; case RedChannel: for (i = start ; i <= end ; ++i) { dev += (i - mean) * (i - mean) * d->histogram[i].red; } break; case GreenChannel: for (i = start ; i <= end ; ++i) { dev += (i - mean) * (i - mean) * d->histogram[i].green; } break; case BlueChannel: for (i = start ; i <= end ; ++i) { dev += (i - mean) * (i - mean) * d->histogram[i].blue; } break; case AlphaChannel: for (i = start ; i <= end ; ++i) { dev += (i - mean) * (i - mean) * d->histogram[i].alpha; } break; default: return 0.0; } return sqrt(dev / count); } double ImageHistogram::getValue(int channel, int bin) const { double value; - if (!d->histogram || bin < 0 || bin > d->histoSegments - 1) + if (!d->histogram || (bin < 0) || (bin > d->histoSegments - 1)) { return 0.0; } switch (channel) { case LuminosityChannel: value = d->histogram[bin].value; break; case RedChannel: value = d->histogram[bin].red; break; case GreenChannel: value = d->histogram[bin].green; break; case BlueChannel: value = d->histogram[bin].blue; break; case AlphaChannel: value = d->histogram[bin].alpha; break; default: return 0.0; } return value; } double ImageHistogram::getMaximum(int channel, int start, int end) const { double max = 0.0; int x; - if (!d->histogram || start < 0 || - end > d->histoSegments - 1 || start > end) + if (!d->histogram || (start < 0) || + (end > d->histoSegments - 1) || (start > end)) { return 0.0; } switch (channel) { case LuminosityChannel: for (x = start ; x <= end ; ++x) { if (d->histogram[x].value > max) { max = d->histogram[x].value; } } break; case RedChannel: for (x = start ; x <= end ; ++x) { if (d->histogram[x].red > max) { max = d->histogram[x].red; } } break; case GreenChannel: for (x = start ; x <= end ; ++x) { if (d->histogram[x].green > max) { max = d->histogram[x].green; } } break; case BlueChannel: for (x = start ; x <= end ; ++x) { if (d->histogram[x].blue > max) { max = d->histogram[x].blue; } } break; case AlphaChannel: for (x = start ; x <= end ; ++x) { if (d->histogram[x].alpha > max) { max = d->histogram[x].alpha; } } break; default: return 0.0; } return max; } } // namespace Digikam diff --git a/core/libs/dimg/filters/levels/imagehistogram.h b/core/libs/dimg/filters/levels/imagehistogram.h index c5cf6c98f5..a5764b3189 100644 --- a/core/libs/dimg/filters/levels/imagehistogram.h +++ b/core/libs/dimg/filters/levels/imagehistogram.h @@ -1,101 +1,107 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2004-07-21 * Description : image histogram manipulation methods. * * Copyright (C) 2004-2020 by Gilles Caulier * * This program is free software; you can redistribute it * and/or modify it under the terms of the GNU General * Public License as published by the Free Software Foundation; * either version 2, or (at your option) * any later version. * * This program 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 General Public License for more details. * * ============================================================ */ #ifndef DIGIKAM_IMAGE_HISTOGRAM_H #define DIGIKAM_IMAGE_HISTOGRAM_H // Qt includes #include #include #include // Local includes #include "digikam_export.h" #include "dynamicthread.h" namespace Digikam { class DImg; class DIGIKAM_EXPORT ImageHistogram : public DynamicThread { Q_OBJECT public: explicit ImageHistogram(const DImg& img, QObject* const parent = nullptr); ~ImageHistogram(); /** * Started computation: synchronous or threaded. */ void calculate(); void calculateInThread(); /** * Stop threaded computation. */ void stopCalculation(); - bool isCalculating() const; + bool isCalculating() const; /** * Methods to access the histogram data. */ - bool isSixteenBit() const; - bool isValid() const; + bool isSixteenBit() const; + bool isValid() const; double getCount(int channel, int start, int end) const; double getMean(int channel, int start, int end) const; double getPixels() const; double getStdDev(int channel, int start, int end) const; double getValue(int channel, int bin) const; double getMaximum(int channel, int start, int end) const; int getHistogramSegments() const; int getMaxSegmentIndex() const; int getMedian(int channel, int start, int end) const; Q_SIGNALS: void calculationFinished(bool success); - /// when calculation in thread is initiated, from other thread + + /** + * when calculation in thread is initiated, from other thread + */ void calculationAboutToStart(); - /// emitted from calculation thread + + /** + * emitted from calculation thread + */ void calculationStarted(); protected: virtual void run() override; private: class Private; Private* const d; }; } // namespace Digikam #endif // DIGIKAM_IMAGE_HISTOGRAM_H diff --git a/core/libs/dimg/filters/levels/imagelevels.cpp b/core/libs/dimg/filters/levels/imagelevels.cpp index 9544251652..6ebbd9d43d 100644 --- a/core/libs/dimg/filters/levels/imagelevels.cpp +++ b/core/libs/dimg/filters/levels/imagelevels.cpp @@ -1,818 +1,820 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2004-07-29 * Description : image levels manipulation methods. * * Copyright (C) 2004-2020 by Gilles Caulier * * This program is free software; you can redistribute it * and/or modify it under the terms of the GNU General * Public License as published by the Free Software Foundation; * either version 2, or (at your option) * any later version. * * This program 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 General Public License for more details. * * ============================================================ */ -/* Map RGB to intensity */ - +/** + * Map RGB to intensity + */ #define LEVELS_RGB_INTENSITY_RED 0.30 #define LEVELS_RGB_INTENSITY_GREEN 0.59 #define LEVELS_RGB_INTENSITY_BLUE 0.11 #define LEVELS_RGB_INTENSITY(r,g,b) ((r) * LEVELS_RGB_INTENSITY_RED + \ (g) * LEVELS_RGB_INTENSITY_GREEN + \ (b) * LEVELS_RGB_INTENSITY_BLUE) #include "imagelevels.h" // Qt includes #include // C++ includes #include #include #include #include #include // Local includes #include "digikam_debug.h" #include "imagehistogram.h" #include "digikam_globals.h" namespace Digikam { class Q_DECL_HIDDEN ImageLevels::Private { public: enum PixelType { RedPixel = 0, GreenPixel, BluePixel, AlphaPixel }; struct _Levels { double gamma[5]; int low_input[5]; int high_input[5]; int low_output[5]; int high_output[5]; }; struct _Lut { unsigned short** luts; int nchannels; }; public: explicit Private() + : levels(nullptr), + lut(nullptr), + sixteenBit(false), + dirty(false) { - levels = nullptr; - lut = nullptr; - dirty = false; - sixteenBit = false; } - // Levels data. + /// Levels data. struct _Levels* levels; - // Lut data. + /// Lut data. struct _Lut* lut; bool sixteenBit; bool dirty; }; ImageLevels::ImageLevels(bool sixteenBit) : d(new Private) { d->lut = new Private::_Lut; d->levels = new Private::_Levels; d->sixteenBit = sixteenBit; memset(d->levels, 0, sizeof(struct Private::_Levels)); d->lut->luts = nullptr; d->lut->nchannels = 0; reset(); } ImageLevels::~ImageLevels() { if (d->lut) { if (d->lut->luts) { for (int i = 0 ; i < d->lut->nchannels ; ++i) { delete [] d->lut->luts[i]; } delete [] d->lut->luts; } delete d->lut; } if (d->levels) { delete d->levels; } delete d; } bool ImageLevels::isDirty() { return d->dirty; } bool ImageLevels::isSixteenBits() { return d->sixteenBit; } void ImageLevels::reset() { for (int channel = 0 ; channel < 5 ; ++channel) { levelsChannelReset(channel); } } void ImageLevels::levelsChannelReset(int channel) { if (!d->levels) { return; } d->levels->gamma[channel] = 1.0; d->levels->low_input[channel] = 0; d->levels->high_input[channel] = d->sixteenBit ? 65535 : 255; d->levels->low_output[channel] = 0; d->levels->high_output[channel] = d->sixteenBit ? 65535 : 255; d->dirty = false; } void ImageLevels::levelsAuto(ImageHistogram* const hist) { if (!d->levels || !hist) { return; } levelsChannelReset(LuminosityChannel); for (int channel = RedChannel ; channel <= BlueChannel ; ++channel) { levelsChannelAuto(hist, channel); } d->dirty = true; } void ImageLevels::levelsChannelAuto(ImageHistogram* const hist, int channel) { if (!d->levels || !hist) { return; } d->levels->gamma[channel] = 1.0; d->levels->low_output[channel] = 0; d->levels->high_output[channel] = d->sixteenBit ? 65535 : 255; double count = hist->getCount(channel, 0, d->sixteenBit ? 65535 : 255); if (count == 0.0) { d->levels->low_input[channel] = 0; d->levels->high_input[channel] = 0; } else { // Set the low input double new_count = 0.0; double percentage = 0.0; double next_percentage = 0.0; int i; for (i = 0 ; i < (d->sixteenBit ? 65535 : 255) ; ++i) { new_count += hist->getValue(channel, i); percentage = new_count / count; next_percentage = (new_count + hist->getValue(channel, i + 1)) / count; if (fabs(percentage - 0.006) < fabs(next_percentage - 0.006)) { d->levels->low_input[channel] = i + 1; break; } } // Set the high input new_count = 0.0; for (i = (d->sixteenBit ? 65535 : 255) ; i > 0 ; --i) { new_count += hist->getValue(channel, i); percentage = new_count / count; next_percentage = (new_count + hist->getValue(channel, i - 1)) / count; if (fabs(percentage - 0.006) < fabs(next_percentage - 0.006)) { d->levels->high_input[channel] = i - 1; break; } } } d->dirty = true; } int ImageLevels::levelsInputFromColor(int channel, const DColor& color) { switch (channel) { case LuminosityChannel: return qMax(qMax(color.red(), color.green()), color.blue()); case RedChannel: return color.red(); case GreenChannel: return color.green(); case BlueChannel: return color.blue(); } return 0; // just to please the compiler. } void ImageLevels::levelsBlackToneAdjustByColors(int channel, const DColor& color) { if (!d->levels) { return; } d->levels->low_input[channel] = levelsInputFromColor(channel, color); d->dirty = true; } void ImageLevels::levelsWhiteToneAdjustByColors(int channel, const DColor& color) { if (!d->levels) { return; } d->levels->high_input[channel] = levelsInputFromColor(channel, color); d->dirty = true; } void ImageLevels::levelsGrayToneAdjustByColors(int channel, const DColor& color) { if (!d->levels) { return; } int input; int range; double inten; double out_light; unsigned short lightness; // Calculate lightness value. lightness = (unsigned short)LEVELS_RGB_INTENSITY(color.red(), color.green(), color.blue()); input = levelsInputFromColor(channel, color); range = d->levels->high_input[channel] - d->levels->low_input[channel]; if (range <= 0) { return; } input -= d->levels->low_input[channel]; if (input < 0) { return; } // Normalize input and lightness. inten = (double) input / (double) range; out_light = (double) lightness / (double) range; if (out_light <= 0) { return; } // Map selected color to corresponding lightness. d->levels->gamma[channel] = log(inten) / log(out_light); d->dirty = true; } void ImageLevels::levelsCalculateTransfers() { double inten; int i, j; if (!d->levels) { return; } // Recalculate the levels arrays. for (j = 0 ; j < 5 ; ++j) { for (i = 0 ; i <= (d->sixteenBit ? 65535 : 255) ; ++i) { // determine input intensity. if (d->levels->high_input[j] != d->levels->low_input[j]) { inten = ((double)(i - d->levels->low_input[j]) / (double)(d->levels->high_input[j] - d->levels->low_input[j])); } else { inten = (double)(i - d->levels->low_input[j]); } inten = CLAMP(inten, 0.0, 1.0); if (d->levels->gamma[j] != 0.0) { inten = pow(inten, (1.0 / d->levels->gamma[j])); (void)inten; // Remove clang warnings. } } } } float ImageLevels::levelsLutFunc(int n_channels, int channel, float value) { double inten; int j; if (!d->levels) { return 0.0; } if (n_channels == 1) { j = 0; } else { j = channel + 1; } inten = value; // For color images this runs through the loop with j = channel +1 // the first time and j = 0 the second time. // // For bw images this runs through the loop with j = 0 the first and // only time. - for (; j >= 0 ; j -= (channel + 1)) + for ( ; j >= 0 ; j -= (channel + 1)) { // Don't apply the overall curve to the alpha channel. - if (j == 0 && (n_channels == 2 || n_channels == 4) && channel == n_channels - 1) + if ((j == 0) && ((n_channels == 2) || (n_channels == 4)) && (channel == n_channels - 1)) { return inten; } // Determine input intensity. if (d->levels->high_input[j] != d->levels->low_input[j]) { inten = ((double)((float)(d->sixteenBit ? 65535 : 255) * inten - d->levels->low_input[j]) / (double)(d->levels->high_input[j] - d->levels->low_input[j])); } else { inten = (double)((float)(d->sixteenBit ? 65535 : 255) * inten - d->levels->low_input[j]); } if (d->levels->gamma[j] != 0.0) { if (inten >= 0.0) { inten = pow(inten, (1.0 / d->levels->gamma[j])); } else { inten = -pow(-inten, (1.0 / d->levels->gamma[j])); } } // determine the output intensity. if (d->levels->high_output[j] >= d->levels->low_output[j]) { inten = (double)(inten * (d->levels->high_output[j] - d->levels->low_output[j]) + d->levels->low_output[j]); } else { inten = (double)(d->levels->low_output[j] - inten * (d->levels->low_output[j] - d->levels->high_output[j])); } inten /= (float)(d->sixteenBit ? 65535 : 255); } return inten; } void ImageLevels::levelsLutSetup(int nchannels) { int i; uint v; double val; if (d->lut->luts) { for (i = 0 ; i < d->lut->nchannels ; ++i) { delete [] d->lut->luts[i]; } delete [] d->lut->luts; } d->lut->nchannels = nchannels; d->lut->luts = new unsigned short*[d->lut->nchannels]; for (i = 0 ; i < d->lut->nchannels ; ++i) { d->lut->luts[i] = new unsigned short[(d->sixteenBit ? 65535 : 255) + 1]; for (v = 0 ; v <= (uint)(d->sixteenBit ? 65535 : 255) ; ++v) { // to add gamma correction use func(v ^ g) ^ 1/g instead. val = (float)(d->sixteenBit ? 65535 : 255) * levelsLutFunc(d->lut->nchannels, i, v / (float)(d->sixteenBit ? 65535 : 255)) + 0.5; d->lut->luts[i][v] = (unsigned short)CLAMP(val, 0.0, (d->sixteenBit ? 65535.0 : 255.0)); } } } void ImageLevels::levelsLutProcess(uchar* const srcPR, uchar* const destPR, int w, int h) { unsigned short* lut0 = nullptr, *lut1 = nullptr, *lut2 = nullptr, *lut3 = nullptr; int i; if (d->lut->nchannels > 0) { lut0 = d->lut->luts[0]; } if (d->lut->nchannels > 1) { lut1 = d->lut->luts[1]; } if (d->lut->nchannels > 2) { lut2 = d->lut->luts[2]; } if (d->lut->nchannels > 3) { lut3 = d->lut->luts[3]; } if (!d->sixteenBit) // 8 bits image. { uchar red, green, blue, alpha; uchar* ptr = srcPR; uchar* dst = destPR; for (i = 0 ; i < w * h ; ++i) { blue = ptr[0]; green = ptr[1]; red = ptr[2]; alpha = ptr[3]; if (d->lut->nchannels > 0) { red = lut0[red]; } if (d->lut->nchannels > 1) { green = lut1[green]; } if (d->lut->nchannels > 2) { blue = lut2[blue]; } if (d->lut->nchannels > 3) { alpha = lut3[alpha]; } dst[0] = blue; dst[1] = green; dst[2] = red; dst[3] = alpha; - ptr += 4; - dst += 4; + ptr += 4; + dst += 4; } } else // 16 bits image. { unsigned short red, green, blue, alpha; unsigned short* ptr = reinterpret_cast(srcPR); unsigned short* dst = reinterpret_cast(destPR); for (i = 0 ; i < w * h ; ++i) { blue = ptr[0]; green = ptr[1]; red = ptr[2]; alpha = ptr[3]; if (d->lut->nchannels > 0) { red = lut0[red]; } if (d->lut->nchannels > 1) { green = lut1[green]; } if (d->lut->nchannels > 2) { blue = lut2[blue]; } if (d->lut->nchannels > 3) { alpha = lut3[alpha]; } dst[0] = blue; dst[1] = green; dst[2] = red; dst[3] = alpha; - ptr += 4; - dst += 4; + ptr += 4; + dst += 4; } } } void ImageLevels::setLevelGammaValue(int channel, double val) { - if (d->levels && channel >= 0 && channel < 5) + if (d->levels && (channel >= 0) && (channel < 5)) { d->levels->gamma[channel] = val; d->dirty = true; } } void ImageLevels::setLevelLowInputValue(int channel, int val) { - if (d->levels && channel >= 0 && channel < 5) + if (d->levels && (channel >= 0) && (channel < 5)) { d->levels->low_input[channel] = val; d->dirty = true; } } void ImageLevels::setLevelHighInputValue(int channel, int val) { - if (d->levels && channel >= 0 && channel < 5) + if (d->levels && (channel >= 0) && (channel < 5)) { d->levels->high_input[channel] = val; d->dirty = true; } } void ImageLevels::setLevelLowOutputValue(int channel, int val) { - if (d->levels && channel >= 0 && channel < 5) + if (d->levels && (channel >= 0) && (channel < 5)) { d->levels->low_output[channel] = val; d->dirty = true; } } void ImageLevels::setLevelHighOutputValue(int channel, int val) { - if (d->levels && channel >= 0 && channel < 5) + if (d->levels && (channel >= 0) && (channel < 5)) { d->levels->high_output[channel] = val; d->dirty = true; } } double ImageLevels::getLevelGammaValue(int channel) { - if (d->levels && channel >= 0 && channel < 5) + if (d->levels && (channel >= 0) && (channel < 5)) { return (d->levels->gamma[channel]); } return 0.0; } int ImageLevels::getLevelLowInputValue(int channel) { - if (d->levels && channel >= 0 && channel < 5) + if (d->levels && (channel >= 0) && (channel < 5)) { return (d->levels->low_input[channel]); } return 0; } int ImageLevels::getLevelHighInputValue(int channel) { - if (d->levels && channel >= 0 && channel < 5) + if (d->levels && (channel >= 0) && (channel < 5)) { return (d->levels->high_input[channel]); } return 0; } int ImageLevels::getLevelLowOutputValue(int channel) { - if (d->levels && channel >= 0 && channel < 5) + if (d->levels && (channel >= 0) && (channel < 5)) { return (d->levels->low_output[channel]); } return 0; } int ImageLevels::getLevelHighOutputValue(int channel) { - if (d->levels && channel >= 0 && channel < 5) + if (d->levels && (channel >= 0) && (channel < 5)) { return (d->levels->high_output[channel]); } return 0; } bool ImageLevels::loadLevelsFromGimpLevelsFile(const QUrl& fileUrl) { // TODO : support QUrl ! FILE* file = nullptr; int low_input[5]; int high_input[5]; int low_output[5]; int high_output[5]; double gamma[5]; int i, fields; char buf[50]; char* nptr = nullptr; file = fopen(QFile::encodeName(fileUrl.toLocalFile()).constData(), "r"); if (!file) { return false; } if (! fgets(buf, sizeof(buf), file)) { fclose(file); return false; } if (strcmp(buf, "# GIMP Levels File\n") != 0) { fclose(file); return false; } for (i = 0 ; i < 5 ; ++i) { // FIXME: scanf without field width limits can crash with huge input data + fields = fscanf(file, "%d %d %d %d ", &low_input[i], &high_input[i], &low_output[i], &high_output[i]); if (fields != 4) { qCWarning(DIGIKAM_DIMG_LOG) << "Invalid Gimp levels file!"; fclose(file); return false; } if (!fgets(buf, 50, file)) { qCWarning(DIGIKAM_DIMG_LOG) << "Invalid Gimp levels file!"; fclose(file); return false; } gamma[i] = strtod(buf, &nptr); if (buf == nptr || errno == ERANGE) { qCWarning(DIGIKAM_DIMG_LOG) << "Invalid Gimp levels file!"; fclose(file); return false; } } for (i = 0 ; i < 5 ; ++i) { setLevelGammaValue(i, gamma[i]); setLevelLowInputValue(i, d->sixteenBit ? low_input[i] * 255 : low_input[i]); setLevelHighInputValue(i, d->sixteenBit ? high_input[i] * 255 : high_input[i]); setLevelLowOutputValue(i, d->sixteenBit ? low_output[i] * 255 : low_output[i]); setLevelHighOutputValue(i, d->sixteenBit ? high_output[i] * 255 : high_output[i]); } fclose(file); return true; } bool ImageLevels::saveLevelsToGimpLevelsFile(const QUrl& fileUrl) { // TODO : support QUrl ! FILE* file = nullptr; int i; file = fopen(QFile::encodeName(fileUrl.toLocalFile()).constData(), "w"); if (!file) { return false; } fprintf(file, "# GIMP Levels File\n"); for (i = 0 ; i < 5 ; ++i) { char buf[256]; sprintf(buf, "%f", getLevelGammaValue(i)); fprintf(file, "%d %d %d %d %s\n", d->sixteenBit ? getLevelLowInputValue(i) / 255 : getLevelLowInputValue(i), d->sixteenBit ? getLevelHighInputValue(i) / 255 : getLevelHighInputValue(i), d->sixteenBit ? getLevelLowOutputValue(i) / 255 : getLevelLowOutputValue(i), d->sixteenBit ? getLevelHighInputValue(i) / 255 : getLevelHighInputValue(i), buf); } fflush(file); fclose(file); return true; } } // namespace Digikam diff --git a/core/libs/dimg/filters/levels/imagelevels.h b/core/libs/dimg/filters/levels/imagelevels.h index f22db7b162..4030564fd4 100644 --- a/core/libs/dimg/filters/levels/imagelevels.h +++ b/core/libs/dimg/filters/levels/imagelevels.h @@ -1,98 +1,101 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2004-07-29 * Description : image levels manipulation methods. * * Copyright (C) 2004-2020 by Gilles Caulier * * This program is free software; you can redistribute it * and/or modify it under the terms of the GNU General * Public License as published by the Free Software Foundation; * either version 2, or (at your option) * any later version. * * This program 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 General Public License for more details. * * ============================================================ */ #ifndef DIGIKAM_IMAGE_LEVELS_H #define DIGIKAM_IMAGE_LEVELS_H // Qt includes #include // Local includes #include "dcolor.h" #include "digikam_export.h" namespace Digikam { class ImageHistogram; class DIGIKAM_EXPORT ImageLevels { public: explicit ImageLevels(bool sixteenBit); ~ImageLevels(); bool isDirty(); bool isSixteenBits(); void reset(); - // Methods for to manipulate the levels data. - + /** + * Methods to manipulate the levels data. + */ void levelsChannelReset(int channel); void levelsAuto(ImageHistogram* const hist); void levelsChannelAuto(ImageHistogram* const hist, int channel); int levelsInputFromColor(int channel, const DColor& color); void levelsBlackToneAdjustByColors(int channel, const DColor& color); void levelsGrayToneAdjustByColors(int channel, const DColor& color); void levelsWhiteToneAdjustByColors(int channel, const DColor& color); void levelsCalculateTransfers(); float levelsLutFunc(int nchannels, int channel, float value); void levelsLutSetup(int nchannels); void levelsLutProcess(uchar* const srcPR, uchar* const destPR, int w, int h); - // Methods for to set manually the levels values. - + /** + * Methods to set manually the levels values. + */ void setLevelGammaValue(int channel, double val); void setLevelLowInputValue(int channel, int val); void setLevelHighInputValue(int channel, int val); void setLevelLowOutputValue(int channel, int val); void setLevelHighOutputValue(int channel, int val); double getLevelGammaValue(int channel); int getLevelLowInputValue(int channel); int getLevelHighInputValue(int channel); int getLevelLowOutputValue(int channel); int getLevelHighOutputValue(int channel); - // Methods for to save/load the levels values to/from a Gimp levels text file. - + /** + * Methods to save/load the levels values to/from a Gimp levels text file. + */ bool saveLevelsToGimpLevelsFile(const QUrl& fileUrl); bool loadLevelsFromGimpLevelsFile(const QUrl& fileUrl); private: // Hidden copy constructor and assignment operator. ImageLevels(const ImageLevels&); ImageLevels& operator=(const ImageLevels&); class Private; Private* const d; }; } // namespace Digikam #endif // DIGIKAM_IMAGE_LEVELS_H diff --git a/core/libs/dimg/filters/levels/levelsfilter.cpp b/core/libs/dimg/filters/levels/levelsfilter.cpp index 0beb1be722..2d6eb23f19 100644 --- a/core/libs/dimg/filters/levels/levelsfilter.cpp +++ b/core/libs/dimg/filters/levels/levelsfilter.cpp @@ -1,141 +1,142 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2010-25-02 * Description : Levels image filter * * Copyright (C) 2010-2020 by Gilles Caulier * Copyright (C) 2010 by Martin Klapetek * * This program is free software; you can redistribute it * and/or modify it under the terms of the GNU General * Public License as published by the Free Software Foundation; * either version 2, or (at your option) * any later version. * * This program 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 General Public License for more details. * * ============================================================ */ #include "levelsfilter.h" // KDE includes #include // Local includes #include "dimg.h" #include "imagelevels.h" namespace Digikam { LevelsFilter::LevelsFilter(QObject* const parent) : DImgThreadedFilter(parent) { initFilter(); } LevelsFilter::LevelsFilter(DImg* const orgImage, QObject* const parent, const LevelsContainer& settings) : DImgThreadedFilter(orgImage, parent, QLatin1String("LevelsFilter")), m_settings(settings) { initFilter(); } LevelsFilter::LevelsFilter(const LevelsContainer& settings, DImgThreadedFilter* const master, const DImg& orgImage, DImg& destImage, int progressBegin, int progressEnd) : DImgThreadedFilter(master, orgImage, destImage, progressBegin, progressEnd, QLatin1String("LevelsFilter")), m_settings(settings) { initFilter(); destImage = m_destImage; } LevelsFilter::~LevelsFilter() { cancelFilter(); } QString LevelsFilter::DisplayableName() { return QString::fromUtf8(I18N_NOOP("Levels Adjust Tool")); } void LevelsFilter::filterImage() { ImageLevels levels(m_orgImage.sixteenBit()); for (int i = 0 ; i < 5 ; ++i) { postProgress(i * 10); levels.setLevelLowInputValue(i, m_settings.lInput[i]); levels.setLevelHighInputValue(i, m_settings.hInput[i]); levels.setLevelLowOutputValue(i, m_settings.lOutput[i]); levels.setLevelHighOutputValue(i, m_settings.hOutput[i]); levels.setLevelGammaValue(i, m_settings.gamma[i]); } postProgress(50); m_destImage = DImg(m_orgImage.width(), m_orgImage.height(), m_orgImage.sixteenBit(), m_orgImage.hasAlpha()); postProgress(60); levels.levelsCalculateTransfers(); postProgress(70); // Process all channels Levels + levels.levelsLutSetup(AlphaChannel); postProgress(80); levels.levelsLutProcess(m_orgImage.bits(), m_destImage.bits(), m_orgImage.width(), m_orgImage.height()); postProgress(90); } FilterAction LevelsFilter::filterAction() { FilterAction action(FilterIdentifier(), CurrentVersion()); action.setDisplayableName(DisplayableName()); for (int i = 0 ; i < 5 ; ++i) { action.addParameter(QString::fromLatin1("gamma[%1]").arg(i), m_settings.gamma[i]); action.addParameter(QString::fromLatin1("hInput[%1]").arg(i), m_settings.hInput[i]); action.addParameter(QString::fromLatin1("hOutput[%1]").arg(i), m_settings.hOutput[i]); action.addParameter(QString::fromLatin1("lInput[%1]").arg(i), m_settings.lInput[i]); action.addParameter(QString::fromLatin1("lOutput[%1]").arg(i), m_settings.lOutput[i]); } return action; } void LevelsFilter::readParameters(const Digikam::FilterAction& action) { for (int i = 0 ; i < 5 ; ++i) { m_settings.gamma[i] = action.parameter(QString::fromLatin1("gamma[%1]").arg(i)).toDouble(); m_settings.hInput[i] = action.parameter(QString::fromLatin1("hInput[%1]").arg(i)).toInt(); m_settings.hOutput[i] = action.parameter(QString::fromLatin1("hOutput[%1]").arg(i)).toInt(); m_settings.lInput[i] = action.parameter(QString::fromLatin1("lInput[%1]").arg(i)).toInt(); m_settings.lOutput[i] = action.parameter(QString::fromLatin1("lOutput[%1]").arg(i)).toInt(); } } } // namespace Digikam