Krita Source Code Documentation
Loading...
Searching...
No Matches
KisHistogramPainter.cpp
Go to the documentation of this file.
1/*
2 * This file is part of Krita
3 *
4 * SPDX-FileCopyrightText: 2021 Deif Lou <ginoba@gmail.com>
5 *
6 * SPDX-License-Identifier: GPL-2.0-or-later
7 */
8
9#include <cmath>
10#include <algorithm>
11
12#include <QHash>
13#include <QPair>
14#include <QPolygonF>
15
16#include <kis_histogram.h>
17#include <KoColorSpace.h>
18#include <kis_algebra_2d.h>
19#include <KoColor.h>
21
22#include "KisHistogramPainter.h"
23
25{
26 QPolygonF linearHistogram;
28 quint32 highest;
31 QColor color;
32 QPainter::CompositionMode compositionMode;
33};
34
36{
37public:
38 static constexpr qreal maximumOrientationDeviation = M_PI / 16.0;
39 static constexpr int maximumNumberOfSimplifiedPoints = 3;
40 static constexpr qreal histogramHeightFactor = 0.9;
41 static constexpr qreal maximumNeighborWeight = 0.33;
42 static constexpr qreal percentageForPercentile = 0.98;
43
44 QHash<int, HistogramShapeInfo> histogramChannelShapeInfo;
47 qreal scale{1.0};
48 bool isLogarithmic{false};
49
50 QImage paintChannels(const QSize &imageSize, const QVector<int> &channels = {}, bool logarithmic = false);
51
52 static qreal orientationDeviation(const QPointF &A, const QPointF &B, const QPointF &C);
53 static QPair<QPolygonF, QPolygonF> computeHistogramShape(KisHistogram *histogram,
54 int channel,
55 quint32 highest);
56 static qreal bestCutOffHeight(QPolygonF polygon);
57 static void smoothHistogramShape(QPolygonF &polygon);
58 static void simplifyHistogramShape(QPolygonF &polygon);
59 static QPair<QColor, QPainter::CompositionMode> computeChannelPaintingInfo(const KoColorSpace *colorSpace,
60 int channel);
61 static void paintHistogramShape(QImage &image,
62 const QPolygonF &polygon,
63 qreal scale,
64 const QColor &color,
65 QPainter::CompositionMode compositionMode);
66};
67
69 const QPointF &B,
70 const QPointF &C)
71{
72 const QPointF AB = B - A;
73 const QPointF BC = C - B;
74 const qreal angle =KisAlgebra2D::angleBetweenVectors(AB, BC);
75 return angle < -M_PI ? angle + 2.0 * M_PI : (angle > M_PI ? angle - 2.0 * M_PI : angle);
76}
77
78QPair<QPolygonF, QPolygonF>
80 int channel,
81 quint32 highest)
82{
83 Q_ASSERT(histogram);
84 Q_ASSERT(channel >= 0);
85
86 QPolygonF linearHistogramShape, logarithmicHistogramShape;
87 const int bins = histogram->producer()->numberOfBins();
88 const qreal heightfactor = 1.0 / static_cast<qreal>(highest);
89 const qreal logHeightFactor = 1.0 / std::log(static_cast<qreal>(highest + 1));
90 const qreal widthFactor = 1.0 / static_cast<qreal>(bins);
91
92 // Add extra points at the beginning and end so that the shape is a bit
93 // hidden on the bottom (hides the pen line on the bottom when painting)
94 linearHistogramShape.append(QPointF(0.0, -0.1));
95 linearHistogramShape.append(QPointF(0.0, 0.0));
96 logarithmicHistogramShape.append(QPointF(0.0, -0.1));
97 logarithmicHistogramShape.append(QPointF(0.0, 0.0));
98
99 for (int i = 0; i < bins; ++i) {
100 const QPointF p = QPointF((static_cast<qreal>(i) + 0.5) * widthFactor,
101 static_cast<qreal>(histogram->getValue(i)) * heightfactor);
102 const QPointF logP = QPointF(p.x(), std::log(static_cast<qreal>(histogram->getValue(i) + 1)) * logHeightFactor);
103 linearHistogramShape.append(p);
104 logarithmicHistogramShape.append(logP);
105 }
106
107 linearHistogramShape.append(QPointF(1.0, 0.0));
108 linearHistogramShape.append(QPointF(1.0, -0.1));
109 logarithmicHistogramShape.append(QPointF(1.0, 0.0));
110 logarithmicHistogramShape.append(QPointF(1.0, -0.1));
111
112 return {linearHistogramShape, logarithmicHistogramShape};
113}
114
116{
117 const int binOfPercentile = static_cast<int>(std::round((1.0 - percentageForPercentile) * (polygon.size() - 4 - 1)));
118 std::nth_element(polygon.begin() + 2,
119 polygon.begin() + 2 + binOfPercentile,
120 polygon.end() - 2,
121 [](const QPointF &p1, const QPointF &p2) ->bool
122 {
123 return p1.y() > p2.y();
124 });
125 const qreal percentile = polygon[binOfPercentile + 2].y();
126 return qFuzzyIsNull(percentile) ? 1.0 : percentile;
127}
128
130{
131 if (polygon.size() < 5) {
132 return;
133 }
134
135 for (int i = 2; i < polygon.size() - 2; ++i) {
136 const qreal leftValue = polygon[i - 1].y();
137 const qreal centerValue = polygon[i].y();
138 const qreal rightValue = polygon[i + 1].y();
139 const qreal leftDelta = std::abs(centerValue - leftValue);
140 const qreal rightDelta = std::abs(centerValue - rightValue);
141 const qreal leftWeight = maximumNeighborWeight * std::exp(-pow2(10.0 * leftDelta));
142 const qreal rightWeight = maximumNeighborWeight * std::exp(-pow2(10.0 * rightDelta));
143 const qreal centerWeight = 1.0 - leftWeight - rightWeight;
144 polygon[i].setY(leftValue * leftWeight + centerValue * centerWeight + rightValue * rightWeight);
145 }
146}
147
149{
150 if (polygon.size() < 5) {
151 return;
152 }
153
154 qreal accumulatedOrientationDeviation = 0.0;
155 int numberOfSimplifiedPoints = 0;
156
157 for (int i = polygon.size() - 3; i > 1; --i) {
158 accumulatedOrientationDeviation += orientationDeviation(polygon[i + 1], polygon[i], polygon[i - 1]);
159 ++numberOfSimplifiedPoints;
160 if (std::abs(accumulatedOrientationDeviation) > maximumOrientationDeviation ||
161 numberOfSimplifiedPoints > maximumNumberOfSimplifiedPoints) {
162 accumulatedOrientationDeviation = 0.0;
163 numberOfSimplifiedPoints = 0;
164 } else {
165 polygon.remove(i);
166 }
167 }
168}
169
170QPair<QColor, QPainter::CompositionMode>
172 int channel)
173{
174 Q_ASSERT(colorSpace);
175 Q_ASSERT(channel >= 0);
176
177 QColor color;
178 QPainter::CompositionMode compositionMode = QPainter::CompositionMode_SourceOver;
179
180 if (colorSpace->colorModelId() == RGBAColorModelID) {
181 if (channel == 0) {
182 color = Qt::blue;
183 } else if (channel == 1) {
184 color = Qt::green;
185 } else if (channel == 2) {
186 color = Qt::red;
187 }
188 compositionMode = QPainter::CompositionMode_Plus;
189 } else if (colorSpace->colorModelId() == XYZAColorModelID) {
190 if (channel == 0) {
191 color = Qt::red;
192 } else if (channel == 1) {
193 color = Qt::green;
194 } else if (channel == 2) {
195 color = Qt::blue;
196 }
197 compositionMode = QPainter::CompositionMode_Plus;
198 } else if (colorSpace->colorModelId() == CMYKAColorModelID) {
199 if (channel == 0) {
200 color = Qt::cyan;
201 } else if (channel == 1) {
202 color = Qt::magenta;
203 } else if (channel == 2) {
204 color = Qt::yellow;
205 } else if (channel == 3) {
206 color = Qt::black;
207 }
208 if (channel != 4) {
209 color = KoColor(color, KoColorSpaceRegistry::instance()->rgb8())
210 .convertedTo(colorSpace,
213 .toQColor();
214 compositionMode = QPainter::CompositionMode_Multiply;
215 }
216 }
217
218 return {color, compositionMode};
219}
220
222 const QPolygonF &polygon,
223 qreal scale,
224 const QColor &color,
225 QPainter::CompositionMode compositionMode)
226{
227 const qreal w = static_cast<qreal>(image.width());
228 const qreal h = static_cast<qreal>(image.height());
229 const qreal maxH = h * histogramHeightFactor;
230
231 QPainter p(&image);
232 p.setRenderHint(QPainter::Antialiasing);
233 p.translate(0.0, h);
234 p.scale(w, -scale * maxH);
235 QPen pen(color, 2);
236 pen.setCosmetic(true);
237 QBrush brush(QColor(color.red(), color.green(), color.blue(), 200));
238 p.setPen(pen);
239 p.setBrush(brush);
240 p.setCompositionMode(compositionMode);
241 p.drawPolygon(polygon);
242}
243
245 const QVector<int> &channels,
246 bool logarithmic)
247{
248 QImage image(imageSize, QImage::Format_ARGB32);
249 image.fill(0);
250
251 const int nChannels = histogramChannelShapeInfo.size();
252
253 if (nChannels == 0 || channels.size() == 0) {
254 return image;
255 }
256
257 qreal overallHighest = 0.0;
258 for (int channel : channels) {
259 if (!histogramChannelShapeInfo.contains(channel)) {
260 continue;
261 }
262
263 const qreal channelHighest = static_cast<qreal>(histogramChannelShapeInfo[channel].highest);
264 if (channelHighest > overallHighest) {
265 overallHighest = channelHighest;
266 }
267 }
268
269 for (int channel : channels) {
270 if (!histogramChannelShapeInfo.contains(channel)) {
271 continue;
272 }
273
274 const HistogramShapeInfo &info = histogramChannelShapeInfo[channel];
276 image,
277 logarithmic ? info.logarithmicHistogram : info.linearHistogram,
278 logarithmic
279 ? scale * std::log(info.highest + 1.0) / std::log(overallHighest + 1.0)
280 : scale * info.highest / overallHighest,
281 info.color.isValid() ? info.color : defaultColor,
282 info.compositionMode
283 );
284 }
285
286 return image;
287}
288
290 : m_d(new Private)
291{
292 m_d->defaultColor = Qt::gray;
293}
294
297
298void KisHistogramPainter::setup(KisHistogram *histogram, const KoColorSpace *colorSpace, QVector<int> channels)
299{
300 Q_ASSERT(histogram);
301 Q_ASSERT(colorSpace);
302
303 const int nChannels = static_cast<int>(colorSpace->channelCount());
304
305 if (channels.size() == 0) {
306 for (int i = 0; i < nChannels; ++i) {
307 channels.append(i);
308 }
309 }
310
311 m_d->histogramChannelShapeInfo.clear();
312
313 for (int channel : channels) {
314 if (channel < 0 || channel >= nChannels || m_d->histogramChannelShapeInfo.contains(channel)) {
315 continue;
316 }
317 histogram->setChannel(channel);
318 const quint32 highest = histogram->calculations().getHighest();
319 QPair<QPolygonF, QPolygonF> shapes = Private::computeHistogramShape(histogram, channel, highest);
320 const QPair<QColor, QPainter::CompositionMode> channelPaintingInfo =
321 Private::computeChannelPaintingInfo(colorSpace, channel);
322 const qreal linearBestCutOffHeight = Private::bestCutOffHeight(shapes.first);
323 const qreal logarithmicBestCutOffHeight = Private::bestCutOffHeight(shapes.second);
324
325 Private::smoothHistogramShape(shapes.first);
326 Private::smoothHistogramShape(shapes.second);
328 Private::simplifyHistogramShape(shapes.second);
329
330 m_d->histogramChannelShapeInfo.insert(
331 channel,
332 {
333 shapes.first,
334 shapes.second,
335 highest,
336 linearBestCutOffHeight,
337 logarithmicBestCutOffHeight,
338 channelPaintingInfo.first,
339 channelPaintingInfo.second
340 }
341 );
342 }
343}
344
345QImage KisHistogramPainter::paint(const QSize &imageSize)
346{
347 return m_d->paintChannels(imageSize, m_d->channelsToPaint, m_d->isLogarithmic);
348}
349
350QImage KisHistogramPainter::paint(int w, int h)
351{
352 return paint(QSize(w, h));
353}
354
355void KisHistogramPainter::paint(QPainter &painter, const QRect &rect)
356{
357 const QImage image = m_d->paintChannels(rect.size(), m_d->channelsToPaint, m_d->isLogarithmic);
358 painter.drawImage(rect.topLeft(), image);
359}
360
362{
363 return m_d->histogramChannelShapeInfo.size();
364}
365
367{
368 return m_d->histogramChannelShapeInfo.keys();
369}
370
372{
373 return m_d->channelsToPaint;
374}
375
377{
378 setChannels({channel});
379}
380
382{
383 m_d->channelsToPaint = channels;
384}
385
387{
388 return m_d->defaultColor;
389}
390
391void KisHistogramPainter::setDefaultColor(const QColor &newDefaultColor)
392{
393 m_d->defaultColor = newDefaultColor;
394}
395
397{
398 return m_d->scale;
399}
400
402{
403 m_d->scale = newScale;
404}
405
410
412{
413 qreal overallHighest = 0.0;
414 qreal fittedChannelHighest = 0.0;
415 qreal bestCutOffHeight = 0.0;
416 for (int channel : m_d->channelsToPaint) {
417 if (!m_d->histogramChannelShapeInfo.contains(channel)) {
418 continue;
419 }
420
421 const qreal channelBestCutOffHeight =
423 ? m_d->histogramChannelShapeInfo[channel].logarithmicBestCutOffHeight
424 : m_d->histogramChannelShapeInfo[channel].linearBestCutOffHeight;
425 const qreal channelHighest = static_cast<qreal>(m_d->histogramChannelShapeInfo[channel].highest);
426
427 if (channelBestCutOffHeight * channelHighest > bestCutOffHeight * fittedChannelHighest) {
428 bestCutOffHeight = channelBestCutOffHeight;
429 fittedChannelHighest = channelHighest;
430 }
431
432 if (channelHighest > overallHighest) {
433 overallHighest = channelHighest;
434 }
435 }
436 const qreal overallBestCutOffHeight = bestCutOffHeight * fittedChannelHighest / overallHighest;
437 if (overallBestCutOffHeight < 0.8) {
438 setScale(1.0 / overallBestCutOffHeight);
439 } else {
440 setScale(1.0);
441 }
442}
443
445{
446 return m_d->isLogarithmic;
447}
448
450{
451 m_d->isLogarithmic = logarithmic;
452}
const Params2D p
QPointF p2
QPointF p1
const KoID XYZAColorModelID("XYZA", ki18n("XYZ/Alpha"))
const KoID CMYKAColorModelID("CMYKA", ki18n("CMYK/Alpha"))
const KoID RGBAColorModelID("RGBA", ki18n("RGB/Alpha"))
#define C(i, j)
QHash< int, HistogramShapeInfo > histogramChannelShapeInfo
static qreal bestCutOffHeight(QPolygonF polygon)
static void smoothHistogramShape(QPolygonF &polygon)
static constexpr qreal maximumNeighborWeight
static QPair< QColor, QPainter::CompositionMode > computeChannelPaintingInfo(const KoColorSpace *colorSpace, int channel)
static constexpr qreal maximumOrientationDeviation
static qreal orientationDeviation(const QPointF &A, const QPointF &B, const QPointF &C)
static constexpr qreal percentageForPercentile
static void paintHistogramShape(QImage &image, const QPolygonF &polygon, qreal scale, const QColor &color, QPainter::CompositionMode compositionMode)
static void simplifyHistogramShape(QPolygonF &polygon)
static constexpr qreal histogramHeightFactor
static constexpr int maximumNumberOfSimplifiedPoints
static QPair< QPolygonF, QPolygonF > computeHistogramShape(KisHistogram *histogram, int channel, quint32 highest)
QImage paintChannels(const QSize &imageSize, const QVector< int > &channels={}, bool logarithmic=false)
QList< int > availableChannels() const
Get a list containing all the indices of the channels that were setup using the "setup" function.
void setLogarithmic(bool logarithmic)
Set if the histogram shape being shown is formed by the logarithmic mapping of the original histogram...
QImage paint(const QSize &imageSize)
Returns a RGBA image of size "imageSize" with the result of rendering the selected histogram channels...
const QVector< int > & channels() const
Get the list of channels that are currently activated (the only ones that will be painted)
void setDefaultColor(const QColor &newDefaultColor)
Set the default color used when there is no preference to color the selected histogram channel.
int totalNumberOfAvailableChannels() const
Get the number of channels that are being used. It can be less than the total number of channels in t...
void setScaleToFit()
Set the scale used to paint the histograms to 1.
void setScaleToCutLongPeaks()
Sometimes there can be some outliers in the histogram that show in the painted shape as long vertical...
qreal scale() const
Return the vertical scale that is used to paint the histogram shape. A scale of 1 will paint all the ...
void setChannels(const QVector< int > &channels)
Set currently active channels (the ones that will be painted)
void setup(KisHistogram *histogram, const KoColorSpace *colorSpace, QVector< int > channels={})
Sets up the painter by passing a KisHistogram, the color space for the histogram and the channels tha...
QColor defaultColor() const
Returns the color that is being used to paint a generic histogram. All the histogram channels will us...
bool isLogarithmic() const
Returns true if the histogram shape being painted is formed by the logarithmic mapping of the origina...
void setChannel(int channel)
Set currently active channel (the one that will be painted)
QScopedPointer< Private > m_d
void setScale(qreal newScale)
Set the scale used to paint the histograms.
quint32 getHighest()
This function return the highest value of the histogram.
void setChannel(qint32 channel)
quint32 getValue(quint8 i)
KoHistogramProducer * producer()
Calculations calculations()
virtual KoID colorModelId() const =0
virtual quint32 channelCount() const =0
KoColor convertedTo(const KoColorSpace *cs, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) const
Definition KoColor.cpp:163
void toQColor(QColor *c) const
a convenience method for the above.
Definition KoColor.cpp:198
virtual qint32 numberOfBins()=0
static bool qFuzzyIsNull(half h)
T pow2(const T &x)
Definition kis_global.h:166
#define M_PI
Definition kis_global.h:111
qreal angleBetweenVectors(const QPointF &v1, const QPointF &v2)
QPainter::CompositionMode compositionMode
static KoColorSpaceRegistry * instance()