10#include <QtConcurrent>
18T median(std::vector<T> &values)
20 const int middle = values.size() / 2;
21 std::nth_element(values.begin(), values.begin() + middle, values.end());
22 return values[middle];
27T sampledMax(T
const *values,
int size,
int sampleBy)
30 for (
int i = 0; i < size; i += sampleBy)
31 if (maxVal < values[i])
39T median(T
const *values,
int size,
int sampleBy)
41 const int downsampled_size = size / sampleBy;
42 std::vector<T> samples(downsampled_size);
43 for (
int index = 0, i = 0; i < downsampled_size; ++i, index += sampleBy)
44 samples[i] = values[index];
45 return median(samples);
56void stretchOneChannel(T *input_buffer,
QImage *output_image,
57 const StretchParams &stretch_params,
58 int input_range,
int image_height,
int image_width,
int sampling)
63 constexpr int maxOutput = 255;
66 const float maxInput = input_range > 1 ? input_range - 1 : input_range;
68 const float midtones = stretch_params.grey_red.midtones;
69 const float highlights = stretch_params.grey_red.highlights;
70 const float shadows = stretch_params.grey_red.shadows;
74 const float hsRangeFactor = highlights == shadows ? 1.0f : 1.0f / (highlights - shadows);
76 const T nativeShadows = shadows * maxInput;
77 const T nativeHighlights = highlights * maxInput;
79 const float k1 = (midtones - 1) * hsRangeFactor * maxOutput / maxInput;
80 const float k2 = ((2 * midtones) - 1) * hsRangeFactor / maxInput;
83 for (
int j = 0, jout = 0; j < image_height; j += sampling, jout++)
87 T * inputLine = input_buffer + j * image_width;
88 auto * scanLine = output_image->
scanLine(jout);
90 for (
int i = 0, iout = 0; i < image_width; i += sampling, iout++)
92 const T input = inputLine[i];
93 if (input < nativeShadows) scanLine[iout] = 0;
94 else if (input >= nativeHighlights) scanLine[iout] = maxOutput;
97 const T inputFloored = (input - nativeShadows);
98 scanLine[iout] = (inputFloored * k1) / (inputFloored * k2 - midtones);
104 future.waitForFinished();
115void stretchThreeChannels(T *inputBuffer,
QImage *outputImage,
116 const StretchParams &stretchParams,
117 int inputRange,
int imageHeight,
int imageWidth,
int sampling)
122 constexpr int maxOutput = 255;
125 const float maxInput = inputRange > 1 ? inputRange - 1 : inputRange;
127 const float midtonesR = stretchParams.grey_red.midtones;
128 const float highlightsR = stretchParams.grey_red.highlights;
129 const float shadowsR = stretchParams.grey_red.shadows;
130 const float midtonesG = stretchParams.green.midtones;
131 const float highlightsG = stretchParams.green.highlights;
132 const float shadowsG = stretchParams.green.shadows;
133 const float midtonesB = stretchParams.blue.midtones;
134 const float highlightsB = stretchParams.blue.highlights;
135 const float shadowsB = stretchParams.blue.shadows;
139 const float hsRangeFactorR = highlightsR == shadowsR ? 1.0f : 1.0f / (highlightsR - shadowsR);
140 const float hsRangeFactorG = highlightsG == shadowsG ? 1.0f : 1.0f / (highlightsG - shadowsG);
141 const float hsRangeFactorB = highlightsB == shadowsB ? 1.0f : 1.0f / (highlightsB - shadowsB);
143 const T nativeShadowsR = shadowsR * maxInput;
144 const T nativeShadowsG = shadowsG * maxInput;
145 const T nativeShadowsB = shadowsB * maxInput;
146 const T nativeHighlightsR = highlightsR * maxInput;
147 const T nativeHighlightsG = highlightsG * maxInput;
148 const T nativeHighlightsB = highlightsB * maxInput;
150 const float k1R = (midtonesR - 1) * hsRangeFactorR * maxOutput / maxInput;
151 const float k1G = (midtonesG - 1) * hsRangeFactorG * maxOutput / maxInput;
152 const float k1B = (midtonesB - 1) * hsRangeFactorB * maxOutput / maxInput;
153 const float k2R = ((2 * midtonesR) - 1) * hsRangeFactorR / maxInput;
154 const float k2G = ((2 * midtonesG) - 1) * hsRangeFactorG / maxInput;
155 const float k2B = ((2 * midtonesB) - 1) * hsRangeFactorB / maxInput;
157 const int size = imageWidth * imageHeight;
159 for (
int j = 0, jout = 0; j < imageHeight; j += sampling, jout++)
164 T * inputLineR = inputBuffer + j * imageWidth;
165 T * inputLineG = inputLineR + size;
166 T * inputLineB = inputLineG + size;
168 auto * scanLine =
reinterpret_cast<QRgb*
>(outputImage->
scanLine(jout));
170 for (
int i = 0, iout = 0; i < imageWidth; i += sampling, iout++)
172 const T inputR = inputLineR[i];
173 const T inputG = inputLineG[i];
174 const T inputB = inputLineB[i];
178 if (inputR < nativeShadowsR)
red = 0;
179 else if (inputR >= nativeHighlightsR)
red = maxOutput;
182 const T inputFloored = (inputR - nativeShadowsR);
183 red = (inputFloored * k1R) / (inputFloored * k2R - midtonesR);
186 if (inputG < nativeShadowsG)
green = 0;
187 else if (inputG >= nativeHighlightsG)
green = maxOutput;
190 const T inputFloored = (inputG - nativeShadowsG);
191 green = (inputFloored * k1G) / (inputFloored * k2G - midtonesG);
194 if (inputB < nativeShadowsB)
blue = 0;
195 else if (inputB >= nativeHighlightsB)
blue = maxOutput;
198 const T inputFloored = (inputB - nativeShadowsB);
199 blue = (inputFloored * k1B) / (inputFloored * k2B - midtonesB);
201 scanLine[iout] = qRgb(red, green, blue);
206 future.waitForFinished();
210void stretchChannels(T *input_buffer,
QImage *output_image,
211 const StretchParams &stretch_params,
212 int input_range,
int image_height,
int image_width,
int num_channels,
int sampling)
214 if (num_channels == 1)
215 stretchOneChannel(input_buffer, output_image, stretch_params, input_range,
216 image_height, image_width, sampling);
217 else if (num_channels == 3)
218 stretchThreeChannels(input_buffer, output_image, stretch_params, input_range,
219 image_height, image_width, sampling);
224void computeParamsOneChannel(T
const *buffer, StretchParams1Channel *params,
225 int inputRange,
int height,
int width)
228 constexpr int maxSamples = 500000;
229 const int sampleBy = width * height < maxSamples ? 1 : width * height / maxSamples;
231 T medianSample = median(buffer, width * height, sampleBy);
233 const int numSamples = width * height / sampleBy;
234 std::vector<T> deviations(numSamples);
235 for (
int index = 0, i = 0; i < numSamples; ++i, index += sampleBy)
237 if (medianSample > buffer[index])
238 deviations[i] = medianSample - buffer[index];
240 deviations[i] = buffer[index] - medianSample;
244 const float medDev = median(deviations);
245 const float normalizedMedian = medianSample /
static_cast<float>(inputRange);
246 const float MADN = 1.4826 * medDev /
static_cast<float>(inputRange);
248 const bool upperHalf = normalizedMedian > 0.5;
250 const float shadows = (upperHalf || MADN == 0) ? 0.0 :
251 fmin(1.0, fmax(0.0, (normalizedMedian + -2.8 * MADN)));
253 const float highlights = (!upperHalf || MADN == 0) ? 1.0 :
254 fmin(1.0, fmax(0.0, (normalizedMedian - -2.8 * MADN)));
257 constexpr float B = 0.25;
260 X = normalizedMedian - shadows;
266 M = highlights - normalizedMedian;
269 if (X == 0) midtones = 0.0f;
270 else if (X == M) midtones = 0.5f;
271 else if (X == 1) midtones = 1.0f;
272 else midtones = ((M - 1) * X) / ((2 * M - 1) * X - M);
275 params->shadows = shadows;
276 params->highlights = highlights;
277 params->midtones = midtones;
278 params->shadows_expansion = 0.0;
279 params->highlights_expansion = 1.0;
286int getRange(
int data_type)
311Stretch::Stretch(
int width,
int height,
int channels,
int data_type)
314 image_height = height;
315 image_channels = channels;
316 dataType = data_type;
317 input_range = getRange(dataType);
320void Stretch::run(uint8_t
const *input,
QImage *outputImage,
int sampling)
322 Q_ASSERT(outputImage->
width() == (image_width + sampling - 1) / sampling);
323 Q_ASSERT(outputImage->
height() == (image_height + sampling - 1) / sampling);
324 recalculateInputRange(input);
329 stretchChannels(
reinterpret_cast<uint8_t const*
>(input), outputImage, params,
330 input_range, image_height, image_width, image_channels, sampling);
333 stretchChannels(
reinterpret_cast<short const*
>(input), outputImage, params,
334 input_range, image_height, image_width, image_channels, sampling);
337 stretchChannels(
reinterpret_cast<unsigned short const*
>(input), outputImage, params,
338 input_range, image_height, image_width, image_channels, sampling);
341 stretchChannels(
reinterpret_cast<long const*
>(input), outputImage, params,
342 input_range, image_height, image_width, image_channels, sampling);
345 stretchChannels(
reinterpret_cast<float const*
>(input), outputImage, params,
346 input_range, image_height, image_width, image_channels, sampling);
349 stretchChannels(
reinterpret_cast<long long const*
>(input), outputImage, params,
350 input_range, image_height, image_width, image_channels, sampling);
353 stretchChannels(
reinterpret_cast<double const*
>(input), outputImage, params,
354 input_range, image_height, image_width, image_channels, sampling);
363void Stretch::recalculateInputRange(uint8_t
const *input)
365 if (input_range <= 1)
return;
366 if (dataType != TFLOAT && dataType != TDOUBLE)
return;
369 if (dataType == TFLOAT)
370 mx = sampledMax(
reinterpret_cast<float const*
>(input), image_height * image_width, 1000);
371 else if (dataType == TDOUBLE)
372 mx = sampledMax(
reinterpret_cast<double const*
>(input), image_height * image_width, 1000);
373 if (mx <= 1.01f) input_range = 1;
376StretchParams Stretch::computeParams(uint8_t
const *input)
378 recalculateInputRange(input);
379 StretchParams result;
380 for (
int channel = 0; channel < image_channels; ++channel)
382 int offset = channel * image_width * image_height;
383 StretchParams1Channel *params = channel == 0 ? &result.grey_red :
384 (channel == 1 ? &result.green : &result.blue);
389 auto buffer =
reinterpret_cast<uint8_t const*
>(input);
390 computeParamsOneChannel(buffer + offset, params, input_range,
391 image_height, image_width);
396 auto buffer =
reinterpret_cast<short const*
>(input);
397 computeParamsOneChannel(buffer + offset, params, input_range,
398 image_height, image_width);
403 auto buffer =
reinterpret_cast<unsigned short const*
>(input);
404 computeParamsOneChannel(buffer + offset, params, input_range,
405 image_height, image_width);
410 auto buffer =
reinterpret_cast<long const*
>(input);
411 computeParamsOneChannel(buffer + offset, params, input_range,
412 image_height, image_width);
417 auto buffer =
reinterpret_cast<float const*
>(input);
418 computeParamsOneChannel(buffer + offset, params, input_range,
419 image_height, image_width);
424 auto buffer =
reinterpret_cast<long long const*
>(input);
425 computeParamsOneChannel(buffer + offset, params, input_range,
426 image_height, image_width);
431 auto buffer =
reinterpret_cast<double const*
>(input);
432 computeParamsOneChannel(buffer + offset, params, input_range,
433 image_height, image_width);
void append(QList< T > &&value)
QFuture< T > run(Function function,...)