Coverage Report

Created: 2024-11-01 07:22

/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(), &center[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(), &center[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(), &center[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