Coverage Report

Created: 2025-06-16 07:00

/src/libjxl/lib/jxl/enc_frame.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/jxl/enc_frame.h"
7
8
#include <jxl/cms_interface.h>
9
#include <jxl/encode.h>
10
#include <jxl/memory_manager.h>
11
#include <jxl/types.h>
12
13
#include <algorithm>
14
#include <array>
15
#include <cmath>
16
#include <cstddef>
17
#include <cstdint>
18
#include <memory>
19
#include <numeric>
20
#include <utility>
21
#include <vector>
22
23
#include "lib/jxl/ac_context.h"
24
#include "lib/jxl/ac_strategy.h"
25
#include "lib/jxl/base/bits.h"
26
#include "lib/jxl/base/common.h"
27
#include "lib/jxl/base/compiler_specific.h"
28
#include "lib/jxl/base/data_parallel.h"
29
#include "lib/jxl/base/override.h"
30
#include "lib/jxl/base/printf_macros.h"
31
#include "lib/jxl/base/rect.h"
32
#include "lib/jxl/base/span.h"
33
#include "lib/jxl/base/status.h"
34
#include "lib/jxl/chroma_from_luma.h"
35
#include "lib/jxl/coeff_order.h"
36
#include "lib/jxl/coeff_order_fwd.h"
37
#include "lib/jxl/color_encoding_internal.h"
38
#include "lib/jxl/common.h"  // kMaxNumPasses
39
#include "lib/jxl/dct_util.h"
40
#include "lib/jxl/dec_external_image.h"
41
#include "lib/jxl/dec_modular.h"
42
#include "lib/jxl/enc_ac_strategy.h"
43
#include "lib/jxl/enc_adaptive_quantization.h"
44
#include "lib/jxl/enc_ans.h"
45
#include "lib/jxl/enc_ans_params.h"
46
#include "lib/jxl/enc_aux_out.h"
47
#include "lib/jxl/enc_bit_writer.h"
48
#include "lib/jxl/enc_cache.h"
49
#include "lib/jxl/enc_chroma_from_luma.h"
50
#include "lib/jxl/enc_coeff_order.h"
51
#include "lib/jxl/enc_context_map.h"
52
#include "lib/jxl/enc_entropy_coder.h"
53
#include "lib/jxl/enc_external_image.h"
54
#include "lib/jxl/enc_fields.h"
55
#include "lib/jxl/enc_group.h"
56
#include "lib/jxl/enc_heuristics.h"
57
#include "lib/jxl/enc_modular.h"
58
#include "lib/jxl/enc_noise.h"
59
#include "lib/jxl/enc_params.h"
60
#include "lib/jxl/enc_patch_dictionary.h"
61
#include "lib/jxl/enc_photon_noise.h"
62
#include "lib/jxl/enc_progressive_split.h"
63
#include "lib/jxl/enc_quant_weights.h"
64
#include "lib/jxl/enc_splines.h"
65
#include "lib/jxl/enc_toc.h"
66
#include "lib/jxl/enc_xyb.h"
67
#include "lib/jxl/encode_internal.h"
68
#include "lib/jxl/fields.h"
69
#include "lib/jxl/frame_dimensions.h"
70
#include "lib/jxl/frame_header.h"
71
#include "lib/jxl/image.h"
72
#include "lib/jxl/image_bundle.h"
73
#include "lib/jxl/image_metadata.h"
74
#include "lib/jxl/image_ops.h"
75
#include "lib/jxl/jpeg/enc_jpeg_data.h"
76
#include "lib/jxl/jpeg/jpeg_data.h"
77
#include "lib/jxl/loop_filter.h"
78
#include "lib/jxl/modular/options.h"
79
#include "lib/jxl/noise.h"
80
#include "lib/jxl/padded_bytes.h"
81
#include "lib/jxl/passes_state.h"
82
#include "lib/jxl/quant_weights.h"
83
#include "lib/jxl/quantizer.h"
84
#include "lib/jxl/splines.h"
85
#include "lib/jxl/toc.h"
86
87
namespace jxl {
88
89
283
Status ParamsPostInit(CompressParams* p) {
90
283
  if (!p->manual_noise.empty() &&
91
283
      p->manual_noise.size() != NoiseParams::kNumNoisePoints) {
92
0
    return JXL_FAILURE("Invalid number of noise lut entries");
93
0
  }
94
283
  if (!p->manual_xyb_factors.empty() && p->manual_xyb_factors.size() != 3) {
95
0
    return JXL_FAILURE("Invalid number of XYB quantization factors");
96
0
  }
97
283
  if (!p->modular_mode && p->butteraugli_distance < kMinButteraugliDistance) {
98
0
    p->butteraugli_distance = kMinButteraugliDistance;
99
0
  }
100
283
  if (p->original_butteraugli_distance == -1.0) {
101
186
    p->original_butteraugli_distance = p->butteraugli_distance;
102
186
  }
103
283
  if (p->resampling <= 0) {
104
186
    p->resampling = 1;
105
    // For very low bit rates, using 2x2 resampling gives better results on
106
    // most photographic images, with an adjusted butteraugli score chosen to
107
    // give roughly the same amount of bits per pixel.
108
186
    if (!p->already_downsampled && p->butteraugli_distance >= 10) {
109
      // TODO(Jonnyawsom3): Explore 4x4 resampling at distance 25. Lower bpp
110
      // but results are inconsistent and images under 4K become far too blurry.
111
0
      p->resampling = 2;
112
      // Adding 0.25 balances photo with non-photo, shifting towards lower bpp
113
      // to avoid large overshoot while maintaining quality equal to before.
114
0
      p->butteraugli_distance = (p->butteraugli_distance * 0.25) + 0.25;
115
0
    }
116
186
  }
117
283
  if (p->ec_resampling <= 0) {
118
186
    p->ec_resampling = p->resampling;
119
186
  }
120
283
  return true;
121
283
}
122
123
namespace {
124
125
template <typename T>
126
uint32_t GetBitDepth(JxlBitDepth bit_depth, const T& metadata,
127
283
                     JxlPixelFormat format) {
128
283
  if (bit_depth.type == JXL_BIT_DEPTH_FROM_PIXEL_FORMAT) {
129
283
    return BitsPerChannel(format.data_type);
130
283
  } else if (bit_depth.type == JXL_BIT_DEPTH_FROM_CODESTREAM) {
131
0
    return metadata.bit_depth.bits_per_sample;
132
0
  } else if (bit_depth.type == JXL_BIT_DEPTH_CUSTOM) {
133
0
    return bit_depth.bits_per_sample;
134
0
  } else {
135
0
    return 0;
136
0
  }
137
283
}
enc_frame.cc:unsigned int jxl::(anonymous namespace)::GetBitDepth<jxl::ImageMetadata>(JxlBitDepth, jxl::ImageMetadata const&, JxlPixelFormat)
Line
Count
Source
127
283
                     JxlPixelFormat format) {
128
283
  if (bit_depth.type == JXL_BIT_DEPTH_FROM_PIXEL_FORMAT) {
129
283
    return BitsPerChannel(format.data_type);
130
283
  } else if (bit_depth.type == JXL_BIT_DEPTH_FROM_CODESTREAM) {
131
0
    return metadata.bit_depth.bits_per_sample;
132
0
  } else if (bit_depth.type == JXL_BIT_DEPTH_CUSTOM) {
133
0
    return bit_depth.bits_per_sample;
134
0
  } else {
135
0
    return 0;
136
0
  }
137
283
}
Unexecuted instantiation: enc_frame.cc:unsigned int jxl::(anonymous namespace)::GetBitDepth<jxl::ExtraChannelInfo>(JxlBitDepth, jxl::ExtraChannelInfo const&, JxlPixelFormat)
138
139
Status CopyColorChannels(JxlChunkedFrameInputSource input, Rect rect,
140
                         const FrameInfo& frame_info,
141
                         const ImageMetadata& metadata, ThreadPool* pool,
142
                         Image3F* color, ImageF* alpha,
143
283
                         bool* has_interleaved_alpha) {
144
283
  JxlPixelFormat format = {4, JXL_TYPE_UINT8, JXL_NATIVE_ENDIAN, 0};
145
283
  input.get_color_channels_pixel_format(input.opaque, &format);
146
283
  *has_interleaved_alpha = format.num_channels == 2 || format.num_channels == 4;
147
283
  size_t bits_per_sample =
148
283
      GetBitDepth(frame_info.image_bit_depth, metadata, format);
149
283
  size_t row_offset;
150
283
  auto buffer = GetColorBuffer(input, rect.x0(), rect.y0(), rect.xsize(),
151
283
                               rect.ysize(), &row_offset);
152
283
  if (!buffer) {
153
0
    return JXL_FAILURE("no buffer for color channels given");
154
0
  }
155
283
  size_t color_channels = frame_info.ib_needs_color_transform
156
283
                              ? metadata.color_encoding.Channels()
157
283
                              : 3;
158
283
  if (format.num_channels < color_channels) {
159
0
    return JXL_FAILURE("Expected %" PRIuS
160
0
                       " color channels, received only %u channels",
161
0
                       color_channels, format.num_channels);
162
0
  }
163
283
  const uint8_t* data = reinterpret_cast<const uint8_t*>(buffer.get());
164
1.10k
  for (size_t c = 0; c < color_channels; ++c) {
165
819
    JXL_RETURN_IF_ERROR(ConvertFromExternalNoSizeCheck(
166
819
        data, rect.xsize(), rect.ysize(), row_offset, bits_per_sample, format,
167
819
        c, pool, &color->Plane(c)));
168
819
  }
169
283
  if (color_channels == 1) {
170
15
    JXL_RETURN_IF_ERROR(CopyImageTo(color->Plane(0), &color->Plane(1)));
171
15
    JXL_RETURN_IF_ERROR(CopyImageTo(color->Plane(0), &color->Plane(2)));
172
15
  }
173
283
  if (alpha) {
174
14
    if (*has_interleaved_alpha) {
175
14
      JXL_RETURN_IF_ERROR(ConvertFromExternalNoSizeCheck(
176
14
          data, rect.xsize(), rect.ysize(), row_offset, bits_per_sample, format,
177
14
          format.num_channels - 1, pool, alpha));
178
14
    } else {
179
      // if alpha is not passed, but it is expected, then assume
180
      // it is all-opaque
181
0
      FillImage(1.0f, alpha);
182
0
    }
183
14
  }
184
283
  return true;
185
283
}
186
187
Status CopyExtraChannels(JxlChunkedFrameInputSource input, Rect rect,
188
                         const FrameInfo& frame_info,
189
                         const ImageMetadata& metadata,
190
                         bool has_interleaved_alpha, ThreadPool* pool,
191
283
                         std::vector<ImageF>* extra_channels) {
192
297
  for (size_t ec = 0; ec < metadata.num_extra_channels; ec++) {
193
14
    if (has_interleaved_alpha &&
194
14
        metadata.extra_channel_info[ec].type == ExtraChannel::kAlpha) {
195
      // Skip this alpha channel, but still request additional alpha channels
196
      // if they exist.
197
14
      has_interleaved_alpha = false;
198
14
      continue;
199
14
    }
200
0
    JxlPixelFormat ec_format = {1, JXL_TYPE_UINT8, JXL_NATIVE_ENDIAN, 0};
201
0
    input.get_extra_channel_pixel_format(input.opaque, ec, &ec_format);
202
0
    ec_format.num_channels = 1;
203
0
    size_t row_offset;
204
0
    auto buffer =
205
0
        GetExtraChannelBuffer(input, ec, rect.x0(), rect.y0(), rect.xsize(),
206
0
                              rect.ysize(), &row_offset);
207
0
    if (!buffer) {
208
0
      return JXL_FAILURE("no buffer for extra channel given");
209
0
    }
210
0
    size_t bits_per_sample = GetBitDepth(
211
0
        frame_info.image_bit_depth, metadata.extra_channel_info[ec], ec_format);
212
0
    if (!ConvertFromExternalNoSizeCheck(
213
0
            reinterpret_cast<const uint8_t*>(buffer.get()), rect.xsize(),
214
0
            rect.ysize(), row_offset, bits_per_sample, ec_format, 0, pool,
215
0
            &(*extra_channels)[ec])) {
216
0
      return JXL_FAILURE("Failed to set buffer for extra channel");
217
0
    }
218
0
  }
219
283
  return true;
220
283
}
221
222
void SetProgressiveMode(const CompressParams& cparams,
223
283
                        ProgressiveSplitter* progressive_splitter) {
224
283
  constexpr PassDefinition progressive_passes_dc_vlf_lf_full_ac[] = {
225
283
      {/*num_coefficients=*/2, /*shift=*/0,
226
283
       /*suitable_for_downsampling_of_at_least=*/4},
227
283
      {/*num_coefficients=*/3, /*shift=*/0,
228
283
       /*suitable_for_downsampling_of_at_least=*/2},
229
283
      {/*num_coefficients=*/8, /*shift=*/0,
230
283
       /*suitable_for_downsampling_of_at_least=*/0},
231
283
  };
232
283
  constexpr PassDefinition progressive_passes_dc_quant_ac_full_ac[] = {
233
283
      {/*num_coefficients=*/8, /*shift=*/1,
234
283
       /*suitable_for_downsampling_of_at_least=*/2},
235
283
      {/*num_coefficients=*/8, /*shift=*/0,
236
283
       /*suitable_for_downsampling_of_at_least=*/0},
237
283
  };
238
283
  bool progressive_mode = ApplyOverride(cparams.progressive_mode, false);
239
283
  bool qprogressive_mode = ApplyOverride(cparams.qprogressive_mode, false);
240
283
  if (cparams.custom_progressive_mode) {
241
0
    progressive_splitter->SetProgressiveMode(*cparams.custom_progressive_mode);
242
283
  } else if (qprogressive_mode) {
243
0
    progressive_splitter->SetProgressiveMode(
244
0
        ProgressiveMode{progressive_passes_dc_quant_ac_full_ac});
245
283
  } else if (progressive_mode) {
246
0
    progressive_splitter->SetProgressiveMode(
247
0
        ProgressiveMode{progressive_passes_dc_vlf_lf_full_ac});
248
0
  }
249
283
}
250
251
283
uint64_t FrameFlagsFromParams(const CompressParams& cparams) {
252
283
  uint64_t flags = 0;
253
254
283
  const float dist = cparams.butteraugli_distance;
255
256
  // We don't add noise at low butteraugli distances because the original
257
  // noise is stored within the compressed image and adding noise makes things
258
  // worse.
259
283
  if (ApplyOverride(cparams.noise, dist >= kMinButteraugliForNoise) ||
260
283
      cparams.photon_noise_iso > 0 ||
261
283
      cparams.manual_noise.size() == NoiseParams::kNumNoisePoints) {
262
0
    flags |= FrameHeader::kNoise;
263
0
  }
264
265
283
  if (cparams.progressive_dc > 0 && cparams.modular_mode == false) {
266
0
    flags |= FrameHeader::kUseDcFrame;
267
0
  }
268
269
283
  return flags;
270
283
}
271
272
Status LoopFilterFromParams(const CompressParams& cparams, bool streaming_mode,
273
283
                            FrameHeader* JXL_RESTRICT frame_header) {
274
283
  LoopFilter* loop_filter = &frame_header->loop_filter;
275
276
  // Gaborish defaults to enabled in Hare or slower.
277
283
  loop_filter->gab = ApplyOverride(
278
283
      cparams.gaborish, cparams.speed_tier <= SpeedTier::kHare &&
279
283
                            frame_header->encoding == FrameEncoding::kVarDCT &&
280
283
                            cparams.decoding_speed_tier < 4 &&
281
283
                            cparams.butteraugli_distance > 0.5f &&
282
283
                            !cparams.disable_perceptual_optimizations);
283
284
283
  if (cparams.epf != -1) {
285
0
    loop_filter->epf_iters = cparams.epf;
286
283
  } else if (cparams.disable_perceptual_optimizations) {
287
0
    loop_filter->epf_iters = 0;
288
0
    return true;
289
283
  } else {
290
283
    if (frame_header->encoding == FrameEncoding::kModular) {
291
97
      loop_filter->epf_iters = 0;
292
186
    } else {
293
186
      constexpr float kThresholds[3] = {0.7, 1.5, 4.0};
294
186
      loop_filter->epf_iters = 0;
295
186
      if (cparams.decoding_speed_tier < 3) {
296
744
        for (size_t i = cparams.decoding_speed_tier == 2 ? 1 : 0; i < 3; i++) {
297
558
          if (cparams.butteraugli_distance >= kThresholds[i]) {
298
186
            loop_filter->epf_iters++;
299
186
          }
300
558
        }
301
186
      }
302
186
    }
303
283
  }
304
  // Strength of EPF in modular mode.
305
283
  if (frame_header->encoding == FrameEncoding::kModular &&
306
283
      !cparams.IsLossless()) {
307
    // TODO(veluca): this formula is nonsense.
308
97
    loop_filter->epf_sigma_for_modular =
309
97
        std::max(cparams.butteraugli_distance, 1.0f);
310
97
  }
311
283
  if (frame_header->encoding == FrameEncoding::kModular &&
312
283
      cparams.lossy_palette) {
313
0
    loop_filter->epf_sigma_for_modular = 1.0f;
314
0
  }
315
316
283
  return true;
317
283
}
318
319
Status MakeFrameHeader(size_t xsize, size_t ysize,
320
                       const CompressParams& cparams,
321
                       const ProgressiveSplitter& progressive_splitter,
322
                       const FrameInfo& frame_info,
323
                       const jpeg::JPEGData* jpeg_data, bool streaming_mode,
324
283
                       FrameHeader* JXL_RESTRICT frame_header) {
325
283
  frame_header->nonserialized_is_preview = frame_info.is_preview;
326
283
  frame_header->is_last = frame_info.is_last;
327
283
  frame_header->save_before_color_transform =
328
283
      frame_info.save_before_color_transform;
329
283
  frame_header->frame_type = frame_info.frame_type;
330
283
  frame_header->name = frame_info.name;
331
332
283
  JXL_RETURN_IF_ERROR(progressive_splitter.InitPasses(&frame_header->passes));
333
334
283
  if (cparams.modular_mode) {
335
97
    frame_header->encoding = FrameEncoding::kModular;
336
97
    if (cparams.modular_group_size_shift == -1) {
337
      // By default, use the smallest group size for faster decoding 2
338
      // and higher. Greatly speeds up decoding via multithreading at
339
      // the cost of density.
340
97
      if (cparams.decoding_speed_tier >= 2 ||
341
        // Force decoding speed to tier 2 for progressive lossless.
342
97
        (cparams.decoding_speed_tier >= 1 && cparams.responsive == 1
343
97
        && cparams.IsLossless())) {
344
0
        frame_header->group_size_shift = 0;
345
      // no point using groups when only one group is full and the others are
346
      // less than half full: multithreading will not really help much, while
347
      // compression does suffer
348
97
      } else if (xsize <= 400 && ysize <= 400) {
349
97
        frame_header->group_size_shift = 2;
350
97
      } else {
351
0
        frame_header->group_size_shift = 1;
352
0
      }
353
97
    } else {
354
0
      frame_header->group_size_shift = cparams.modular_group_size_shift;
355
0
    }
356
97
  }
357
358
283
  if (jpeg_data) {
359
    // we are transcoding a JPEG, so we don't get to choose
360
0
    frame_header->encoding = FrameEncoding::kVarDCT;
361
0
    frame_header->x_qm_scale = 2;
362
0
    frame_header->b_qm_scale = 2;
363
0
    JXL_RETURN_IF_ERROR(SetChromaSubsamplingFromJpegData(
364
0
        *jpeg_data, &frame_header->chroma_subsampling));
365
0
    JXL_RETURN_IF_ERROR(SetColorTransformFromJpegData(
366
0
        *jpeg_data, &frame_header->color_transform));
367
283
  } else {
368
283
    frame_header->color_transform = cparams.color_transform;
369
283
    if (!cparams.modular_mode &&
370
283
        (frame_header->chroma_subsampling.MaxHShift() != 0 ||
371
186
         frame_header->chroma_subsampling.MaxVShift() != 0)) {
372
0
      return JXL_FAILURE(
373
0
          "Chroma subsampling is not supported in VarDCT mode when not "
374
0
          "recompressing JPEGs");
375
0
    }
376
283
  }
377
283
  if (frame_header->color_transform != ColorTransform::kYCbCr &&
378
283
      (frame_header->chroma_subsampling.MaxHShift() != 0 ||
379
283
       frame_header->chroma_subsampling.MaxVShift() != 0)) {
380
0
    return JXL_FAILURE(
381
0
        "Chroma subsampling is not supported when color transform is not "
382
0
        "YCbCr");
383
0
  }
384
385
283
  frame_header->flags = FrameFlagsFromParams(cparams);
386
  // Non-photon noise is not supported in the Modular encoder for now.
387
283
  if (frame_header->encoding != FrameEncoding::kVarDCT &&
388
283
      cparams.photon_noise_iso == 0 && cparams.manual_noise.empty()) {
389
97
    frame_header->UpdateFlag(false, FrameHeader::Flags::kNoise);
390
97
  }
391
392
283
  JXL_RETURN_IF_ERROR(
393
283
      LoopFilterFromParams(cparams, streaming_mode, frame_header));
394
395
283
  frame_header->dc_level = frame_info.dc_level;
396
283
  if (frame_header->dc_level > 2) {
397
    // With 3 or more progressive_dc frames, the implementation does not yet
398
    // work, see enc_cache.cc.
399
0
    return JXL_FAILURE("progressive_dc > 2 is not yet supported");
400
0
  }
401
283
  if (cparams.progressive_dc > 0 &&
402
283
      (cparams.ec_resampling != 1 || cparams.resampling != 1)) {
403
0
    return JXL_FAILURE("Resampling not supported with DC frames");
404
0
  }
405
283
  if (cparams.resampling != 1 && cparams.resampling != 2 &&
406
283
      cparams.resampling != 4 && cparams.resampling != 8) {
407
0
    return JXL_FAILURE("Invalid resampling factor");
408
0
  }
409
283
  if (cparams.ec_resampling != 1 && cparams.ec_resampling != 2 &&
410
283
      cparams.ec_resampling != 4 && cparams.ec_resampling != 8) {
411
0
    return JXL_FAILURE("Invalid ec_resampling factor");
412
0
  }
413
  // Resized frames.
414
283
  if (frame_info.frame_type != FrameType::kDCFrame) {
415
283
    frame_header->frame_origin = frame_info.origin;
416
283
    size_t ups = 1;
417
283
    if (cparams.already_downsampled) ups = cparams.resampling;
418
419
    // TODO(lode): this is not correct in case of odd original image sizes in
420
    // combination with cparams.already_downsampled. Likely these values should
421
    // be set to respectively frame_header->default_xsize() and
422
    // frame_header->default_ysize() instead, the original (non downsampled)
423
    // intended decoded image dimensions. But it may be more subtle than that
424
    // if combined with crop. This issue causes custom_size_or_origin to be
425
    // incorrectly set to true in case of already_downsampled with odd output
426
    // image size when no cropping is used.
427
283
    frame_header->frame_size.xsize = xsize * ups;
428
283
    frame_header->frame_size.ysize = ysize * ups;
429
283
    if (frame_info.origin.x0 != 0 || frame_info.origin.y0 != 0 ||
430
283
        frame_header->frame_size.xsize != frame_header->default_xsize() ||
431
283
        frame_header->frame_size.ysize != frame_header->default_ysize()) {
432
97
      frame_header->custom_size_or_origin = true;
433
97
    }
434
283
  }
435
  // Upsampling.
436
283
  frame_header->upsampling = cparams.resampling;
437
283
  const std::vector<ExtraChannelInfo>& extra_channels =
438
283
      frame_header->nonserialized_metadata->m.extra_channel_info;
439
283
  frame_header->extra_channel_upsampling.clear();
440
283
  frame_header->extra_channel_upsampling.resize(extra_channels.size(),
441
283
                                                cparams.ec_resampling);
442
283
  frame_header->save_as_reference = frame_info.save_as_reference;
443
444
  // Set blending-related information.
445
283
  if (frame_info.blend || frame_header->custom_size_or_origin) {
446
    // Set blend_channel to the first alpha channel. These values are only
447
    // encoded in case a blend mode involving alpha is used and there are more
448
    // than one extra channels.
449
97
    size_t index = 0;
450
97
    if (frame_info.alpha_channel == -1) {
451
97
      if (extra_channels.size() > 1) {
452
0
        for (size_t i = 0; i < extra_channels.size(); i++) {
453
0
          if (extra_channels[i].type == ExtraChannel::kAlpha) {
454
0
            index = i;
455
0
            break;
456
0
          }
457
0
        }
458
0
      }
459
97
    } else {
460
0
      index = static_cast<size_t>(frame_info.alpha_channel);
461
0
      JXL_ENSURE(index == 0 || index < extra_channels.size());
462
0
    }
463
97
    frame_header->blending_info.alpha_channel = index;
464
97
    frame_header->blending_info.mode =
465
97
        frame_info.blend ? frame_info.blendmode : BlendMode::kReplace;
466
97
    frame_header->blending_info.source = frame_info.source;
467
97
    frame_header->blending_info.clamp = frame_info.clamp;
468
97
    const auto& extra_channel_info = frame_info.extra_channel_blending_info;
469
97
    for (size_t i = 0; i < extra_channels.size(); i++) {
470
0
      if (i < extra_channel_info.size()) {
471
0
        frame_header->extra_channel_blending_info[i] = extra_channel_info[i];
472
0
      } else {
473
0
        frame_header->extra_channel_blending_info[i].alpha_channel = index;
474
0
        BlendMode default_blend = frame_info.blendmode;
475
0
        if (extra_channels[i].type != ExtraChannel::kBlack && i != index) {
476
          // K needs to be blended, spot colors and other stuff gets added
477
0
          default_blend = BlendMode::kAdd;
478
0
        }
479
0
        frame_header->extra_channel_blending_info[i].mode =
480
0
            frame_info.blend ? default_blend : BlendMode::kReplace;
481
0
        frame_header->extra_channel_blending_info[i].source = 1;
482
0
      }
483
0
    }
484
97
  }
485
486
283
  frame_header->animation_frame.duration = frame_info.duration;
487
283
  frame_header->animation_frame.timecode = frame_info.timecode;
488
489
283
  if (jpeg_data) {
490
0
    frame_header->UpdateFlag(false, FrameHeader::kUseDcFrame);
491
0
    frame_header->UpdateFlag(true, FrameHeader::kSkipAdaptiveDCSmoothing);
492
0
  }
493
494
283
  return true;
495
283
}
496
497
// Invisible (alpha = 0) pixels tend to be a mess in optimized PNGs.
498
// Since they have no visual impact whatsoever, we can replace them with
499
// something that compresses better and reduces artifacts near the edges. This
500
// does some kind of smooth stuff that seems to work.
501
// Replace invisible pixels with a weighted average of the pixel to the left,
502
// the pixel to the topright, and non-invisible neighbours.
503
// Produces downward-blurry smears, with in the upwards direction only a 1px
504
// edge duplication but not more. It would probably be better to smear in all
505
// directions. That requires an alpha-weighed convolution with a large enough
506
// kernel though, which might be overkill...
507
14
void SimplifyInvisible(Image3F* image, const ImageF& alpha, bool lossless) {
508
56
  for (size_t c = 0; c < 3; ++c) {
509
1.11k
    for (size_t y = 0; y < image->ysize(); ++y) {
510
1.06k
      float* JXL_RESTRICT row = image->PlaneRow(c, y);
511
1.06k
      const float* JXL_RESTRICT prow =
512
1.06k
          (y > 0 ? image->PlaneRow(c, y - 1) : nullptr);
513
1.06k
      const float* JXL_RESTRICT nrow =
514
1.06k
          (y + 1 < image->ysize() ? image->PlaneRow(c, y + 1) : nullptr);
515
1.06k
      const float* JXL_RESTRICT a = alpha.Row(y);
516
1.06k
      const float* JXL_RESTRICT pa = (y > 0 ? alpha.Row(y - 1) : nullptr);
517
1.06k
      const float* JXL_RESTRICT na =
518
1.06k
          (y + 1 < image->ysize() ? alpha.Row(y + 1) : nullptr);
519
57.6k
      for (size_t x = 0; x < image->xsize(); ++x) {
520
56.6k
        if (a[x] == 0) {
521
29.7k
          if (lossless) {
522
0
            row[x] = 0;
523
0
            continue;
524
0
          }
525
29.7k
          float d = 0.f;
526
29.7k
          row[x] = 0;
527
29.7k
          if (x > 0) {
528
28.8k
            row[x] += row[x - 1];
529
28.8k
            d++;
530
28.8k
            if (a[x - 1] > 0.f) {
531
270
              row[x] += row[x - 1];
532
270
              d++;
533
270
            }
534
28.8k
          }
535
29.7k
          if (x + 1 < image->xsize()) {
536
28.9k
            if (y > 0) {
537
27.2k
              row[x] += prow[x + 1];
538
27.2k
              d++;
539
27.2k
            }
540
28.9k
            if (a[x + 1] > 0.f) {
541
300
              row[x] += 2.f * row[x + 1];
542
300
              d += 2.f;
543
300
            }
544
28.9k
            if (y > 0 && pa[x + 1] > 0.f) {
545
366
              row[x] += 2.f * prow[x + 1];
546
366
              d += 2.f;
547
366
            }
548
28.9k
            if (y + 1 < image->ysize() && na[x + 1] > 0.f) {
549
399
              row[x] += 2.f * nrow[x + 1];
550
399
              d += 2.f;
551
399
            }
552
28.9k
          }
553
29.7k
          if (y > 0 && pa[x] > 0.f) {
554
237
            row[x] += 2.f * prow[x];
555
237
            d += 2.f;
556
237
          }
557
29.7k
          if (y + 1 < image->ysize() && na[x] > 0.f) {
558
261
            row[x] += 2.f * nrow[x];
559
261
            d += 2.f;
560
261
          }
561
29.7k
          if (d > 1.f) row[x] /= d;
562
29.7k
        }
563
56.6k
      }
564
1.06k
    }
565
42
  }
566
14
}
567
568
struct PixelStatsForChromacityAdjustment {
569
  float dx = 0;
570
  float db = 0;
571
  float exposed_blue = 0;
572
186
  static float CalcPlane(const ImageF* JXL_RESTRICT plane, const Rect& rect) {
573
186
    float xmax = 0;
574
186
    float ymax = 0;
575
58.6k
    for (size_t ty = 1; ty < rect.ysize(); ++ty) {
576
25.4M
      for (size_t tx = 1; tx < rect.xsize(); ++tx) {
577
25.3M
        float cur = rect.Row(plane, ty)[tx];
578
25.3M
        float prev_row = rect.Row(plane, ty - 1)[tx];
579
25.3M
        float prev = rect.Row(plane, ty)[tx - 1];
580
25.3M
        xmax = std::max(xmax, std::abs(cur - prev));
581
25.3M
        ymax = std::max(ymax, std::abs(cur - prev_row));
582
25.3M
      }
583
58.4k
    }
584
186
    return std::max(xmax, ymax);
585
186
  }
586
  void CalcExposedBlue(const ImageF* JXL_RESTRICT plane_y,
587
186
                       const ImageF* JXL_RESTRICT plane_b, const Rect& rect) {
588
186
    float eb = 0;
589
186
    float xmax = 0;
590
186
    float ymax = 0;
591
58.6k
    for (size_t ty = 1; ty < rect.ysize(); ++ty) {
592
25.4M
      for (size_t tx = 1; tx < rect.xsize(); ++tx) {
593
25.3M
        float cur_y = rect.Row(plane_y, ty)[tx];
594
25.3M
        float cur_b = rect.Row(plane_b, ty)[tx];
595
25.3M
        float exposed_b = cur_b - cur_y * 1.2;
596
25.3M
        float diff_b = cur_b - cur_y;
597
25.3M
        float prev_row = rect.Row(plane_b, ty - 1)[tx];
598
25.3M
        float prev = rect.Row(plane_b, ty)[tx - 1];
599
25.3M
        float diff_prev_row = prev_row - rect.Row(plane_y, ty - 1)[tx];
600
25.3M
        float diff_prev = prev - rect.Row(plane_y, ty)[tx - 1];
601
25.3M
        xmax = std::max(xmax, std::abs(diff_b - diff_prev));
602
25.3M
        ymax = std::max(ymax, std::abs(diff_b - diff_prev_row));
603
25.3M
        if (exposed_b >= 0) {
604
5.76M
          exposed_b *= std::abs(cur_b - prev) + std::abs(cur_b - prev_row);
605
5.76M
          eb = std::max(eb, exposed_b);
606
5.76M
        }
607
25.3M
      }
608
58.4k
    }
609
186
    exposed_blue = eb;
610
186
    db = std::max(xmax, ymax);
611
186
  }
612
186
  void Calc(const Image3F* JXL_RESTRICT opsin, const Rect& rect) {
613
186
    dx = CalcPlane(&opsin->Plane(0), rect);
614
186
    CalcExposedBlue(&opsin->Plane(1), &opsin->Plane(2), rect);
615
186
  }
616
186
  int HowMuchIsXChannelPixelized() const {
617
186
    if (dx >= 0.026) {
618
96
      return 3;
619
96
    }
620
90
    if (dx >= 0.022) {
621
28
      return 2;
622
28
    }
623
62
    if (dx >= 0.015) {
624
4
      return 1;
625
4
    }
626
58
    return 0;
627
62
  }
628
186
  int HowMuchIsBChannelPixelized() const {
629
186
    int add = exposed_blue >= 0.13 ? 1 : 0;
630
186
    if (db > 0.38) {
631
94
      return 2 + add;
632
94
    }
633
92
    if (db > 0.33) {
634
15
      return 1 + add;
635
15
    }
636
77
    if (db > 0.28) {
637
3
      return add;
638
3
    }
639
74
    return 0;
640
77
  }
641
};
642
643
void ComputeChromacityAdjustments(const CompressParams& cparams,
644
                                  const Image3F& opsin, const Rect& rect,
645
283
                                  FrameHeader* frame_header) {
646
283
  if (frame_header->encoding != FrameEncoding::kVarDCT ||
647
283
      cparams.max_error_mode) {
648
97
    return;
649
97
  }
650
  // 1) Distance based approach for chromacity adjustment:
651
186
  float x_qm_scale_steps[3] = {2.5f, 5.5f, 9.5f};
652
186
  frame_header->x_qm_scale = 3;
653
558
  for (float x_qm_scale_step : x_qm_scale_steps) {
654
558
    if (cparams.original_butteraugli_distance > x_qm_scale_step) {
655
0
      frame_header->x_qm_scale++;
656
0
    }
657
558
  }
658
  // 2) Pixel-based approach for chromacity adjustment:
659
  // look at the individual pixels and make a guess how difficult
660
  // the image would be based on the worst case pixel.
661
186
  PixelStatsForChromacityAdjustment pixel_stats;
662
186
  if (cparams.speed_tier <= SpeedTier::kSquirrel) {
663
186
    pixel_stats.Calc(&opsin, rect);
664
186
  }
665
  // For X take the most severe adjustment.
666
186
  frame_header->x_qm_scale = std::max<int>(
667
186
      frame_header->x_qm_scale, 2 + pixel_stats.HowMuchIsXChannelPixelized());
668
  // B only adjusted by pixel-based approach.
669
186
  frame_header->b_qm_scale = 2 + pixel_stats.HowMuchIsBChannelPixelized();
670
186
}
671
672
void ComputeNoiseParams(const CompressParams& cparams, bool streaming_mode,
673
                        bool color_is_jpeg, const Image3F& opsin,
674
                        const FrameDimensions& frame_dim,
675
283
                        FrameHeader* frame_header, NoiseParams* noise_params) {
676
283
  if (cparams.photon_noise_iso > 0) {
677
0
    *noise_params = SimulatePhotonNoise(frame_dim.xsize, frame_dim.ysize,
678
0
                                        cparams.photon_noise_iso);
679
283
  } else if (cparams.manual_noise.size() == NoiseParams::kNumNoisePoints) {
680
0
    for (size_t i = 0; i < NoiseParams::kNumNoisePoints; i++) {
681
0
      noise_params->lut[i] = cparams.manual_noise[i];
682
0
    }
683
283
  } else if (frame_header->encoding == FrameEncoding::kVarDCT &&
684
283
             frame_header->flags & FrameHeader::kNoise && !color_is_jpeg &&
685
283
             !streaming_mode) {
686
    // Don't start at zero amplitude since adding noise is expensive -- it
687
    // significantly slows down decoding, and this is unlikely to
688
    // completely go away even with advanced optimizations. After the
689
    // kNoiseModelingRampUpDistanceRange we have reached the full level,
690
    // i.e. noise is no longer represented by the compressed image, so we
691
    // can add full noise by the noise modeling itself.
692
0
    static const float kNoiseModelingRampUpDistanceRange = 0.6;
693
0
    static const float kNoiseLevelAtStartOfRampUp = 0.25;
694
0
    static const float kNoiseRampupStart = 1.0;
695
    // TODO(user) test and properly select quality_coef with smooth
696
    // filter
697
0
    float quality_coef = 1.0f;
698
0
    const float rampup = (cparams.butteraugli_distance - kNoiseRampupStart) /
699
0
                         kNoiseModelingRampUpDistanceRange;
700
0
    if (rampup < 1.0f) {
701
0
      quality_coef = kNoiseLevelAtStartOfRampUp +
702
0
                     (1.0f - kNoiseLevelAtStartOfRampUp) * rampup;
703
0
    }
704
0
    if (rampup < 0.0f) {
705
0
      quality_coef = kNoiseRampupStart;
706
0
    }
707
0
    if (!GetNoiseParameter(opsin, noise_params, quality_coef)) {
708
0
      frame_header->flags &= ~FrameHeader::kNoise;
709
0
    }
710
0
  }
711
283
}
712
713
Status DownsampleColorChannels(const CompressParams& cparams,
714
                               const FrameHeader& frame_header,
715
283
                               bool color_is_jpeg, Image3F* opsin) {
716
283
  if (color_is_jpeg || frame_header.upsampling == 1 ||
717
283
      cparams.already_downsampled) {
718
283
    return true;
719
283
  }
720
0
  if (frame_header.encoding == FrameEncoding::kVarDCT &&
721
0
      frame_header.upsampling == 2) {
722
    // TODO(lode): use the regular DownsampleImage, or adapt to the custom
723
    // coefficients, if there is are custom upscaling coefficients in
724
    // CustomTransformData
725
0
    if (cparams.speed_tier <= SpeedTier::kGlacier) {
726
      // TODO(Jonnyawsom3): Until optimized, enabled only for Glacier and
727
      // TectonicPlate. It's an 80% slowdown and downsampling is only active
728
      // at high distances by default anyway, making improvements negligible.
729
0
      JXL_RETURN_IF_ERROR(DownsampleImage2_Iterative(opsin));
730
0
    } else {
731
0
      JXL_RETURN_IF_ERROR(DownsampleImage2_Sharper(opsin));
732
0
    }
733
0
  } else {
734
0
    JXL_ASSIGN_OR_RETURN(*opsin,
735
0
                         DownsampleImage(*opsin, frame_header.upsampling));
736
0
  }
737
0
  if (frame_header.encoding == FrameEncoding::kVarDCT) {
738
0
    JXL_RETURN_IF_ERROR(PadImageToBlockMultipleInPlace(opsin));
739
0
  }
740
0
  return true;
741
0
}
742
743
template <size_t L, typename V, typename R>
744
0
void FindIndexOfSumMaximum(const V* array, R* idx, V* sum) {
745
0
  static_assert(L > 0, "Empty arrays have undefined maximum");
746
0
  V maxval = 0;
747
0
  V val = 0;
748
0
  R maxidx = 0;
749
0
  for (size_t i = 0; i < L; ++i) {
750
0
    val += array[i];
751
0
    if (val > maxval) {
752
0
      maxval = val;
753
0
      maxidx = i;
754
0
    }
755
0
  }
756
0
  *idx = maxidx;
757
0
  *sum = maxval;
758
0
}
759
760
Status ComputeJPEGTranscodingData(const jpeg::JPEGData& jpeg_data,
761
                                  const FrameHeader& frame_header,
762
                                  ThreadPool* pool,
763
                                  ModularFrameEncoder* enc_modular,
764
0
                                  PassesEncoderState* enc_state) {
765
0
  PassesSharedState& shared = enc_state->shared;
766
0
  JxlMemoryManager* memory_manager = enc_state->memory_manager();
767
0
  const FrameDimensions& frame_dim = shared.frame_dim;
768
769
0
  const size_t xsize = frame_dim.xsize_padded;
770
0
  const size_t ysize = frame_dim.ysize_padded;
771
0
  const size_t xsize_blocks = frame_dim.xsize_blocks;
772
0
  const size_t ysize_blocks = frame_dim.ysize_blocks;
773
774
  // no-op chroma from luma
775
0
  JXL_ASSIGN_OR_RETURN(shared.cmap, ColorCorrelationMap::Create(
776
0
                                        memory_manager, xsize, ysize, false));
777
0
  shared.ac_strategy.FillDCT8();
778
0
  FillImage(static_cast<uint8_t>(0), &shared.epf_sharpness);
779
780
0
  enc_state->coeffs.clear();
781
0
  while (enc_state->coeffs.size() < enc_state->passes.size()) {
782
0
    JXL_ASSIGN_OR_RETURN(
783
0
        std::unique_ptr<ACImageT<int32_t>> coeffs,
784
0
        ACImageT<int32_t>::Make(memory_manager, kGroupDim * kGroupDim,
785
0
                                frame_dim.num_groups));
786
0
    enc_state->coeffs.emplace_back(std::move(coeffs));
787
0
  }
788
789
  // convert JPEG quantization table to a Quantizer object
790
0
  float dcquantization[3];
791
0
  std::vector<QuantEncoding> qe(kNumQuantTables, QuantEncoding::Library<0>());
792
793
0
  auto jpeg_c_map =
794
0
      JpegOrder(frame_header.color_transform, jpeg_data.components.size() == 1);
795
796
0
  std::vector<int> qt(192);
797
0
  std::array<int32_t, 3> qt_dc;
798
0
  for (size_t c = 0; c < 3; c++) {
799
0
    size_t jpeg_c = jpeg_c_map[c];
800
0
    const int32_t* quant =
801
0
        jpeg_data.quant[jpeg_data.components[jpeg_c].quant_idx].values.data();
802
803
0
    dcquantization[c] = 255 * 8.0f / quant[0];
804
0
    for (size_t y = 0; y < 8; y++) {
805
0
      for (size_t x = 0; x < 8; x++) {
806
        // JPEG XL transposes the DCT, JPEG doesn't.
807
0
        qt[c * 64 + 8 * x + y] = quant[8 * y + x];
808
0
      }
809
0
    }
810
0
    qt_dc[c] = qt[c * 64];
811
0
  }
812
0
  JXL_RETURN_IF_ERROR(DequantMatricesSetCustomDC(
813
0
      memory_manager, &shared.matrices, dcquantization));
814
0
  float dcquantization_r[3] = {1.0f / dcquantization[0],
815
0
                               1.0f / dcquantization[1],
816
0
                               1.0f / dcquantization[2]};
817
818
0
  std::vector<int32_t> scaled_qtable(192);
819
0
  for (size_t c = 0; c < 3; c++) {
820
0
    for (size_t i = 0; i < 64; i++) {
821
0
      scaled_qtable[64 * c + i] =
822
0
          (1 << kCFLFixedPointPrecision) * qt[64 + i] / qt[64 * c + i];
823
0
    }
824
0
  }
825
826
0
  qe[static_cast<size_t>(AcStrategyType::DCT)] =
827
0
      QuantEncoding::RAW(std::move(qt));
828
0
  JXL_RETURN_IF_ERROR(
829
0
      DequantMatricesSetCustom(&shared.matrices, qe, enc_modular));
830
831
  // Ensure that InvGlobalScale() is 1.
832
0
  shared.quantizer = Quantizer(shared.matrices, 1, kGlobalScaleDenom);
833
  // Recompute MulDC() and InvMulDC().
834
0
  shared.quantizer.RecomputeFromGlobalScale();
835
836
  // Per-block dequant scaling should be 1.
837
0
  FillImage(static_cast<int32_t>(shared.quantizer.InvGlobalScale()),
838
0
            &shared.raw_quant_field);
839
840
0
  auto jpeg_row = [&](size_t c, size_t y) {
841
0
    return jpeg_data.components[jpeg_c_map[c]].coeffs.data() +
842
0
           jpeg_data.components[jpeg_c_map[c]].width_in_blocks * kDCTBlockSize *
843
0
               y;
844
0
  };
845
846
0
  bool DCzero = (frame_header.color_transform == ColorTransform::kYCbCr);
847
  // Compute chroma-from-luma for AC (doesn't seem to be useful for DC)
848
0
  if (frame_header.chroma_subsampling.Is444() &&
849
0
      enc_state->cparams.force_cfl_jpeg_recompression &&
850
0
      jpeg_data.components.size() == 3) {
851
0
    for (size_t c : {0, 2}) {
852
0
      ImageSB* map = (c == 0 ? &shared.cmap.ytox_map : &shared.cmap.ytob_map);
853
0
      const float kScale = kDefaultColorFactor;
854
0
      const int kOffset = 127;
855
0
      const float kBase = c == 0 ? shared.cmap.base().YtoXRatio(0)
856
0
                                 : shared.cmap.base().YtoBRatio(0);
857
0
      const float kZeroThresh =
858
0
          kScale * kZeroBiasDefault[c] *
859
0
          0.9999f;  // just epsilon less for better rounding
860
861
0
      auto process_row = [&](const uint32_t task,
862
0
                             const size_t thread) -> Status {
863
0
        size_t ty = task;
864
0
        int8_t* JXL_RESTRICT row_out = map->Row(ty);
865
0
        for (size_t tx = 0; tx < map->xsize(); ++tx) {
866
0
          const size_t y0 = ty * kColorTileDimInBlocks;
867
0
          const size_t x0 = tx * kColorTileDimInBlocks;
868
0
          const size_t y1 = std::min(frame_dim.ysize_blocks,
869
0
                                     (ty + 1) * kColorTileDimInBlocks);
870
0
          const size_t x1 = std::min(frame_dim.xsize_blocks,
871
0
                                     (tx + 1) * kColorTileDimInBlocks);
872
0
          int32_t d_num_zeros[257] = {0};
873
          // TODO(veluca): this needs SIMD + fixed point adaptation, and/or
874
          // conversion to the new CfL algorithm.
875
0
          for (size_t y = y0; y < y1; ++y) {
876
0
            const int16_t* JXL_RESTRICT row_m = jpeg_row(1, y);
877
0
            const int16_t* JXL_RESTRICT row_s = jpeg_row(c, y);
878
0
            for (size_t x = x0; x < x1; ++x) {
879
0
              for (size_t coeffpos = 1; coeffpos < kDCTBlockSize; coeffpos++) {
880
0
                const float scaled_m = row_m[x * kDCTBlockSize + coeffpos] *
881
0
                                       scaled_qtable[64 * c + coeffpos] *
882
0
                                       (1.0f / (1 << kCFLFixedPointPrecision));
883
0
                const float scaled_s =
884
0
                    kScale * row_s[x * kDCTBlockSize + coeffpos] +
885
0
                    (kOffset - kBase * kScale) * scaled_m;
886
0
                if (std::abs(scaled_m) > 1e-8f) {
887
0
                  float from;
888
0
                  float to;
889
0
                  if (scaled_m > 0) {
890
0
                    from = (scaled_s - kZeroThresh) / scaled_m;
891
0
                    to = (scaled_s + kZeroThresh) / scaled_m;
892
0
                  } else {
893
0
                    from = (scaled_s + kZeroThresh) / scaled_m;
894
0
                    to = (scaled_s - kZeroThresh) / scaled_m;
895
0
                  }
896
0
                  if (from < 0.0f) {
897
0
                    from = 0.0f;
898
0
                  }
899
0
                  if (to > 255.0f) {
900
0
                    to = 255.0f;
901
0
                  }
902
                  // Instead of clamping the both values
903
                  // we just check that range is sane.
904
0
                  if (from <= to) {
905
0
                    d_num_zeros[static_cast<int>(std::ceil(from))]++;
906
0
                    d_num_zeros[static_cast<int>(std::floor(to + 1))]--;
907
0
                  }
908
0
                }
909
0
              }
910
0
            }
911
0
          }
912
0
          int best = 0;
913
0
          int32_t best_sum = 0;
914
0
          FindIndexOfSumMaximum<256>(d_num_zeros, &best, &best_sum);
915
0
          int32_t offset_sum = 0;
916
0
          for (int i = 0; i < 256; ++i) {
917
0
            if (i <= kOffset) {
918
0
              offset_sum += d_num_zeros[i];
919
0
            }
920
0
          }
921
0
          row_out[tx] = 0;
922
0
          if (best_sum > offset_sum + 1) {
923
0
            row_out[tx] = best - kOffset;
924
0
          }
925
0
        }
926
0
        return true;
927
0
      };
928
929
0
      JXL_RETURN_IF_ERROR(RunOnPool(pool, 0, map->ysize(), ThreadPool::NoInit,
930
0
                                    process_row, "FindCorrelation"));
931
0
    }
932
0
  }
933
934
0
  JXL_ASSIGN_OR_RETURN(
935
0
      Image3F dc, Image3F::Create(memory_manager, xsize_blocks, ysize_blocks));
936
0
  if (!frame_header.chroma_subsampling.Is444()) {
937
0
    ZeroFillImage(&dc);
938
0
    for (auto& coeff : enc_state->coeffs) {
939
0
      coeff->ZeroFill();
940
0
    }
941
0
  }
942
  // JPEG DC is from -1024 to 1023.
943
0
  std::vector<size_t> dc_counts[3] = {};
944
0
  dc_counts[0].resize(2048);
945
0
  dc_counts[1].resize(2048);
946
0
  dc_counts[2].resize(2048);
947
0
  size_t total_dc[3] = {};
948
0
  for (size_t c : {1, 0, 2}) {
949
0
    if (jpeg_data.components.size() == 1 && c != 1) {
950
0
      for (auto& coeff : enc_state->coeffs) {
951
0
        coeff->ZeroFillPlane(c);
952
0
      }
953
0
      ZeroFillImage(&dc.Plane(c));
954
      // Ensure no division by 0.
955
0
      dc_counts[c][1024] = 1;
956
0
      total_dc[c] = 1;
957
0
      continue;
958
0
    }
959
0
    size_t hshift = frame_header.chroma_subsampling.HShift(c);
960
0
    size_t vshift = frame_header.chroma_subsampling.VShift(c);
961
0
    ImageSB& map = (c == 0 ? shared.cmap.ytox_map : shared.cmap.ytob_map);
962
0
    for (size_t group_index = 0; group_index < frame_dim.num_groups;
963
0
         group_index++) {
964
0
      const size_t gx = group_index % frame_dim.xsize_groups;
965
0
      const size_t gy = group_index / frame_dim.xsize_groups;
966
0
      int32_t* coeffs[kMaxNumPasses];
967
0
      for (size_t i = 0; i < enc_state->coeffs.size(); i++) {
968
0
        coeffs[i] = enc_state->coeffs[i]->PlaneRow(c, group_index, 0).ptr32;
969
0
      }
970
0
      int32_t block[64];
971
0
      for (size_t by = gy * kGroupDimInBlocks;
972
0
           by < ysize_blocks && by < (gy + 1) * kGroupDimInBlocks; ++by) {
973
0
        if ((by >> vshift) << vshift != by) continue;
974
0
        const int16_t* JXL_RESTRICT inputjpeg = jpeg_row(c, by >> vshift);
975
0
        const int16_t* JXL_RESTRICT inputjpegY = jpeg_row(1, by);
976
0
        float* JXL_RESTRICT fdc = dc.PlaneRow(c, by >> vshift);
977
0
        const int8_t* JXL_RESTRICT cm =
978
0
            map.ConstRow(by / kColorTileDimInBlocks);
979
0
        for (size_t bx = gx * kGroupDimInBlocks;
980
0
             bx < xsize_blocks && bx < (gx + 1) * kGroupDimInBlocks; ++bx) {
981
0
          if ((bx >> hshift) << hshift != bx) continue;
982
0
          size_t base = (bx >> hshift) * kDCTBlockSize;
983
0
          int idc;
984
0
          if (DCzero) {
985
0
            idc = inputjpeg[base];
986
0
          } else {
987
0
            idc = inputjpeg[base] + 1024 / qt_dc[c];
988
0
          }
989
0
          dc_counts[c][std::min(static_cast<uint32_t>(idc + 1024),
990
0
                                static_cast<uint32_t>(2047))]++;
991
0
          total_dc[c]++;
992
0
          fdc[bx >> hshift] = idc * dcquantization_r[c];
993
0
          if (c == 1 || !enc_state->cparams.force_cfl_jpeg_recompression ||
994
0
              !frame_header.chroma_subsampling.Is444()) {
995
0
            for (size_t y = 0; y < 8; y++) {
996
0
              for (size_t x = 0; x < 8; x++) {
997
0
                block[y * 8 + x] = inputjpeg[base + x * 8 + y];
998
0
              }
999
0
            }
1000
0
          } else {
1001
0
            const int32_t scale =
1002
0
                ColorCorrelation::RatioJPEG(cm[bx / kColorTileDimInBlocks]);
1003
1004
0
            for (size_t y = 0; y < 8; y++) {
1005
0
              for (size_t x = 0; x < 8; x++) {
1006
0
                int Y = inputjpegY[kDCTBlockSize * bx + x * 8 + y];
1007
0
                int QChroma = inputjpeg[kDCTBlockSize * bx + x * 8 + y];
1008
                // Fixed-point multiply of CfL scale with quant table ratio
1009
                // first, and Y value second.
1010
0
                int coeff_scale = (scale * scaled_qtable[64 * c + y * 8 + x] +
1011
0
                                   (1 << (kCFLFixedPointPrecision - 1))) >>
1012
0
                                  kCFLFixedPointPrecision;
1013
0
                int cfl_factor =
1014
0
                    (Y * coeff_scale + (1 << (kCFLFixedPointPrecision - 1))) >>
1015
0
                    kCFLFixedPointPrecision;
1016
0
                int QCR = QChroma - cfl_factor;
1017
0
                block[y * 8 + x] = QCR;
1018
0
              }
1019
0
            }
1020
0
          }
1021
0
          enc_state->progressive_splitter.SplitACCoefficients(
1022
0
              block, AcStrategy::FromRawStrategy(AcStrategyType::DCT), bx, by,
1023
0
              coeffs);
1024
0
          for (size_t i = 0; i < enc_state->coeffs.size(); i++) {
1025
0
            coeffs[i] += kDCTBlockSize;
1026
0
          }
1027
0
        }
1028
0
      }
1029
0
    }
1030
0
  }
1031
1032
0
  auto& dct = enc_state->shared.block_ctx_map.dc_thresholds;
1033
0
  auto& num_dc_ctxs = enc_state->shared.block_ctx_map.num_dc_ctxs;
1034
0
  num_dc_ctxs = 1;
1035
0
  for (size_t i = 0; i < 3; i++) {
1036
0
    dct[i].clear();
1037
0
    int num_thresholds = (CeilLog2Nonzero(total_dc[i]) - 12) / 2;
1038
    // up to 3 buckets per channel:
1039
    // dark/medium/bright, yellow/unsat/blue, green/unsat/red
1040
0
    num_thresholds = jxl::Clamp1(num_thresholds, 0, 2);
1041
0
    size_t cumsum = 0;
1042
0
    size_t cut = total_dc[i] / (num_thresholds + 1);
1043
0
    for (int j = 0; j < 2048; j++) {
1044
0
      cumsum += dc_counts[i][j];
1045
0
      if (cumsum > cut) {
1046
0
        dct[i].push_back(j - 1025);
1047
0
        cut = total_dc[i] * (dct[i].size() + 1) / (num_thresholds + 1);
1048
0
      }
1049
0
    }
1050
0
    num_dc_ctxs *= dct[i].size() + 1;
1051
0
  }
1052
1053
0
  auto& ctx_map = enc_state->shared.block_ctx_map.ctx_map;
1054
0
  ctx_map.clear();
1055
0
  ctx_map.resize(3 * kNumOrders * num_dc_ctxs, 0);
1056
1057
0
  int lbuckets = (dct[1].size() + 1);
1058
0
  for (size_t i = 0; i < num_dc_ctxs; i++) {
1059
    // up to 9 contexts for luma
1060
0
    ctx_map[i] = i / lbuckets;
1061
    // up to 3 contexts for chroma
1062
0
    ctx_map[kNumOrders * num_dc_ctxs + i] =
1063
0
        ctx_map[2 * kNumOrders * num_dc_ctxs + i] =
1064
0
            num_dc_ctxs / lbuckets + (i % lbuckets);
1065
0
  }
1066
0
  enc_state->shared.block_ctx_map.num_ctxs =
1067
0
      *std::max_element(ctx_map.begin(), ctx_map.end()) + 1;
1068
1069
  // disable DC frame for now
1070
0
  auto compute_dc_coeffs = [&](const uint32_t group_index,
1071
0
                               size_t /* thread */) -> Status {
1072
0
    const Rect r = enc_state->shared.frame_dim.DCGroupRect(group_index);
1073
0
    JXL_RETURN_IF_ERROR(enc_modular->AddVarDCTDC(frame_header, dc, r,
1074
0
                                                 group_index,
1075
0
                                                 /*nl_dc=*/false, enc_state,
1076
0
                                                 /*jpeg_transcode=*/true));
1077
0
    JXL_RETURN_IF_ERROR(enc_modular->AddACMetadata(
1078
0
        r, group_index, /*jpeg_transcode=*/true, enc_state));
1079
0
    return true;
1080
0
  };
1081
0
  JXL_RETURN_IF_ERROR(RunOnPool(pool, 0, shared.frame_dim.num_dc_groups,
1082
0
                                ThreadPool::NoInit, compute_dc_coeffs,
1083
0
                                "Compute DC coeffs"));
1084
1085
0
  return true;
1086
0
}
1087
1088
Status ComputeVarDCTEncodingData(const FrameHeader& frame_header,
1089
                                 const Image3F* linear,
1090
                                 Image3F* JXL_RESTRICT opsin, const Rect& rect,
1091
                                 const JxlCmsInterface& cms, ThreadPool* pool,
1092
                                 ModularFrameEncoder* enc_modular,
1093
                                 PassesEncoderState* enc_state,
1094
186
                                 AuxOut* aux_out) {
1095
186
  JXL_ENSURE((rect.xsize() % kBlockDim) == 0 &&
1096
186
             (rect.ysize() % kBlockDim) == 0);
1097
186
  JxlMemoryManager* memory_manager = enc_state->memory_manager();
1098
  // Save pre-Gaborish opsin for AR control field heuristics computation.
1099
186
  Image3F orig_opsin;
1100
186
  JXL_ASSIGN_OR_RETURN(
1101
186
      orig_opsin, Image3F::Create(memory_manager, rect.xsize(), rect.ysize()));
1102
186
  JXL_RETURN_IF_ERROR(CopyImageTo(rect, *opsin, Rect(orig_opsin), &orig_opsin));
1103
186
  JXL_RETURN_IF_ERROR(orig_opsin.ShrinkTo(enc_state->shared.frame_dim.xsize,
1104
186
                                          enc_state->shared.frame_dim.ysize));
1105
1106
186
  JXL_RETURN_IF_ERROR(LossyFrameHeuristics(frame_header, enc_state, enc_modular,
1107
186
                                           linear, opsin, rect, cms, pool,
1108
186
                                           aux_out));
1109
1110
186
  JXL_RETURN_IF_ERROR(InitializePassesEncoder(
1111
186
      frame_header, *opsin, rect, cms, pool, enc_state, enc_modular, aux_out));
1112
1113
186
  JXL_RETURN_IF_ERROR(
1114
186
      ComputeARHeuristics(frame_header, enc_state, orig_opsin, rect, pool));
1115
1116
186
  JXL_RETURN_IF_ERROR(ComputeACMetadata(pool, enc_state, enc_modular));
1117
1118
186
  return true;
1119
186
}
1120
1121
Status ComputeAllCoeffOrders(PassesEncoderState& enc_state,
1122
186
                             const FrameDimensions& frame_dim) {
1123
186
  auto used_orders_info = ComputeUsedOrders(
1124
186
      enc_state.cparams.speed_tier, enc_state.shared.ac_strategy,
1125
186
      Rect(enc_state.shared.raw_quant_field));
1126
186
  enc_state.used_orders.resize(enc_state.progressive_splitter.GetNumPasses());
1127
372
  for (size_t i = 0; i < enc_state.progressive_splitter.GetNumPasses(); i++) {
1128
186
    JXL_RETURN_IF_ERROR(ComputeCoeffOrder(
1129
186
        enc_state.cparams.speed_tier, *enc_state.coeffs[i],
1130
186
        enc_state.shared.ac_strategy, frame_dim, enc_state.used_orders[i],
1131
186
        enc_state.used_acs, used_orders_info.first, used_orders_info.second,
1132
186
        &enc_state.shared.coeff_orders[i * enc_state.shared.coeff_order_size]));
1133
186
  }
1134
186
  enc_state.used_acs |= used_orders_info.first;
1135
186
  return true;
1136
186
}
1137
1138
// Working area for TokenizeCoefficients (per-group!)
1139
struct EncCache {
1140
  // Allocates memory when first called.
1141
595
  Status InitOnce(JxlMemoryManager* memory_manager) {
1142
595
    if (num_nzeroes.xsize() == 0) {
1143
186
      JXL_ASSIGN_OR_RETURN(num_nzeroes,
1144
186
                           Image3I::Create(memory_manager, kGroupDimInBlocks,
1145
186
                                           kGroupDimInBlocks));
1146
186
    }
1147
595
    return true;
1148
595
  }
1149
  // TokenizeCoefficients
1150
  Image3I num_nzeroes;
1151
};
1152
1153
Status TokenizeAllCoefficients(const FrameHeader& frame_header,
1154
                               ThreadPool* pool,
1155
186
                               PassesEncoderState* enc_state) {
1156
186
  PassesSharedState& shared = enc_state->shared;
1157
186
  std::vector<EncCache> group_caches;
1158
186
  JxlMemoryManager* memory_manager = enc_state->memory_manager();
1159
186
  const auto tokenize_group_init = [&](const size_t num_threads) -> Status {
1160
186
    group_caches.resize(num_threads);
1161
186
    return true;
1162
186
  };
1163
186
  const auto tokenize_group = [&](const uint32_t group_index,
1164
595
                                  const size_t thread) -> Status {
1165
    // Tokenize coefficients.
1166
595
    const Rect rect = shared.frame_dim.BlockGroupRect(group_index);
1167
1.19k
    for (size_t idx_pass = 0; idx_pass < enc_state->passes.size(); idx_pass++) {
1168
595
      JXL_ENSURE(enc_state->coeffs[idx_pass]->Type() == ACType::k32);
1169
595
      const int32_t* JXL_RESTRICT ac_rows[3] = {
1170
595
          enc_state->coeffs[idx_pass]->PlaneRow(0, group_index, 0).ptr32,
1171
595
          enc_state->coeffs[idx_pass]->PlaneRow(1, group_index, 0).ptr32,
1172
595
          enc_state->coeffs[idx_pass]->PlaneRow(2, group_index, 0).ptr32,
1173
595
      };
1174
      // Ensure group cache is initialized.
1175
595
      JXL_RETURN_IF_ERROR(group_caches[thread].InitOnce(memory_manager));
1176
595
      JXL_RETURN_IF_ERROR(TokenizeCoefficients(
1177
595
          &shared.coeff_orders[idx_pass * shared.coeff_order_size], rect,
1178
595
          ac_rows, shared.ac_strategy, frame_header.chroma_subsampling,
1179
595
          &group_caches[thread].num_nzeroes,
1180
595
          &enc_state->passes[idx_pass].ac_tokens[group_index], shared.quant_dc,
1181
595
          shared.raw_quant_field, shared.block_ctx_map));
1182
595
    }
1183
595
    return true;
1184
595
  };
1185
186
  JXL_RETURN_IF_ERROR(RunOnPool(pool, 0, shared.frame_dim.num_groups,
1186
186
                                tokenize_group_init, tokenize_group,
1187
186
                                "TokenizeGroup"));
1188
186
  return true;
1189
186
}
1190
1191
Status EncodeGlobalDCInfo(const PassesSharedState& shared, BitWriter* writer,
1192
186
                          AuxOut* aux_out) {
1193
  // Encode quantizer DC and global scale.
1194
186
  QuantizerParams params = shared.quantizer.GetParams();
1195
186
  JXL_RETURN_IF_ERROR(
1196
186
      WriteQuantizerParams(params, writer, LayerType::Quant, aux_out));
1197
186
  JXL_RETURN_IF_ERROR(EncodeBlockCtxMap(shared.block_ctx_map, writer, aux_out));
1198
186
  JXL_RETURN_IF_ERROR(ColorCorrelationEncodeDC(shared.cmap.base(), writer,
1199
186
                                               LayerType::Dc, aux_out));
1200
186
  return true;
1201
186
}
1202
1203
// In streaming mode, this function only performs the histogram clustering and
1204
// saves the histogram bitstreams in enc_state, the actual AC global bitstream
1205
// is written in OutputAcGlobal() function after all the groups are processed.
1206
Status EncodeGlobalACInfo(PassesEncoderState* enc_state, BitWriter* writer,
1207
186
                          ModularFrameEncoder* enc_modular, AuxOut* aux_out) {
1208
186
  PassesSharedState& shared = enc_state->shared;
1209
186
  JxlMemoryManager* memory_manager = enc_state->memory_manager();
1210
186
  JXL_RETURN_IF_ERROR(DequantMatricesEncode(memory_manager, shared.matrices,
1211
186
                                            writer, LayerType::Quant, aux_out,
1212
186
                                            enc_modular));
1213
186
  size_t num_histo_bits = CeilLog2Nonzero(shared.frame_dim.num_groups);
1214
186
  if (!enc_state->streaming_mode && num_histo_bits != 0) {
1215
141
    JXL_RETURN_IF_ERROR(
1216
141
        writer->WithMaxBits(num_histo_bits, LayerType::Ac, aux_out, [&] {
1217
141
          writer->Write(num_histo_bits, shared.num_histograms - 1);
1218
141
          return true;
1219
141
        }));
1220
141
  }
1221
1222
372
  for (size_t i = 0; i < enc_state->progressive_splitter.GetNumPasses(); i++) {
1223
    // Encode coefficient orders.
1224
186
    if (!enc_state->streaming_mode) {
1225
186
      size_t order_bits = 0;
1226
186
      JXL_RETURN_IF_ERROR(U32Coder::CanEncode(
1227
186
          kOrderEnc, enc_state->used_orders[i], &order_bits));
1228
186
      JXL_RETURN_IF_ERROR(
1229
186
          writer->WithMaxBits(order_bits, LayerType::Order, aux_out, [&] {
1230
186
            return U32Coder::Write(kOrderEnc, enc_state->used_orders[i],
1231
186
                                   writer);
1232
186
          }));
1233
186
      JXL_RETURN_IF_ERROR(
1234
186
          EncodeCoeffOrders(enc_state->used_orders[i],
1235
186
                            &shared.coeff_orders[i * shared.coeff_order_size],
1236
186
                            writer, LayerType::Order, aux_out));
1237
186
    }
1238
1239
    // Encode histograms.
1240
186
    HistogramParams hist_params(enc_state->cparams.speed_tier,
1241
186
                                shared.block_ctx_map.NumACContexts());
1242
186
    if (enc_state->cparams.speed_tier > SpeedTier::kTortoise) {
1243
186
      hist_params.lz77_method = HistogramParams::LZ77Method::kNone;
1244
186
    }
1245
186
    if (enc_state->cparams.decoding_speed_tier >= 1) {
1246
0
      hist_params.max_histograms = 6;
1247
0
    }
1248
186
    size_t num_histogram_groups = shared.num_histograms;
1249
186
    if (enc_state->streaming_mode) {
1250
0
      size_t prev_num_histograms =
1251
0
          enc_state->passes[i].codes.encoding_info.size();
1252
0
      if (enc_state->initialize_global_state) {
1253
0
        prev_num_histograms += kNumFixedHistograms;
1254
0
        hist_params.add_fixed_histograms = true;
1255
0
      }
1256
0
      size_t remaining_histograms = kClustersLimit - prev_num_histograms;
1257
      // Heuristic to assign budget of new histograms to DC groups.
1258
      // TODO(szabadka) Tune this together with the DC group ordering.
1259
0
      size_t max_histograms = remaining_histograms < 20
1260
0
                                  ? std::min<size_t>(remaining_histograms, 4)
1261
0
                                  : remaining_histograms / 4;
1262
0
      hist_params.max_histograms =
1263
0
          std::min(max_histograms, hist_params.max_histograms);
1264
0
      num_histogram_groups = 1;
1265
0
    }
1266
186
    hist_params.streaming_mode = enc_state->streaming_mode;
1267
186
    hist_params.initialize_global_state = enc_state->initialize_global_state;
1268
186
    JXL_ASSIGN_OR_RETURN(
1269
186
        size_t cost,
1270
186
        BuildAndEncodeHistograms(
1271
186
            memory_manager, hist_params,
1272
186
            num_histogram_groups * shared.block_ctx_map.NumACContexts(),
1273
186
            enc_state->passes[i].ac_tokens, &enc_state->passes[i].codes, writer,
1274
186
            LayerType::Ac, aux_out));
1275
186
    (void)cost;
1276
186
  }
1277
1278
186
  return true;
1279
186
}
1280
Status EncodeGroups(const FrameHeader& frame_header,
1281
                    PassesEncoderState* enc_state,
1282
                    ModularFrameEncoder* enc_modular, ThreadPool* pool,
1283
                    std::vector<std::unique_ptr<BitWriter>>* group_codes,
1284
283
                    AuxOut* aux_out) {
1285
283
  const PassesSharedState& shared = enc_state->shared;
1286
283
  JxlMemoryManager* memory_manager = shared.memory_manager;
1287
283
  const FrameDimensions& frame_dim = shared.frame_dim;
1288
283
  const size_t num_groups = frame_dim.num_groups;
1289
283
  const size_t num_passes = enc_state->progressive_splitter.GetNumPasses();
1290
283
  const size_t global_ac_index = frame_dim.num_dc_groups + 1;
1291
283
  const bool is_small_image =
1292
283
      !enc_state->streaming_mode && num_groups == 1 && num_passes == 1;
1293
283
  const size_t num_toc_entries =
1294
283
      is_small_image ? 1
1295
283
                     : AcGroupIndex(0, 0, num_groups, frame_dim.num_dc_groups) +
1296
141
                           num_groups * num_passes;
1297
283
  JXL_ENSURE(group_codes->empty());
1298
283
  group_codes->reserve(num_toc_entries);
1299
1.39k
  for (size_t i = 0; i < num_toc_entries; ++i) {
1300
1.11k
    group_codes->emplace_back(jxl::make_unique<BitWriter>(memory_manager));
1301
1.11k
  }
1302
1303
2.88k
  const auto get_output = [&](const size_t index) -> BitWriter* {
1304
2.88k
    return (*group_codes)[is_small_image ? 0 : index].get();
1305
2.88k
  };
1306
1.28k
  auto ac_group_code = [&](size_t pass, size_t group) {
1307
1.28k
    return get_output(AcGroupIndex(pass, group, frame_dim.num_groups,
1308
1.28k
                                   frame_dim.num_dc_groups));
1309
1.28k
  };
1310
1311
283
  if (enc_state->initialize_global_state) {
1312
283
    if (frame_header.flags & FrameHeader::kPatches) {
1313
97
      JXL_RETURN_IF_ERROR(PatchDictionaryEncoder::Encode(
1314
97
          shared.image_features.patches, get_output(0), LayerType::Dictionary,
1315
97
          aux_out));
1316
97
    }
1317
283
    if (frame_header.flags & FrameHeader::kSplines) {
1318
0
      JXL_RETURN_IF_ERROR(EncodeSplines(shared.image_features.splines,
1319
0
                                        get_output(0), LayerType::Splines,
1320
0
                                        HistogramParams(), aux_out));
1321
0
    }
1322
283
    if (frame_header.flags & FrameHeader::kNoise) {
1323
0
      JXL_RETURN_IF_ERROR(EncodeNoise(shared.image_features.noise_params,
1324
0
                                      get_output(0), LayerType::Noise,
1325
0
                                      aux_out));
1326
0
    }
1327
1328
283
    JXL_RETURN_IF_ERROR(DequantMatricesEncodeDC(shared.matrices, get_output(0),
1329
283
                                                LayerType::Quant, aux_out));
1330
283
    if (frame_header.encoding == FrameEncoding::kVarDCT) {
1331
186
      JXL_RETURN_IF_ERROR(EncodeGlobalDCInfo(shared, get_output(0), aux_out));
1332
186
    }
1333
283
    JXL_RETURN_IF_ERROR(enc_modular->EncodeGlobalInfo(enc_state->streaming_mode,
1334
283
                                                      get_output(0), aux_out));
1335
283
    JXL_RETURN_IF_ERROR(enc_modular->EncodeStream(get_output(0), aux_out,
1336
283
                                                  LayerType::ModularGlobal,
1337
283
                                                  ModularStreamId::Global()));
1338
283
  }
1339
1340
283
  std::vector<std::unique_ptr<AuxOut>> aux_outs;
1341
283
  auto resize_aux_outs = [&aux_outs,
1342
849
                          aux_out](const size_t num_threads) -> Status {
1343
849
    if (aux_out == nullptr) {
1344
849
      aux_outs.resize(num_threads);
1345
849
    } else {
1346
0
      while (aux_outs.size() > num_threads) {
1347
0
        aux_out->Assimilate(*aux_outs.back());
1348
0
        aux_outs.pop_back();
1349
0
      }
1350
0
      while (num_threads > aux_outs.size()) {
1351
0
        aux_outs.emplace_back(jxl::make_unique<AuxOut>());
1352
0
      }
1353
0
    }
1354
849
    return true;
1355
849
  };
1356
1357
283
  const auto process_dc_group = [&](const uint32_t group_index,
1358
283
                                    const size_t thread) -> Status {
1359
283
    AuxOut* my_aux_out = aux_outs[thread].get();
1360
283
    uint32_t input_index = enc_state->streaming_mode ? 0 : group_index;
1361
283
    BitWriter* output = get_output(input_index + 1);
1362
283
    if (frame_header.encoding == FrameEncoding::kVarDCT &&
1363
283
        !(frame_header.flags & FrameHeader::kUseDcFrame)) {
1364
186
      JXL_RETURN_IF_ERROR(
1365
186
          output->WithMaxBits(2, LayerType::Dc, my_aux_out, [&] {
1366
186
            output->Write(2, enc_modular->extra_dc_precision[group_index]);
1367
186
            return true;
1368
186
          }));
1369
186
      JXL_RETURN_IF_ERROR(
1370
186
          enc_modular->EncodeStream(output, my_aux_out, LayerType::Dc,
1371
186
                                    ModularStreamId::VarDCTDC(group_index)));
1372
186
    }
1373
283
    JXL_RETURN_IF_ERROR(
1374
283
        enc_modular->EncodeStream(output, my_aux_out, LayerType::ModularDcGroup,
1375
283
                                  ModularStreamId::ModularDC(group_index)));
1376
283
    if (frame_header.encoding == FrameEncoding::kVarDCT) {
1377
186
      const Rect& rect = enc_state->shared.frame_dim.DCGroupRect(input_index);
1378
186
      size_t nb_bits = CeilLog2Nonzero(rect.xsize() * rect.ysize());
1379
186
      if (nb_bits != 0) {
1380
168
        JXL_RETURN_IF_ERROR(output->WithMaxBits(
1381
168
            nb_bits, LayerType::ControlFields, my_aux_out, [&] {
1382
168
              output->Write(nb_bits,
1383
168
                            enc_modular->ac_metadata_size[group_index] - 1);
1384
168
              return true;
1385
168
            }));
1386
168
      }
1387
186
      JXL_RETURN_IF_ERROR(enc_modular->EncodeStream(
1388
186
          output, my_aux_out, LayerType::ControlFields,
1389
186
          ModularStreamId::ACMetadata(group_index)));
1390
186
    }
1391
283
    return true;
1392
283
  };
1393
283
  if (enc_state->streaming_mode) {
1394
0
    JXL_ENSURE(frame_dim.num_dc_groups == 1);
1395
0
    JXL_RETURN_IF_ERROR(resize_aux_outs(1));
1396
0
    JXL_RETURN_IF_ERROR(process_dc_group(enc_state->dc_group_index, 0));
1397
283
  } else {
1398
283
    JXL_RETURN_IF_ERROR(RunOnPool(pool, 0, frame_dim.num_dc_groups,
1399
283
                                  resize_aux_outs, process_dc_group,
1400
283
                                  "EncodeDCGroup"));
1401
283
  }
1402
283
  if (frame_header.encoding == FrameEncoding::kVarDCT) {
1403
186
    JXL_RETURN_IF_ERROR(EncodeGlobalACInfo(
1404
186
        enc_state, get_output(global_ac_index), enc_modular, aux_out));
1405
186
  }
1406
1407
283
  const auto process_group = [&](const uint32_t group_index,
1408
692
                                 const size_t thread) -> Status {
1409
692
    AuxOut* my_aux_out = aux_outs[thread].get();
1410
1411
692
    size_t ac_group_id =
1412
692
        enc_state->streaming_mode
1413
692
            ? enc_modular->ComputeStreamingAbsoluteAcGroupId(
1414
0
                  enc_state->dc_group_index, group_index, shared.frame_dim)
1415
692
            : group_index;
1416
1417
1.38k
    for (size_t i = 0; i < num_passes; i++) {
1418
692
      JXL_DEBUG_V(2, "Encoding AC group %u [abs %" PRIuS "] pass %" PRIuS,
1419
692
                  group_index, ac_group_id, i);
1420
692
      if (frame_header.encoding == FrameEncoding::kVarDCT) {
1421
595
        JXL_RETURN_IF_ERROR(EncodeGroupTokenizedCoefficients(
1422
595
            group_index, i, enc_state->histogram_idx[group_index], *enc_state,
1423
595
            ac_group_code(i, group_index), my_aux_out));
1424
595
      }
1425
      // Write all modular encoded data (color?, alpha, depth, extra channels)
1426
692
      JXL_RETURN_IF_ERROR(enc_modular->EncodeStream(
1427
692
          ac_group_code(i, group_index), my_aux_out, LayerType::ModularAcGroup,
1428
692
          ModularStreamId::ModularAC(ac_group_id, i)));
1429
692
      JXL_DEBUG_V(2,
1430
692
                  "AC group %u [abs %" PRIuS "] pass %" PRIuS
1431
692
                  " encoded size is %" PRIuS " bits",
1432
692
                  group_index, ac_group_id, i,
1433
692
                  ac_group_code(i, group_index)->BitsWritten());
1434
692
    }
1435
692
    return true;
1436
692
  };
1437
283
  JXL_RETURN_IF_ERROR(RunOnPool(pool, 0, num_groups, resize_aux_outs,
1438
283
                                process_group, "EncodeGroupCoefficients"));
1439
  // Resizing aux_outs to 0 also Assimilates the array.
1440
283
  static_cast<void>(resize_aux_outs(0));
1441
1442
1.11k
  for (std::unique_ptr<BitWriter>& bw : *group_codes) {
1443
1.11k
    JXL_RETURN_IF_ERROR(bw->WithMaxBits(8, LayerType::Ac, aux_out, [&] {
1444
1.11k
      bw->ZeroPadToByte();  // end of group.
1445
1.11k
      return true;
1446
1.11k
    }));
1447
1.11k
  }
1448
283
  return true;
1449
283
}
1450
1451
Status ComputeEncodingData(
1452
    const CompressParams& cparams, const FrameInfo& frame_info,
1453
    const CodecMetadata* metadata, JxlEncoderChunkedFrameAdapter& frame_data,
1454
    const jpeg::JPEGData* jpeg_data, size_t x0, size_t y0, size_t xsize,
1455
    size_t ysize, const JxlCmsInterface& cms, ThreadPool* pool,
1456
    FrameHeader& mutable_frame_header, ModularFrameEncoder& enc_modular,
1457
    PassesEncoderState& enc_state,
1458
283
    std::vector<std::unique_ptr<BitWriter>>* group_codes, AuxOut* aux_out) {
1459
283
  JXL_ENSURE(x0 + xsize <= frame_data.xsize);
1460
283
  JXL_ENSURE(y0 + ysize <= frame_data.ysize);
1461
283
  JxlMemoryManager* memory_manager = enc_state.memory_manager();
1462
283
  const FrameHeader& frame_header = mutable_frame_header;
1463
283
  PassesSharedState& shared = enc_state.shared;
1464
283
  shared.metadata = metadata;
1465
283
  if (enc_state.streaming_mode) {
1466
0
    shared.frame_dim.Set(
1467
0
        xsize, ysize, frame_header.group_size_shift,
1468
0
        /*max_hshift=*/0, /*max_vshift=*/0,
1469
0
        mutable_frame_header.encoding == FrameEncoding::kModular,
1470
0
        /*upsampling=*/1);
1471
283
  } else {
1472
283
    shared.frame_dim = frame_header.ToFrameDimensions();
1473
283
  }
1474
1475
283
  shared.image_features.patches.SetShared(&shared.reference_frames);
1476
283
  const FrameDimensions& frame_dim = shared.frame_dim;
1477
283
  JXL_ASSIGN_OR_RETURN(
1478
283
      shared.ac_strategy,
1479
283
      AcStrategyImage::Create(memory_manager, frame_dim.xsize_blocks,
1480
283
                              frame_dim.ysize_blocks));
1481
283
  JXL_ASSIGN_OR_RETURN(shared.raw_quant_field,
1482
283
                       ImageI::Create(memory_manager, frame_dim.xsize_blocks,
1483
283
                                      frame_dim.ysize_blocks));
1484
283
  JXL_ASSIGN_OR_RETURN(shared.epf_sharpness,
1485
283
                       ImageB::Create(memory_manager, frame_dim.xsize_blocks,
1486
283
                                      frame_dim.ysize_blocks));
1487
283
  JXL_ASSIGN_OR_RETURN(
1488
283
      shared.cmap, ColorCorrelationMap::Create(memory_manager, frame_dim.xsize,
1489
283
                                               frame_dim.ysize));
1490
283
  shared.coeff_order_size = kCoeffOrderMaxSize;
1491
283
  if (frame_header.encoding == FrameEncoding::kVarDCT) {
1492
186
    shared.coeff_orders.resize(frame_header.passes.num_passes *
1493
186
                               kCoeffOrderMaxSize);
1494
186
  }
1495
1496
283
  JXL_ASSIGN_OR_RETURN(shared.quant_dc,
1497
283
                       ImageB::Create(memory_manager, frame_dim.xsize_blocks,
1498
283
                                      frame_dim.ysize_blocks));
1499
283
  JXL_ASSIGN_OR_RETURN(shared.dc_storage,
1500
283
                       Image3F::Create(memory_manager, frame_dim.xsize_blocks,
1501
283
                                       frame_dim.ysize_blocks));
1502
283
  shared.dc = &shared.dc_storage;
1503
1504
283
  const size_t num_extra_channels = metadata->m.num_extra_channels;
1505
283
  const ExtraChannelInfo* alpha_eci = metadata->m.Find(ExtraChannel::kAlpha);
1506
283
  const ExtraChannelInfo* black_eci = metadata->m.Find(ExtraChannel::kBlack);
1507
283
  const size_t alpha_idx = alpha_eci - metadata->m.extra_channel_info.data();
1508
283
  const size_t black_idx = black_eci - metadata->m.extra_channel_info.data();
1509
283
  const ColorEncoding c_enc = metadata->m.color_encoding;
1510
1511
  // Make the image patch bigger than the currently processed group in streaming
1512
  // mode so that we can take into account border pixels around the group when
1513
  // computing inverse Gaborish and adaptive quantization map.
1514
283
  int max_border = enc_state.streaming_mode ? kBlockDim : 0;
1515
283
  Rect frame_rect(0, 0, frame_data.xsize, frame_data.ysize);
1516
283
  Rect frame_area_rect = Rect(x0, y0, xsize, ysize);
1517
283
  Rect patch_rect = frame_area_rect.Extend(max_border, frame_rect);
1518
283
  JXL_ENSURE(patch_rect.IsInside(frame_rect));
1519
1520
  // Allocating a large enough image avoids a copy when padding.
1521
566
  JXL_ASSIGN_OR_RETURN(
1522
566
      Image3F color,
1523
566
      Image3F::Create(memory_manager, RoundUpToBlockDim(patch_rect.xsize()),
1524
566
                      RoundUpToBlockDim(patch_rect.ysize())));
1525
566
  JXL_RETURN_IF_ERROR(color.ShrinkTo(patch_rect.xsize(), patch_rect.ysize()));
1526
283
  std::vector<ImageF> extra_channels(num_extra_channels);
1527
283
  for (auto& extra_channel : extra_channels) {
1528
14
    JXL_ASSIGN_OR_RETURN(
1529
14
        extra_channel,
1530
14
        ImageF::Create(memory_manager, patch_rect.xsize(), patch_rect.ysize()));
1531
14
  }
1532
283
  ImageF* alpha = alpha_eci ? &extra_channels[alpha_idx] : nullptr;
1533
283
  ImageF* black = black_eci ? &extra_channels[black_idx] : nullptr;
1534
283
  bool has_interleaved_alpha = false;
1535
283
  JxlChunkedFrameInputSource input = frame_data.GetInputSource();
1536
283
  if (!jpeg_data) {
1537
283
    JXL_RETURN_IF_ERROR(CopyColorChannels(input, patch_rect, frame_info,
1538
283
                                          metadata->m, pool, &color, alpha,
1539
283
                                          &has_interleaved_alpha));
1540
283
  }
1541
283
  JXL_RETURN_IF_ERROR(CopyExtraChannels(input, patch_rect, frame_info,
1542
283
                                        metadata->m, has_interleaved_alpha,
1543
283
                                        pool, &extra_channels));
1544
1545
283
  enc_state.cparams = cparams;
1546
1547
283
  Image3F linear_storage;
1548
283
  Image3F* linear = nullptr;
1549
1550
283
  if (!jpeg_data) {
1551
283
    if (frame_header.color_transform == ColorTransform::kXYB &&
1552
283
        frame_info.ib_needs_color_transform) {
1553
186
      if (frame_header.encoding == FrameEncoding::kVarDCT &&
1554
186
          cparams.speed_tier <= SpeedTier::kKitten) {
1555
0
        JXL_ASSIGN_OR_RETURN(linear_storage,
1556
0
                             Image3F::Create(memory_manager, patch_rect.xsize(),
1557
0
                                             patch_rect.ysize()));
1558
0
        linear = &linear_storage;
1559
0
      }
1560
186
      JXL_RETURN_IF_ERROR(ToXYB(c_enc, metadata->m.IntensityTarget(), black,
1561
186
                                pool, &color, cms, linear));
1562
186
    } else {
1563
      // Nothing to do.
1564
      // RGB or YCbCr: forward YCbCr is not implemented, this is only used when
1565
      // the input is already in YCbCr
1566
      // If encoding a special DC or reference frame: input is already in XYB.
1567
97
    }
1568
283
    bool lossless = cparams.IsLossless();
1569
283
    if (alpha && !alpha_eci->alpha_associated &&
1570
283
        frame_header.frame_type == FrameType::kRegularFrame &&
1571
283
        !ApplyOverride(cparams.keep_invisible, cparams.IsLossless()) &&
1572
283
        cparams.ec_resampling == cparams.resampling &&
1573
283
        !cparams.disable_perceptual_optimizations) {
1574
      // simplify invisible pixels
1575
14
      SimplifyInvisible(&color, *alpha, lossless);
1576
14
      if (linear) {
1577
0
        SimplifyInvisible(linear, *alpha, lossless);
1578
0
      }
1579
14
    }
1580
283
    JXL_RETURN_IF_ERROR(PadImageToBlockMultipleInPlace(&color));
1581
283
  }
1582
1583
  // Rectangle within color that corresponds to the currently processed group in
1584
  // streaming mode.
1585
283
  Rect group_rect(x0 - patch_rect.x0(), y0 - patch_rect.y0(),
1586
283
                  RoundUpToBlockDim(xsize), RoundUpToBlockDim(ysize));
1587
1588
283
  if (enc_state.initialize_global_state && !jpeg_data) {
1589
283
    ComputeChromacityAdjustments(cparams, color, group_rect,
1590
283
                                 &mutable_frame_header);
1591
283
  }
1592
1593
283
  bool has_jpeg_data = (jpeg_data != nullptr);
1594
283
  ComputeNoiseParams(cparams, enc_state.streaming_mode, has_jpeg_data, color,
1595
283
                     frame_dim, &mutable_frame_header,
1596
283
                     &shared.image_features.noise_params);
1597
1598
283
  JXL_RETURN_IF_ERROR(
1599
283
      DownsampleColorChannels(cparams, frame_header, has_jpeg_data, &color));
1600
1601
283
  if (cparams.ec_resampling != 1 && !cparams.already_downsampled) {
1602
0
    for (ImageF& ec : extra_channels) {
1603
0
      JXL_ASSIGN_OR_RETURN(ec, DownsampleImage(ec, cparams.ec_resampling));
1604
0
    }
1605
0
  }
1606
1607
283
  if (!enc_state.streaming_mode) {
1608
283
    group_rect = Rect(color);
1609
283
  }
1610
1611
283
  if (frame_header.encoding == FrameEncoding::kVarDCT) {
1612
186
    enc_state.passes.resize(enc_state.progressive_splitter.GetNumPasses());
1613
186
    for (PassesEncoderState::PassData& pass : enc_state.passes) {
1614
186
      pass.ac_tokens.resize(shared.frame_dim.num_groups);
1615
186
    }
1616
186
    if (jpeg_data) {
1617
0
      JXL_RETURN_IF_ERROR(ComputeJPEGTranscodingData(
1618
0
          *jpeg_data, frame_header, pool, &enc_modular, &enc_state));
1619
186
    } else {
1620
186
      JXL_RETURN_IF_ERROR(ComputeVarDCTEncodingData(
1621
186
          frame_header, linear, &color, group_rect, cms, pool, &enc_modular,
1622
186
          &enc_state, aux_out));
1623
186
    }
1624
186
    JXL_RETURN_IF_ERROR(ComputeAllCoeffOrders(enc_state, frame_dim));
1625
186
    if (!enc_state.streaming_mode) {
1626
186
      shared.num_histograms = 1;
1627
186
      enc_state.histogram_idx.resize(frame_dim.num_groups);
1628
186
    }
1629
186
    JXL_RETURN_IF_ERROR(
1630
186
        TokenizeAllCoefficients(frame_header, pool, &enc_state));
1631
186
  }
1632
1633
283
  if (cparams.modular_mode || !extra_channels.empty()) {
1634
111
    JXL_RETURN_IF_ERROR(enc_modular.ComputeEncodingData(
1635
111
        frame_header, metadata->m, &color, extra_channels, group_rect,
1636
111
        frame_dim, frame_area_rect, &enc_state, cms, pool, aux_out,
1637
111
        /*do_color=*/cparams.modular_mode));
1638
111
  }
1639
1640
283
  if (!enc_state.streaming_mode) {
1641
    // If checks pass here, a Global MA tree is used.
1642
283
    if (cparams.speed_tier < SpeedTier::kTortoise ||
1643
283
        !cparams.ModularPartIsLossless() || cparams.lossy_palette ||
1644
283
        (cparams.responsive == 1 && !cparams.IsLossless()) ||
1645
        // Allow Local trees for progressive lossless but not lossy.
1646
283
        (cparams.buffering && cparams.responsive < 0) ||
1647
283
        !cparams.custom_fixed_tree.empty()) {
1648
      // Use local trees if doing lossless modular, unless at very slow speeds.
1649
283
      JXL_RETURN_IF_ERROR(enc_modular.ComputeTree(pool));
1650
283
      JXL_RETURN_IF_ERROR(enc_modular.ComputeTokens(pool));
1651
283
    }
1652
283
    mutable_frame_header.UpdateFlag(shared.image_features.patches.HasAny(),
1653
283
                                    FrameHeader::kPatches);
1654
283
    mutable_frame_header.UpdateFlag(shared.image_features.splines.HasAny(),
1655
283
                                    FrameHeader::kSplines);
1656
283
  }
1657
1658
283
  JXL_RETURN_IF_ERROR(EncodeGroups(frame_header, &enc_state, &enc_modular, pool,
1659
283
                                   group_codes, aux_out));
1660
283
  if (enc_state.streaming_mode) {
1661
0
    const size_t group_index = enc_state.dc_group_index;
1662
0
    enc_modular.ClearStreamData(ModularStreamId::VarDCTDC(group_index));
1663
0
    enc_modular.ClearStreamData(ModularStreamId::ACMetadata(group_index));
1664
0
    enc_modular.ClearModularStreamData();
1665
0
  }
1666
283
  return true;
1667
283
}
1668
1669
Status PermuteGroups(const CompressParams& cparams,
1670
                     const FrameDimensions& frame_dim, size_t num_passes,
1671
                     std::vector<coeff_order_t>* permutation,
1672
283
                     std::vector<std::unique_ptr<BitWriter>>* group_codes) {
1673
283
  const size_t num_groups = frame_dim.num_groups;
1674
283
  if (!cparams.centerfirst || (num_passes == 1 && num_groups == 1)) {
1675
283
    return true;
1676
283
  }
1677
  // Don't permute global DC/AC or DC.
1678
0
  permutation->resize(frame_dim.num_dc_groups + 2);
1679
0
  std::iota(permutation->begin(), permutation->end(), 0);
1680
0
  std::vector<coeff_order_t> ac_group_order(num_groups);
1681
0
  std::iota(ac_group_order.begin(), ac_group_order.end(), 0);
1682
0
  size_t group_dim = frame_dim.group_dim;
1683
1684
  // The center of the image is either given by parameters or chosen
1685
  // to be the middle of the image by default if center_x, center_y resp.
1686
  // are not provided.
1687
1688
0
  int64_t imag_cx;
1689
0
  if (cparams.center_x != static_cast<size_t>(-1)) {
1690
0
    JXL_RETURN_IF_ERROR(cparams.center_x < frame_dim.xsize);
1691
0
    imag_cx = cparams.center_x;
1692
0
  } else {
1693
0
    imag_cx = frame_dim.xsize / 2;
1694
0
  }
1695
1696
0
  int64_t imag_cy;
1697
0
  if (cparams.center_y != static_cast<size_t>(-1)) {
1698
0
    JXL_RETURN_IF_ERROR(cparams.center_y < frame_dim.ysize);
1699
0
    imag_cy = cparams.center_y;
1700
0
  } else {
1701
0
    imag_cy = frame_dim.ysize / 2;
1702
0
  }
1703
1704
  // The center of the group containing the center of the image.
1705
0
  int64_t cx = (imag_cx / group_dim) * group_dim + group_dim / 2;
1706
0
  int64_t cy = (imag_cy / group_dim) * group_dim + group_dim / 2;
1707
  // This identifies in what area of the central group the center of the image
1708
  // lies in.
1709
0
  double direction = -std::atan2(imag_cy - cy, imag_cx - cx);
1710
  // This identifies the side of the central group the center of the image
1711
  // lies closest to. This can take values 0, 1, 2, 3 corresponding to left,
1712
  // bottom, right, top.
1713
0
  int64_t side = std::fmod((direction + 5 * kPi / 4), 2 * kPi) * 2 / kPi;
1714
0
  auto get_distance_from_center = [&](size_t gid) {
1715
0
    Rect r = frame_dim.GroupRect(gid);
1716
0
    int64_t gcx = r.x0() + group_dim / 2;
1717
0
    int64_t gcy = r.y0() + group_dim / 2;
1718
0
    int64_t dx = gcx - cx;
1719
0
    int64_t dy = gcy - cy;
1720
    // The angle is determined by taking atan2 and adding an appropriate
1721
    // starting point depending on the side we want to start on.
1722
0
    double angle = std::remainder(
1723
0
        std::atan2(dy, dx) + kPi / 4 + side * (kPi / 2), 2 * kPi);
1724
    // Concentric squares in clockwise order.
1725
0
    return std::make_pair(std::max(std::abs(dx), std::abs(dy)), angle);
1726
0
  };
1727
0
  std::sort(ac_group_order.begin(), ac_group_order.end(),
1728
0
            [&](coeff_order_t a, coeff_order_t b) {
1729
0
              return get_distance_from_center(a) < get_distance_from_center(b);
1730
0
            });
1731
0
  std::vector<coeff_order_t> inv_ac_group_order(ac_group_order.size(), 0);
1732
0
  for (size_t i = 0; i < ac_group_order.size(); i++) {
1733
0
    inv_ac_group_order[ac_group_order[i]] = i;
1734
0
  }
1735
0
  for (size_t i = 0; i < num_passes; i++) {
1736
0
    size_t pass_start = permutation->size();
1737
0
    for (coeff_order_t v : inv_ac_group_order) {
1738
0
      permutation->push_back(pass_start + v);
1739
0
    }
1740
0
  }
1741
0
  std::vector<std::unique_ptr<BitWriter>> new_group_codes(group_codes->size());
1742
0
  for (size_t i = 0; i < permutation->size(); i++) {
1743
0
    new_group_codes[(*permutation)[i]] = std::move((*group_codes)[i]);
1744
0
  }
1745
0
  group_codes->swap(new_group_codes);
1746
0
  return true;
1747
0
}
1748
1749
bool CanDoStreamingEncoding(const CompressParams& cparams,
1750
                            const FrameInfo& frame_info,
1751
                            const CodecMetadata& metadata,
1752
283
                            const JxlEncoderChunkedFrameAdapter& frame_data) {
1753
283
  if (cparams.buffering == 0) {
1754
0
    return false;
1755
0
  }
1756
283
  if (cparams.buffering == -1) {
1757
283
    if (cparams.speed_tier < SpeedTier::kTortoise) return false;
1758
283
    if (cparams.speed_tier < SpeedTier::kSquirrel &&
1759
283
        cparams.butteraugli_distance > 0.5f) {
1760
0
      return false;
1761
0
    }
1762
283
    if (cparams.speed_tier == SpeedTier::kSquirrel &&
1763
283
        cparams.butteraugli_distance >= 3.f) {
1764
0
      return false;
1765
0
    }
1766
283
  }
1767
1768
  // TODO(veluca): handle different values of `buffering`.
1769
283
  if (frame_data.xsize <= 2048 && frame_data.ysize <= 2048) {
1770
283
    return false;
1771
283
  }
1772
0
  if (frame_data.IsJPEG()) {
1773
0
    return false;
1774
0
  }
1775
0
  if (cparams.noise == Override::kOn || cparams.patches == Override::kOn) {
1776
0
    return false;
1777
0
  }
1778
0
  if (cparams.progressive_dc != 0 || frame_info.dc_level != 0) {
1779
0
    return false;
1780
0
  }
1781
0
  if (cparams.custom_progressive_mode ||
1782
0
      cparams.qprogressive_mode == Override::kOn ||
1783
0
      cparams.progressive_mode == Override::kOn) {
1784
0
    return false;
1785
0
  }
1786
0
  if (cparams.resampling != 1 || cparams.ec_resampling != 1) {
1787
0
    return false;
1788
0
  }
1789
0
  if (cparams.lossy_palette) {
1790
0
    return false;
1791
0
  }
1792
0
  if (cparams.max_error_mode) {
1793
0
    return false;
1794
0
  }
1795
  // Progressive lossless uses Local MA trees, but requires a full
1796
  // buffer to compress well, so no special check.
1797
0
  if (!cparams.ModularPartIsLossless() || cparams.responsive > 0) {
1798
0
    if (metadata.m.num_extra_channels > 0 || cparams.modular_mode) {
1799
0
      return false;
1800
0
    }
1801
0
  }
1802
0
  ColorTransform ok_color_transform =
1803
0
      cparams.modular_mode ? ColorTransform::kNone : ColorTransform::kXYB;
1804
0
  if (cparams.color_transform != ok_color_transform) {
1805
0
    return false;
1806
0
  }
1807
0
  return true;
1808
0
}
1809
1810
Status ComputePermutationForStreaming(size_t xsize, size_t ysize,
1811
                                      size_t group_size, size_t num_passes,
1812
                                      std::vector<coeff_order_t>& permutation,
1813
0
                                      std::vector<size_t>& dc_group_order) {
1814
  // This is only valid in VarDCT mode, otherwise there can be group shift.
1815
0
  const size_t dc_group_size = group_size * kBlockDim;
1816
0
  const size_t group_xsize = DivCeil(xsize, group_size);
1817
0
  const size_t group_ysize = DivCeil(ysize, group_size);
1818
0
  const size_t dc_group_xsize = DivCeil(xsize, dc_group_size);
1819
0
  const size_t dc_group_ysize = DivCeil(ysize, dc_group_size);
1820
0
  const size_t num_groups = group_xsize * group_ysize;
1821
0
  const size_t num_dc_groups = dc_group_xsize * dc_group_ysize;
1822
0
  const size_t num_sections = 2 + num_dc_groups + num_passes * num_groups;
1823
0
  permutation.resize(num_sections);
1824
0
  size_t new_ix = 0;
1825
  // DC Global is first
1826
0
  permutation[0] = new_ix++;
1827
  // TODO(szabadka) Change the dc group order to center-first.
1828
0
  for (size_t dc_y = 0; dc_y < dc_group_ysize; ++dc_y) {
1829
0
    for (size_t dc_x = 0; dc_x < dc_group_xsize; ++dc_x) {
1830
0
      size_t dc_ix = dc_y * dc_group_xsize + dc_x;
1831
0
      dc_group_order.push_back(dc_ix);
1832
0
      permutation[1 + dc_ix] = new_ix++;
1833
0
      size_t ac_y0 = dc_y * kBlockDim;
1834
0
      size_t ac_x0 = dc_x * kBlockDim;
1835
0
      size_t ac_y1 = std::min<size_t>(group_ysize, ac_y0 + kBlockDim);
1836
0
      size_t ac_x1 = std::min<size_t>(group_xsize, ac_x0 + kBlockDim);
1837
0
      for (size_t pass = 0; pass < num_passes; ++pass) {
1838
0
        for (size_t ac_y = ac_y0; ac_y < ac_y1; ++ac_y) {
1839
0
          for (size_t ac_x = ac_x0; ac_x < ac_x1; ++ac_x) {
1840
0
            size_t group_ix = ac_y * group_xsize + ac_x;
1841
0
            size_t old_ix =
1842
0
                AcGroupIndex(pass, group_ix, num_groups, num_dc_groups);
1843
0
            permutation[old_ix] = new_ix++;
1844
0
          }
1845
0
        }
1846
0
      }
1847
0
    }
1848
0
  }
1849
  // AC Global is last
1850
0
  permutation[1 + num_dc_groups] = new_ix++;
1851
0
  JXL_ENSURE(new_ix == num_sections);
1852
0
  return true;
1853
0
}
1854
1855
constexpr size_t kGroupSizeOffset[4] = {
1856
    static_cast<size_t>(0),
1857
    static_cast<size_t>(1024),
1858
    static_cast<size_t>(17408),
1859
    static_cast<size_t>(4211712),
1860
};
1861
constexpr size_t kTOCBits[4] = {12, 16, 24, 32};
1862
1863
0
size_t TOCBucket(size_t group_size) {
1864
0
  size_t bucket = 0;
1865
0
  while (bucket < 3 && group_size >= kGroupSizeOffset[bucket + 1]) ++bucket;
1866
0
  return bucket;
1867
0
}
1868
1869
0
size_t TOCSize(const std::vector<size_t>& group_sizes) {
1870
0
  size_t toc_bits = 0;
1871
0
  for (size_t group_size : group_sizes) {
1872
0
    toc_bits += kTOCBits[TOCBucket(group_size)];
1873
0
  }
1874
0
  return (toc_bits + 7) / 8;
1875
0
}
1876
1877
StatusOr<PaddedBytes> EncodeTOC(JxlMemoryManager* memory_manager,
1878
                                const std::vector<size_t>& group_sizes,
1879
0
                                AuxOut* aux_out) {
1880
0
  BitWriter writer{memory_manager};
1881
0
  JXL_RETURN_IF_ERROR(writer.WithMaxBits(
1882
0
      32 * group_sizes.size(), LayerType::Toc, aux_out, [&]() -> Status {
1883
0
        for (size_t group_size : group_sizes) {
1884
0
          JXL_RETURN_IF_ERROR(U32Coder::Write(kTocDist, group_size, &writer));
1885
0
        }
1886
0
        writer.ZeroPadToByte();  // before first group
1887
0
        return true;
1888
0
      }));
1889
0
  return std::move(writer).TakeBytes();
1890
0
}
1891
1892
Status ComputeGroupDataOffset(size_t frame_header_size, size_t dc_global_size,
1893
                              size_t num_sections, size_t& min_dc_global_size,
1894
0
                              size_t& group_offset) {
1895
0
  size_t max_toc_bits = (num_sections - 1) * 32;
1896
0
  size_t min_toc_bits = (num_sections - 1) * 12;
1897
0
  size_t max_padding = (max_toc_bits - min_toc_bits + 7) / 8;
1898
0
  min_dc_global_size = dc_global_size;
1899
0
  size_t dc_global_bucket = TOCBucket(min_dc_global_size);
1900
0
  while (TOCBucket(min_dc_global_size + max_padding) > dc_global_bucket) {
1901
0
    dc_global_bucket = TOCBucket(min_dc_global_size + max_padding);
1902
0
    min_dc_global_size = kGroupSizeOffset[dc_global_bucket];
1903
0
  }
1904
0
  JXL_ENSURE(TOCBucket(min_dc_global_size) == dc_global_bucket);
1905
0
  JXL_ENSURE(TOCBucket(min_dc_global_size + max_padding) == dc_global_bucket);
1906
0
  max_toc_bits += kTOCBits[dc_global_bucket];
1907
0
  size_t max_toc_size = (max_toc_bits + 7) / 8;
1908
0
  group_offset = frame_header_size + max_toc_size + min_dc_global_size;
1909
0
  return true;
1910
0
}
1911
1912
size_t ComputeDcGlobalPadding(const std::vector<size_t>& group_sizes,
1913
                              size_t frame_header_size,
1914
                              size_t group_data_offset,
1915
0
                              size_t min_dc_global_size) {
1916
0
  std::vector<size_t> new_group_sizes = group_sizes;
1917
0
  new_group_sizes[0] = min_dc_global_size;
1918
0
  size_t toc_size = TOCSize(new_group_sizes);
1919
0
  size_t actual_offset = frame_header_size + toc_size + group_sizes[0];
1920
0
  return group_data_offset - actual_offset;
1921
0
}
1922
1923
Status OutputGroups(std::vector<std::unique_ptr<BitWriter>>&& group_codes,
1924
                    std::vector<size_t>* group_sizes,
1925
0
                    JxlEncoderOutputProcessorWrapper* output_processor) {
1926
0
  JXL_ENSURE(group_codes.size() >= 4);
1927
0
  {
1928
0
    PaddedBytes dc_group = std::move(*group_codes[1]).TakeBytes();
1929
0
    group_sizes->push_back(dc_group.size());
1930
0
    JXL_RETURN_IF_ERROR(AppendData(*output_processor, dc_group));
1931
0
  }
1932
0
  for (size_t i = 3; i < group_codes.size(); ++i) {
1933
0
    PaddedBytes ac_group = std::move(*group_codes[i]).TakeBytes();
1934
0
    group_sizes->push_back(ac_group.size());
1935
0
    JXL_RETURN_IF_ERROR(AppendData(*output_processor, ac_group));
1936
0
  }
1937
0
  return true;
1938
0
}
1939
1940
0
void RemoveUnusedHistograms(EntropyEncodingData& codes) {
1941
0
  std::vector<int> remap(256, -1);
1942
0
  std::vector<uint8_t> inv_remap;
1943
0
  for (uint8_t& context : codes.context_map) {
1944
0
    const uint8_t histo_ix = context;
1945
0
    if (remap[histo_ix] == -1) {
1946
0
      remap[histo_ix] = inv_remap.size();
1947
0
      inv_remap.push_back(histo_ix);
1948
0
    }
1949
0
    context = remap[histo_ix];
1950
0
  }
1951
0
  EntropyEncodingData new_codes;
1952
0
  new_codes.use_prefix_code = codes.use_prefix_code;
1953
0
  new_codes.lz77 = codes.lz77;
1954
0
  new_codes.context_map = std::move(codes.context_map);
1955
0
  for (uint8_t histo_idx : inv_remap) {
1956
0
    new_codes.encoding_info.emplace_back(
1957
0
        std::move(codes.encoding_info[histo_idx]));
1958
0
    new_codes.uint_config.emplace_back(codes.uint_config[histo_idx]);
1959
0
    new_codes.encoded_histograms.emplace_back(
1960
0
        std::move(codes.encoded_histograms[histo_idx]));
1961
0
  }
1962
0
  codes = std::move(new_codes);
1963
0
}
1964
1965
Status OutputAcGlobal(PassesEncoderState& enc_state,
1966
                      const FrameDimensions& frame_dim,
1967
                      std::vector<size_t>* group_sizes,
1968
                      JxlEncoderOutputProcessorWrapper* output_processor,
1969
0
                      AuxOut* aux_out) {
1970
0
  JXL_ENSURE(frame_dim.num_groups > 1);
1971
0
  JxlMemoryManager* memory_manager = enc_state.memory_manager();
1972
0
  BitWriter writer{memory_manager};
1973
0
  {
1974
0
    size_t num_histo_bits = CeilLog2Nonzero(frame_dim.num_groups);
1975
0
    JXL_RETURN_IF_ERROR(
1976
0
        writer.WithMaxBits(num_histo_bits + 1, LayerType::Ac, aux_out, [&] {
1977
0
          writer.Write(1, 1);  // default dequant matrices
1978
0
          writer.Write(num_histo_bits, frame_dim.num_dc_groups - 1);
1979
0
          return true;
1980
0
        }));
1981
0
  }
1982
0
  const PassesSharedState& shared = enc_state.shared;
1983
0
  for (size_t i = 0; i < enc_state.progressive_splitter.GetNumPasses(); i++) {
1984
    // Encode coefficient orders.
1985
0
    size_t order_bits = 0;
1986
0
    JXL_RETURN_IF_ERROR(
1987
0
        U32Coder::CanEncode(kOrderEnc, enc_state.used_orders[i], &order_bits));
1988
0
    JXL_RETURN_IF_ERROR(
1989
0
        writer.WithMaxBits(order_bits, LayerType::Order, aux_out, [&] {
1990
0
          return U32Coder::Write(kOrderEnc, enc_state.used_orders[i], &writer);
1991
0
        }));
1992
0
    JXL_RETURN_IF_ERROR(
1993
0
        EncodeCoeffOrders(enc_state.used_orders[i],
1994
0
                          &shared.coeff_orders[i * shared.coeff_order_size],
1995
0
                          &writer, LayerType::Order, aux_out));
1996
    // Fix up context map and entropy codes to remove any fix histograms that
1997
    // were not selected by clustering.
1998
0
    RemoveUnusedHistograms(enc_state.passes[i].codes);
1999
0
    JXL_RETURN_IF_ERROR(EncodeHistograms(enc_state.passes[i].codes, &writer,
2000
0
                                         LayerType::Ac, aux_out));
2001
0
  }
2002
0
  JXL_RETURN_IF_ERROR(writer.WithMaxBits(8, LayerType::Ac, aux_out, [&] {
2003
0
    writer.ZeroPadToByte();  // end of group.
2004
0
    return true;
2005
0
  }));
2006
0
  PaddedBytes ac_global = std::move(writer).TakeBytes();
2007
0
  group_sizes->push_back(ac_global.size());
2008
0
  JXL_RETURN_IF_ERROR(AppendData(*output_processor, ac_global));
2009
0
  return true;
2010
0
}
2011
2012
JXL_NOINLINE Status EncodeFrameStreaming(
2013
    JxlMemoryManager* memory_manager, const CompressParams& cparams,
2014
    const FrameInfo& frame_info, const CodecMetadata* metadata,
2015
    JxlEncoderChunkedFrameAdapter& frame_data, const JxlCmsInterface& cms,
2016
    ThreadPool* pool, JxlEncoderOutputProcessorWrapper* output_processor,
2017
0
    AuxOut* aux_out) {
2018
0
  auto enc_state = jxl::make_unique<PassesEncoderState>(memory_manager);
2019
0
  SetProgressiveMode(cparams, &enc_state->progressive_splitter);
2020
0
  FrameHeader frame_header(metadata);
2021
0
  std::unique_ptr<jpeg::JPEGData> jpeg_data;
2022
0
  if (frame_data.IsJPEG()) {
2023
0
    jpeg_data = frame_data.TakeJPEGData();
2024
0
    JXL_ENSURE(jpeg_data);
2025
0
  }
2026
0
  JXL_RETURN_IF_ERROR(MakeFrameHeader(frame_data.xsize, frame_data.ysize,
2027
0
                                      cparams, enc_state->progressive_splitter,
2028
0
                                      frame_info, jpeg_data.get(), true,
2029
0
                                      &frame_header));
2030
0
  const size_t num_passes = enc_state->progressive_splitter.GetNumPasses();
2031
0
  JXL_ASSIGN_OR_RETURN(
2032
0
      auto enc_modular,
2033
0
      ModularFrameEncoder::Create(memory_manager, frame_header, cparams, true));
2034
0
  std::vector<coeff_order_t> permutation;
2035
0
  std::vector<size_t> dc_group_order;
2036
0
  size_t group_size = frame_header.ToFrameDimensions().group_dim;
2037
0
  JXL_RETURN_IF_ERROR(ComputePermutationForStreaming(
2038
0
      frame_data.xsize, frame_data.ysize, group_size, num_passes, permutation,
2039
0
      dc_group_order));
2040
0
  enc_state->shared.num_histograms = dc_group_order.size();
2041
0
  size_t dc_group_size = group_size * kBlockDim;
2042
0
  size_t dc_group_xsize = DivCeil(frame_data.xsize, dc_group_size);
2043
0
  size_t min_dc_global_size = 0;
2044
0
  size_t group_data_offset = 0;
2045
0
  PaddedBytes frame_header_bytes{memory_manager};
2046
0
  PaddedBytes dc_global_bytes{memory_manager};
2047
0
  std::vector<size_t> group_sizes;
2048
0
  size_t start_pos = output_processor->CurrentPosition();
2049
0
  for (size_t i = 0; i < dc_group_order.size(); ++i) {
2050
0
    size_t dc_ix = dc_group_order[i];
2051
0
    size_t dc_y = dc_ix / dc_group_xsize;
2052
0
    size_t dc_x = dc_ix % dc_group_xsize;
2053
0
    size_t y0 = dc_y * dc_group_size;
2054
0
    size_t x0 = dc_x * dc_group_size;
2055
0
    size_t ysize = std::min<size_t>(dc_group_size, frame_data.ysize - y0);
2056
0
    size_t xsize = std::min<size_t>(dc_group_size, frame_data.xsize - x0);
2057
0
    size_t group_xsize = DivCeil(xsize, group_size);
2058
0
    size_t group_ysize = DivCeil(ysize, group_size);
2059
0
    JXL_DEBUG_V(2,
2060
0
                "Encoding DC group #%" PRIuS " dc_y = %" PRIuS " dc_x = %" PRIuS
2061
0
                " (x0, y0) = (%" PRIuS ", %" PRIuS ") (xsize, ysize) = (%" PRIuS
2062
0
                ", %" PRIuS ")",
2063
0
                dc_ix, dc_y, dc_x, x0, y0, xsize, ysize);
2064
0
    enc_state->streaming_mode = true;
2065
0
    enc_state->initialize_global_state = (i == 0);
2066
0
    enc_state->dc_group_index = dc_ix;
2067
0
    enc_state->histogram_idx =
2068
0
        std::vector<size_t>(group_xsize * group_ysize, i);
2069
0
    std::vector<std::unique_ptr<BitWriter>> group_codes;
2070
0
    JXL_RETURN_IF_ERROR(ComputeEncodingData(
2071
0
        cparams, frame_info, metadata, frame_data, jpeg_data.get(), x0, y0,
2072
0
        xsize, ysize, cms, pool, frame_header, *enc_modular, *enc_state,
2073
0
        &group_codes, aux_out));
2074
0
    JXL_ENSURE(enc_state->special_frames.empty());
2075
0
    if (i == 0) {
2076
0
      BitWriter writer{memory_manager};
2077
0
      JXL_RETURN_IF_ERROR(WriteFrameHeader(frame_header, &writer, aux_out));
2078
0
      JXL_RETURN_IF_ERROR(
2079
0
          writer.WithMaxBits(8, LayerType::Header, aux_out, [&]() -> Status {
2080
0
            writer.Write(1, 1);  // write permutation
2081
0
            JXL_RETURN_IF_ERROR(EncodePermutation(
2082
0
                permutation.data(), /*skip=*/0, permutation.size(), &writer,
2083
0
                LayerType::Header, aux_out));
2084
0
            writer.ZeroPadToByte();
2085
0
            return true;
2086
0
          }));
2087
0
      frame_header_bytes = std::move(writer).TakeBytes();
2088
0
      dc_global_bytes = std::move(*group_codes[0]).TakeBytes();
2089
0
      JXL_RETURN_IF_ERROR(ComputeGroupDataOffset(
2090
0
          frame_header_bytes.size(), dc_global_bytes.size(), permutation.size(),
2091
0
          min_dc_global_size, group_data_offset));
2092
0
      JXL_DEBUG_V(2, "Frame header size: %" PRIuS, frame_header_bytes.size());
2093
0
      JXL_DEBUG_V(2, "DC global size: %" PRIuS ", min size for TOC: %" PRIuS,
2094
0
                  dc_global_bytes.size(), min_dc_global_size);
2095
0
      JXL_DEBUG_V(2, "Num groups: %" PRIuS " group data offset: %" PRIuS,
2096
0
                  permutation.size(), group_data_offset);
2097
0
      group_sizes.push_back(dc_global_bytes.size());
2098
0
      JXL_RETURN_IF_ERROR(
2099
0
          output_processor->Seek(start_pos + group_data_offset));
2100
0
    }
2101
0
    JXL_RETURN_IF_ERROR(
2102
0
        OutputGroups(std::move(group_codes), &group_sizes, output_processor));
2103
0
  }
2104
0
  if (frame_header.encoding == FrameEncoding::kVarDCT) {
2105
0
    JXL_RETURN_IF_ERROR(
2106
0
        OutputAcGlobal(*enc_state, frame_header.ToFrameDimensions(),
2107
0
                       &group_sizes, output_processor, aux_out));
2108
0
  } else {
2109
0
    group_sizes.push_back(0);
2110
0
  }
2111
0
  JXL_ENSURE(group_sizes.size() == permutation.size());
2112
0
  size_t end_pos = output_processor->CurrentPosition();
2113
0
  JXL_RETURN_IF_ERROR(output_processor->Seek(start_pos));
2114
0
  size_t padding_size =
2115
0
      ComputeDcGlobalPadding(group_sizes, frame_header_bytes.size(),
2116
0
                             group_data_offset, min_dc_global_size);
2117
0
  group_sizes[0] += padding_size;
2118
0
  JXL_ASSIGN_OR_RETURN(PaddedBytes toc_bytes,
2119
0
                       EncodeTOC(memory_manager, group_sizes, aux_out));
2120
0
  std::vector<uint8_t> padding_bytes(padding_size);
2121
0
  JXL_RETURN_IF_ERROR(AppendData(*output_processor, frame_header_bytes));
2122
0
  JXL_RETURN_IF_ERROR(AppendData(*output_processor, toc_bytes));
2123
0
  JXL_RETURN_IF_ERROR(AppendData(*output_processor, dc_global_bytes));
2124
0
  JXL_RETURN_IF_ERROR(AppendData(*output_processor, padding_bytes));
2125
0
  JXL_DEBUG_V(2, "TOC size: %" PRIuS " padding bytes after DC global: %" PRIuS,
2126
0
              toc_bytes.size(), padding_size);
2127
0
  JXL_ENSURE(output_processor->CurrentPosition() ==
2128
0
             start_pos + group_data_offset);
2129
0
  JXL_RETURN_IF_ERROR(output_processor->Seek(end_pos));
2130
0
  return true;
2131
0
}
2132
2133
Status EncodeFrameOneShot(JxlMemoryManager* memory_manager,
2134
                          const CompressParams& cparams,
2135
                          const FrameInfo& frame_info,
2136
                          const CodecMetadata* metadata,
2137
                          JxlEncoderChunkedFrameAdapter& frame_data,
2138
                          const JxlCmsInterface& cms, ThreadPool* pool,
2139
                          JxlEncoderOutputProcessorWrapper* output_processor,
2140
283
                          AuxOut* aux_out) {
2141
283
  auto enc_state = jxl::make_unique<PassesEncoderState>(memory_manager);
2142
283
  SetProgressiveMode(cparams, &enc_state->progressive_splitter);
2143
283
  FrameHeader frame_header(metadata);
2144
283
  std::unique_ptr<jpeg::JPEGData> jpeg_data;
2145
283
  if (frame_data.IsJPEG()) {
2146
0
    jpeg_data = frame_data.TakeJPEGData();
2147
0
    JXL_ENSURE(jpeg_data);
2148
0
  }
2149
283
  JXL_RETURN_IF_ERROR(MakeFrameHeader(frame_data.xsize, frame_data.ysize,
2150
283
                                      cparams, enc_state->progressive_splitter,
2151
283
                                      frame_info, jpeg_data.get(), false,
2152
283
                                      &frame_header));
2153
283
  const size_t num_passes = enc_state->progressive_splitter.GetNumPasses();
2154
283
  JXL_ASSIGN_OR_RETURN(auto enc_modular,
2155
283
                       ModularFrameEncoder::Create(memory_manager, frame_header,
2156
283
                                                   cparams, false));
2157
283
  std::vector<std::unique_ptr<BitWriter>> group_codes;
2158
283
  JXL_RETURN_IF_ERROR(ComputeEncodingData(
2159
283
      cparams, frame_info, metadata, frame_data, jpeg_data.get(), 0, 0,
2160
283
      frame_data.xsize, frame_data.ysize, cms, pool, frame_header, *enc_modular,
2161
283
      *enc_state, &group_codes, aux_out));
2162
2163
283
  BitWriter writer{memory_manager};
2164
283
  JXL_RETURN_IF_ERROR(writer.AppendByteAligned(enc_state->special_frames));
2165
283
  JXL_RETURN_IF_ERROR(WriteFrameHeader(frame_header, &writer, aux_out));
2166
2167
283
  std::vector<coeff_order_t> permutation;
2168
283
  JXL_RETURN_IF_ERROR(PermuteGroups(cparams, enc_state->shared.frame_dim,
2169
283
                                    num_passes, &permutation, &group_codes));
2170
2171
283
  JXL_RETURN_IF_ERROR(
2172
283
      WriteGroupOffsets(group_codes, permutation, &writer, aux_out));
2173
2174
283
  JXL_RETURN_IF_ERROR(writer.AppendByteAligned(group_codes));
2175
283
  PaddedBytes frame_bytes = std::move(writer).TakeBytes();
2176
283
  JXL_RETURN_IF_ERROR(AppendData(*output_processor, frame_bytes));
2177
2178
283
  return true;
2179
283
}
2180
2181
}  // namespace
2182
2183
std::vector<CompressParams> TectonicPlateSettingsLessPalette(
2184
0
    const CompressParams& cparams_orig) {
2185
0
  std::vector<CompressParams> all_params;
2186
0
  CompressParams cparams_attempt = cparams_orig;
2187
0
  cparams_attempt.speed_tier = SpeedTier::kGlacier;
2188
2189
0
  cparams_attempt.options.max_properties = 4;
2190
0
  cparams_attempt.options.nb_repeats = 1.0f;
2191
0
  cparams_attempt.modular_group_size_shift = 0;
2192
0
  cparams_attempt.channel_colors_percent = 0;
2193
0
  cparams_attempt.options.predictor = Predictor::Variable;
2194
0
  cparams_attempt.channel_colors_pre_transform_percent = 95.f;
2195
0
  cparams_attempt.palette_colors = 1024;
2196
0
  cparams_attempt.options.wp_tree_mode = ModularOptions::TreeMode::kDefault;
2197
0
  cparams_attempt.patches = Override::kDefault;
2198
0
  all_params.push_back(cparams_attempt);
2199
0
  cparams_attempt.channel_colors_percent = 80.f;
2200
0
  cparams_attempt.modular_group_size_shift = 1;
2201
0
  cparams_attempt.palette_colors = 0;
2202
0
  cparams_attempt.channel_colors_pre_transform_percent = 0;
2203
0
  all_params.push_back(cparams_attempt);
2204
0
  cparams_attempt.channel_colors_pre_transform_percent = 95.f;
2205
0
  cparams_attempt.modular_group_size_shift = 2;
2206
0
  all_params.push_back(cparams_attempt);
2207
0
  cparams_attempt.modular_group_size_shift = 3;
2208
0
  cparams_attempt.patches = Override::kOff;
2209
0
  cparams_attempt.options.wp_tree_mode = ModularOptions::TreeMode::kNoWP;
2210
0
  all_params.push_back(cparams_attempt);
2211
0
  cparams_attempt.palette_colors = 1024;
2212
0
  cparams_attempt.options.wp_tree_mode = ModularOptions::TreeMode::kDefault;
2213
0
  all_params.push_back(cparams_attempt);
2214
0
  cparams_attempt.patches = Override::kDefault;
2215
0
  cparams_attempt.options.wp_tree_mode = ModularOptions::TreeMode::kNoWP;
2216
0
  all_params.push_back(cparams_attempt);
2217
0
  cparams_attempt.options.wp_tree_mode = ModularOptions::TreeMode::kDefault;
2218
0
  cparams_attempt.channel_colors_pre_transform_percent = 0;
2219
0
  all_params.push_back(cparams_attempt);
2220
0
  cparams_attempt.channel_colors_pre_transform_percent = 95.f;
2221
0
  cparams_attempt.options.nb_repeats = 0.9f;
2222
0
  cparams_attempt.modular_group_size_shift = 2;
2223
0
  all_params.push_back(cparams_attempt);
2224
0
  cparams_attempt.modular_group_size_shift = 3;
2225
0
  cparams_attempt.palette_colors = 0;
2226
0
  cparams_attempt.options.wp_tree_mode = ModularOptions::TreeMode::kNoWP;
2227
0
  all_params.push_back(cparams_attempt);
2228
0
  cparams_attempt.options.wp_tree_mode = ModularOptions::TreeMode::kDefault;
2229
0
  cparams_attempt.channel_colors_pre_transform_percent = 0;
2230
0
  all_params.push_back(cparams_attempt);
2231
0
  cparams_attempt.palette_colors = 1024;
2232
0
  cparams_attempt.options.nb_repeats = 0.95f;
2233
0
  cparams_attempt.modular_group_size_shift = 1;
2234
0
  cparams_attempt.channel_colors_percent = 0;
2235
0
  all_params.push_back(cparams_attempt);
2236
0
  cparams_attempt.modular_group_size_shift = 2;
2237
0
  cparams_attempt.palette_colors = 0;
2238
0
  all_params.push_back(cparams_attempt);
2239
0
  cparams_attempt.channel_colors_percent = 80.f;
2240
0
  cparams_attempt.options.wp_tree_mode = ModularOptions::TreeMode::kNoWP;
2241
0
  all_params.push_back(cparams_attempt);
2242
0
  cparams_attempt.palette_colors = 1024;
2243
0
  cparams_attempt.channel_colors_pre_transform_percent = 95.f;
2244
0
  cparams_attempt.options.wp_tree_mode = ModularOptions::TreeMode::kDefault;
2245
0
  cparams_attempt.modular_group_size_shift = 3;
2246
0
  all_params.push_back(cparams_attempt);
2247
0
  cparams_attempt.palette_colors = 0;
2248
0
  cparams_attempt.patches = Override::kOff;
2249
0
  all_params.push_back(cparams_attempt);
2250
0
  cparams_attempt.patches = Override::kDefault;
2251
0
  cparams_attempt.options.wp_tree_mode = ModularOptions::TreeMode::kNoWP;
2252
0
  all_params.push_back(cparams_attempt);
2253
0
  cparams_attempt.palette_colors = 1024;
2254
0
  cparams_attempt.patches = Override::kOff;
2255
0
  all_params.push_back(cparams_attempt);
2256
0
  cparams_attempt.options.nb_repeats = 0.5f;
2257
0
  cparams_attempt.patches = Override::kDefault;
2258
0
  cparams_attempt.options.wp_tree_mode = ModularOptions::TreeMode::kDefault;
2259
0
  all_params.push_back(cparams_attempt);
2260
0
  cparams_attempt.options.predictor = Predictor::Zero;
2261
0
  cparams_attempt.options.nb_repeats = 0;
2262
0
  cparams_attempt.channel_colors_percent = 0;
2263
0
  cparams_attempt.channel_colors_pre_transform_percent = 0;
2264
0
  cparams_attempt.patches = Override::kOff;
2265
0
  all_params.push_back(cparams_attempt);
2266
0
  cparams_attempt.channel_colors_percent = 80.f;
2267
0
  cparams_attempt.channel_colors_pre_transform_percent = 95.f;
2268
0
  cparams_attempt.options.nb_repeats = 1.0f;
2269
0
  cparams_attempt.palette_colors = 0;
2270
0
  all_params.push_back(cparams_attempt);
2271
0
  cparams_attempt.patches = Override::kDefault;
2272
0
  cparams_attempt.options.predictor = Predictor::Best;
2273
0
  all_params.push_back(cparams_attempt);
2274
0
  cparams_attempt.options.nb_repeats = 0.9f;
2275
0
  cparams_attempt.patches = Override::kOff;
2276
0
  all_params.push_back(cparams_attempt);
2277
0
  cparams_attempt.palette_colors = 1024;
2278
0
  cparams_attempt.patches = Override::kDefault;
2279
0
  cparams_attempt.options.predictor = Predictor::Weighted;
2280
0
  cparams_attempt.options.nb_repeats = 1.0f;
2281
0
  all_params.push_back(cparams_attempt);
2282
0
  cparams_attempt.options.nb_repeats = 0.95f;
2283
0
  cparams_attempt.modular_group_size_shift = 2;
2284
0
  cparams_attempt.palette_colors = 0;
2285
0
  cparams_attempt.channel_colors_pre_transform_percent = 0;
2286
0
  all_params.push_back(cparams_attempt);
2287
0
  return all_params;
2288
0
}
2289
2290
std::vector<CompressParams> TectonicPlateSettingsMorePalette(
2291
0
    const CompressParams& cparams_orig) {
2292
0
  std::vector<CompressParams> all_params;
2293
0
  CompressParams cparams_attempt = cparams_orig;
2294
0
  cparams_attempt.speed_tier = SpeedTier::kGlacier;
2295
2296
0
  cparams_attempt.options.max_properties = 4;
2297
0
  cparams_attempt.options.nb_repeats = 1.0f;
2298
0
  cparams_attempt.modular_group_size_shift = 0;
2299
0
  cparams_attempt.palette_colors = 70000;
2300
0
  cparams_attempt.options.predictor = Predictor::Variable;
2301
0
  cparams_attempt.channel_colors_percent = 80.f;
2302
0
  cparams_attempt.channel_colors_pre_transform_percent = 95.f;
2303
0
  cparams_attempt.options.wp_tree_mode = ModularOptions::TreeMode::kDefault;
2304
0
  cparams_attempt.patches = Override::kDefault;
2305
0
  all_params.push_back(cparams_attempt);
2306
0
  cparams_attempt.modular_group_size_shift = 2;
2307
0
  cparams_attempt.channel_colors_percent = 0;
2308
0
  cparams_attempt.patches = Override::kOff;
2309
0
  cparams_attempt.options.wp_tree_mode = ModularOptions::TreeMode::kNoWP;
2310
0
  all_params.push_back(cparams_attempt);
2311
0
  cparams_attempt.channel_colors_percent = 80.f;
2312
0
  cparams_attempt.options.wp_tree_mode = ModularOptions::TreeMode::kDefault;
2313
0
  cparams_attempt.modular_group_size_shift = 3;
2314
0
  all_params.push_back(cparams_attempt);
2315
0
  cparams_attempt.options.nb_repeats = 0.9f;
2316
0
  all_params.push_back(cparams_attempt);
2317
0
  cparams_attempt.patches = Override::kDefault;
2318
0
  cparams_attempt.options.nb_repeats = 0.95f;
2319
0
  cparams_attempt.modular_group_size_shift = 0;
2320
0
  all_params.push_back(cparams_attempt);
2321
0
  cparams_attempt.modular_group_size_shift = 3;
2322
0
  all_params.push_back(cparams_attempt);
2323
0
  cparams_attempt.patches = Override::kOff;
2324
0
  cparams_attempt.options.wp_tree_mode = ModularOptions::TreeMode::kNoWP;
2325
0
  all_params.push_back(cparams_attempt);
2326
0
  cparams_attempt.options.nb_repeats = 0.5f;
2327
0
  all_params.push_back(cparams_attempt);
2328
0
  cparams_attempt.options.wp_tree_mode = ModularOptions::TreeMode::kDefault;
2329
0
  cparams_attempt.options.predictor = Predictor::Zero;
2330
0
  cparams_attempt.options.nb_repeats = 0;
2331
0
  all_params.push_back(cparams_attempt);
2332
0
  cparams_attempt.patches = Override::kDefault;
2333
0
  cparams_attempt.channel_colors_pre_transform_percent = 0;
2334
0
  all_params.push_back(cparams_attempt);
2335
0
  cparams_attempt.options.nb_repeats = 0.01f;
2336
0
  cparams_attempt.palette_colors = 0;
2337
0
  cparams_attempt.patches = Override::kOff;
2338
0
  cparams_attempt.options.wp_tree_mode = ModularOptions::TreeMode::kNoWP;
2339
0
  all_params.push_back(cparams_attempt);
2340
0
  cparams_attempt.channel_colors_pre_transform_percent = 95.f;
2341
0
  cparams_attempt.options.wp_tree_mode = ModularOptions::TreeMode::kDefault;
2342
0
  cparams_attempt.palette_colors = 70000;
2343
0
  all_params.push_back(cparams_attempt);
2344
0
  cparams_attempt.options.nb_repeats = 1.0f;
2345
0
  cparams_attempt.modular_group_size_shift = 0;
2346
0
  cparams_attempt.channel_colors_percent = 0;
2347
0
  cparams_attempt.channel_colors_pre_transform_percent = 0;
2348
0
  cparams_attempt.options.wp_tree_mode = ModularOptions::TreeMode::kNoWP;
2349
0
  all_params.push_back(cparams_attempt);
2350
0
  cparams_attempt.channel_colors_pre_transform_percent = 95.f;
2351
0
  cparams_attempt.modular_group_size_shift = 1;
2352
0
  all_params.push_back(cparams_attempt);
2353
0
  cparams_attempt.modular_group_size_shift = 2;
2354
0
  all_params.push_back(cparams_attempt);
2355
0
  cparams_attempt.channel_colors_percent = 80.f;
2356
0
  cparams_attempt.options.wp_tree_mode = ModularOptions::TreeMode::kDefault;
2357
0
  cparams_attempt.modular_group_size_shift = 3;
2358
0
  all_params.push_back(cparams_attempt);
2359
0
  cparams_attempt.options.nb_repeats = 0.5f;
2360
0
  cparams_attempt.modular_group_size_shift = 1;
2361
0
  cparams_attempt.channel_colors_percent = 0;
2362
0
  cparams_attempt.options.wp_tree_mode = ModularOptions::TreeMode::kNoWP;
2363
0
  all_params.push_back(cparams_attempt);
2364
0
  cparams_attempt.options.wp_tree_mode = ModularOptions::TreeMode::kDefault;
2365
0
  cparams_attempt.modular_group_size_shift = 2;
2366
0
  all_params.push_back(cparams_attempt);
2367
0
  cparams_attempt.channel_colors_percent = 80.f;
2368
0
  cparams_attempt.modular_group_size_shift = 3;
2369
0
  cparams_attempt.options.wp_tree_mode = ModularOptions::TreeMode::kNoWP;
2370
0
  all_params.push_back(cparams_attempt);
2371
0
  cparams_attempt.options.wp_tree_mode = ModularOptions::TreeMode::kDefault;
2372
0
  cparams_attempt.options.predictor = Predictor::Select;
2373
0
  cparams_attempt.options.nb_repeats = 1.0f;
2374
0
  all_params.push_back(cparams_attempt);
2375
0
  return all_params;
2376
0
}
2377
2378
Status EncodeFrame(JxlMemoryManager* memory_manager,
2379
                   const CompressParams& cparams_orig,
2380
                   const FrameInfo& frame_info, const CodecMetadata* metadata,
2381
                   JxlEncoderChunkedFrameAdapter& frame_data,
2382
                   const JxlCmsInterface& cms, ThreadPool* pool,
2383
                   JxlEncoderOutputProcessorWrapper* output_processor,
2384
283
                   AuxOut* aux_out) {
2385
283
  CompressParams cparams = cparams_orig;
2386
283
  if (cparams.speed_tier == SpeedTier::kTectonicPlate &&
2387
283
      !cparams.IsLossless()) {
2388
0
    cparams.speed_tier = SpeedTier::kGlacier;
2389
0
  }
2390
  // Lightning mode is handled externally, so switch to Thunder mode to handle
2391
  // potentially weird cases.
2392
283
  if (cparams.speed_tier == SpeedTier::kLightning) {
2393
0
    cparams.speed_tier = SpeedTier::kThunder;
2394
0
  }
2395
283
  if (cparams.speed_tier == SpeedTier::kTectonicPlate) {
2396
    // Test palette performance to inform later trials.
2397
0
    std::vector<CompressParams> all_params;
2398
0
    CompressParams cparams_attempt = cparams_orig;
2399
0
    cparams_attempt.speed_tier = SpeedTier::kGlacier;
2400
2401
0
    cparams_attempt.options.max_properties = 4;
2402
0
    cparams_attempt.options.nb_repeats = 1.0f;
2403
0
    cparams_attempt.modular_group_size_shift = 3;
2404
0
    cparams_attempt.palette_colors = 0;
2405
0
    cparams_attempt.options.predictor = Predictor::Variable;
2406
0
    cparams_attempt.channel_colors_percent = 80.f;
2407
0
    cparams_attempt.channel_colors_pre_transform_percent = 95.f;
2408
0
    cparams_attempt.options.wp_tree_mode = ModularOptions::TreeMode::kDefault;
2409
0
    cparams_attempt.patches = Override::kDefault;
2410
0
    all_params.push_back(cparams_attempt);
2411
0
    cparams_attempt.options.predictor = Predictor::Zero;
2412
0
    cparams_attempt.options.nb_repeats = 0.01f;
2413
0
    cparams_attempt.palette_colors = 70000;
2414
0
    cparams_attempt.patches = Override::kOff;
2415
0
    cparams_attempt.options.wp_tree_mode = ModularOptions::TreeMode::kNoWP;
2416
0
    all_params.push_back(cparams_attempt);
2417
2418
0
    std::vector<size_t> size;
2419
0
    size.resize(all_params.size());
2420
2421
0
    const auto process_variant = [&](size_t task, size_t) -> Status {
2422
0
      JxlEncoderOutputProcessorWrapper local_output(memory_manager);
2423
0
      JXL_RETURN_IF_ERROR(EncodeFrame(memory_manager, all_params[task],
2424
0
                                      frame_info, metadata, frame_data, cms,
2425
0
                                      nullptr, &local_output, aux_out));
2426
0
      size[task] = local_output.CurrentPosition();
2427
0
      return true;
2428
0
    };
2429
0
    JXL_RETURN_IF_ERROR(RunOnPool(pool, 0, all_params.size(),
2430
0
                                  ThreadPool::NoInit, process_variant,
2431
0
                                  "Compress kTectonicPlate"));
2432
2433
0
    std::vector<CompressParams> all_params_test = all_params;
2434
0
    std::vector<size_t> size_test = size;
2435
0
    size_t best_idx_test = 0;
2436
2437
0
    if (size_test[0] <= size_test[1]) {
2438
0
      all_params = TectonicPlateSettingsLessPalette(cparams_orig);
2439
0
    } else {
2440
0
      best_idx_test = 1;
2441
0
      all_params = TectonicPlateSettingsMorePalette(cparams_orig);
2442
0
    }
2443
2444
0
    size.clear();
2445
0
    size.resize(all_params.size());
2446
2447
0
    JXL_RETURN_IF_ERROR(RunOnPool(pool, 0, all_params.size(),
2448
0
                                  ThreadPool::NoInit, process_variant,
2449
0
                                  "Compress kTectonicPlate"));
2450
2451
0
    size_t best_idx = 0;
2452
0
    for (size_t i = 1; i < all_params.size(); i++) {
2453
0
      if (size[best_idx] > size[i]) {
2454
0
        best_idx = i;
2455
0
      }
2456
0
    }
2457
0
    if (size[best_idx] < size_test[best_idx_test]) {
2458
0
      cparams = all_params[best_idx];
2459
0
    } else {
2460
0
      cparams = all_params_test[best_idx_test];
2461
0
    }
2462
0
  }
2463
2464
283
  JXL_RETURN_IF_ERROR(ParamsPostInit(&cparams));
2465
2466
283
  if (cparams.butteraugli_distance < 0) {
2467
0
    return JXL_FAILURE("Expected non-negative distance");
2468
0
  }
2469
2470
283
  if (cparams.progressive_dc < 0) {
2471
186
    if (cparams.progressive_dc != -1) {
2472
0
      return JXL_FAILURE("Invalid progressive DC setting value (%d)",
2473
0
                         cparams.progressive_dc);
2474
0
    }
2475
186
    cparams.progressive_dc = 0;
2476
186
  }
2477
283
  if (cparams.ec_resampling < cparams.resampling) {
2478
0
    cparams.ec_resampling = cparams.resampling;
2479
0
  }
2480
283
  if (cparams.resampling > 1 || frame_info.is_preview) {
2481
0
    cparams.progressive_dc = 0;
2482
0
  }
2483
2484
283
  if (frame_info.dc_level + cparams.progressive_dc > 4) {
2485
0
    return JXL_FAILURE("Too many levels of progressive DC");
2486
0
  }
2487
2488
283
  if (cparams.modular_mode == false &&
2489
283
      cparams.butteraugli_distance < kMinButteraugliDistance) {
2490
0
    return JXL_FAILURE("Butteraugli distance is too low (%f)",
2491
0
                       cparams.butteraugli_distance);
2492
0
  }
2493
2494
283
  if (frame_data.IsJPEG()) {
2495
0
    cparams.gaborish = Override::kOff;
2496
0
    cparams.epf = 0;
2497
0
    cparams.modular_mode = false;
2498
0
  }
2499
2500
283
  if (frame_data.xsize == 0 || frame_data.ysize == 0) {
2501
0
    return JXL_FAILURE("Empty image");
2502
0
  }
2503
2504
  // Assert that this metadata is correctly set up for the compression params,
2505
  // this should have been done by enc_file.cc
2506
283
  JXL_ENSURE(metadata->m.xyb_encoded ==
2507
283
             (cparams.color_transform == ColorTransform::kXYB));
2508
2509
283
  if (frame_data.IsJPEG() && cparams.color_transform == ColorTransform::kXYB) {
2510
0
    return JXL_FAILURE("Can't add JPEG frame to XYB codestream");
2511
0
  }
2512
2513
283
  if (CanDoStreamingEncoding(cparams, frame_info, *metadata, frame_data)) {
2514
0
    return EncodeFrameStreaming(memory_manager, cparams, frame_info, metadata,
2515
0
                                frame_data, cms, pool, output_processor,
2516
0
                                aux_out);
2517
283
  } else {
2518
283
    return EncodeFrameOneShot(memory_manager, cparams, frame_info, metadata,
2519
283
                              frame_data, cms, pool, output_processor, aux_out);
2520
283
  }
2521
283
}
2522
2523
Status EncodeFrame(JxlMemoryManager* memory_manager,
2524
                   const CompressParams& cparams_orig,
2525
                   const FrameInfo& frame_info, const CodecMetadata* metadata,
2526
                   ImageBundle& ib, const JxlCmsInterface& cms,
2527
97
                   ThreadPool* pool, BitWriter* writer, AuxOut* aux_out) {
2528
97
  JxlEncoderChunkedFrameAdapter frame_data(ib.xsize(), ib.ysize(),
2529
97
                                           ib.extra_channels().size());
2530
97
  std::vector<uint8_t> color;
2531
97
  if (ib.IsJPEG()) {
2532
0
    frame_data.SetJPEGData(std::move(ib.jpeg_data));
2533
97
  } else {
2534
97
    uint32_t num_channels =
2535
97
        ib.IsGray() && frame_info.ib_needs_color_transform ? 1 : 3;
2536
97
    size_t stride = ib.xsize() * num_channels * 4;
2537
97
    color.resize(ib.ysize() * stride);
2538
97
    JXL_RETURN_IF_ERROR(ConvertToExternal(
2539
97
        ib, /*bits_per_sample=*/32, /*float_out=*/true, num_channels,
2540
97
        JXL_NATIVE_ENDIAN, stride, pool, color.data(), color.size(),
2541
97
        /*out_callback=*/{}, Orientation::kIdentity));
2542
97
    JxlPixelFormat format{num_channels, JXL_TYPE_FLOAT, JXL_NATIVE_ENDIAN, 0};
2543
97
    frame_data.SetFromBuffer(0, color.data(), color.size(), format);
2544
97
  }
2545
97
  for (size_t ec = 0; ec < ib.extra_channels().size(); ++ec) {
2546
0
    JxlPixelFormat ec_format{1, JXL_TYPE_FLOAT, JXL_NATIVE_ENDIAN, 0};
2547
0
    size_t ec_stride = ib.xsize() * 4;
2548
0
    std::vector<uint8_t> ec_data(ib.ysize() * ec_stride);
2549
0
    const ImageF* channel = &ib.extra_channels()[ec];
2550
0
    JXL_RETURN_IF_ERROR(ConvertChannelsToExternal(
2551
0
        &channel, 1,
2552
0
        /*bits_per_sample=*/32,
2553
0
        /*float_out=*/true, JXL_NATIVE_ENDIAN, ec_stride, pool, ec_data.data(),
2554
0
        ec_data.size(), /*out_callback=*/{}, Orientation::kIdentity));
2555
0
    frame_data.SetFromBuffer(1 + ec, ec_data.data(), ec_data.size(), ec_format);
2556
0
  }
2557
97
  FrameInfo fi = frame_info;
2558
97
  fi.origin = ib.origin;
2559
97
  fi.blend = ib.blend;
2560
97
  fi.blendmode = ib.blendmode;
2561
97
  fi.duration = ib.duration;
2562
97
  fi.timecode = ib.timecode;
2563
97
  fi.name = ib.name;
2564
97
  JxlEncoderOutputProcessorWrapper output_processor(memory_manager);
2565
97
  JXL_RETURN_IF_ERROR(EncodeFrame(memory_manager, cparams_orig, fi, metadata,
2566
97
                                  frame_data, cms, pool, &output_processor,
2567
97
                                  aux_out));
2568
97
  JXL_RETURN_IF_ERROR(output_processor.SetFinalizedPosition());
2569
97
  std::vector<uint8_t> output;
2570
97
  JXL_RETURN_IF_ERROR(output_processor.CopyOutput(output));
2571
97
  JXL_RETURN_IF_ERROR(writer->AppendByteAligned(Bytes(output)));
2572
97
  return true;
2573
97
}
2574
2575
}  // namespace jxl