/src/libjxl/lib/jpegli/color_quantize.cc
Line | Count | Source (jump to first uncovered line) |
1 | | // Copyright (c) the JPEG XL Project Authors. All rights reserved. |
2 | | // |
3 | | // Use of this source code is governed by a BSD-style |
4 | | // license that can be found in the LICENSE file. |
5 | | |
6 | | #include "lib/jpegli/color_quantize.h" |
7 | | |
8 | | #include <cmath> |
9 | | #include <limits> |
10 | | #include <unordered_map> |
11 | | |
12 | | #include "lib/jpegli/decode_internal.h" |
13 | | #include "lib/jpegli/error.h" |
14 | | |
15 | | namespace jpegli { |
16 | | |
17 | | namespace { |
18 | | |
19 | | constexpr int kNumColorCellBits[kMaxComponents] = {3, 4, 3, 3}; |
20 | | constexpr int kCompW[kMaxComponents] = {2, 3, 1, 1}; |
21 | | |
22 | 0 | int Pow(int a, int b) { |
23 | 0 | int r = 1; |
24 | 0 | for (int i = 0; i < b; ++i) { |
25 | 0 | r *= a; |
26 | 0 | } |
27 | 0 | return r; |
28 | 0 | } |
29 | | |
30 | 0 | int ComponentOrder(j_decompress_ptr cinfo, int i) { |
31 | 0 | if (cinfo->out_color_components == 3) { |
32 | 0 | return i < 2 ? 1 - i : i; |
33 | 0 | } |
34 | 0 | return i; |
35 | 0 | } |
36 | | |
37 | 0 | int GetColorComponent(int i, int N) { |
38 | 0 | return (i * 255 + (N - 1) / 2) / (N - 1); |
39 | 0 | } |
40 | | |
41 | | } // namespace |
42 | | |
43 | 0 | void ChooseColorMap1Pass(j_decompress_ptr cinfo) { |
44 | 0 | jpeg_decomp_master* m = cinfo->master; |
45 | 0 | int components = cinfo->out_color_components; |
46 | 0 | int desired = std::min(cinfo->desired_number_of_colors, 256); |
47 | 0 | int num = 1; |
48 | 0 | while (Pow(num + 1, components) <= desired) { |
49 | 0 | ++num; |
50 | 0 | } |
51 | 0 | if (num == 1) { |
52 | 0 | JPEGLI_ERROR("Too few colors (%d) in requested colormap", desired); |
53 | 0 | } |
54 | 0 | int actual = Pow(num, components); |
55 | 0 | for (int i = 0; i < components; ++i) { |
56 | 0 | m->num_colors_[i] = num; |
57 | 0 | } |
58 | 0 | while (actual < desired) { |
59 | 0 | int total = actual; |
60 | 0 | for (int i = 0; i < components; ++i) { |
61 | 0 | int c = ComponentOrder(cinfo, i); |
62 | 0 | int new_total = (actual / m->num_colors_[c]) * (m->num_colors_[c] + 1); |
63 | 0 | if (new_total <= desired) { |
64 | 0 | ++m->num_colors_[c]; |
65 | 0 | actual = new_total; |
66 | 0 | } |
67 | 0 | } |
68 | 0 | if (actual == total) { |
69 | 0 | break; |
70 | 0 | } |
71 | 0 | } |
72 | 0 | cinfo->actual_number_of_colors = actual; |
73 | 0 | cinfo->colormap = (*cinfo->mem->alloc_sarray)( |
74 | 0 | reinterpret_cast<j_common_ptr>(cinfo), JPOOL_IMAGE, actual, components); |
75 | 0 | int next_color[kMaxComponents] = {0}; |
76 | 0 | for (int i = 0; i < actual; ++i) { |
77 | 0 | for (int c = 0; c < components; ++c) { |
78 | 0 | cinfo->colormap[c][i] = |
79 | 0 | GetColorComponent(next_color[c], m->num_colors_[c]); |
80 | 0 | } |
81 | 0 | int c = components - 1; |
82 | 0 | while (c > 0 && next_color[c] + 1 == m->num_colors_[c]) { |
83 | 0 | next_color[c--] = 0; |
84 | 0 | } |
85 | 0 | ++next_color[c]; |
86 | 0 | } |
87 | 0 | if (!m->colormap_lut_) { |
88 | 0 | m->colormap_lut_ = Allocate<uint8_t>(cinfo, components * 256, JPOOL_IMAGE); |
89 | 0 | } |
90 | 0 | int stride = actual; |
91 | 0 | for (int c = 0; c < components; ++c) { |
92 | 0 | int N = m->num_colors_[c]; |
93 | 0 | stride /= N; |
94 | 0 | for (int i = 0; i < 256; ++i) { |
95 | 0 | int index = ((2 * i - 1) * (N - 1) + 254) / 510; |
96 | 0 | m->colormap_lut_[c * 256 + i] = index * stride; |
97 | 0 | } |
98 | 0 | } |
99 | 0 | } |
100 | | |
101 | | namespace { |
102 | | |
103 | | // 2^13 priority levels for the PQ seems to be a good compromise between |
104 | | // accuracy, running time and stack space usage. |
105 | | const int kMaxPriority = 1 << 13; |
106 | | const int kMaxLevel = 3; |
107 | | |
108 | | // This function is used in the multi-resolution grid to be able to compute |
109 | | // the keys for the different resolutions by just shifting the first key. |
110 | 0 | inline int InterlaceBitsRGB(uint8_t r, uint8_t g, uint8_t b) { |
111 | 0 | int z = 0; |
112 | 0 | for (int i = 0; i < 7; ++i) { |
113 | 0 | z += (r >> 5) & 4; |
114 | 0 | z += (g >> 6) & 2; |
115 | 0 | z += (b >> 7); |
116 | 0 | z <<= 3; |
117 | 0 | r <<= 1; |
118 | 0 | g <<= 1; |
119 | 0 | b <<= 1; |
120 | 0 | } |
121 | 0 | z += (r >> 5) & 4; |
122 | 0 | z += (g >> 6) & 2; |
123 | 0 | z += (b >> 7); |
124 | 0 | return z; |
125 | 0 | } |
126 | | |
127 | | // This function will compute the actual priorities of the colors based on |
128 | | // the current distance from the palette, the population count and the signals |
129 | | // from the multi-resolution grid. |
130 | 0 | inline int Priority(int d, int n, const int* density, const int* radius) { |
131 | 0 | int p = d * n; |
132 | 0 | for (int level = 0; level < kMaxLevel; ++level) { |
133 | 0 | if (d > radius[level]) { |
134 | 0 | p += density[level] * (d - radius[level]); |
135 | 0 | } |
136 | 0 | } |
137 | 0 | return std::min(kMaxPriority - 1, p >> 4); |
138 | 0 | } |
139 | | |
140 | | inline int ColorIntQuadDistanceRGB(uint8_t r1, uint8_t g1, uint8_t b1, |
141 | 0 | uint8_t r2, uint8_t g2, uint8_t b2) { |
142 | | // weights for the intensity calculation |
143 | 0 | static constexpr int ired = 2; |
144 | 0 | static constexpr int igreen = 5; |
145 | 0 | static constexpr int iblue = 1; |
146 | | // normalization factor for the intensity calculation (2^ishift) |
147 | 0 | static constexpr int ishift = 3; |
148 | 0 | const int rd = r1 - r2; |
149 | 0 | const int gd = g1 - g2; |
150 | 0 | const int bd = b1 - b2; |
151 | 0 | const int id = ired * rd + igreen * gd + iblue * bd; |
152 | 0 | return rd * rd + gd * gd + bd * bd + ((id * id) >> (2 * ishift)); |
153 | 0 | } |
154 | | |
155 | 0 | inline int ScaleQuadDistanceRGB(int d) { |
156 | 0 | return static_cast<int>(std::lround(sqrt(d * 0.25))); |
157 | 0 | } |
158 | | |
159 | | // The function updates the minimal distances, the clustering and the |
160 | | // quantization error after the insertion of the new color into the palette. |
161 | | void AddToRGBPalette(const uint8_t* red, const uint8_t* green, |
162 | | const uint8_t* blue, |
163 | | const int* count, // histogram of colors |
164 | | const int index, // index of color to be added |
165 | | const int k, // size of current palette |
166 | | const int n, // number of colors |
167 | | int* dist, // array of distances from palette |
168 | | int* cluster, // mapping of color indices to palette |
169 | | int* center, // the inverse mapping |
170 | 0 | int64_t* error) { // measure of the quantization error |
171 | 0 | center[k] = index; |
172 | 0 | cluster[index] = k; |
173 | 0 | *error -= |
174 | 0 | static_cast<int64_t>(dist[index]) * static_cast<int64_t>(count[index]); |
175 | 0 | dist[index] = 0; |
176 | 0 | for (int j = 0; j < n; ++j) { |
177 | 0 | if (dist[j] > 0) { |
178 | 0 | const int d = ColorIntQuadDistanceRGB( |
179 | 0 | red[index], green[index], blue[index], red[j], green[j], blue[j]); |
180 | 0 | if (d < dist[j]) { |
181 | 0 | *error += static_cast<int64_t>((d - dist[j])) * |
182 | 0 | static_cast<int64_t>(count[j]); |
183 | 0 | dist[j] = d; |
184 | 0 | cluster[j] = k; |
185 | 0 | } |
186 | 0 | } |
187 | 0 | } |
188 | 0 | } |
189 | | |
190 | | struct RGBPixelHasher { |
191 | | // A quick but good-enough hash to get 24 bits of RGB into the lower 12 bits. |
192 | 0 | size_t operator()(uint32_t a) const { return (a ^ (a >> 12)) * 0x9e3779b9; } |
193 | | }; |
194 | | |
195 | | struct WangHasher { |
196 | | // Thomas Wang's Hash. Nearly perfect and still quite fast. Above (for |
197 | | // pixels) we use a simpler hash because the number of hash calls is |
198 | | // proportional to the number of pixels and that hash dominates; we want the |
199 | | // cost to be minimal and we start with a large table. We can use a better |
200 | | // hash for the histogram since the number of hash calls is proportional to |
201 | | // the number of unique colors in the image, which is hopefully much smaller. |
202 | | // Note that the difference is slight; e.g. replacing RGBPixelHasher with |
203 | | // WangHasher only slows things down by 5% on an Opteron. |
204 | 0 | size_t operator()(uint32_t a) const { |
205 | 0 | a = (a ^ 61) ^ (a >> 16); |
206 | 0 | a = a + (a << 3); |
207 | 0 | a = a ^ (a >> 4); |
208 | 0 | a = a * 0x27d4eb2d; |
209 | 0 | a = a ^ (a >> 15); |
210 | 0 | return a; |
211 | 0 | } |
212 | | }; |
213 | | |
214 | | // Build an index of all the different colors in the input |
215 | | // image. To do this we map the 24 bit RGB representation of the colors |
216 | | // to a unique integer index assigned to the different colors in order of |
217 | | // appearance in the image. Return the number of unique colors found. |
218 | | // The colors are pre-quantized to 3 * 6 bits precision. |
219 | | int BuildRGBColorIndex(const uint8_t* const image, int const num_pixels, |
220 | | int* const count, uint8_t* const red, |
221 | 0 | uint8_t* const green, uint8_t* const blue) { |
222 | | // Impossible because rgb are in the low 24 bits, and the upper 8 bits is 0. |
223 | 0 | const uint32_t impossible_pixel_value = 0x10000000; |
224 | 0 | std::unordered_map<uint32_t, int, RGBPixelHasher> index_map(1 << 12); |
225 | 0 | std::unordered_map<uint32_t, int, RGBPixelHasher>::iterator index_map_lookup; |
226 | 0 | const uint8_t* imagep = &image[0]; |
227 | 0 | uint32_t prev_pixel = impossible_pixel_value; |
228 | 0 | int index = 0; |
229 | 0 | int n = 0; |
230 | 0 | for (int i = 0; i < num_pixels; ++i) { |
231 | 0 | uint8_t r = ((*imagep++) & 0xfc) + 2; |
232 | 0 | uint8_t g = ((*imagep++) & 0xfc) + 2; |
233 | 0 | uint8_t b = ((*imagep++) & 0xfc) + 2; |
234 | 0 | uint32_t pixel = (b << 16) | (g << 8) | r; |
235 | 0 | if (pixel != prev_pixel) { |
236 | 0 | prev_pixel = pixel; |
237 | 0 | index_map_lookup = index_map.find(pixel); |
238 | 0 | if (index_map_lookup != index_map.end()) { |
239 | 0 | index = index_map_lookup->second; |
240 | 0 | } else { |
241 | 0 | index_map[pixel] = index = n++; |
242 | 0 | red[index] = r; |
243 | 0 | green[index] = g; |
244 | 0 | blue[index] = b; |
245 | 0 | } |
246 | 0 | } |
247 | 0 | ++count[index]; |
248 | 0 | } |
249 | 0 | return n; |
250 | 0 | } |
251 | | |
252 | | } // namespace |
253 | | |
254 | 0 | void ChooseColorMap2Pass(j_decompress_ptr cinfo) { |
255 | 0 | if (cinfo->out_color_space != JCS_RGB) { |
256 | 0 | JPEGLI_ERROR("Two-pass quantizer must use RGB output color space."); |
257 | 0 | } |
258 | 0 | jpeg_decomp_master* m = cinfo->master; |
259 | 0 | const size_t num_pixels = cinfo->output_width * cinfo->output_height; |
260 | 0 | const int max_color_count = std::max<size_t>(num_pixels, 1u << 18); |
261 | 0 | const int max_palette_size = cinfo->desired_number_of_colors; |
262 | 0 | std::unique_ptr<uint8_t[]> red(new uint8_t[max_color_count]); |
263 | 0 | std::unique_ptr<uint8_t[]> green(new uint8_t[max_color_count]); |
264 | 0 | std::unique_ptr<uint8_t[]> blue(new uint8_t[max_color_count]); |
265 | 0 | std::vector<int> count(max_color_count, 0); |
266 | | // number of colors |
267 | 0 | int n = BuildRGBColorIndex(m->pixels_, num_pixels, count.data(), &red[0], |
268 | 0 | &green[0], &blue[0]); |
269 | |
|
270 | 0 | std::vector<int> dist(n, std::numeric_limits<int>::max()); |
271 | 0 | std::vector<int> cluster(n); |
272 | 0 | std::vector<bool> in_palette(n, false); |
273 | 0 | int center[256]; |
274 | 0 | int k = 0; // palette size |
275 | 0 | const int count_threshold = (num_pixels * 4) / max_palette_size; |
276 | 0 | static constexpr int kAveragePixelErrorThreshold = 1; |
277 | 0 | const int64_t error_threshold = num_pixels * kAveragePixelErrorThreshold; |
278 | 0 | int64_t error = 0; // quantization error |
279 | |
|
280 | 0 | int max_count = 0; |
281 | 0 | int winner = 0; |
282 | 0 | for (int i = 0; i < n; ++i) { |
283 | 0 | if (count[i] > max_count) { |
284 | 0 | max_count = count[i]; |
285 | 0 | winner = i; |
286 | 0 | } |
287 | 0 | if (!in_palette[i] && count[i] > count_threshold) { |
288 | 0 | AddToRGBPalette(&red[0], &green[0], &blue[0], count.data(), i, k++, n, |
289 | 0 | dist.data(), cluster.data(), ¢er[0], &error); |
290 | 0 | in_palette[i] = true; |
291 | 0 | } |
292 | 0 | } |
293 | 0 | if (k == 0) { |
294 | 0 | AddToRGBPalette(&red[0], &green[0], &blue[0], count.data(), winner, k++, n, |
295 | 0 | dist.data(), cluster.data(), ¢er[0], &error); |
296 | 0 | in_palette[winner] = true; |
297 | 0 | } |
298 | | |
299 | | // Calculation of the multi-resolution density grid. |
300 | 0 | std::vector<int> density(n * kMaxLevel); |
301 | 0 | std::vector<int> radius(n * kMaxLevel); |
302 | 0 | std::unordered_map<uint32_t, int, WangHasher> histogram[kMaxLevel]; |
303 | 0 | for (int level = 0; level < kMaxLevel; ++level) { |
304 | | // This value is never used because key = InterlaceBitsRGB(...) >> 6 |
305 | 0 | } |
306 | |
|
307 | 0 | for (int i = 0; i < n; ++i) { |
308 | 0 | if (!in_palette[i]) { |
309 | 0 | const int key = InterlaceBitsRGB(red[i], green[i], blue[i]) >> 6; |
310 | 0 | for (int level = 0; level < kMaxLevel; ++level) { |
311 | 0 | histogram[level][key >> (3 * level)] += count[i]; |
312 | 0 | } |
313 | 0 | } |
314 | 0 | } |
315 | 0 | for (int i = 0; i < n; ++i) { |
316 | 0 | if (!in_palette[i]) { |
317 | 0 | for (int level = 0; level < kMaxLevel; ++level) { |
318 | 0 | const int mask = (4 << level) - 1; |
319 | 0 | const int rd = std::max(red[i] & mask, mask - (red[i] & mask)); |
320 | 0 | const int gd = std::max(green[i] & mask, mask - (green[i] & mask)); |
321 | 0 | const int bd = std::max(blue[i] & mask, mask - (blue[i] & mask)); |
322 | 0 | radius[i * kMaxLevel + level] = |
323 | 0 | ScaleQuadDistanceRGB(ColorIntQuadDistanceRGB(0, 0, 0, rd, gd, bd)); |
324 | 0 | } |
325 | 0 | const int key = InterlaceBitsRGB(red[i], green[i], blue[i]) >> 6; |
326 | 0 | if (kMaxLevel > 0) { |
327 | 0 | density[i * kMaxLevel] = histogram[0][key] - count[i]; |
328 | 0 | } |
329 | 0 | for (int level = 1; level < kMaxLevel; ++level) { |
330 | 0 | density[i * kMaxLevel + level] = |
331 | 0 | (histogram[level][key >> (3 * level)] - |
332 | 0 | histogram[level - 1][key >> (3 * level - 3)]); |
333 | 0 | } |
334 | 0 | } |
335 | 0 | } |
336 | | |
337 | | // Calculate the initial error now that the palette has been initialized. |
338 | 0 | error = 0; |
339 | 0 | for (int i = 0; i < n; ++i) { |
340 | 0 | error += static_cast<int64_t>(dist[i]) * static_cast<int64_t>(count[i]); |
341 | 0 | } |
342 | |
|
343 | 0 | std::unique_ptr<std::vector<int>[]> bucket_array( |
344 | 0 | new std::vector<int>[kMaxPriority]); |
345 | 0 | int top_priority = -1; |
346 | 0 | for (int i = 0; i < n; ++i) { |
347 | 0 | if (!in_palette[i]) { |
348 | 0 | int priority = Priority(ScaleQuadDistanceRGB(dist[i]), count[i], |
349 | 0 | &density[i * kMaxLevel], &radius[i * kMaxLevel]); |
350 | 0 | bucket_array[priority].push_back(i); |
351 | 0 | top_priority = std::max(priority, top_priority); |
352 | 0 | } |
353 | 0 | } |
354 | 0 | double error_accum = 0; |
355 | 0 | while (top_priority >= 0 && k < max_palette_size) { |
356 | 0 | if (error < error_threshold) { |
357 | 0 | error_accum += std::min(error_threshold, error_threshold - error); |
358 | 0 | if (error_accum >= 10 * error_threshold) { |
359 | 0 | break; |
360 | 0 | } |
361 | 0 | } |
362 | 0 | int i = bucket_array[top_priority].back(); |
363 | 0 | int priority = Priority(ScaleQuadDistanceRGB(dist[i]), count[i], |
364 | 0 | &density[i * kMaxLevel], &radius[i * kMaxLevel]); |
365 | 0 | if (priority < top_priority) { |
366 | 0 | bucket_array[priority].push_back(i); |
367 | 0 | } else { |
368 | 0 | AddToRGBPalette(&red[0], &green[0], &blue[0], count.data(), i, k++, n, |
369 | 0 | dist.data(), cluster.data(), ¢er[0], &error); |
370 | 0 | } |
371 | 0 | bucket_array[top_priority].pop_back(); |
372 | 0 | while (top_priority >= 0 && bucket_array[top_priority].empty()) { |
373 | 0 | --top_priority; |
374 | 0 | } |
375 | 0 | } |
376 | |
|
377 | 0 | cinfo->actual_number_of_colors = k; |
378 | 0 | cinfo->colormap = (*cinfo->mem->alloc_sarray)( |
379 | 0 | reinterpret_cast<j_common_ptr>(cinfo), JPOOL_IMAGE, k, 3); |
380 | 0 | for (int i = 0; i < k; ++i) { |
381 | 0 | int index = center[i]; |
382 | 0 | cinfo->colormap[0][i] = red[index]; |
383 | 0 | cinfo->colormap[1][i] = green[index]; |
384 | 0 | cinfo->colormap[2][i] = blue[index]; |
385 | 0 | } |
386 | 0 | } |
387 | | |
388 | | namespace { |
389 | | |
390 | | void FindCandidatesForCell(j_decompress_ptr cinfo, int ncomp, const int cell[], |
391 | 0 | std::vector<uint8_t>* candidates) { |
392 | 0 | int cell_min[kMaxComponents]; |
393 | 0 | int cell_max[kMaxComponents]; |
394 | 0 | int cell_center[kMaxComponents]; |
395 | 0 | for (int c = 0; c < ncomp; ++c) { |
396 | 0 | cell_min[c] = cell[c] << (8 - kNumColorCellBits[c]); |
397 | 0 | cell_max[c] = cell_min[c] + (1 << (8 - kNumColorCellBits[c])) - 1; |
398 | 0 | cell_center[c] = (cell_min[c] + cell_max[c]) >> 1; |
399 | 0 | } |
400 | 0 | int min_maxdist = std::numeric_limits<int>::max(); |
401 | 0 | int mindist[256]; |
402 | 0 | for (int i = 0; i < cinfo->actual_number_of_colors; ++i) { |
403 | 0 | int dmin = 0; |
404 | 0 | int dmax = 0; |
405 | 0 | for (int c = 0; c < ncomp; ++c) { |
406 | 0 | int palette_c = cinfo->colormap[c][i]; |
407 | 0 | int dminc = 0; |
408 | 0 | int dmaxc; |
409 | 0 | if (palette_c < cell_min[c]) { |
410 | 0 | dminc = cell_min[c] - palette_c; |
411 | 0 | dmaxc = cell_max[c] - palette_c; |
412 | 0 | } else if (palette_c > cell_max[c]) { |
413 | 0 | dminc = palette_c - cell_max[c]; |
414 | 0 | dmaxc = palette_c - cell_min[c]; |
415 | 0 | } else if (palette_c > cell_center[c]) { |
416 | 0 | dmaxc = palette_c - cell_min[c]; |
417 | 0 | } else { |
418 | 0 | dmaxc = cell_max[c] - palette_c; |
419 | 0 | } |
420 | 0 | dminc *= kCompW[c]; |
421 | 0 | dmaxc *= kCompW[c]; |
422 | 0 | dmin += dminc * dminc; |
423 | 0 | dmax += dmaxc * dmaxc; |
424 | 0 | } |
425 | 0 | mindist[i] = dmin; |
426 | 0 | min_maxdist = std::min(dmax, min_maxdist); |
427 | 0 | } |
428 | 0 | for (int i = 0; i < cinfo->actual_number_of_colors; ++i) { |
429 | 0 | if (mindist[i] < min_maxdist) { |
430 | 0 | candidates->push_back(i); |
431 | 0 | } |
432 | 0 | } |
433 | 0 | } |
434 | | |
435 | | } // namespace |
436 | | |
437 | 0 | void CreateInverseColorMap(j_decompress_ptr cinfo) { |
438 | 0 | jpeg_decomp_master* m = cinfo->master; |
439 | 0 | int ncomp = cinfo->out_color_components; |
440 | 0 | JPEGLI_CHECK(ncomp > 0); |
441 | 0 | JPEGLI_CHECK(ncomp <= kMaxComponents); |
442 | 0 | int num_cells = 1; |
443 | 0 | for (int c = 0; c < ncomp; ++c) { |
444 | 0 | num_cells *= (1 << kNumColorCellBits[c]); |
445 | 0 | } |
446 | 0 | m->candidate_lists_.resize(num_cells); |
447 | |
|
448 | 0 | int next_cell[kMaxComponents] = {0}; |
449 | 0 | for (int i = 0; i < num_cells; ++i) { |
450 | 0 | m->candidate_lists_[i].clear(); |
451 | 0 | FindCandidatesForCell(cinfo, ncomp, next_cell, &m->candidate_lists_[i]); |
452 | 0 | int c = ncomp - 1; |
453 | 0 | while (c > 0 && next_cell[c] + 1 == (1 << kNumColorCellBits[c])) { |
454 | 0 | next_cell[c--] = 0; |
455 | 0 | } |
456 | 0 | ++next_cell[c]; |
457 | 0 | } |
458 | 0 | m->regenerate_inverse_colormap_ = false; |
459 | 0 | } |
460 | | |
461 | 0 | int LookupColorIndex(j_decompress_ptr cinfo, const JSAMPLE* pixel) { |
462 | 0 | jpeg_decomp_master* m = cinfo->master; |
463 | 0 | int num_channels = cinfo->out_color_components; |
464 | 0 | int index = 0; |
465 | 0 | if (m->quant_mode_ == 1) { |
466 | 0 | for (int c = 0; c < num_channels; ++c) { |
467 | 0 | index += m->colormap_lut_[c * 256 + pixel[c]]; |
468 | 0 | } |
469 | 0 | } else { |
470 | 0 | size_t cell_idx = 0; |
471 | 0 | size_t stride = 1; |
472 | 0 | for (int c = num_channels - 1; c >= 0; --c) { |
473 | 0 | cell_idx += (pixel[c] >> (8 - kNumColorCellBits[c])) * stride; |
474 | 0 | stride <<= kNumColorCellBits[c]; |
475 | 0 | } |
476 | 0 | JPEGLI_CHECK(cell_idx < m->candidate_lists_.size()); |
477 | 0 | int mindist = std::numeric_limits<int>::max(); |
478 | 0 | const auto& candidates = m->candidate_lists_[cell_idx]; |
479 | 0 | for (uint8_t i : candidates) { |
480 | 0 | int dist = 0; |
481 | 0 | for (int c = 0; c < num_channels; ++c) { |
482 | 0 | int d = (cinfo->colormap[c][i] - pixel[c]) * kCompW[c]; |
483 | 0 | dist += d * d; |
484 | 0 | } |
485 | 0 | if (dist < mindist) { |
486 | 0 | mindist = dist; |
487 | 0 | index = i; |
488 | 0 | } |
489 | 0 | } |
490 | 0 | } |
491 | 0 | JPEGLI_CHECK(index < cinfo->actual_number_of_colors); |
492 | 0 | return index; |
493 | 0 | } |
494 | | |
495 | 0 | void CreateOrderedDitherTables(j_decompress_ptr cinfo) { |
496 | 0 | jpeg_decomp_master* m = cinfo->master; |
497 | 0 | static constexpr size_t kDitherSize = 4; |
498 | 0 | static constexpr size_t kDitherMask = kDitherSize - 1; |
499 | 0 | static constexpr float kBaseDitherMatrix[] = { |
500 | 0 | 0, 8, 2, 10, // |
501 | 0 | 12, 4, 14, 6, // |
502 | 0 | 3, 11, 1, 9, // |
503 | 0 | 15, 7, 13, 5, // |
504 | 0 | }; |
505 | 0 | m->dither_size_ = kDitherSize; |
506 | 0 | m->dither_mask_ = kDitherMask; |
507 | 0 | size_t ncells = m->dither_size_ * m->dither_size_; |
508 | 0 | for (int c = 0; c < cinfo->out_color_components; ++c) { |
509 | 0 | float spread = 1.0f / (m->num_colors_[c] - 1); |
510 | 0 | float mul = spread / ncells; |
511 | 0 | float offset = 0.5f * spread; |
512 | 0 | if (m->dither_[c] == nullptr) { |
513 | 0 | m->dither_[c] = Allocate<float>(cinfo, ncells, JPOOL_IMAGE_ALIGNED); |
514 | 0 | } |
515 | 0 | for (size_t idx = 0; idx < ncells; ++idx) { |
516 | 0 | m->dither_[c][idx] = kBaseDitherMatrix[idx] * mul - offset; |
517 | 0 | } |
518 | 0 | } |
519 | 0 | } |
520 | | |
521 | 0 | void InitFSDitherState(j_decompress_ptr cinfo) { |
522 | 0 | jpeg_decomp_master* m = cinfo->master; |
523 | 0 | for (int c = 0; c < cinfo->out_color_components; ++c) { |
524 | 0 | if (m->error_row_[c] == nullptr) { |
525 | 0 | m->error_row_[c] = |
526 | 0 | Allocate<float>(cinfo, cinfo->output_width, JPOOL_IMAGE_ALIGNED); |
527 | 0 | m->error_row_[c + kMaxComponents] = |
528 | 0 | Allocate<float>(cinfo, cinfo->output_width, JPOOL_IMAGE_ALIGNED); |
529 | 0 | } |
530 | 0 | memset(m->error_row_[c], 0.0, cinfo->output_width * sizeof(float)); |
531 | 0 | memset(m->error_row_[c + kMaxComponents], 0.0, |
532 | 0 | cinfo->output_width * sizeof(float)); |
533 | 0 | } |
534 | 0 | } |
535 | | |
536 | | } // namespace jpegli |