Krita Source Code Documentation
Loading...
Searching...
No Matches
KisAutoLevels.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
11#include <kis_histogram.h>
12
13#include "KisAutoLevels.h"
14
16{
17
18QPair<qreal, qreal> getMeanAndMedian(ChannelHistogram histogram, qreal begin, qreal end)
19{
20 Q_ASSERT(histogram.histogram);
21 Q_ASSERT(histogram.channel >= 0 && histogram.channel < histogram.histogram->producer()->channels().size());
22
23 histogram.histogram->setChannel(histogram.channel);
24
25 const int numberOfBins = histogram.histogram->producer()->numberOfBins();
26 const int beginBin = static_cast<int>(begin * static_cast<qreal>(numberOfBins));
27 const int endBin = static_cast<int>(end * static_cast<qreal>(numberOfBins)) + 1;
28
29 qreal numberOfSamples = 0.0;
30 qreal meanSum = 0.0;
31
32 for (int i = beginBin; i < endBin; ++i) {
33 const qreal current = static_cast<qreal>(histogram.histogram->getValue(i));
34 numberOfSamples += current;
35 meanSum += current * static_cast<qreal>(i);
36 }
37
38 const qreal mean = meanSum / (numberOfSamples * static_cast<qreal>(numberOfBins));
39 qreal probabilityAccumulator = 0.0;
40 qreal median = 0.0;
41
42 for (int i = beginBin; i < endBin; ++i) {
43 const qreal probability = static_cast<qreal>(histogram.histogram->getValue(i)) / numberOfSamples;
44 probabilityAccumulator += probability;
45 if (probabilityAccumulator >= 0.5) {
46 median = static_cast<qreal>(i) / static_cast<qreal>(numberOfBins);
47 break;
48 }
49 }
50
51 return {mean, median};
52}
53
54QPair<qreal, qreal> getInputBlackAndWhitePoints(ChannelHistogram histogram,
55 qreal shadowsClipping,
56 qreal highlightsClipping)
57{
58 Q_ASSERT(histogram.histogram);
59 Q_ASSERT(histogram.channel >= 0 && histogram.channel < histogram.histogram->producer()->channels().size());
60
61 histogram.histogram->setChannel(histogram.channel);
62
63 int numberOfBins = histogram.histogram->producer()->numberOfBins();
64
65 Q_ASSERT(numberOfBins > 1);
66
67 const qreal totalNumberOfSamples = static_cast<qreal>(histogram.histogram->producer()->count());
68
69 // This basically integrates the probability mass function given by the
70 // histogram, from the left and the right, until the thresholds given by the
71 // clipping are reached, to obtain the black and white points
72
73 int blackPoint = 0;
74 qreal accumulator = 0.0;
75 for (int i = 0; i < numberOfBins; ++i) {
76 const qreal sampleCountForBin = static_cast<qreal>(histogram.histogram->getValue(i));
77 const qreal probability = sampleCountForBin / totalNumberOfSamples;
78
79 accumulator += probability;
80 if (accumulator > shadowsClipping) {
81 break;
82 }
83 blackPoint = i;
84 }
85
86 int whitePoint = numberOfBins - 1;
87 accumulator = 0.0;
88 for (int i = numberOfBins - 1; i >= 0; --i) {
89 const qreal sampleCountForBin = static_cast<qreal>(histogram.histogram->getValue(i));
90 const qreal probability = sampleCountForBin / totalNumberOfSamples;
91
92 accumulator += probability;
93 if (accumulator > highlightsClipping) {
94 break;
95 }
96 whitePoint = i;
97 }
98
99 if (whitePoint <= blackPoint) {
100 if (blackPoint + 1 == numberOfBins) {
101 whitePoint = blackPoint;
102 --blackPoint;
103 } else {
104 whitePoint = blackPoint + 1;
105 }
106 }
107
108 return
109 {
110 static_cast<qreal>(blackPoint) / static_cast<qreal>(numberOfBins),
111 static_cast<qreal>(whitePoint) / static_cast<qreal>(numberOfBins)
112 };
113}
114
115QPair<KoColor, KoColor> getDarkestAndWhitestColors(const KisPaintDeviceSP device,
116 qreal shadowsClipping,
117 qreal highlightsClipping);
118
119qreal getGamma(qreal blackPoint, qreal whitePoint, qreal inputIntensity, qreal outputIntensity)
120{
121 Q_ASSERT(blackPoint < whitePoint);
122 Q_ASSERT(inputIntensity >= blackPoint && inputIntensity <= whitePoint);
123
124 if (qFuzzyIsNull(outputIntensity)) {
125 return 0.01;
126 }
127 if (qFuzzyCompare(outputIntensity, 1.0)) {
128 return 10.0;
129 }
130
131 const qreal inputIntensityAfterLinearMapping =
132 (inputIntensity - blackPoint) / (whitePoint - blackPoint);
133
134 return qBound(0.01, log(inputIntensityAfterLinearMapping) / log(outputIntensity), 10.0);
135}
136
137
139 QVector<ChannelHistogram> &channelsHistograms,
140 qreal shadowsClipping,
141 qreal highlightsClipping,
142 qreal maximumInputBlackAndWhiteOffset,
143 MidtonesAdjustmentMethod midtonesAdjustmentMethod,
144 qreal midtonesAdjustmentAmount,
145 const QVector<qreal> &outputBlackPoints,
146 const QVector<qreal> &outputWhitePoints,
147 const QVector<qreal> &outputMidtones)
148{
149 Q_ASSERT(lightnessHistogram.histogram);
150 Q_ASSERT(lightnessHistogram.channel >= 0);
151 Q_ASSERT(lightnessHistogram.channel < lightnessHistogram.histogram->producer()->channels().size());
152 Q_ASSERT(outputBlackPoints.size() == channelsHistograms.size());
153 Q_ASSERT(outputWhitePoints.size() == channelsHistograms.size());
154 Q_ASSERT(outputMidtones.size() == channelsHistograms.size());
155
156 QVector<KisLevelsCurve> levelsCurves;
157
158 const QPair<qreal, qreal> inputBlackAndWhitePoints =
159 getInputBlackAndWhitePoints(lightnessHistogram, shadowsClipping, highlightsClipping);
160 const qreal inputBlackPoint = qMin(maximumInputBlackAndWhiteOffset, inputBlackAndWhitePoints.first);
161 const qreal inputWhitePoint = qMax(1.0 - maximumInputBlackAndWhiteOffset, inputBlackAndWhitePoints.second);
162 const qreal linearMappingMidPoint = (inputBlackPoint + inputWhitePoint) / 2.0;
163
164 for (int i = 0; i < channelsHistograms.size(); ++i) {
165 ChannelHistogram &channelHistogram = channelsHistograms[i];
166
167 qreal gamma = 1.0;
168 if (midtonesAdjustmentMethod != MidtonesAdjustmentMethod_None &&
169 channelHistogram.histogram &&
170 channelHistogram.channel >= 0 &&
171 channelHistogram.channel < channelHistogram.histogram->producer()->channels().size()) {
172
173 qreal inputIntensity;
174 QPair<qreal, qreal> meanAndMedian = getMeanAndMedian(channelHistogram, inputBlackPoint, inputWhitePoint);
175
176 if (midtonesAdjustmentMethod == MidtonesAdjustmentMethod_UseMean) {
177 inputIntensity = meanAndMedian.first;
178 } else {
179 inputIntensity = meanAndMedian.second;
180 }
181
182 inputIntensity = linearMappingMidPoint + (inputIntensity - linearMappingMidPoint) * midtonesAdjustmentAmount;
183 gamma = getGamma(inputBlackPoint, inputWhitePoint, inputIntensity, outputMidtones[i]);
184 }
185
186 levelsCurves.append(
188 inputBlackPoint,
189 inputWhitePoint,
190 gamma,
191 outputBlackPoints[i],
192 outputWhitePoints[i]
193 )
194 );
195 }
196
197 return levelsCurves;
198}
199
201 qreal shadowsClipping,
202 qreal highlightsClipping,
203 qreal maximumInputBlackAndWhiteOffset,
204 MidtonesAdjustmentMethod midtonesAdjustmentMethod,
205 qreal midtonesAdjustmentAmount,
206 const QVector<qreal> &outputBlackPoints,
207 const QVector<qreal> &outputWhitePoints,
208 const QVector<qreal> &outputMidtones)
209{
210 QVector<KisLevelsCurve> levelsCurves;
211
212 for (int i = 0; i < channelsHistograms.size(); ++i) {
213 QVector<ChannelHistogram> channelHistogram{channelsHistograms[i]};
214 levelsCurves.append(
216 channelHistogram[0],
217 channelHistogram,
218 shadowsClipping,
219 highlightsClipping,
220 maximumInputBlackAndWhiteOffset,
221 midtonesAdjustmentMethod,
222 midtonesAdjustmentAmount,
223 {outputBlackPoints[i]},
224 {outputWhitePoints[i]},
225 {outputMidtones[i]}
226 )
227 );
228 }
229
230 return levelsCurves;
231}
232
233}
void setChannel(qint32 channel)
quint32 getValue(quint8 i)
KoHistogramProducer * producer()
This class holds the parameters for a levels adjustment. It is modeled after KisCubicCurve and has si...
virtual qint32 numberOfBins()=0
virtual QList< KoChannelInfo * > channels()=0
virtual qint32 count()=0
static bool qFuzzyCompare(half p1, half p2)
static bool qFuzzyIsNull(half h)
This namespace contains functions to compute the levels adjustment parameters automatically from a hi...
QPair< qreal, qreal > getMeanAndMedian(ChannelHistogram histogram, qreal begin, qreal end)
QPair< KoColor, KoColor > getDarkestAndWhitestColors(const KisPaintDeviceSP device, qreal shadowsClipping, qreal highlightsClipping)
Finds the darkest and whitest colors in the device having into account the clipping.
QVector< KisLevelsCurve > adjustMonochromaticContrast(ChannelHistogram lightnessHistogram, QVector< ChannelHistogram > &channelsHistograms, qreal shadowsClipping, qreal highlightsClipping, qreal maximumInputBlackAndWhiteOffset, MidtonesAdjustmentMethod midtonesAdjustmentMethod, qreal midtonesAdjustmentAmount, const QVector< qreal > &outputBlackPoints, const QVector< qreal > &outputWhitePoints, const QVector< qreal > &outputMidtones)
Creates a KisLevelsCurve for every channel in "channelsHistograms". Computes the input black and whit...
QVector< KisLevelsCurve > adjustPerChannelContrast(QVector< ChannelHistogram > &channelsHistograms, qreal shadowsClipping, qreal highlightsClipping, qreal maximumInputBlackAndWhiteOffset, MidtonesAdjustmentMethod midtonesAdjustmentMethod, qreal midtonesAdjustmentAmount, const QVector< qreal > &outputBlackPoints, const QVector< qreal > &outputWhitePoints, const QVector< qreal > &outputMidtones)
Creates a KisLevelsCurve for every channel in "channelsHistograms". Computes the input black and whit...
MidtonesAdjustmentMethod
The different methods to enhance the mid tones.
@ MidtonesAdjustmentMethod_None
@ MidtonesAdjustmentMethod_UseMean
qreal getGamma(qreal blackPoint, qreal whitePoint, qreal inputIntensity, qreal outputIntensity)
Computes a gamma value that "moves" the input midpoint towards the output midpoint.
QPair< qreal, qreal > getInputBlackAndWhitePoints(ChannelHistogram histogram, qreal shadowsClipping, qreal highlightsClipping)
Takes a reference histogram (luma, lightness) and computes the black and white points to maximize the...
Convenience class that associates a KisHistogram and a channel index. This is useful because setting ...