/src/gdal/alg/gdaldither.cpp
Line | Count | Source |
1 | | /****************************************************************************** |
2 | | * |
3 | | * Project: CIETMap Phase 2 |
4 | | * Purpose: Convert RGB (24bit) to a pseudo-colored approximation using |
5 | | * Floyd-Steinberg dithering (error diffusion). |
6 | | * Author: Frank Warmerdam, warmerdam@pobox.com |
7 | | * |
8 | | ****************************************************************************** |
9 | | * Copyright (c) 2001, Frank Warmerdam |
10 | | * Copyright (c) 2007, Even Rouault <even dot rouault at spatialys.com> |
11 | | * |
12 | | * SPDX-License-Identifier: MIT |
13 | | ****************************************************************************** |
14 | | * |
15 | | * Notes: |
16 | | * |
17 | | * [1] Floyd-Steinberg dither: |
18 | | * I should point out that the actual fractions we used were, assuming |
19 | | * you are at X, moving left to right: |
20 | | * |
21 | | * X 7/16 |
22 | | * 3/16 5/16 1/16 |
23 | | * |
24 | | * Note that the error goes to four neighbors, not three. I think this |
25 | | * will probably do better (at least for black and white) than the |
26 | | * 3/8-3/8-1/4 distribution, at the cost of greater processing. I have |
27 | | * seen the 3/8-3/8-1/4 distribution described as "our" algorithm before, |
28 | | * but I have no idea who the credit really belongs to. |
29 | | * -- |
30 | | * Lou Steinberg |
31 | | */ |
32 | | |
33 | | #include "cpl_port.h" |
34 | | #include "gdal_alg.h" |
35 | | #include "gdal_alg_priv.h" |
36 | | |
37 | | #include <cmath> |
38 | | #include <cstdlib> |
39 | | #include <cstring> |
40 | | #include <algorithm> |
41 | | |
42 | | #include "cpl_conv.h" |
43 | | #include "cpl_error.h" |
44 | | #include "cpl_progress.h" |
45 | | #include "cpl_vsi.h" |
46 | | #include "gdal.h" |
47 | | #include "gdal_priv.h" |
48 | | |
49 | | #ifdef USE_NEON_OPTIMIZATIONS |
50 | | #define USE_SSE2 |
51 | | #include "include_sse2neon.h" |
52 | | #elif defined(__x86_64) || defined(_M_X64) |
53 | | #define USE_SSE2 |
54 | | #include <emmintrin.h> |
55 | | #endif |
56 | | |
57 | | #ifdef USE_SSE2 |
58 | | |
59 | 0 | #define CAST_PCT(x) reinterpret_cast<GByte *>(x) |
60 | | #define ALIGN_INT_ARRAY_ON_16_BYTE(x) \ |
61 | 0 | (((reinterpret_cast<GUIntptr_t>(x) % 16) != 0) \ |
62 | 0 | ? reinterpret_cast<int *>(reinterpret_cast<GByte *>(x) + 16 - \ |
63 | 0 | (reinterpret_cast<GUIntptr_t>(x) % 16)) \ |
64 | 0 | : (x)) |
65 | | #else |
66 | | #define CAST_PCT(x) x |
67 | | #endif |
68 | | |
69 | | #ifndef MAKE_COLOR_CODE_defined |
70 | | #define MAKE_COLOR_CODE_defined |
71 | | |
72 | | static int MAKE_COLOR_CODE(int r, int g, int b) |
73 | 0 | { |
74 | 0 | return r | (g << 8) | (b << 16); |
75 | 0 | } |
76 | | #endif |
77 | | |
78 | | static void FindNearestColor(int nColors, const int *panPCT, |
79 | | GByte *pabyColorMap, int nCLevels, int nDstNoData); |
80 | | static int FindNearestColor(int nColors, const int *panPCT, int nRedValue, |
81 | | int nGreenValue, int nBlueValue, int nDstNoData); |
82 | | |
83 | | // Structure for a hashmap from a color code to a color index of the |
84 | | // color table. |
85 | | |
86 | | // NOTE: if changing the size of this structure, edit |
87 | | // MEDIAN_CUT_AND_DITHER_BUFFER_SIZE_65536 in gdal_alg_priv.h and take |
88 | | // into account HashHistogram in gdalmediancut.cpp. |
89 | | typedef struct |
90 | | { |
91 | | GUInt32 nColorCode; |
92 | | GUInt32 nColorCode2; |
93 | | GUInt32 nColorCode3; |
94 | | GByte nIndex; |
95 | | GByte nIndex2; |
96 | | GByte nIndex3; |
97 | | GByte nPadding; |
98 | | } ColorIndex; |
99 | | |
100 | | /************************************************************************/ |
101 | | /* GDALDitherRGB2PCT() */ |
102 | | /************************************************************************/ |
103 | | |
104 | | #ifndef IsColorCodeSet_defined |
105 | | #define IsColorCodeSet_defined |
106 | | |
107 | | static inline bool IsColorCodeSet(GUInt32 nColorCode) |
108 | 0 | { |
109 | 0 | return (nColorCode >> 31) == 0; |
110 | 0 | } |
111 | | #endif |
112 | | |
113 | | /** |
114 | | * 24bit to 8bit conversion with dithering. |
115 | | * |
116 | | * This functions utilizes Floyd-Steinberg dithering in the process of |
117 | | * converting a 24bit RGB image into a pseudocolored 8bit image using a |
118 | | * provided color table. |
119 | | * |
120 | | * The red, green and blue input bands do not necessarily need to come |
121 | | * from the same file, but they must be the same width and height. They will |
122 | | * be clipped to 8bit during reading, so non-eight bit bands are generally |
123 | | * inappropriate. Likewise the hTarget band will be written with 8bit values |
124 | | * and must match the width and height of the source bands. |
125 | | * |
126 | | * The color table cannot have more than 256 entries. |
127 | | * |
128 | | * Since GDAL 3.13, source nodata values or mask band will be taken into account |
129 | | * to determine which pixels are valid, provided that a destination nodata value |
130 | | * is set to hTarget. |
131 | | * |
132 | | * @param hRed Red input band. |
133 | | * @param hGreen Green input band. |
134 | | * @param hBlue Blue input band. |
135 | | * @param hTarget Output band. |
136 | | * @param hColorTable the color table to use with the output band. |
137 | | * @param pfnProgress callback for reporting algorithm progress matching the |
138 | | * GDALProgressFunc() semantics. May be NULL. |
139 | | * @param pProgressArg callback argument passed to pfnProgress. |
140 | | * |
141 | | * @return CE_None on success or CE_Failure if an error occurs. |
142 | | */ |
143 | | |
144 | | int CPL_STDCALL GDALDitherRGB2PCT(GDALRasterBandH hRed, GDALRasterBandH hGreen, |
145 | | GDALRasterBandH hBlue, |
146 | | GDALRasterBandH hTarget, |
147 | | GDALColorTableH hColorTable, |
148 | | GDALProgressFunc pfnProgress, |
149 | | void *pProgressArg) |
150 | | |
151 | 0 | { |
152 | 0 | return GDALDitherRGB2PCTInternal(hRed, hGreen, hBlue, hTarget, hColorTable, |
153 | 0 | 5, nullptr, TRUE, pfnProgress, |
154 | 0 | pProgressArg); |
155 | 0 | } |
156 | | |
157 | | int GDALDitherRGB2PCTInternal( |
158 | | GDALRasterBandH hRed, GDALRasterBandH hGreen, GDALRasterBandH hBlue, |
159 | | GDALRasterBandH hTarget, GDALColorTableH hColorTable, int nBits, |
160 | | // NULL or at least 256 * 256 * 256 * sizeof(GInt16) bytes. |
161 | | GInt16 *pasDynamicColorMap, int bDither, GDALProgressFunc pfnProgress, |
162 | | void *pProgressArg) |
163 | 0 | { |
164 | 0 | VALIDATE_POINTER1(hRed, "GDALDitherRGB2PCT", CE_Failure); |
165 | 0 | VALIDATE_POINTER1(hGreen, "GDALDitherRGB2PCT", CE_Failure); |
166 | 0 | VALIDATE_POINTER1(hBlue, "GDALDitherRGB2PCT", CE_Failure); |
167 | 0 | VALIDATE_POINTER1(hTarget, "GDALDitherRGB2PCT", CE_Failure); |
168 | 0 | VALIDATE_POINTER1(hColorTable, "GDALDitherRGB2PCT", CE_Failure); |
169 | | |
170 | | /* -------------------------------------------------------------------- */ |
171 | | /* Validate parameters. */ |
172 | | /* -------------------------------------------------------------------- */ |
173 | 0 | const int nXSize = GDALGetRasterBandXSize(hRed); |
174 | 0 | const int nYSize = GDALGetRasterBandYSize(hRed); |
175 | |
|
176 | 0 | if (GDALGetRasterBandXSize(hGreen) != nXSize || |
177 | 0 | GDALGetRasterBandYSize(hGreen) != nYSize || |
178 | 0 | GDALGetRasterBandXSize(hBlue) != nXSize || |
179 | 0 | GDALGetRasterBandYSize(hBlue) != nYSize) |
180 | 0 | { |
181 | 0 | CPLError(CE_Failure, CPLE_IllegalArg, |
182 | 0 | "Green or blue band doesn't match size of red band."); |
183 | |
|
184 | 0 | return CE_Failure; |
185 | 0 | } |
186 | | |
187 | 0 | if (GDALGetRasterBandXSize(hTarget) != nXSize || |
188 | 0 | GDALGetRasterBandYSize(hTarget) != nYSize) |
189 | 0 | { |
190 | 0 | CPLError(CE_Failure, CPLE_IllegalArg, |
191 | 0 | "GDALDitherRGB2PCT(): " |
192 | 0 | "Target band doesn't match size of source bands."); |
193 | |
|
194 | 0 | return CE_Failure; |
195 | 0 | } |
196 | | |
197 | 0 | if (pfnProgress == nullptr) |
198 | 0 | pfnProgress = GDALDummyProgress; |
199 | |
|
200 | 0 | int nSrcNoData = -1; |
201 | 0 | GDALRasterBandH hMaskBand = nullptr; |
202 | 0 | GByte byDstNoData = 0; |
203 | 0 | int nDstNoData = -1; |
204 | 0 | int bDstHasNoData = FALSE; |
205 | 0 | const double dfDstNoData = |
206 | 0 | GDALGetRasterNoDataValue(hTarget, &bDstHasNoData); |
207 | 0 | if (bDstHasNoData && dfDstNoData >= 0 && dfDstNoData <= 255 && |
208 | 0 | std::round(dfDstNoData) == dfDstNoData) |
209 | 0 | { |
210 | 0 | byDstNoData = static_cast<GByte>(dfDstNoData); |
211 | 0 | nDstNoData = byDstNoData; |
212 | |
|
213 | 0 | int bSrcHasNoDataR = FALSE; |
214 | 0 | const double dfSrcNoDataR = |
215 | 0 | GDALGetRasterNoDataValue(hRed, &bSrcHasNoDataR); |
216 | 0 | int bSrcHasNoDataG = FALSE; |
217 | 0 | const double dfSrcNoDataG = |
218 | 0 | GDALGetRasterNoDataValue(hGreen, &bSrcHasNoDataG); |
219 | 0 | int bSrcHasNoDataB = FALSE; |
220 | 0 | const double dfSrcNoDataB = |
221 | 0 | GDALGetRasterNoDataValue(hBlue, &bSrcHasNoDataB); |
222 | 0 | if (bSrcHasNoDataR && bSrcHasNoDataG && bSrcHasNoDataB && |
223 | 0 | dfSrcNoDataR == dfSrcNoDataG && dfSrcNoDataR == dfSrcNoDataB && |
224 | 0 | dfSrcNoDataR >= 0 && dfSrcNoDataR <= 255 && |
225 | 0 | std::round(dfSrcNoDataR) == dfSrcNoDataR) |
226 | 0 | { |
227 | 0 | nSrcNoData = static_cast<int>(dfSrcNoDataR); |
228 | 0 | } |
229 | 0 | else |
230 | 0 | { |
231 | 0 | const int nMaskFlags = GDALGetMaskFlags(hRed); |
232 | 0 | if ((nMaskFlags & GMF_PER_DATASET)) |
233 | 0 | hMaskBand = GDALGetMaskBand(hRed); |
234 | 0 | } |
235 | 0 | } |
236 | | |
237 | | /* -------------------------------------------------------------------- */ |
238 | | /* Setup more direct colormap. */ |
239 | | /* -------------------------------------------------------------------- */ |
240 | 0 | int iColor; |
241 | 0 | #ifdef USE_SSE2 |
242 | 0 | int anPCTUnaligned[256 + 4]; // 4 for alignment on 16-byte boundary. |
243 | 0 | int *anPCT = ALIGN_INT_ARRAY_ON_16_BYTE(anPCTUnaligned); |
244 | | #else |
245 | | int anPCT[256 * 4] = {}; |
246 | | #endif |
247 | 0 | const int nColors = GDALGetColorEntryCount(hColorTable); |
248 | |
|
249 | 0 | if (nColors == 0) |
250 | 0 | { |
251 | 0 | CPLError(CE_Failure, CPLE_IllegalArg, |
252 | 0 | "GDALDitherRGB2PCT(): " |
253 | 0 | "Color table must not be empty."); |
254 | |
|
255 | 0 | return CE_Failure; |
256 | 0 | } |
257 | 0 | else if (nColors > 256) |
258 | 0 | { |
259 | 0 | CPLError(CE_Failure, CPLE_IllegalArg, |
260 | 0 | "GDALDitherRGB2PCT(): " |
261 | 0 | "Color table cannot have more than 256 entries."); |
262 | |
|
263 | 0 | return CE_Failure; |
264 | 0 | } |
265 | | |
266 | 0 | iColor = 0; |
267 | 0 | do |
268 | 0 | { |
269 | 0 | GDALColorEntry sEntry; |
270 | |
|
271 | 0 | GDALGetColorEntryAsRGB(hColorTable, iColor, &sEntry); |
272 | 0 | CAST_PCT(anPCT)[4 * iColor + 0] = static_cast<GByte>(sEntry.c1); |
273 | 0 | CAST_PCT(anPCT)[4 * iColor + 1] = static_cast<GByte>(sEntry.c2); |
274 | 0 | CAST_PCT(anPCT)[4 * iColor + 2] = static_cast<GByte>(sEntry.c3); |
275 | 0 | CAST_PCT(anPCT)[4 * iColor + 3] = 0; |
276 | |
|
277 | 0 | iColor++; |
278 | 0 | } while (iColor < nColors); |
279 | |
|
280 | 0 | #ifdef USE_SSE2 |
281 | | // Pad to multiple of 8 colors. |
282 | 0 | const int nColorsMod8 = nColors % 8; |
283 | 0 | if (nColorsMod8) |
284 | 0 | { |
285 | 0 | int iDest = nColors; |
286 | 0 | for (iColor = 0; iColor < 8 - nColorsMod8 && iDest < 256; |
287 | 0 | iColor++, iDest++) |
288 | 0 | { |
289 | 0 | anPCT[iDest] = anPCT[nColors - 1]; |
290 | 0 | } |
291 | 0 | } |
292 | 0 | #endif |
293 | | |
294 | | /* -------------------------------------------------------------------- */ |
295 | | /* Setup various variables. */ |
296 | | /* -------------------------------------------------------------------- */ |
297 | 0 | const int nCLevels = 1 << nBits; |
298 | 0 | const int nCLevelsCube = nCLevels * nCLevels * nCLevels; |
299 | 0 | ColorIndex *psColorIndexMap = nullptr; |
300 | |
|
301 | 0 | GByte *pabyRed = static_cast<GByte *>(VSI_MALLOC_VERBOSE(nXSize)); |
302 | 0 | GByte *pabyGreen = static_cast<GByte *>(VSI_MALLOC_VERBOSE(nXSize)); |
303 | 0 | GByte *pabyBlue = static_cast<GByte *>(VSI_MALLOC_VERBOSE(nXSize)); |
304 | 0 | std::unique_ptr<GByte, VSIFreeReleaser> pabyMask; |
305 | 0 | if (hMaskBand) |
306 | 0 | pabyMask.reset(static_cast<GByte *>(VSI_MALLOC_VERBOSE(nXSize))); |
307 | |
|
308 | 0 | GByte *pabyIndex = static_cast<GByte *>(VSI_MALLOC_VERBOSE(nXSize)); |
309 | |
|
310 | 0 | int *panError = |
311 | 0 | static_cast<int *>(VSI_CALLOC_VERBOSE(sizeof(int), (nXSize + 2) * 3)); |
312 | |
|
313 | 0 | if (pabyRed == nullptr || pabyGreen == nullptr || pabyBlue == nullptr || |
314 | 0 | pabyIndex == nullptr || panError == nullptr || (hMaskBand && !pabyMask)) |
315 | 0 | { |
316 | 0 | CPLFree(pabyRed); |
317 | 0 | CPLFree(pabyGreen); |
318 | 0 | CPLFree(pabyBlue); |
319 | 0 | CPLFree(pabyIndex); |
320 | 0 | CPLFree(panError); |
321 | |
|
322 | 0 | return CE_Failure; |
323 | 0 | } |
324 | | |
325 | 0 | GByte *pabyColorMap = nullptr; |
326 | 0 | if (pasDynamicColorMap == nullptr) |
327 | 0 | { |
328 | | /* -------------------------------------------------------------------- |
329 | | */ |
330 | | /* Build a 24bit to 8 bit color mapping. */ |
331 | | /* -------------------------------------------------------------------- |
332 | | */ |
333 | |
|
334 | 0 | pabyColorMap = static_cast<GByte *>( |
335 | 0 | VSI_MALLOC_VERBOSE(nCLevelsCube * sizeof(GByte))); |
336 | 0 | if (pabyColorMap == nullptr) |
337 | 0 | { |
338 | 0 | CPLFree(pabyRed); |
339 | 0 | CPLFree(pabyGreen); |
340 | 0 | CPLFree(pabyBlue); |
341 | 0 | CPLFree(pabyIndex); |
342 | 0 | CPLFree(panError); |
343 | 0 | CPLFree(pabyColorMap); |
344 | |
|
345 | 0 | return CE_Failure; |
346 | 0 | } |
347 | | |
348 | 0 | FindNearestColor(nColors, anPCT, pabyColorMap, nCLevels, nDstNoData); |
349 | 0 | } |
350 | 0 | else |
351 | 0 | { |
352 | 0 | pabyColorMap = nullptr; |
353 | 0 | if (nBits == 8 && static_cast<GIntBig>(nXSize) * nYSize <= 65536) |
354 | 0 | { |
355 | | // If the image is small enough, then the number of colors |
356 | | // will be limited and using a hashmap, rather than a full table |
357 | | // will be more efficient. |
358 | 0 | psColorIndexMap = |
359 | 0 | reinterpret_cast<ColorIndex *>(pasDynamicColorMap); |
360 | 0 | memset(psColorIndexMap, 0xFF, sizeof(ColorIndex) * PRIME_FOR_65536); |
361 | 0 | } |
362 | 0 | else |
363 | 0 | { |
364 | 0 | memset(pasDynamicColorMap, 0xFF, 256 * 256 * 256 * sizeof(GInt16)); |
365 | 0 | } |
366 | 0 | } |
367 | | |
368 | | /* ==================================================================== */ |
369 | | /* Loop over all scanlines of data to process. */ |
370 | | /* ==================================================================== */ |
371 | 0 | CPLErr err = CE_None; |
372 | |
|
373 | 0 | for (int iScanline = 0; iScanline < nYSize; iScanline++) |
374 | 0 | { |
375 | | /* -------------------------------------------------------------------- |
376 | | */ |
377 | | /* Report progress */ |
378 | | /* -------------------------------------------------------------------- |
379 | | */ |
380 | 0 | if (!pfnProgress(iScanline / static_cast<double>(nYSize), nullptr, |
381 | 0 | pProgressArg)) |
382 | 0 | { |
383 | 0 | CPLError(CE_Failure, CPLE_UserInterrupt, "User Terminated"); |
384 | 0 | CPLFree(pabyRed); |
385 | 0 | CPLFree(pabyGreen); |
386 | 0 | CPLFree(pabyBlue); |
387 | 0 | CPLFree(pabyIndex); |
388 | 0 | CPLFree(panError); |
389 | 0 | CPLFree(pabyColorMap); |
390 | |
|
391 | 0 | return CE_Failure; |
392 | 0 | } |
393 | | |
394 | | /* -------------------------------------------------------------------- |
395 | | */ |
396 | | /* Read source data. */ |
397 | | /* -------------------------------------------------------------------- |
398 | | */ |
399 | 0 | CPLErr err1 = GDALRasterIO(hRed, GF_Read, 0, iScanline, nXSize, 1, |
400 | 0 | pabyRed, nXSize, 1, GDT_UInt8, 0, 0); |
401 | 0 | if (err1 == CE_None) |
402 | 0 | err1 = GDALRasterIO(hGreen, GF_Read, 0, iScanline, nXSize, 1, |
403 | 0 | pabyGreen, nXSize, 1, GDT_UInt8, 0, 0); |
404 | 0 | if (err1 == CE_None) |
405 | 0 | err1 = GDALRasterIO(hBlue, GF_Read, 0, iScanline, nXSize, 1, |
406 | 0 | pabyBlue, nXSize, 1, GDT_UInt8, 0, 0); |
407 | 0 | if (err1 == CE_None && hMaskBand) |
408 | 0 | err1 = GDALRasterIO(hMaskBand, GF_Read, 0, iScanline, nXSize, 1, |
409 | 0 | pabyMask.get(), nXSize, 1, GDT_UInt8, 0, 0); |
410 | 0 | if (err1 != CE_None) |
411 | 0 | { |
412 | 0 | CPLFree(pabyRed); |
413 | 0 | CPLFree(pabyGreen); |
414 | 0 | CPLFree(pabyBlue); |
415 | 0 | CPLFree(pabyIndex); |
416 | 0 | CPLFree(panError); |
417 | 0 | CPLFree(pabyColorMap); |
418 | |
|
419 | 0 | return err1; |
420 | 0 | } |
421 | | |
422 | | /* -------------------------------------------------------------------- |
423 | | */ |
424 | | /* Apply the error from the previous line to this one. */ |
425 | | /* -------------------------------------------------------------------- |
426 | | */ |
427 | 0 | if (bDither) |
428 | 0 | { |
429 | 0 | for (int i = 0; i < nXSize; i++) |
430 | 0 | { |
431 | 0 | if (nDstNoData >= 0 && |
432 | 0 | ((pabyRed[i] == nSrcNoData && pabyGreen[i] == nSrcNoData && |
433 | 0 | pabyBlue[i] == nSrcNoData) || |
434 | 0 | (pabyMask && (pabyMask.get())[i] == 0))) |
435 | 0 | { |
436 | 0 | continue; |
437 | 0 | } |
438 | | |
439 | 0 | pabyRed[i] = static_cast<GByte>(std::max( |
440 | 0 | 0, std::min(255, (pabyRed[i] + panError[i * 3 + 0 + 3])))); |
441 | 0 | pabyGreen[i] = static_cast<GByte>(std::max( |
442 | 0 | 0, |
443 | 0 | std::min(255, (pabyGreen[i] + panError[i * 3 + 1 + 3])))); |
444 | 0 | pabyBlue[i] = static_cast<GByte>(std::max( |
445 | 0 | 0, std::min(255, (pabyBlue[i] + panError[i * 3 + 2 + 3])))); |
446 | 0 | } |
447 | |
|
448 | 0 | memset(panError, 0, sizeof(int) * (nXSize + 2) * 3); |
449 | 0 | } |
450 | | |
451 | | /* -------------------------------------------------------------------- |
452 | | */ |
453 | | /* Figure out the nearest color to the RGB value. */ |
454 | | /* -------------------------------------------------------------------- |
455 | | */ |
456 | 0 | int nLastRedError = 0; |
457 | 0 | int nLastGreenError = 0; |
458 | 0 | int nLastBlueError = 0; |
459 | |
|
460 | 0 | for (int i = 0; i < nXSize; i++) |
461 | 0 | { |
462 | 0 | if (nDstNoData >= 0 && |
463 | 0 | ((pabyRed[i] == nSrcNoData && pabyGreen[i] == nSrcNoData && |
464 | 0 | pabyBlue[i] == nSrcNoData) || |
465 | 0 | (pabyMask && (pabyMask.get())[i] == 0))) |
466 | 0 | { |
467 | 0 | nLastRedError = 0; |
468 | 0 | nLastGreenError = 0; |
469 | 0 | nLastBlueError = 0; |
470 | 0 | pabyIndex[i] = byDstNoData; |
471 | 0 | continue; |
472 | 0 | } |
473 | | |
474 | 0 | const int nRedValue = |
475 | 0 | std::max(0, std::min(255, pabyRed[i] + nLastRedError)); |
476 | 0 | const int nGreenValue = |
477 | 0 | std::max(0, std::min(255, pabyGreen[i] + nLastGreenError)); |
478 | 0 | const int nBlueValue = |
479 | 0 | std::max(0, std::min(255, pabyBlue[i] + nLastBlueError)); |
480 | |
|
481 | 0 | int iIndex = 0; |
482 | 0 | int nError = 0; |
483 | 0 | int nSixth = 0; |
484 | 0 | if (psColorIndexMap) |
485 | 0 | { |
486 | 0 | const GUInt32 nColorCode = |
487 | 0 | MAKE_COLOR_CODE(nRedValue, nGreenValue, nBlueValue); |
488 | 0 | GUInt32 nIdx = nColorCode % PRIME_FOR_65536; |
489 | 0 | while (true) |
490 | 0 | { |
491 | 0 | if (psColorIndexMap[nIdx].nColorCode == nColorCode) |
492 | 0 | { |
493 | 0 | iIndex = psColorIndexMap[nIdx].nIndex; |
494 | 0 | break; |
495 | 0 | } |
496 | 0 | if (!IsColorCodeSet(psColorIndexMap[nIdx].nColorCode)) |
497 | 0 | { |
498 | 0 | psColorIndexMap[nIdx].nColorCode = nColorCode; |
499 | 0 | iIndex = FindNearestColor(nColors, anPCT, nRedValue, |
500 | 0 | nGreenValue, nBlueValue, |
501 | 0 | nDstNoData); |
502 | 0 | psColorIndexMap[nIdx].nIndex = |
503 | 0 | static_cast<GByte>(iIndex); |
504 | 0 | break; |
505 | 0 | } |
506 | 0 | if (psColorIndexMap[nIdx].nColorCode2 == nColorCode) |
507 | 0 | { |
508 | 0 | iIndex = psColorIndexMap[nIdx].nIndex2; |
509 | 0 | break; |
510 | 0 | } |
511 | 0 | if (!IsColorCodeSet(psColorIndexMap[nIdx].nColorCode2)) |
512 | 0 | { |
513 | 0 | psColorIndexMap[nIdx].nColorCode2 = nColorCode; |
514 | 0 | iIndex = FindNearestColor(nColors, anPCT, nRedValue, |
515 | 0 | nGreenValue, nBlueValue, |
516 | 0 | nDstNoData); |
517 | 0 | psColorIndexMap[nIdx].nIndex2 = |
518 | 0 | static_cast<GByte>(iIndex); |
519 | 0 | break; |
520 | 0 | } |
521 | 0 | if (psColorIndexMap[nIdx].nColorCode3 == nColorCode) |
522 | 0 | { |
523 | 0 | iIndex = psColorIndexMap[nIdx].nIndex3; |
524 | 0 | break; |
525 | 0 | } |
526 | 0 | if (!IsColorCodeSet(psColorIndexMap[nIdx].nColorCode3)) |
527 | 0 | { |
528 | 0 | psColorIndexMap[nIdx].nColorCode3 = nColorCode; |
529 | 0 | iIndex = FindNearestColor(nColors, anPCT, nRedValue, |
530 | 0 | nGreenValue, nBlueValue, |
531 | 0 | nDstNoData); |
532 | 0 | psColorIndexMap[nIdx].nIndex3 = |
533 | 0 | static_cast<GByte>(iIndex); |
534 | 0 | break; |
535 | 0 | } |
536 | | |
537 | 0 | do |
538 | 0 | { |
539 | 0 | nIdx += 257; |
540 | 0 | if (nIdx >= PRIME_FOR_65536) |
541 | 0 | nIdx -= PRIME_FOR_65536; |
542 | 0 | } while ( |
543 | 0 | IsColorCodeSet(psColorIndexMap[nIdx].nColorCode) && |
544 | 0 | psColorIndexMap[nIdx].nColorCode != nColorCode && |
545 | 0 | IsColorCodeSet(psColorIndexMap[nIdx].nColorCode2) && |
546 | 0 | psColorIndexMap[nIdx].nColorCode2 != nColorCode && |
547 | 0 | IsColorCodeSet(psColorIndexMap[nIdx].nColorCode3) && |
548 | 0 | psColorIndexMap[nIdx].nColorCode3 != nColorCode); |
549 | 0 | } |
550 | 0 | } |
551 | 0 | else if (pasDynamicColorMap == nullptr) |
552 | 0 | { |
553 | 0 | const int iRed = nRedValue * nCLevels / 256; |
554 | 0 | const int iGreen = nGreenValue * nCLevels / 256; |
555 | 0 | const int iBlue = nBlueValue * nCLevels / 256; |
556 | |
|
557 | 0 | iIndex = pabyColorMap[iRed + iGreen * nCLevels + |
558 | 0 | iBlue * nCLevels * nCLevels]; |
559 | 0 | } |
560 | 0 | else |
561 | 0 | { |
562 | 0 | const GUInt32 nColorCode = |
563 | 0 | MAKE_COLOR_CODE(nRedValue, nGreenValue, nBlueValue); |
564 | 0 | GInt16 *psIndex = &pasDynamicColorMap[nColorCode]; |
565 | 0 | if (*psIndex < 0) |
566 | 0 | { |
567 | 0 | *psIndex = static_cast<GInt16>( |
568 | 0 | FindNearestColor(nColors, anPCT, nRedValue, nGreenValue, |
569 | 0 | nBlueValue, nDstNoData)); |
570 | 0 | iIndex = *psIndex; |
571 | 0 | } |
572 | 0 | else |
573 | 0 | { |
574 | 0 | iIndex = *psIndex; |
575 | 0 | } |
576 | 0 | } |
577 | |
|
578 | 0 | pabyIndex[i] = static_cast<GByte>(iIndex); |
579 | 0 | if (!bDither) |
580 | 0 | continue; |
581 | | |
582 | | /* -------------------------------------------------------------------- |
583 | | */ |
584 | | /* Compute Red error, and carry it on to the next error line. |
585 | | */ |
586 | | /* -------------------------------------------------------------------- |
587 | | */ |
588 | 0 | nError = nRedValue - CAST_PCT(anPCT)[4 * iIndex + 0]; |
589 | 0 | nSixth = nError / 6; |
590 | |
|
591 | 0 | panError[i * 3] += nSixth; |
592 | 0 | panError[i * 3 + 6] = nSixth; |
593 | 0 | panError[i * 3 + 3] += nError - 5 * nSixth; |
594 | |
|
595 | 0 | nLastRedError = 2 * nSixth; |
596 | | |
597 | | /* -------------------------------------------------------------------- |
598 | | */ |
599 | | /* Compute Green error, and carry it on to the next error line. |
600 | | */ |
601 | | /* -------------------------------------------------------------------- |
602 | | */ |
603 | 0 | nError = nGreenValue - CAST_PCT(anPCT)[4 * iIndex + 1]; |
604 | 0 | nSixth = nError / 6; |
605 | |
|
606 | 0 | panError[i * 3 + 1] += nSixth; |
607 | 0 | panError[i * 3 + 6 + 1] = nSixth; |
608 | 0 | panError[i * 3 + 3 + 1] += nError - 5 * nSixth; |
609 | |
|
610 | 0 | nLastGreenError = 2 * nSixth; |
611 | | |
612 | | /* -------------------------------------------------------------------- |
613 | | */ |
614 | | /* Compute Blue error, and carry it on to the next error line. |
615 | | */ |
616 | | /* -------------------------------------------------------------------- |
617 | | */ |
618 | 0 | nError = nBlueValue - CAST_PCT(anPCT)[4 * iIndex + 2]; |
619 | 0 | nSixth = nError / 6; |
620 | |
|
621 | 0 | panError[i * 3 + 2] += nSixth; |
622 | 0 | panError[i * 3 + 6 + 2] = nSixth; |
623 | 0 | panError[i * 3 + 3 + 2] += nError - 5 * nSixth; |
624 | |
|
625 | 0 | nLastBlueError = 2 * nSixth; |
626 | 0 | } |
627 | | |
628 | | /* -------------------------------------------------------------------- |
629 | | */ |
630 | | /* Write results. */ |
631 | | /* -------------------------------------------------------------------- |
632 | | */ |
633 | 0 | err = GDALRasterIO(hTarget, GF_Write, 0, iScanline, nXSize, 1, |
634 | 0 | pabyIndex, nXSize, 1, GDT_UInt8, 0, 0); |
635 | 0 | if (err != CE_None) |
636 | 0 | break; |
637 | 0 | } |
638 | | |
639 | 0 | pfnProgress(1.0, nullptr, pProgressArg); |
640 | | |
641 | | /* -------------------------------------------------------------------- */ |
642 | | /* Cleanup */ |
643 | | /* -------------------------------------------------------------------- */ |
644 | 0 | CPLFree(pabyRed); |
645 | 0 | CPLFree(pabyGreen); |
646 | 0 | CPLFree(pabyBlue); |
647 | 0 | CPLFree(pabyIndex); |
648 | 0 | CPLFree(panError); |
649 | 0 | CPLFree(pabyColorMap); |
650 | |
|
651 | 0 | return err; |
652 | 0 | } |
653 | | |
654 | | static int FindNearestColor(int nColors, const int *panPCT, int nRedValue, |
655 | | int nGreenValue, int nBlueValue, int nDstNoData) |
656 | | |
657 | 0 | { |
658 | 0 | #ifdef USE_SSE2 |
659 | 0 | int nBestDist = 768; |
660 | 0 | int nBestIndex = 0; |
661 | |
|
662 | 0 | int anDistanceUnaligned[16 + 4] = |
663 | 0 | {}; // 4 for alignment on 16-byte boundary. |
664 | 0 | int *anDistance = ALIGN_INT_ARRAY_ON_16_BYTE(anDistanceUnaligned); |
665 | |
|
666 | 0 | const __m128i ff = _mm_set1_epi32(0xFFFFFFFF); |
667 | 0 | const __m128i mask_low = _mm_srli_epi64(ff, 32); |
668 | 0 | const __m128i mask_high = _mm_slli_epi64(ff, 32); |
669 | |
|
670 | 0 | const unsigned int nColorVal = |
671 | 0 | MAKE_COLOR_CODE(nRedValue, nGreenValue, nBlueValue); |
672 | 0 | const __m128i thisColor = _mm_set1_epi32(nColorVal); |
673 | 0 | const __m128i thisColor_low = _mm_srli_epi64(thisColor, 32); |
674 | 0 | const __m128i thisColor_high = _mm_slli_epi64(thisColor, 32); |
675 | |
|
676 | 0 | for (int iColor = 0; iColor < nColors; iColor += 8) |
677 | 0 | { |
678 | 0 | const __m128i pctColor = |
679 | 0 | _mm_load_si128(reinterpret_cast<const __m128i *>(&panPCT[iColor])); |
680 | 0 | const __m128i pctColor2 = _mm_load_si128( |
681 | 0 | reinterpret_cast<const __m128i *>(&panPCT[iColor + 4])); |
682 | |
|
683 | 0 | _mm_store_si128( |
684 | 0 | reinterpret_cast<__m128i *>(anDistance), |
685 | 0 | _mm_sad_epu8(_mm_and_si128(pctColor, mask_low), thisColor_low)); |
686 | 0 | _mm_store_si128( |
687 | 0 | reinterpret_cast<__m128i *>(anDistance + 4), |
688 | 0 | _mm_sad_epu8(_mm_and_si128(pctColor, mask_high), thisColor_high)); |
689 | 0 | _mm_store_si128( |
690 | 0 | reinterpret_cast<__m128i *>(anDistance + 8), |
691 | 0 | _mm_sad_epu8(_mm_and_si128(pctColor2, mask_low), thisColor_low)); |
692 | 0 | _mm_store_si128( |
693 | 0 | reinterpret_cast<__m128i *>(anDistance + 12), |
694 | 0 | _mm_sad_epu8(_mm_and_si128(pctColor2, mask_high), thisColor_high)); |
695 | |
|
696 | 0 | if (anDistance[0] < nBestDist && iColor != nDstNoData) |
697 | 0 | { |
698 | 0 | nBestIndex = iColor; |
699 | 0 | nBestDist = anDistance[0]; |
700 | 0 | } |
701 | 0 | if (anDistance[4] < nBestDist && iColor + 1 != nDstNoData) |
702 | 0 | { |
703 | 0 | nBestIndex = iColor + 1; |
704 | 0 | nBestDist = anDistance[4]; |
705 | 0 | } |
706 | 0 | if (anDistance[2] < nBestDist && iColor + 2 != nDstNoData) |
707 | 0 | { |
708 | 0 | nBestIndex = iColor + 2; |
709 | 0 | nBestDist = anDistance[2]; |
710 | 0 | } |
711 | 0 | if (anDistance[6] < nBestDist && iColor + 3 != nDstNoData) |
712 | 0 | { |
713 | 0 | nBestIndex = iColor + 3; |
714 | 0 | nBestDist = anDistance[6]; |
715 | 0 | } |
716 | 0 | if (anDistance[8 + 0] < nBestDist && iColor + 4 != nDstNoData) |
717 | 0 | { |
718 | 0 | nBestIndex = iColor + 4; |
719 | 0 | nBestDist = anDistance[8 + 0]; |
720 | 0 | } |
721 | 0 | if (anDistance[8 + 4] < nBestDist && iColor + 5 != nDstNoData) |
722 | 0 | { |
723 | 0 | nBestIndex = iColor + 4 + 1; |
724 | 0 | nBestDist = anDistance[8 + 4]; |
725 | 0 | } |
726 | 0 | if (anDistance[8 + 2] < nBestDist && iColor + 6 != nDstNoData) |
727 | 0 | { |
728 | 0 | nBestIndex = iColor + 4 + 2; |
729 | 0 | nBestDist = anDistance[8 + 2]; |
730 | 0 | } |
731 | 0 | if (anDistance[8 + 6] < nBestDist && iColor + 7 != nDstNoData) |
732 | 0 | { |
733 | 0 | nBestIndex = iColor + 4 + 3; |
734 | 0 | nBestDist = anDistance[8 + 6]; |
735 | 0 | } |
736 | 0 | } |
737 | 0 | return nBestIndex; |
738 | | #else |
739 | | int nBestDist = 768; |
740 | | int nBestIndex = 0; |
741 | | |
742 | | for (int iColor = 0; iColor < nColors; iColor++) |
743 | | { |
744 | | if (iColor != nDstNoData) |
745 | | { |
746 | | const int nThisDist = |
747 | | std::abs(nRedValue - panPCT[4 * iColor + 0]) + |
748 | | std::abs(nGreenValue - panPCT[4 * iColor + 1]) + |
749 | | std::abs(nBlueValue - panPCT[4 * iColor + 2]); |
750 | | |
751 | | if (nThisDist < nBestDist) |
752 | | { |
753 | | nBestIndex = iColor; |
754 | | nBestDist = nThisDist; |
755 | | } |
756 | | } |
757 | | } |
758 | | return nBestIndex; |
759 | | #endif |
760 | 0 | } |
761 | | |
762 | | /************************************************************************/ |
763 | | /* FindNearestColor() */ |
764 | | /* */ |
765 | | /* Finear near PCT color for any RGB color. */ |
766 | | /************************************************************************/ |
767 | | |
768 | | static void FindNearestColor(int nColors, const int *panPCT, |
769 | | GByte *pabyColorMap, int nCLevels, int nDstNoData) |
770 | | |
771 | 0 | { |
772 | | /* -------------------------------------------------------------------- */ |
773 | | /* Loop over all the cells in the high density cube. */ |
774 | | /* -------------------------------------------------------------------- */ |
775 | 0 | for (int iBlue = 0; iBlue < nCLevels; iBlue++) |
776 | 0 | { |
777 | 0 | for (int iGreen = 0; iGreen < nCLevels; iGreen++) |
778 | 0 | { |
779 | 0 | for (int iRed = 0; iRed < nCLevels; iRed++) |
780 | 0 | { |
781 | 0 | const int nRedValue = (iRed * 255) / (nCLevels - 1); |
782 | 0 | const int nGreenValue = (iGreen * 255) / (nCLevels - 1); |
783 | 0 | const int nBlueValue = (iBlue * 255) / (nCLevels - 1); |
784 | |
|
785 | 0 | const int nBestIndex = |
786 | 0 | FindNearestColor(nColors, panPCT, nRedValue, nGreenValue, |
787 | 0 | nBlueValue, nDstNoData); |
788 | 0 | pabyColorMap[iRed + iGreen * nCLevels + |
789 | 0 | iBlue * nCLevels * nCLevels] = |
790 | 0 | static_cast<GByte>(nBestIndex); |
791 | 0 | } |
792 | 0 | } |
793 | 0 | } |
794 | 0 | } |