Coverage Report

Created: 2025-07-11 06:19

/src/guetzli/guetzli/output_image.cc
Line
Count
Source (jump to first uncovered line)
1
/*
2
 * Copyright 2016 Google Inc.
3
 *
4
 * Licensed under the Apache License, Version 2.0 (the "License");
5
 * you may not use this file except in compliance with the License.
6
 * You may obtain a copy of the License at
7
 *
8
 * http://www.apache.org/licenses/LICENSE-2.0
9
 *
10
 * Unless required by applicable law or agreed to in writing, software
11
 * distributed under the License is distributed on an "AS IS" BASIS,
12
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
 * See the License for the specific language governing permissions and
14
 * limitations under the License.
15
 */
16
17
#include "guetzli/output_image.h"
18
19
#include <algorithm>
20
#include <assert.h>
21
#include <stdio.h>
22
#include <string.h>
23
#include <cmath>
24
#include <cstdlib>
25
26
#include "guetzli/idct.h"
27
#include "guetzli/color_transform.h"
28
#include "guetzli/dct_double.h"
29
#include "guetzli/gamma_correct.h"
30
#include "guetzli/preprocess_downsample.h"
31
#include "guetzli/quantize.h"
32
33
namespace guetzli {
34
35
OutputImageComponent::OutputImageComponent(int w, int h)
36
8.12k
    : width_(w), height_(h) {
37
8.12k
  Reset(1, 1);
38
8.12k
}
39
40
134k
void OutputImageComponent::Reset(int factor_x, int factor_y) {
41
134k
  factor_x_ = factor_x;
42
134k
  factor_y_ = factor_y;
43
134k
  width_in_blocks_ = (width_ + 8 * factor_x_ - 1) / (8 * factor_x_);
44
134k
  height_in_blocks_ = (height_ + 8 * factor_y_ - 1) / (8 * factor_y_);
45
134k
  num_blocks_ = width_in_blocks_ * height_in_blocks_;
46
134k
  coeffs_ = std::vector<coeff_t>(num_blocks_ * kDCTBlockSize);
47
134k
  pixels_ = std::vector<uint16_t>(width_ * height_, 128 << 4);
48
8.76M
  for (int i = 0; i < kDCTBlockSize; ++i) quant_[i] = 1;
49
134k
}
50
51
194k
bool OutputImageComponent::IsAllZero() const {
52
194k
  int numcoeffs = num_blocks_ * kDCTBlockSize;
53
69.8M
  for (int i = 0; i < numcoeffs; ++i) {
54
69.8M
    if (coeffs_[i] != 0) return false;
55
69.8M
  }
56
68.6k
  return true;
57
194k
}
58
59
void OutputImageComponent::GetCoeffBlock(int block_x, int block_y,
60
5.50M
                                         coeff_t block[kDCTBlockSize]) const {
61
5.50M
  assert(block_x < width_in_blocks_);
62
5.50M
  assert(block_y < height_in_blocks_);
63
5.50M
  int offset = (block_y * width_in_blocks_ + block_x) * kDCTBlockSize;
64
5.50M
  memcpy(block, &coeffs_[offset], kDCTBlockSize * sizeof(coeffs_[0]));
65
5.50M
}
66
67
void OutputImageComponent::ToPixels(int xmin, int ymin, int xsize, int ysize,
68
18.1M
                                    uint8_t* out, int stride) const {
69
18.1M
  assert(xmin >= 0);
70
18.1M
  assert(ymin >= 0);
71
18.1M
  assert(xmin < width_);
72
18.1M
  assert(ymin < height_);
73
18.1M
  const int yend1 = ymin + ysize;
74
18.1M
  const int yend0 = std::min(yend1, height_);
75
18.1M
  int y = ymin;
76
175M
  for (; y < yend0; ++y) {
77
157M
    const int xend1 = xmin + xsize;
78
157M
    const int xend0 = std::min(xend1, width_);
79
157M
    int x = xmin;
80
157M
    int px = y * width_ + xmin;
81
2.35G
    for (; x < xend0; ++x, ++px, out += stride) {
82
2.19G
      *out = static_cast<uint8_t>((pixels_[px] + 8 - (x & 1)) >> 4);
83
2.19G
    }
84
157M
    const int offset = -stride;
85
204M
    for (; x < xend1; ++x) {
86
46.9M
      *out = out[offset];
87
46.9M
      out += stride;
88
46.9M
    }
89
157M
  }
90
23.1M
  for (; y < yend1; ++y) {
91
5.06M
    const int offset = -stride * xsize;
92
45.6M
    for (int x = 0; x < xsize; ++x) {
93
40.5M
      *out = out[offset];
94
40.5M
      out += stride;
95
40.5M
    }
96
5.06M
  }
97
18.1M
}
98
99
0
void OutputImageComponent::ToFloatPixels(float* out, int stride) const {
100
0
  assert(factor_x_ == 1);
101
0
  assert(factor_y_ == 1);
102
0
  for (int block_y = 0; block_y < height_in_blocks_; ++block_y) {
103
0
    for (int block_x = 0; block_x < width_in_blocks_; ++block_x) {
104
0
      coeff_t block[kDCTBlockSize];
105
0
      GetCoeffBlock(block_x, block_y, block);
106
0
      double blockd[kDCTBlockSize];
107
0
      for (int k = 0; k < kDCTBlockSize; ++k) {
108
0
        blockd[k] = block[k];
109
0
      }
110
0
      ComputeBlockIDCTDouble(blockd);
111
0
      for (int iy = 0; iy < 8; ++iy) {
112
0
        for (int ix = 0; ix < 8; ++ix) {
113
0
          int y = block_y * 8 + iy;
114
0
          int x = block_x * 8 + ix;
115
0
          if (y >= height_ || x >= width_) continue;
116
0
          out[(y * width_ + x) * stride] = static_cast<float>(blockd[8 * iy + ix] + 128.0);
117
0
        }
118
0
      }
119
0
    }
120
0
  }
121
0
}
122
123
void OutputImageComponent::SetCoeffBlock(int block_x, int block_y,
124
19.0M
                                         const coeff_t block[kDCTBlockSize]) {
125
19.0M
  assert(block_x < width_in_blocks_);
126
19.0M
  assert(block_y < height_in_blocks_);
127
19.0M
  int offset = (block_y * width_in_blocks_ + block_x) * kDCTBlockSize;
128
19.0M
  memcpy(&coeffs_[offset], block, kDCTBlockSize * sizeof(coeffs_[0]));
129
19.0M
  uint8_t idct[kDCTBlockSize];
130
19.0M
  ComputeBlockIDCT(&coeffs_[offset], idct);
131
19.0M
  UpdatePixelsForBlock(block_x, block_y, idct);
132
19.0M
}
133
134
void OutputImageComponent::UpdatePixelsForBlock(
135
19.0M
    int block_x, int block_y, const uint8_t idct[kDCTBlockSize]) {
136
19.0M
  if (factor_x_ == 1 && factor_y_ == 1) {
137
151M
    for (int iy = 0; iy < 8; ++iy) {
138
1.21G
      for (int ix = 0; ix < 8; ++ix) {
139
1.07G
        int x = 8 * block_x + ix;
140
1.07G
        int y = 8 * block_y + iy;
141
1.07G
        if (x >= width_ || y >= height_) continue;
142
999M
        int p = y * width_ + x;
143
999M
        pixels_[p] = idct[8 * iy + ix] << 4;
144
999M
      }
145
134M
    }
146
16.8M
  } else if (factor_x_ == 2 && factor_y_ == 2) {
147
    // Fill in the 10x10 pixel area in the subsampled image that will be the
148
    // basis of the upsampling. This area is enough to hold the 3x3 kernel of
149
    // the fancy upsampler around each pixel.
150
2.16M
    static const int kSubsampledEdgeSize = 10;
151
2.16M
    uint16_t subsampled[kSubsampledEdgeSize * kSubsampledEdgeSize];
152
23.7M
    for (int j = 0; j < kSubsampledEdgeSize; ++j) {
153
      // The order we fill in the rows is:
154
      //   8 rows intersecting the block, row below, row above
155
21.6M
      const int y0 = block_y * 16 + (j < 9 ? j * 2 : -2);
156
237M
      for (int i = 0; i < kSubsampledEdgeSize; ++i) {
157
        // The order we fill in each row is:
158
        //   8 pixels within the block, left edge, right edge
159
216M
        const int ix = ((j < 9 ? (j + 1) * kSubsampledEdgeSize : 0) +
160
216M
                        (i < 9 ? i + 1 : 0));
161
216M
        const int x0 = block_x * 16 + (i < 9 ? i * 2 : -2);
162
216M
        if (x0 < 0) {
163
5.30M
          subsampled[ix] = subsampled[ix + 1];
164
211M
        } else if (y0 < 0) {
165
6.20M
          subsampled[ix] = subsampled[ix + kSubsampledEdgeSize];
166
204M
        } else if (x0 >= width_) {
167
24.1M
          subsampled[ix] = subsampled[ix - 1];
168
180M
        } else if (y0 >= height_) {
169
24.5M
          subsampled[ix] = subsampled[ix - kSubsampledEdgeSize];
170
156M
        } else if (i < 8 && j < 8) {
171
106M
          subsampled[ix] = idct[j * 8 + i] << 4;
172
106M
        } else {
173
          // Reconstruct the subsampled pixels around the edge of the current
174
          // block by computing the inverse of the fancy upsampler.
175
49.5M
          const int y1 = std::max(y0 - 1, 0);
176
49.5M
          const int x1 = std::max(x0 - 1, 0);
177
49.5M
          subsampled[ix] = (pixels_[y0 * width_ + x0] * 9 +
178
49.5M
                            pixels_[y1 * width_ + x1] +
179
49.5M
                            pixels_[y0 * width_ + x1] * -3 +
180
49.5M
                            pixels_[y1 * width_ + x0] * -3) >> 2;
181
49.5M
        }
182
216M
      }
183
21.6M
    }
184
185
    // Determine area to update.
186
2.16M
    int xmin = std::max(block_x * 16 - 1, 0);
187
2.16M
    int xmax = std::min(block_x * 16 + 16, width_ - 1);
188
2.16M
    int ymin = std::max(block_y * 16 - 1, 0);
189
2.16M
    int ymax = std::min(block_y * 16 + 16, height_ - 1);
190
191
    // Apply the fancy upsampler on the subsampled block.
192
35.0M
    for (int y = ymin; y <= ymax; ++y) {
193
32.9M
      const int y0 = ((y & ~1) / 2 - block_y * 8 + 1) * kSubsampledEdgeSize;
194
32.9M
      const int dy = ((y & 1) * 2 - 1) * kSubsampledEdgeSize;
195
32.9M
      uint16_t* rowptr = &pixels_[y * width_];
196
546M
      for (int x = xmin; x <= xmax; ++x) {
197
513M
        const int x0 = (x & ~1) / 2 - block_x * 8 + 1;
198
513M
        const int dx = (x & 1) * 2 - 1;
199
513M
        const int ix = x0 + y0;
200
513M
        rowptr[x] = (subsampled[ix] * 9 + subsampled[ix + dy] * 3 +
201
513M
                     subsampled[ix + dx] * 3 + subsampled[ix + dx + dy]) >> 4;
202
513M
      }
203
32.9M
    }
204
2.16M
  } else {
205
0
    printf("Sampling ratio not supported: factor_x = %d factor_y = %d\n",
206
0
           factor_x_, factor_y_);
207
0
    exit(1);
208
0
  }
209
19.0M
}
210
211
void OutputImageComponent::CopyFromJpegComponent(const JPEGComponent& comp,
212
                                                 int factor_x, int factor_y,
213
126k
                                                 const int* quant) {
214
126k
  Reset(factor_x, factor_y);
215
126k
  assert(width_in_blocks_ <= comp.width_in_blocks);
216
126k
  assert(height_in_blocks_ <= comp.height_in_blocks);
217
126k
  const size_t src_row_size = comp.width_in_blocks * kDCTBlockSize;
218
786k
  for (int block_y = 0; block_y < height_in_blocks_; ++block_y) {
219
659k
    const coeff_t* src_coeffs = &comp.coeffs[block_y * src_row_size];
220
4.30M
    for (int block_x = 0; block_x < width_in_blocks_; ++block_x) {
221
3.64M
      coeff_t block[kDCTBlockSize];
222
237M
      for (int i = 0; i < kDCTBlockSize; ++i) {
223
233M
        block[i] = src_coeffs[i] * quant[i];
224
233M
      }
225
3.64M
      SetCoeffBlock(block_x, block_y, block);
226
3.64M
      src_coeffs += kDCTBlockSize;
227
3.64M
    }
228
659k
  }
229
126k
  memcpy(quant_, quant, sizeof(quant_));
230
126k
}
231
232
126k
void OutputImageComponent::ApplyGlobalQuantization(const int q[kDCTBlockSize]) {
233
740k
  for (int block_y = 0; block_y < height_in_blocks_; ++block_y) {
234
3.81M
    for (int block_x = 0; block_x < width_in_blocks_; ++block_x) {
235
3.20M
      coeff_t block[kDCTBlockSize];
236
3.20M
      GetCoeffBlock(block_x, block_y, block);
237
3.20M
      if (QuantizeBlock(block, q)) {
238
1.11M
        SetCoeffBlock(block_x, block_y, block);
239
1.11M
      }
240
3.20M
    }
241
613k
  }
242
126k
  memcpy(quant_, q, sizeof(quant_));
243
126k
}
244
245
OutputImage::OutputImage(int w, int h)
246
8.12k
    : width_(w),
247
8.12k
      height_(h),
248
8.12k
      components_(3, OutputImageComponent(w, h)) {}
249
250
50.3k
void OutputImage::CopyFromJpegData(const JPEGData& jpg) {
251
177k
  for (size_t i = 0; i < jpg.components.size(); ++i) {
252
126k
    const JPEGComponent& comp = jpg.components[i];
253
126k
    assert(jpg.max_h_samp_factor % comp.h_samp_factor == 0);
254
126k
    assert(jpg.max_v_samp_factor % comp.v_samp_factor == 0);
255
126k
    int factor_x = jpg.max_h_samp_factor / comp.h_samp_factor;
256
126k
    int factor_y = jpg.max_v_samp_factor / comp.v_samp_factor;
257
126k
    assert(comp.quant_idx < jpg.quant.size());
258
126k
    components_[i].CopyFromJpegComponent(comp, factor_x, factor_y,
259
126k
                                         &jpg.quant[comp.quant_idx].values[0]);
260
126k
  }
261
50.3k
}
262
263
namespace {
264
265
void SetDownsampledCoefficients(const std::vector<float>& pixels,
266
                                int factor_x, int factor_y,
267
0
                                OutputImageComponent* comp) {
268
0
  assert(pixels.size() == comp->width() * comp->height());
269
0
  comp->Reset(factor_x, factor_y);
270
0
  for (int block_y = 0; block_y < comp->height_in_blocks(); ++block_y) {
271
0
    for (int block_x = 0; block_x < comp->width_in_blocks(); ++block_x) {
272
0
      double blockd[kDCTBlockSize];
273
0
      int x0 = 8 * block_x * factor_x;
274
0
      int y0 = 8 * block_y * factor_y;
275
0
      assert(x0 < comp->width());
276
0
      assert(y0 < comp->height());
277
0
      for (int iy = 0; iy < 8; ++iy) {
278
0
        for (int ix = 0; ix < 8; ++ix) {
279
0
          float avg = 0.0;
280
0
          for (int j = 0; j < factor_y; ++j) {
281
0
            for (int i = 0; i < factor_x; ++i) {
282
0
              int x = std::min(x0 + ix * factor_x + i, comp->width() - 1);
283
0
              int y = std::min(y0 + iy * factor_y + j, comp->height() - 1);
284
0
              avg += pixels[y * comp->width() + x];
285
0
            }
286
0
          }
287
0
          avg /= factor_x * factor_y;
288
0
          blockd[iy * 8 + ix] = avg;
289
0
        }
290
0
      }
291
0
      ComputeBlockDCTDouble(blockd);
292
0
      blockd[0] -= 1024.0;
293
0
      coeff_t block[kDCTBlockSize];
294
0
      for (int k = 0; k < kDCTBlockSize; ++k) {
295
0
        block[k] = static_cast<coeff_t>(std::round(blockd[k]));
296
0
      }
297
0
      comp->SetCoeffBlock(block_x, block_y, block);
298
0
    }
299
0
  }
300
0
}
301
302
}  // namespace
303
304
0
void OutputImage::Downsample(const DownsampleConfig& cfg) {
305
0
  if (components_[1].IsAllZero() && components_[2].IsAllZero()) {
306
    // If the image is already grayscale, nothing to do.
307
0
    return;
308
0
  }
309
0
  if (cfg.use_silver_screen &&
310
0
      cfg.u_factor_x == 2 && cfg.u_factor_y == 2 &&
311
0
      cfg.v_factor_x == 2 && cfg.v_factor_y == 2) {
312
0
    std::vector<uint8_t> rgb = ToSRGB();
313
0
    std::vector<std::vector<float> > yuv = RGBToYUV420(rgb, width_, height_);
314
0
    SetDownsampledCoefficients(yuv[0], 1, 1, &components_[0]);
315
0
    SetDownsampledCoefficients(yuv[1], 2, 2, &components_[1]);
316
0
    SetDownsampledCoefficients(yuv[2], 2, 2, &components_[2]);
317
0
    return;
318
0
  }
319
  // Get the floating-point precision YUV array represented by the set of
320
  // DCT coefficients.
321
0
  std::vector<std::vector<float> > yuv(3, std::vector<float>(width_ * height_));
322
0
  for (int c = 0; c < 3; ++c) {
323
0
    components_[c].ToFloatPixels(&yuv[c][0], 1);
324
0
  }
325
326
0
  yuv = PreProcessChannel(width_, height_, 2, 1.3f, 0.5f,
327
0
                          cfg.u_sharpen, cfg.u_blur, yuv);
328
0
  yuv = PreProcessChannel(width_, height_, 1, 1.3f, 0.5f,
329
0
                          cfg.v_sharpen, cfg.v_blur, yuv);
330
331
  // Do the actual downsampling (averaging) and forward-DCT.
332
0
  if (cfg.u_factor_x != 1 || cfg.u_factor_y != 1) {
333
0
    SetDownsampledCoefficients(yuv[1], cfg.u_factor_x, cfg.u_factor_y,
334
0
                               &components_[1]);
335
0
  }
336
0
  if (cfg.v_factor_x != 1 || cfg.v_factor_y != 1) {
337
0
    SetDownsampledCoefficients(yuv[2], cfg.v_factor_x, cfg.v_factor_y,
338
0
                               &components_[2]);
339
0
  }
340
0
}
341
342
42.1k
void OutputImage::ApplyGlobalQuantization(const int q[3][kDCTBlockSize]) {
343
168k
  for (int c = 0; c < 3; ++c) {
344
126k
    components_[c].ApplyGlobalQuantization(&q[c][0]);
345
126k
  }
346
42.1k
}
347
348
157k
void OutputImage::SaveToJpegData(JPEGData* jpg) const {
349
157k
  assert(components_[0].factor_x() == 1);
350
157k
  assert(components_[0].factor_y() == 1);
351
157k
  jpg->width = width_;
352
157k
  jpg->height = height_;
353
157k
  jpg->max_h_samp_factor = 1;
354
157k
  jpg->max_v_samp_factor = 1;
355
157k
  jpg->MCU_cols = components_[0].width_in_blocks();
356
157k
  jpg->MCU_rows = components_[0].height_in_blocks();
357
157k
  int ncomp = components_[1].IsAllZero() && components_[2].IsAllZero() ? 1 : 3;
358
409k
  for (int i = 1; i < ncomp; ++i) {
359
251k
    jpg->max_h_samp_factor = std::max(jpg->max_h_samp_factor,
360
251k
                                      components_[i].factor_x());
361
251k
    jpg->max_v_samp_factor = std::max(jpg->max_h_samp_factor,
362
251k
                                      components_[i].factor_y());
363
251k
    jpg->MCU_cols = std::min(jpg->MCU_cols, components_[i].width_in_blocks());
364
251k
    jpg->MCU_rows = std::min(jpg->MCU_rows, components_[i].height_in_blocks());
365
251k
  }
366
157k
  jpg->components.resize(ncomp);
367
157k
  int q[3][kDCTBlockSize];
368
629k
  for (int c = 0; c < 3; ++c) {
369
472k
    memcpy(&q[c][0], components_[c].quant(), kDCTBlockSize * sizeof(q[0][0]));
370
472k
  }
371
566k
  for (int c = 0; c < ncomp; ++c) {
372
409k
    JPEGComponent* comp = &jpg->components[c];
373
409k
    assert(jpg->max_h_samp_factor % components_[c].factor_x() == 0);
374
409k
    assert(jpg->max_v_samp_factor % components_[c].factor_y() == 0);
375
409k
    comp->id = c;
376
409k
    comp->h_samp_factor = jpg->max_h_samp_factor / components_[c].factor_x();
377
409k
    comp->v_samp_factor = jpg->max_v_samp_factor / components_[c].factor_y();
378
409k
    comp->width_in_blocks = jpg->MCU_cols * comp->h_samp_factor;
379
409k
    comp->height_in_blocks = jpg->MCU_rows * comp->v_samp_factor;
380
409k
    comp->num_blocks = comp->width_in_blocks * comp->height_in_blocks;
381
409k
    comp->coeffs.resize(kDCTBlockSize * comp->num_blocks);
382
383
409k
    int last_dc = 0;
384
409k
    const coeff_t* src_coeffs = components_[c].coeffs();
385
409k
    coeff_t* dest_coeffs = &comp->coeffs[0];
386
2.47M
    for (int block_y = 0; block_y < comp->height_in_blocks; ++block_y) {
387
15.7M
      for (int block_x = 0; block_x < comp->width_in_blocks; ++block_x) {
388
13.7M
        if (block_y >= components_[c].height_in_blocks() ||
389
13.7M
            block_x >= components_[c].width_in_blocks()) {
390
728k
          dest_coeffs[0] = last_dc;
391
46.5M
          for (int k = 1; k < kDCTBlockSize; ++k) {
392
45.8M
            dest_coeffs[k] = 0;
393
45.8M
          }
394
13.0M
        } else {
395
845M
          for (int k = 0; k < kDCTBlockSize; ++k) {
396
832M
            const int quant = q[c][k];
397
832M
            int coeff = src_coeffs[k];
398
832M
            assert(coeff % quant == 0);
399
832M
            dest_coeffs[k] = coeff / quant;
400
832M
          }
401
13.0M
          src_coeffs += kDCTBlockSize;
402
13.0M
        }
403
13.7M
        last_dc = dest_coeffs[0];
404
13.7M
        dest_coeffs += kDCTBlockSize;
405
13.7M
      }
406
2.06M
    }
407
409k
  }
408
157k
  SaveQuantTables(q, jpg);
409
157k
}
410
411
std::vector<uint8_t> OutputImage::ToSRGB(int xmin, int ymin,
412
6.04M
                                         int xsize, int ysize) const {
413
6.04M
  std::vector<uint8_t> rgb(xsize * ysize * 3);
414
24.1M
  for (int c = 0; c < 3; ++c) {
415
18.1M
    components_[c].ToPixels(xmin, ymin, xsize, ysize, &rgb[c], 3);
416
18.1M
  }
417
767M
  for (size_t p = 0; p < rgb.size(); p += 3) {
418
761M
    ColorTransformYCbCrToRGB(&rgb[p]);
419
761M
  }
420
6.04M
  return rgb;
421
6.04M
}
422
423
2.83k
std::vector<uint8_t> OutputImage::ToSRGB() const {
424
2.83k
  return ToSRGB(0, 0, width_, height_);
425
2.83k
}
426
427
void OutputImage::ToLinearRGB(int xmin, int ymin, int xsize, int ysize,
428
6.03M
                              std::vector<std::vector<float> >* rgb) const {
429
6.03M
  const double* lut = Srgb8ToLinearTable();
430
6.03M
  std::vector<uint8_t> rgb_pixels = ToSRGB(xmin, ymin, xsize, ysize);
431
760M
  for (int p = 0; p < xsize * ysize; ++p) {
432
3.01G
    for (int i = 0; i < 3; ++i) {
433
2.26G
      (*rgb)[i][p] = static_cast<float>(lut[rgb_pixels[3 * p + i]]);
434
2.26G
    }
435
754M
  }
436
6.03M
}
437
438
153k
void OutputImage::ToLinearRGB(std::vector<std::vector<float> >* rgb) const {
439
153k
  ToLinearRGB(0, 0, width_, height_, rgb);
440
153k
}
441
442
190k
std::string OutputImage::FrameTypeStr() const {
443
190k
  char buf[128];
444
190k
  int len = snprintf(buf, sizeof(buf), "f%d%d%d%d%d%d",
445
190k
                     component(0).factor_x(), component(0).factor_y(),
446
190k
                     component(1).factor_x(), component(1).factor_y(),
447
190k
                     component(2).factor_x(), component(2).factor_y());
448
190k
  return std::string(buf, len);
449
190k
}
450
451
}  // namespace guetzli