Changeset View
Changeset View
Standalone View
Standalone View
kstars/fitsviewer/stretch.cpp
Show All 10 Lines | |||||
11 | #include "stretch.h" | 11 | #include "stretch.h" | ||
12 | 12 | | |||
13 | #include <fitsio.h> | 13 | #include <fitsio.h> | ||
14 | #include <math.h> | 14 | #include <math.h> | ||
15 | #include <QtConcurrent> | 15 | #include <QtConcurrent> | ||
16 | 16 | | |||
17 | namespace { | 17 | namespace { | ||
18 | 18 | | |||
19 | // Returns the median v of the vector. | 19 | // Returns the median value of the vector. | ||
20 | // The vector is modified in an undefined way. | 20 | // The vector is modified in an undefined way. | ||
21 | template <typename T> | 21 | template <typename T> | ||
22 | T median(std::vector<T>& values) | 22 | T median(std::vector<T>& values) | ||
23 | { | 23 | { | ||
24 | const int middle = values.size() / 2; | 24 | const int middle = values.size() / 2; | ||
25 | std::nth_element(values.begin(), values.begin() + middle, values.end()); | 25 | std::nth_element(values.begin(), values.begin() + middle, values.end()); | ||
26 | return values[middle]; | 26 | return values[middle]; | ||
27 | } | 27 | } | ||
28 | 28 | | |||
29 | // Returns the rough max of the buffer. | ||||
30 | template <typename T> | ||||
31 | T sampledMax(T *values, int size, int sampleBy) | ||||
32 | { | ||||
33 | T maxVal = 0; | ||||
34 | for (int i = 0; i < size; i+= sampleBy) | ||||
35 | if (maxVal < values[i]) | ||||
36 | maxVal = values[i]; | ||||
37 | return maxVal; | ||||
38 | } | ||||
39 | | ||||
29 | // Returns the median of the sample values. | 40 | // Returns the median of the sample values. | ||
30 | // The values are not modified. | 41 | // The values are not modified. | ||
31 | template <typename T> | 42 | template <typename T> | ||
32 | T median(T *values, int size, int sampleBy) | 43 | T median(T *values, int size, int sampleBy) | ||
33 | { | 44 | { | ||
34 | const int downsampled_size = size / sampleBy; | 45 | const int downsampled_size = size / sampleBy; | ||
35 | std::vector<T> samples(downsampled_size); | 46 | std::vector<T> samples(downsampled_size); | ||
36 | for (int index = 0, i = 0; i < downsampled_size; ++i, index += sampleBy) | 47 | for (int index = 0, i = 0; i < downsampled_size; ++i, index += sampleBy) | ||
37 | samples[i] = values[index]; | 48 | samples[i] = values[index]; | ||
38 | return median(samples); | 49 | return median(samples); | ||
39 | } | 50 | } | ||
40 | 51 | | |||
41 | // This stretches one channel given the input parameters. | 52 | // This stretches one channel given the input parameters. | ||
42 | // Based on the spec in section 8.5.6 | 53 | // Based on the spec in section 8.5.6 | ||
43 | // http://pixinsight.com/doc/docs/XISF-1.0-spec/XISF-1.0-spec.html | 54 | // http://pixinsight.com/doc/docs/XISF-1.0-spec/XISF-1.0-spec.html | ||
44 | // Uses multiple threads, blocks until done. | 55 | // Uses multiple threads, blocks until done. | ||
45 | // The extension parameters are not used. | 56 | // The extension parameters are not used. | ||
57 | // Sampling is applied to the output (that is, with sampling=2, we compute every other output | ||||
58 | // sample both in width and height, so the output would have about 4X fewer pixels. | ||||
46 | template <typename T> | 59 | template <typename T> | ||
47 | void stretchOneChannel(T *input_buffer, QImage *output_image, | 60 | void stretchOneChannel(T *input_buffer, QImage *output_image, | ||
48 | const StretchParams& stretch_params, | 61 | const StretchParams& stretch_params, | ||
49 | int input_range, int image_height, int image_width) | 62 | int input_range, int image_height, int image_width, int sampling) | ||
50 | { | 63 | { | ||
51 | QVector<QFuture<void>> futures; | 64 | QVector<QFuture<void>> futures; | ||
52 | 65 | | |||
53 | // We're outputting uint8, so the max output is 255. | 66 | // We're outputting uint8, so the max output is 255. | ||
54 | constexpr int maxOutput = 255; | 67 | constexpr int maxOutput = 255; | ||
55 | 68 | | |||
56 | // Maximum possible input value (e.g. 1024*64 - 1 for a 16 bit unsigned int). | 69 | // Maximum possible input value (e.g. 1024*64 - 1 for a 16 bit unsigned int). | ||
57 | const float maxInput = input_range > 1 ? input_range - 1 : input_range; | 70 | const float maxInput = input_range > 1 ? input_range - 1 : input_range; | ||
58 | 71 | | |||
59 | const float midtones = stretch_params.grey_red.midtones; | 72 | const float midtones = stretch_params.grey_red.midtones; | ||
60 | const float highlights = stretch_params.grey_red.highlights; | 73 | const float highlights = stretch_params.grey_red.highlights; | ||
61 | const float shadows = stretch_params.grey_red.shadows; | 74 | const float shadows = stretch_params.grey_red.shadows; | ||
62 | 75 | | |||
63 | // Precomputed expressions moved out of the loop. | 76 | // Precomputed expressions moved out of the loop. | ||
64 | // hightlights - shadows, protecting for divide-by-0, in a 0->1.0 scale. | 77 | // hightlights - shadows, protecting for divide-by-0, in a 0->1.0 scale. | ||
65 | const float hsRangeFactor = highlights == shadows ? 1.0 : 1.0 / (highlights - shadows); | 78 | const float hsRangeFactor = highlights == shadows ? 1.0f : 1.0f / (highlights - shadows); | ||
66 | // Shadow and highlight values translated to the ADU scale. | 79 | // Shadow and highlight values translated to the ADU scale. | ||
67 | const T nativeShadows = shadows * maxInput; | 80 | const T nativeShadows = shadows * maxInput; | ||
68 | const T nativeHighlights = highlights * maxInput; | 81 | const T nativeHighlights = highlights * maxInput; | ||
69 | // Constants based on above needed for the stretch calculations. | 82 | // Constants based on above needed for the stretch calculations. | ||
70 | const float k1 = (midtones - 1) * hsRangeFactor * maxOutput / maxInput; | 83 | const float k1 = (midtones - 1) * hsRangeFactor * maxOutput / maxInput; | ||
71 | const float k2 = ((2 * midtones) - 1) * hsRangeFactor / maxInput; | 84 | const float k2 = ((2 * midtones) - 1) * hsRangeFactor / maxInput; | ||
72 | 85 | | |||
73 | for (int j = 0; j < image_height; j++) | 86 | // Increment the input index by the sampling, the output index increments by 1. | ||
87 | for (int j = 0, jout = 0; j < image_height; j+=sampling, jout++) | ||||
74 | { | 88 | { | ||
75 | futures.append(QtConcurrent::run([ = ]() | 89 | futures.append(QtConcurrent::run([ = ]() | ||
76 | { | 90 | { | ||
77 | T * inputLine = input_buffer + j * image_width; | 91 | T * inputLine = input_buffer + j * image_width; | ||
78 | auto * scanLine = output_image->scanLine(j); | 92 | auto * scanLine = output_image->scanLine(jout); | ||
79 | 93 | | |||
80 | for (int i = 0; i < image_width; i++) | 94 | for (int i = 0, iout = 0; i < image_width; i+=sampling, iout++) | ||
81 | { | 95 | { | ||
82 | const T input = inputLine[i]; | 96 | const T input = inputLine[i]; | ||
83 | if (input < nativeShadows) scanLine[i] = 0; | 97 | if (input < nativeShadows) scanLine[iout] = 0; | ||
84 | else if (input >= nativeHighlights) scanLine[i] = maxOutput; | 98 | else if (input >= nativeHighlights) scanLine[iout] = maxOutput; | ||
85 | else | 99 | else | ||
86 | { | 100 | { | ||
87 | const T inputFloored = (input - nativeShadows); | 101 | const T inputFloored = (input - nativeShadows); | ||
88 | scanLine[i] = (inputFloored * k1) / (inputFloored * k2 - midtones); | 102 | scanLine[iout] = (inputFloored * k1) / (inputFloored * k2 - midtones); | ||
89 | } | 103 | } | ||
90 | } | 104 | } | ||
91 | })); | 105 | })); | ||
92 | } | 106 | } | ||
93 | for(QFuture<void> future : futures) | 107 | for(QFuture<void> future : futures) | ||
94 | future.waitForFinished(); | 108 | future.waitForFinished(); | ||
95 | } | 109 | } | ||
96 | 110 | | |||
97 | // This is like the above 1-channel stretch, but extended for 3 channels. | 111 | // This is like the above 1-channel stretch, but extended for 3 channels. | ||
98 | // This could have been more modular, but the three channels are combined | 112 | // This could have been more modular, but the three channels are combined | ||
99 | // into a single qRgb value at the end, so it seems the simplest thing is to | 113 | // into a single qRgb value at the end, so it seems the simplest thing is to | ||
100 | // replicate the code. It is assume the colors are not interleaved--the red image | 114 | // replicate the code. It is assume the colors are not interleaved--the red image | ||
101 | // is stored fully, then the green, then the blue. | 115 | // is stored fully, then the green, then the blue. | ||
116 | // Sampling is applied to the output (that is, with sampling=2, we compute every other output | ||||
117 | // sample both in width and height, so the output would have about 4X fewer pixels. | ||||
102 | template <typename T> | 118 | template <typename T> | ||
103 | void stretchThreeChannels(T *inputBuffer, QImage *outputImage, | 119 | void stretchThreeChannels(T *inputBuffer, QImage *outputImage, | ||
104 | const StretchParams& stretchParams, | 120 | const StretchParams& stretchParams, | ||
105 | int inputRange, int imageHeight, int imageWidth) | 121 | int inputRange, int imageHeight, int imageWidth, int sampling) | ||
106 | { | 122 | { | ||
107 | QVector<QFuture<void>> futures; | 123 | QVector<QFuture<void>> futures; | ||
108 | 124 | | |||
109 | // We're outputting uint8, so the max output is 255. | 125 | // We're outputting uint8, so the max output is 255. | ||
110 | constexpr int maxOutput = 255; | 126 | constexpr int maxOutput = 255; | ||
111 | 127 | | |||
112 | // Maximum possible input value (e.g. 1024*64 - 1 for a 16 bit unsigned int). | 128 | // Maximum possible input value (e.g. 1024*64 - 1 for a 16 bit unsigned int). | ||
113 | const float maxInput = inputRange > 1 ? inputRange - 1 : inputRange; | 129 | const float maxInput = inputRange > 1 ? inputRange - 1 : inputRange; | ||
114 | 130 | | |||
115 | const float midtonesR = stretchParams.grey_red.midtones; | 131 | const float midtonesR = stretchParams.grey_red.midtones; | ||
116 | const float highlightsR = stretchParams.grey_red.highlights; | 132 | const float highlightsR = stretchParams.grey_red.highlights; | ||
117 | const float shadowsR = stretchParams.grey_red.shadows; | 133 | const float shadowsR = stretchParams.grey_red.shadows; | ||
118 | const float midtonesG = stretchParams.green.midtones; | 134 | const float midtonesG = stretchParams.green.midtones; | ||
119 | const float highlightsG = stretchParams.green.highlights; | 135 | const float highlightsG = stretchParams.green.highlights; | ||
120 | const float shadowsG = stretchParams.green.shadows; | 136 | const float shadowsG = stretchParams.green.shadows; | ||
121 | const float midtonesB = stretchParams.blue.midtones; | 137 | const float midtonesB = stretchParams.blue.midtones; | ||
122 | const float highlightsB = stretchParams.blue.highlights; | 138 | const float highlightsB = stretchParams.blue.highlights; | ||
123 | const float shadowsB = stretchParams.blue.shadows; | 139 | const float shadowsB = stretchParams.blue.shadows; | ||
124 | 140 | | |||
125 | // Precomputed expressions moved out of the loop. | 141 | // Precomputed expressions moved out of the loop. | ||
126 | // hightlights - shadows, protecting for divide-by-0, in a 0->1.0 scale. | 142 | // hightlights - shadows, protecting for divide-by-0, in a 0->1.0 scale. | ||
127 | const float hsRangeFactorR = highlightsR == shadowsR ? 1.0 : 1.0 / (highlightsR - shadowsR); | 143 | const float hsRangeFactorR = highlightsR == shadowsR ? 1.0f : 1.0f / (highlightsR - shadowsR); | ||
128 | const float hsRangeFactorG = highlightsG == shadowsG ? 1.0 : 1.0 / (highlightsG - shadowsG); | 144 | const float hsRangeFactorG = highlightsG == shadowsG ? 1.0f : 1.0f / (highlightsG - shadowsG); | ||
129 | const float hsRangeFactorB = highlightsB == shadowsB ? 1.0 : 1.0 / (highlightsB - shadowsB); | 145 | const float hsRangeFactorB = highlightsB == shadowsB ? 1.0f : 1.0f / (highlightsB - shadowsB); | ||
130 | // Shadow and highlight values translated to the ADU scale. | 146 | // Shadow and highlight values translated to the ADU scale. | ||
131 | const T nativeShadowsR = shadowsR * maxInput; | 147 | const T nativeShadowsR = shadowsR * maxInput; | ||
132 | const T nativeShadowsG = shadowsG * maxInput; | 148 | const T nativeShadowsG = shadowsG * maxInput; | ||
133 | const T nativeShadowsB = shadowsB * maxInput; | 149 | const T nativeShadowsB = shadowsB * maxInput; | ||
134 | const T nativeHighlightsR = highlightsR * maxInput; | 150 | const T nativeHighlightsR = highlightsR * maxInput; | ||
135 | const T nativeHighlightsG = highlightsG * maxInput; | 151 | const T nativeHighlightsG = highlightsG * maxInput; | ||
136 | const T nativeHighlightsB = highlightsB * maxInput; | 152 | const T nativeHighlightsB = highlightsB * maxInput; | ||
137 | // Constants based on above needed for the stretch calculations. | 153 | // Constants based on above needed for the stretch calculations. | ||
138 | const float k1R = (midtonesR - 1) * hsRangeFactorR * maxOutput / maxInput; | 154 | const float k1R = (midtonesR - 1) * hsRangeFactorR * maxOutput / maxInput; | ||
139 | const float k1G = (midtonesG - 1) * hsRangeFactorG * maxOutput / maxInput; | 155 | const float k1G = (midtonesG - 1) * hsRangeFactorG * maxOutput / maxInput; | ||
140 | const float k1B = (midtonesB - 1) * hsRangeFactorB * maxOutput / maxInput; | 156 | const float k1B = (midtonesB - 1) * hsRangeFactorB * maxOutput / maxInput; | ||
141 | const float k2R = ((2 * midtonesR) - 1) * hsRangeFactorR / maxInput; | 157 | const float k2R = ((2 * midtonesR) - 1) * hsRangeFactorR / maxInput; | ||
142 | const float k2G = ((2 * midtonesG) - 1) * hsRangeFactorG / maxInput; | 158 | const float k2G = ((2 * midtonesG) - 1) * hsRangeFactorG / maxInput; | ||
143 | const float k2B = ((2 * midtonesB) - 1) * hsRangeFactorB / maxInput; | 159 | const float k2B = ((2 * midtonesB) - 1) * hsRangeFactorB / maxInput; | ||
144 | 160 | | |||
145 | const int size = imageWidth * imageHeight; | 161 | const int size = imageWidth * imageHeight; | ||
146 | 162 | | |||
147 | for (int j = 0; j < imageHeight; j++) | 163 | for (int j = 0, jout = 0; j < imageHeight; j+=sampling, jout++) | ||
148 | { | 164 | { | ||
149 | futures.append(QtConcurrent::run([ = ]() | 165 | futures.append(QtConcurrent::run([ = ]() | ||
150 | { | 166 | { | ||
151 | // R, G, B input images are stored one after another. | 167 | // R, G, B input images are stored one after another. | ||
152 | T * inputLineR = inputBuffer + j * imageWidth; | 168 | T * inputLineR = inputBuffer + j * imageWidth; | ||
153 | T * inputLineG = inputLineR + size; | 169 | T * inputLineG = inputLineR + size; | ||
154 | T * inputLineB = inputLineG + size; | 170 | T * inputLineB = inputLineG + size; | ||
155 | 171 | | |||
156 | auto * scanLine = reinterpret_cast<QRgb*>(outputImage->scanLine(j)); | 172 | auto * scanLine = reinterpret_cast<QRgb*>(outputImage->scanLine(jout)); | ||
157 | 173 | | |||
158 | for (int i = 0; i < imageWidth; i++) | 174 | for (int i = 0, iout = 0; i < imageWidth; i+=sampling, iout++) | ||
159 | { | 175 | { | ||
160 | const T inputR = inputLineR[i]; | 176 | const T inputR = inputLineR[i]; | ||
161 | const T inputG = inputLineG[i]; | 177 | const T inputG = inputLineG[i]; | ||
162 | const T inputB = inputLineB[i]; | 178 | const T inputB = inputLineB[i]; | ||
163 | 179 | | |||
164 | uint8_t red, green, blue; | 180 | uint8_t red, green, blue; | ||
165 | 181 | | |||
166 | if (inputR < nativeShadowsR) red = 0; | 182 | if (inputR < nativeShadowsR) red = 0; | ||
Show All 14 Lines | 196 | } | |||
181 | 197 | | |||
182 | if (inputB < nativeShadowsB) blue = 0; | 198 | if (inputB < nativeShadowsB) blue = 0; | ||
183 | else if (inputB >= nativeHighlightsB) blue = maxOutput; | 199 | else if (inputB >= nativeHighlightsB) blue = maxOutput; | ||
184 | else | 200 | else | ||
185 | { | 201 | { | ||
186 | const T inputFloored = (inputB - nativeShadowsB); | 202 | const T inputFloored = (inputB - nativeShadowsB); | ||
187 | blue = (inputFloored * k1B) / (inputFloored * k2B - midtonesB); | 203 | blue = (inputFloored * k1B) / (inputFloored * k2B - midtonesB); | ||
188 | } | 204 | } | ||
189 | scanLine[i] = qRgb(red, green, blue); | 205 | scanLine[iout] = qRgb(red, green, blue); | ||
190 | } | 206 | } | ||
191 | })); | 207 | })); | ||
192 | } | 208 | } | ||
193 | for(QFuture<void> future : futures) | 209 | for(QFuture<void> future : futures) | ||
194 | future.waitForFinished(); | 210 | future.waitForFinished(); | ||
195 | } | 211 | } | ||
196 | 212 | | |||
197 | template <typename T> | 213 | template <typename T> | ||
198 | void stretchChannels(T *input_buffer, QImage *output_image, | 214 | void stretchChannels(T *input_buffer, QImage *output_image, | ||
199 | const StretchParams& stretch_params, | 215 | const StretchParams& stretch_params, | ||
200 | int input_range, int image_height, int image_width, int num_channels) | 216 | int input_range, int image_height, int image_width, int num_channels, int sampling) | ||
201 | { | 217 | { | ||
202 | if (num_channels == 1) | 218 | if (num_channels == 1) | ||
203 | stretchOneChannel(input_buffer, output_image, stretch_params, input_range, | 219 | stretchOneChannel(input_buffer, output_image, stretch_params, input_range, | ||
204 | image_height, image_width); | 220 | image_height, image_width, sampling); | ||
205 | else if (num_channels == 3) | 221 | else if (num_channels == 3) | ||
206 | stretchThreeChannels(input_buffer, output_image, stretch_params, input_range, | 222 | stretchThreeChannels(input_buffer, output_image, stretch_params, input_range, | ||
207 | image_height, image_width); | 223 | image_height, image_width, sampling); | ||
208 | } | 224 | } | ||
209 | 225 | | |||
210 | // See section 8.5.7 in above link http://pixinsight.com/doc/docs/XISF-1.0-spec/XISF-1.0-spec.html | 226 | // See section 8.5.7 in above link http://pixinsight.com/doc/docs/XISF-1.0-spec/XISF-1.0-spec.html | ||
211 | template <typename T> | 227 | template <typename T> | ||
212 | void computeParamsOneChannel(T *buffer, StretchParams1Channel *params, | 228 | void computeParamsOneChannel(T *buffer, StretchParams1Channel *params, | ||
213 | int inputRange, int height, int width) | 229 | int inputRange, int height, int width) | ||
214 | { | 230 | { | ||
215 | // Find the median sample. | 231 | // Find the median sample. | ||
216 | constexpr int maxSamples = 500000; | 232 | constexpr int maxSamples = 500000; | ||
217 | const int sampleBy = width * height < maxSamples ? 1 : width * height / maxSamples; | 233 | const int sampleBy = width * height < maxSamples ? 1 : width * height / maxSamples; | ||
218 | const int size = width * height; | | |||
219 | T medianSample = median(buffer, width * height, sampleBy); | | |||
220 | 234 | | |||
235 | T medianSample = median(buffer, width * height, sampleBy); | ||||
221 | // Find the Median deviation: 1.4826 * median of abs(sample[i] - median). | 236 | // Find the Median deviation: 1.4826 * median of abs(sample[i] - median). | ||
222 | const int numSamples = width * height / sampleBy; | 237 | const int numSamples = width * height / sampleBy; | ||
223 | std::vector<T> deviations(numSamples); | 238 | std::vector<T> deviations(numSamples); | ||
224 | for (int index = 0, i = 0; i < numSamples; ++i, index += sampleBy) | 239 | for (int index = 0, i = 0; i < numSamples; ++i, index += sampleBy) | ||
225 | { | 240 | { | ||
226 | if (medianSample > buffer[index]) | 241 | if (medianSample > buffer[index]) | ||
227 | deviations[i] = medianSample - buffer[index]; | 242 | deviations[i] = medianSample - buffer[index]; | ||
228 | else | 243 | else | ||
Show All 18 Lines | |||||
247 | if (!upperHalf) { | 262 | if (!upperHalf) { | ||
248 | X = normalizedMedian - shadows; | 263 | X = normalizedMedian - shadows; | ||
249 | M = B; | 264 | M = B; | ||
250 | } else { | 265 | } else { | ||
251 | X = B; | 266 | X = B; | ||
252 | M = highlights - normalizedMedian; | 267 | M = highlights - normalizedMedian; | ||
253 | } | 268 | } | ||
254 | float midtones; | 269 | float midtones; | ||
255 | if (X == 0) midtones = 0; | 270 | if (X == 0) midtones = 0.0f; | ||
256 | else if (X == M) midtones = 0.5; | 271 | else if (X == M) midtones = 0.5f; | ||
257 | else if (X == 1) midtones = 1.0; | 272 | else if (X == 1) midtones = 1.0f; | ||
258 | else midtones = ((M - 1) * X) / ((2 * M - 1) * X - M); | 273 | else midtones = ((M - 1) * X) / ((2 * M - 1) * X - M); | ||
259 | 274 | | |||
260 | // Store the params. | 275 | // Store the params. | ||
261 | params->shadows = shadows; | 276 | params->shadows = shadows; | ||
262 | params->highlights = highlights; | 277 | params->highlights = highlights; | ||
263 | params->midtones = midtones; | 278 | params->midtones = midtones; | ||
264 | params->shadows_expansion = 0.0; | 279 | params->shadows_expansion = 0.0; | ||
265 | params->highlights_expansion = 1.0; | 280 | params->highlights_expansion = 1.0; | ||
266 | } | 281 | } | ||
267 | 282 | | |||
268 | // Need to know the possible range of input values. | 283 | // Need to know the possible range of input values. | ||
269 | // Using the type of the sample and guessing. | 284 | // Using the type of the sample and guessing. | ||
270 | // Perhaps we should examine the contents for the file | 285 | // Perhaps we should examine the contents for the file | ||
271 | // (e.g. look at maximum value and extrapolate from that). | 286 | // (e.g. look at maximum value and extrapolate from that). | ||
272 | int getRange(int data_type) | 287 | int getRange(int data_type) | ||
273 | { | 288 | { | ||
274 | switch (data_type) | 289 | switch (data_type) | ||
275 | { | 290 | { | ||
276 | case TBYTE: | 291 | case TBYTE: | ||
277 | return 256; | 292 | return 256; | ||
278 | break; | | |||
279 | case TSHORT: | 293 | case TSHORT: | ||
280 | return 64*1024; | 294 | return 64*1024; | ||
281 | break; | | |||
282 | case TUSHORT: | 295 | case TUSHORT: | ||
283 | return 64*1024; | 296 | return 64*1024; | ||
284 | break; | | |||
285 | case TLONG: | 297 | case TLONG: | ||
286 | return 64*1024; | 298 | return 64*1024; | ||
287 | break; | | |||
288 | case TFLOAT: | 299 | case TFLOAT: | ||
289 | return 64*1024; | 300 | return 64*1024; | ||
290 | break; | | |||
291 | case TLONGLONG: | 301 | case TLONGLONG: | ||
292 | return 64*1024; | 302 | return 64*1024; | ||
293 | break; | | |||
294 | case TDOUBLE: | 303 | case TDOUBLE: | ||
295 | return 64*1024; | 304 | return 64*1024; | ||
296 | break; | | |||
297 | default: | 305 | default: | ||
298 | return 64*1024; | 306 | return 64*1024; | ||
299 | break; | | |||
300 | } | 307 | } | ||
301 | } | 308 | } | ||
302 | 309 | | |||
303 | } // namespace | 310 | } // namespace | ||
304 | 311 | | |||
305 | Stretch::Stretch(int width, int height, int channels, int data_type) | 312 | Stretch::Stretch(int width, int height, int channels, int data_type) | ||
306 | { | 313 | { | ||
307 | image_width = width; | 314 | image_width = width; | ||
308 | image_height = height; | 315 | image_height = height; | ||
309 | image_channels = channels; | 316 | image_channels = channels; | ||
310 | dataType = data_type; | 317 | dataType = data_type; | ||
311 | input_range = getRange(dataType); | 318 | input_range = getRange(dataType); | ||
312 | } | 319 | } | ||
313 | 320 | | |||
314 | void Stretch::run(uint8_t *input, QImage *outputImage) | 321 | void Stretch::run(uint8_t *input, QImage *outputImage, int sampling) | ||
315 | { | 322 | { | ||
323 | Q_ASSERT(outputImage->width() == (image_width + sampling - 1) / sampling); | ||||
324 | Q_ASSERT(outputImage->height() == (image_height + sampling - 1) / sampling); | ||||
325 | recalculateInputRange(input); | ||||
326 | | ||||
316 | switch (dataType) | 327 | switch (dataType) | ||
317 | { | 328 | { | ||
318 | case TBYTE: | 329 | case TBYTE: | ||
319 | stretchChannels(reinterpret_cast<uint8_t*>(input), outputImage, params, | 330 | stretchChannels(reinterpret_cast<uint8_t*>(input), outputImage, params, | ||
320 | input_range, image_height, image_width, image_channels); | 331 | input_range, image_height, image_width, image_channels, sampling); | ||
321 | break; | 332 | break; | ||
322 | case TSHORT: | 333 | case TSHORT: | ||
323 | stretchChannels(reinterpret_cast<short*>(input), outputImage, params, | 334 | stretchChannels(reinterpret_cast<short*>(input), outputImage, params, | ||
324 | input_range, image_height, image_width, image_channels); | 335 | input_range, image_height, image_width, image_channels, sampling); | ||
325 | break; | 336 | break; | ||
326 | case TUSHORT: | 337 | case TUSHORT: | ||
327 | stretchChannels(reinterpret_cast<unsigned short*>(input), outputImage, params, | 338 | stretchChannels(reinterpret_cast<unsigned short*>(input), outputImage, params, | ||
328 | input_range, image_height, image_width, image_channels); | 339 | input_range, image_height, image_width, image_channels, sampling); | ||
329 | break; | 340 | break; | ||
330 | case TLONG: | 341 | case TLONG: | ||
331 | stretchChannels(reinterpret_cast<long*>(input), outputImage, params, | 342 | stretchChannels(reinterpret_cast<long*>(input), outputImage, params, | ||
332 | input_range, image_height, image_width, image_channels); | 343 | input_range, image_height, image_width, image_channels, sampling); | ||
333 | break; | 344 | break; | ||
334 | case TFLOAT: | 345 | case TFLOAT: | ||
335 | stretchChannels(reinterpret_cast<float*>(input), outputImage, params, | 346 | stretchChannels(reinterpret_cast<float*>(input), outputImage, params, | ||
336 | input_range, image_height, image_width, image_channels); | 347 | input_range, image_height, image_width, image_channels, sampling); | ||
337 | break; | 348 | break; | ||
338 | case TLONGLONG: | 349 | case TLONGLONG: | ||
339 | stretchChannels(reinterpret_cast<long long*>(input), outputImage, params, | 350 | stretchChannels(reinterpret_cast<long long*>(input), outputImage, params, | ||
340 | input_range, image_height, image_width, image_channels); | 351 | input_range, image_height, image_width, image_channels, sampling); | ||
341 | break; | 352 | break; | ||
342 | case TDOUBLE: | 353 | case TDOUBLE: | ||
343 | stretchChannels(reinterpret_cast<double*>(input), outputImage, params, | 354 | stretchChannels(reinterpret_cast<double*>(input), outputImage, params, | ||
344 | input_range, image_height, image_width, image_channels); | 355 | input_range, image_height, image_width, image_channels, sampling); | ||
345 | break; | 356 | break; | ||
346 | default: | 357 | default: | ||
347 | break; | 358 | break; | ||
348 | } | 359 | } | ||
349 | } | 360 | } | ||
350 | 361 | | |||
362 | // The input range for float/double is ambiguous, and we can't tell without the buffer, | ||||
363 | // so we set it to 64K and possibly reduce it when we see the data. | ||||
364 | void Stretch::recalculateInputRange(uint8_t *input) | ||||
365 | { | ||||
366 | if (input_range <= 1) return; | ||||
367 | if (dataType != TFLOAT && dataType != TDOUBLE) return; | ||||
368 | | ||||
369 | float mx = 0; | ||||
370 | if (dataType == TFLOAT) | ||||
371 | mx = sampledMax(reinterpret_cast<float*>(input), image_height * image_width, 1000); | ||||
372 | else if (dataType == TDOUBLE) | ||||
373 | mx = sampledMax(reinterpret_cast<double*>(input), image_height * image_width, 1000); | ||||
374 | if (mx <= 1.01f) input_range = 1; | ||||
375 | } | ||||
376 | | ||||
351 | StretchParams Stretch::computeParams(uint8_t *input) | 377 | StretchParams Stretch::computeParams(uint8_t *input) | ||
352 | { | 378 | { | ||
379 | recalculateInputRange(input); | ||||
353 | StretchParams result; | 380 | StretchParams result; | ||
354 | for (int channel = 0; channel < image_channels; ++channel) | 381 | for (int channel = 0; channel < image_channels; ++channel) | ||
355 | { | 382 | { | ||
356 | int offset = channel * image_width * image_height; | 383 | int offset = channel * image_width * image_height; | ||
357 | StretchParams1Channel *params = channel == 0 ? &result.grey_red : | 384 | StretchParams1Channel *params = channel == 0 ? &result.grey_red : | ||
358 | (channel == 1 ? &result.green : &result.blue); | 385 | (channel == 1 ? &result.green : &result.blue); | ||
359 | switch (dataType) | 386 | switch (dataType) | ||
360 | { | 387 | { | ||
▲ Show 20 Lines • Show All 55 Lines • Show Last 20 Lines |