Coverage Report

Created: 2025-06-22 08:04

/src/libjxl/lib/jxl/frame_header.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/frame_header.h"
7
8
#if JXL_DEBUG_V_LEVEL >= 1
9
#include <sstream>
10
#include <string>
11
#endif
12
13
#include "lib/jxl/base/printf_macros.h"
14
#include "lib/jxl/base/status.h"
15
#include "lib/jxl/common.h"  // kMaxNumPasses
16
#include "lib/jxl/fields.h"
17
#include "lib/jxl/pack_signed.h"
18
19
namespace jxl {
20
21
constexpr uint8_t YCbCrChromaSubsampling::kHShift[] = {0, 1, 1, 0};
22
constexpr uint8_t YCbCrChromaSubsampling::kVShift[] = {0, 1, 0, 1};
23
24
static Status VisitBlendMode(Visitor* JXL_RESTRICT visitor,
25
442k
                             BlendMode default_value, BlendMode* blend_mode) {
26
442k
  uint32_t encoded = static_cast<uint32_t>(*blend_mode);
27
28
442k
  JXL_QUIET_RETURN_IF_ERROR(visitor->U32(
29
442k
      Val(static_cast<uint32_t>(BlendMode::kReplace)),
30
442k
      Val(static_cast<uint32_t>(BlendMode::kAdd)),
31
442k
      Val(static_cast<uint32_t>(BlendMode::kBlend)), BitsOffset(2, 3),
32
442k
      static_cast<uint32_t>(default_value), &encoded));
33
442k
  if (encoded > static_cast<uint32_t>(BlendMode::kMul)) {
34
21
    return JXL_FAILURE("Invalid blend_mode");
35
21
  }
36
442k
  *blend_mode = static_cast<BlendMode>(encoded);
37
442k
  return true;
38
442k
}
39
40
static Status VisitFrameType(Visitor* JXL_RESTRICT visitor,
41
368k
                             FrameType default_value, FrameType* frame_type) {
42
368k
  uint32_t encoded = static_cast<uint32_t>(*frame_type);
43
44
368k
  JXL_QUIET_RETURN_IF_ERROR(
45
368k
      visitor->U32(Val(static_cast<uint32_t>(FrameType::kRegularFrame)),
46
368k
                   Val(static_cast<uint32_t>(FrameType::kDCFrame)),
47
368k
                   Val(static_cast<uint32_t>(FrameType::kReferenceOnly)),
48
368k
                   Val(static_cast<uint32_t>(FrameType::kSkipProgressive)),
49
368k
                   static_cast<uint32_t>(default_value), &encoded));
50
368k
  *frame_type = static_cast<FrameType>(encoded);
51
368k
  return true;
52
368k
}
53
54
342k
BlendingInfo::BlendingInfo() { Bundle::Init(this); }
55
56
442k
Status BlendingInfo::VisitFields(Visitor* JXL_RESTRICT visitor) {
57
442k
  JXL_QUIET_RETURN_IF_ERROR(
58
442k
      VisitBlendMode(visitor, BlendMode::kReplace, &mode));
59
442k
  if (visitor->Conditional(nonserialized_num_extra_channels > 0 &&
60
442k
                           (mode == BlendMode::kBlend ||
61
359k
                            mode == BlendMode::kAlphaWeightedAdd))) {
62
    // Up to 11 alpha channels for blending.
63
359k
    JXL_QUIET_RETURN_IF_ERROR(visitor->U32(
64
359k
        Val(0), Val(1), Val(2), BitsOffset(3, 3), 0, &alpha_channel));
65
359k
    if (visitor->IsReading() &&
66
359k
        alpha_channel >= nonserialized_num_extra_channels) {
67
17
      return JXL_FAILURE("Invalid alpha channel for blending");
68
17
    }
69
359k
  }
70
441k
  if (visitor->Conditional((nonserialized_num_extra_channels > 0 &&
71
441k
                            (mode == BlendMode::kBlend ||
72
24.7k
                             mode == BlendMode::kAlphaWeightedAdd)) ||
73
441k
                           mode == BlendMode::kMul)) {
74
360k
    JXL_QUIET_RETURN_IF_ERROR(visitor->Bool(false, &clamp));
75
360k
  }
76
  // 'old' frame for blending. Only necessary if this is not a full frame, or
77
  // blending is not kReplace.
78
441k
  if (visitor->Conditional(mode != BlendMode::kReplace ||
79
441k
                           nonserialized_is_partial_frame)) {
80
371k
    JXL_QUIET_RETURN_IF_ERROR(
81
371k
        visitor->U32(Val(0), Val(1), Val(2), Val(3), 0, &source));
82
371k
  }
83
441k
  return true;
84
441k
}
85
86
#if JXL_DEBUG_V_LEVEL >= 1
87
std::string BlendingInfo::DebugString() const {
88
  std::ostringstream os;
89
  os << (mode == BlendMode::kReplace            ? "Replace"
90
         : mode == BlendMode::kAdd              ? "Add"
91
         : mode == BlendMode::kBlend            ? "Blend"
92
         : mode == BlendMode::kAlphaWeightedAdd ? "AlphaWeightedAdd"
93
                                                : "Mul");
94
  if (nonserialized_num_extra_channels > 0 &&
95
      (mode == BlendMode::kBlend || mode == BlendMode::kAlphaWeightedAdd)) {
96
    os << ",alpha=" << alpha_channel << ",clamp=" << clamp;
97
  } else if (mode == BlendMode::kMul) {
98
    os << ",clamp=" << clamp;
99
  }
100
  if (mode != BlendMode::kReplace || nonserialized_is_partial_frame) {
101
    os << ",source=" << source;
102
  }
103
  return os.str();
104
}
105
#endif
106
107
AnimationFrame::AnimationFrame(const CodecMetadata* metadata)
108
258k
    : nonserialized_metadata(metadata) {
109
258k
  Bundle::Init(this);
110
258k
}
111
311k
Status AnimationFrame::VisitFields(Visitor* JXL_RESTRICT visitor) {
112
311k
  if (visitor->Conditional(nonserialized_metadata != nullptr &&
113
311k
                           nonserialized_metadata->m.have_animation)) {
114
311k
    JXL_QUIET_RETURN_IF_ERROR(
115
311k
        visitor->U32(Val(0), Val(1), Bits(8), Bits(32), 0, &duration));
116
311k
  }
117
118
311k
  if (visitor->Conditional(
119
311k
          nonserialized_metadata != nullptr &&
120
311k
          nonserialized_metadata->m.animation.have_timecodes)) {
121
267k
    JXL_QUIET_RETURN_IF_ERROR(visitor->Bits(32, 0, &timecode));
122
267k
  }
123
311k
  return true;
124
311k
}
125
126
500k
YCbCrChromaSubsampling::YCbCrChromaSubsampling() { Bundle::Init(this); }
127
258k
Passes::Passes() { Bundle::Init(this); }
128
356k
Status Passes::VisitFields(Visitor* JXL_RESTRICT visitor) {
129
356k
  JXL_QUIET_RETURN_IF_ERROR(
130
356k
      visitor->U32(Val(1), Val(2), Val(3), BitsOffset(3, 4), 1, &num_passes));
131
356k
  JXL_ENSURE(num_passes <= kMaxNumPasses);  // Cannot happen when reading
132
133
356k
  if (visitor->Conditional(num_passes != 1)) {
134
273k
    JXL_QUIET_RETURN_IF_ERROR(visitor->U32(
135
273k
        Val(0), Val(1), Val(2), BitsOffset(1, 3), 0, &num_downsample));
136
273k
    JXL_ENSURE(num_downsample <= 4);  // 1,2,4,8
137
273k
    if (num_downsample > num_passes) {
138
10
      return JXL_FAILURE("num_downsample %u > num_passes %u", num_downsample,
139
10
                         num_passes);
140
10
    }
141
142
291k
    for (uint32_t i = 0; i < num_passes - 1; i++) {
143
19.2k
      JXL_QUIET_RETURN_IF_ERROR(visitor->Bits(2, 0, &shift[i]));
144
19.2k
    }
145
272k
    shift[num_passes - 1] = 0;
146
147
276k
    for (uint32_t i = 0; i < num_downsample; ++i) {
148
4.47k
      JXL_QUIET_RETURN_IF_ERROR(
149
4.47k
          visitor->U32(Val(1), Val(2), Val(4), Val(8), 1, &downsample[i]));
150
3.83k
      if (i > 0 && downsample[i] >= downsample[i - 1]) {
151
22
        return JXL_FAILURE("downsample sequence should be decreasing");
152
22
      }
153
3.83k
    }
154
274k
    for (uint32_t i = 0; i < num_downsample; ++i) {
155
3.26k
      JXL_QUIET_RETURN_IF_ERROR(
156
3.26k
          visitor->U32(Val(0), Val(1), Val(2), Bits(3), 0, &last_pass[i]));
157
3.02k
      if (i > 0 && last_pass[i] <= last_pass[i - 1]) {
158
8
        return JXL_FAILURE("last_pass sequence should be increasing");
159
8
      }
160
3.01k
      if (last_pass[i] >= num_passes) {
161
12
        return JXL_FAILURE("last_pass %u >= num_passes %u", last_pass[i],
162
12
                           num_passes);
163
12
      }
164
3.01k
    }
165
271k
  }
166
167
355k
  return true;
168
356k
}
169
170
#if JXL_DEBUG_V_LEVEL >= 1
171
std::string Passes::DebugString() const {
172
  std::ostringstream os;
173
  os << "p=" << num_passes;
174
  if (num_downsample) {
175
    os << ",ds=";
176
    for (uint32_t i = 0; i < num_downsample; ++i) {
177
      os << last_pass[i] << ":" << downsample[i];
178
      if (i + 1 < num_downsample) os << ";";
179
    }
180
  }
181
  bool have_shifts = false;
182
  for (uint32_t i = 0; i < num_passes; ++i) {
183
    if (shift[i]) have_shifts = true;
184
  }
185
  if (have_shifts) {
186
    os << ",shifts=";
187
    for (uint32_t i = 0; i < num_passes; ++i) {
188
      os << shift[i];
189
      if (i + 1 < num_passes) os << ";";
190
    }
191
  }
192
  return os.str();
193
}
194
#endif
195
196
FrameHeader::FrameHeader(const CodecMetadata* metadata)
197
258k
    : animation_frame(metadata), nonserialized_metadata(metadata) {
198
258k
  Bundle::Init(this);
199
258k
}
200
201
Status ReadFrameHeader(BitReader* JXL_RESTRICT reader,
202
109k
                       FrameHeader* JXL_RESTRICT frame) {
203
109k
  return Bundle::Read(reader, frame);
204
109k
}
205
206
376k
Status FrameHeader::VisitFields(Visitor* JXL_RESTRICT visitor) {
207
376k
  if (visitor->AllDefault(*this, &all_default)) {
208
    // Overwrite all serialized fields, but not any nonserialized_*.
209
7.99k
    visitor->SetDefault(this);
210
7.99k
    return true;
211
7.99k
  }
212
213
368k
  JXL_QUIET_RETURN_IF_ERROR(
214
368k
      VisitFrameType(visitor, FrameType::kRegularFrame, &frame_type));
215
368k
  if (visitor->IsReading() && nonserialized_is_preview &&
216
368k
      frame_type != kRegularFrame) {
217
4
    return JXL_FAILURE("Only regular frame could be a preview");
218
4
  }
219
220
  // FrameEncoding.
221
368k
  bool is_modular = (encoding == FrameEncoding::kModular);
222
368k
  JXL_QUIET_RETURN_IF_ERROR(visitor->Bool(false, &is_modular));
223
368k
  encoding = (is_modular ? FrameEncoding::kModular : FrameEncoding::kVarDCT);
224
225
  // Flags
226
368k
  JXL_QUIET_RETURN_IF_ERROR(visitor->U64(0, &flags));
227
228
  // Color transform
229
367k
  bool xyb_encoded = nonserialized_metadata == nullptr ||
230
367k
                     nonserialized_metadata->m.xyb_encoded;
231
232
367k
  if (xyb_encoded) {
233
128k
    color_transform = ColorTransform::kXYB;
234
238k
  } else {
235
    // Alternate if kYCbCr.
236
238k
    bool alternate = color_transform == ColorTransform::kYCbCr;
237
238k
    JXL_QUIET_RETURN_IF_ERROR(visitor->Bool(false, &alternate));
238
238k
    color_transform =
239
238k
        (alternate ? ColorTransform::kYCbCr : ColorTransform::kNone);
240
238k
  }
241
242
  // Chroma subsampling for YCbCr, if no DC frame is used.
243
367k
  if (visitor->Conditional(color_transform == ColorTransform::kYCbCr &&
244
367k
                           ((flags & kUseDcFrame) == 0))) {
245
276k
    JXL_QUIET_RETURN_IF_ERROR(visitor->VisitNested(&chroma_subsampling));
246
276k
  }
247
248
367k
  size_t num_extra_channels =
249
367k
      nonserialized_metadata != nullptr
250
367k
          ? nonserialized_metadata->m.extra_channel_info.size()
251
367k
          : 0;
252
253
  // Upsampling
254
367k
  if (visitor->Conditional((flags & kUseDcFrame) == 0)) {
255
361k
    JXL_QUIET_RETURN_IF_ERROR(
256
361k
        visitor->U32(Val(1), Val(2), Val(4), Val(8), 1, &upsampling));
257
361k
    if (nonserialized_metadata != nullptr &&
258
361k
        visitor->Conditional(num_extra_channels != 0)) {
259
275k
      const std::vector<ExtraChannelInfo>& extra_channels =
260
275k
          nonserialized_metadata->m.extra_channel_info;
261
275k
      extra_channel_upsampling.resize(extra_channels.size(), 1);
262
387k
      for (size_t i = 0; i < extra_channels.size(); ++i) {
263
111k
        uint32_t dim_shift =
264
111k
            nonserialized_metadata->m.extra_channel_info[i].dim_shift;
265
111k
        uint32_t& ec_upsampling = extra_channel_upsampling[i];
266
111k
        ec_upsampling >>= dim_shift;
267
111k
        JXL_QUIET_RETURN_IF_ERROR(
268
111k
            visitor->U32(Val(1), Val(2), Val(4), Val(8), 1, &ec_upsampling));
269
111k
        ec_upsampling <<= dim_shift;
270
111k
        if (ec_upsampling < upsampling) {
271
38
          return JXL_FAILURE(
272
38
              "EC upsampling (%u) < color upsampling (%u), which is invalid.",
273
38
              ec_upsampling, upsampling);
274
38
        }
275
111k
        if (ec_upsampling > 8) {
276
15
          return JXL_FAILURE("EC upsampling too large (%u)", ec_upsampling);
277
15
        }
278
111k
      }
279
275k
    } else {
280
85.5k
      extra_channel_upsampling.clear();
281
85.5k
    }
282
361k
  }
283
284
  // Modular- or VarDCT-specific data.
285
366k
  if (visitor->Conditional(encoding == FrameEncoding::kModular)) {
286
304k
    JXL_QUIET_RETURN_IF_ERROR(visitor->Bits(2, 1, &group_size_shift));
287
304k
  }
288
366k
  if (visitor->Conditional(encoding == FrameEncoding::kVarDCT &&
289
366k
                           color_transform == ColorTransform::kXYB)) {
290
272k
    JXL_QUIET_RETURN_IF_ERROR(visitor->Bits(3, 3, &x_qm_scale));
291
271k
    JXL_QUIET_RETURN_IF_ERROR(visitor->Bits(3, 2, &b_qm_scale));
292
271k
  } else {
293
94.6k
    x_qm_scale = b_qm_scale = 2;  // noop
294
94.6k
  }
295
296
  // Not useful for kPatchSource
297
366k
  if (visitor->Conditional(frame_type != FrameType::kReferenceOnly)) {
298
356k
    JXL_QUIET_RETURN_IF_ERROR(visitor->VisitNested(&passes));
299
356k
  }
300
301
364k
  if (visitor->Conditional(frame_type == FrameType::kDCFrame)) {
302
    // Up to 4 pyramid levels - for up to 16384x downsampling.
303
279k
    JXL_QUIET_RETURN_IF_ERROR(
304
279k
        visitor->U32(Val(1), Val(2), Val(3), Val(4), 1, &dc_level));
305
279k
  }
306
364k
  if (frame_type != FrameType::kDCFrame) {
307
352k
    dc_level = 0;
308
352k
  }
309
310
364k
  bool is_partial_frame = false;
311
364k
  if (visitor->Conditional(frame_type != FrameType::kDCFrame)) {
312
352k
    JXL_QUIET_RETURN_IF_ERROR(visitor->Bool(false, &custom_size_or_origin));
313
352k
    if (visitor->Conditional(custom_size_or_origin)) {
314
273k
      const U32Enc enc(Bits(8), BitsOffset(11, 256), BitsOffset(14, 2304),
315
273k
                       BitsOffset(30, 18688));
316
      // Frame offset, only if kRegularFrame or kSkipProgressive.
317
273k
      if (visitor->Conditional(frame_type == FrameType::kRegularFrame ||
318
273k
                               frame_type == FrameType::kSkipProgressive)) {
319
270k
        uint32_t ux0 = PackSigned(frame_origin.x0);
320
270k
        uint32_t uy0 = PackSigned(frame_origin.y0);
321
270k
        JXL_QUIET_RETURN_IF_ERROR(visitor->U32(enc, 0, &ux0));
322
270k
        JXL_QUIET_RETURN_IF_ERROR(visitor->U32(enc, 0, &uy0));
323
269k
        frame_origin.x0 = UnpackSigned(ux0);
324
269k
        frame_origin.y0 = UnpackSigned(uy0);
325
269k
      }
326
      // Frame size
327
273k
      JXL_QUIET_RETURN_IF_ERROR(visitor->U32(enc, 0, &frame_size.xsize));
328
273k
      JXL_QUIET_RETURN_IF_ERROR(visitor->U32(enc, 0, &frame_size.ysize));
329
273k
      if (custom_size_or_origin &&
330
273k
          (frame_size.xsize == 0 || frame_size.ysize == 0)) {
331
19
        return JXL_FAILURE(
332
19
            "Invalid crop dimensions for frame: zero width or height");
333
19
      }
334
272k
      int32_t image_xsize = default_xsize();
335
272k
      int32_t image_ysize = default_ysize();
336
272k
      if (frame_type == FrameType::kRegularFrame ||
337
272k
          frame_type == FrameType::kSkipProgressive) {
338
269k
        is_partial_frame |= frame_origin.x0 > 0;
339
269k
        is_partial_frame |= frame_origin.y0 > 0;
340
269k
        is_partial_frame |= (static_cast<int32_t>(frame_size.xsize) +
341
269k
                             frame_origin.x0) < image_xsize;
342
269k
        is_partial_frame |= (static_cast<int32_t>(frame_size.ysize) +
343
269k
                             frame_origin.y0) < image_ysize;
344
269k
      }
345
272k
    }
346
352k
  }
347
348
  // Blending info, animation info and whether this is the last frame or not.
349
363k
  if (visitor->Conditional(frame_type == FrameType::kRegularFrame ||
350
363k
                           frame_type == FrameType::kSkipProgressive)) {
351
341k
    blending_info.nonserialized_num_extra_channels = num_extra_channels;
352
341k
    blending_info.nonserialized_is_partial_frame = is_partial_frame;
353
341k
    JXL_QUIET_RETURN_IF_ERROR(visitor->VisitNested(&blending_info));
354
341k
    bool replace_all = (blending_info.mode == BlendMode::kReplace);
355
341k
    extra_channel_blending_info.resize(num_extra_channels);
356
434k
    for (size_t i = 0; i < num_extra_channels; i++) {
357
93.8k
      auto& ec_blending_info = extra_channel_blending_info[i];
358
93.8k
      ec_blending_info.nonserialized_is_partial_frame = is_partial_frame;
359
93.8k
      ec_blending_info.nonserialized_num_extra_channels = num_extra_channels;
360
93.8k
      JXL_QUIET_RETURN_IF_ERROR(visitor->VisitNested(&ec_blending_info));
361
93.6k
      replace_all &= (ec_blending_info.mode == BlendMode::kReplace);
362
93.6k
    }
363
341k
    if (visitor->IsReading() && nonserialized_is_preview) {
364
548
      if (!replace_all || custom_size_or_origin) {
365
2
        return JXL_FAILURE("Preview is not compatible with blending");
366
2
      }
367
548
    }
368
341k
    if (visitor->Conditional(nonserialized_metadata != nullptr &&
369
341k
                             nonserialized_metadata->m.have_animation)) {
370
311k
      animation_frame.nonserialized_metadata = nonserialized_metadata;
371
311k
      JXL_QUIET_RETURN_IF_ERROR(visitor->VisitNested(&animation_frame));
372
311k
    }
373
340k
    JXL_QUIET_RETURN_IF_ERROR(visitor->Bool(true, &is_last));
374
340k
  } else {
375
21.4k
    is_last = false;
376
21.4k
  }
377
378
  // ID of that can be used to refer to this frame. 0 for a non-zero-duration
379
  // frame means that it will not be referenced. Not necessary for the last
380
  // frame.
381
362k
  if (visitor->Conditional(frame_type != kDCFrame && !is_last)) {
382
345k
    JXL_QUIET_RETURN_IF_ERROR(
383
345k
        visitor->U32(Val(0), Val(1), Val(2), Val(3), 0, &save_as_reference));
384
345k
  }
385
386
  // If this frame is not blended on another frame post-color-transform, it may
387
  // be stored for being referenced either before or after the color transform.
388
  // If it is blended post-color-transform, it must be blended after. It must
389
  // also be blended after if this is a kRegular frame that does not cover the
390
  // full frame, as samples outside the partial region are from a
391
  // post-color-transform frame.
392
361k
  if (frame_type != FrameType::kDCFrame) {
393
349k
    if (visitor->Conditional(CanBeReferenced() &&
394
349k
                             blending_info.mode == BlendMode::kReplace &&
395
349k
                             !is_partial_frame &&
396
349k
                             (frame_type == FrameType::kRegularFrame ||
397
325k
                              frame_type == FrameType::kSkipProgressive))) {
398
325k
      JXL_QUIET_RETURN_IF_ERROR(
399
325k
          visitor->Bool(false, &save_before_color_transform));
400
325k
    } else if (visitor->Conditional(frame_type == FrameType::kReferenceOnly)) {
401
9.43k
      JXL_QUIET_RETURN_IF_ERROR(
402
9.43k
          visitor->Bool(true, &save_before_color_transform));
403
9.37k
      size_t xsize = custom_size_or_origin ? frame_size.xsize
404
9.37k
                                           : nonserialized_metadata->xsize();
405
9.37k
      size_t ysize = custom_size_or_origin ? frame_size.ysize
406
9.37k
                                           : nonserialized_metadata->ysize();
407
9.37k
      if (!save_before_color_transform &&
408
9.37k
          (xsize < nonserialized_metadata->xsize() ||
409
2.80k
           ysize < nonserialized_metadata->ysize() || frame_origin.x0 != 0 ||
410
2.80k
           frame_origin.y0 != 0)) {
411
16
        return JXL_FAILURE(
412
16
            "non-patch reference frame with invalid crop: %" PRIuS "x%" PRIuS
413
16
            "%+d%+d",
414
16
            xsize, ysize, static_cast<int>(frame_origin.x0),
415
16
            static_cast<int>(frame_origin.y0));
416
16
      }
417
9.37k
    }
418
349k
  } else {
419
11.9k
    save_before_color_transform = true;
420
11.9k
  }
421
422
361k
  JXL_QUIET_RETURN_IF_ERROR(VisitNameString(visitor, &name));
423
424
360k
  loop_filter.nonserialized_is_modular = is_modular;
425
360k
  JXL_RETURN_IF_ERROR(visitor->VisitNested(&loop_filter));
426
427
354k
  JXL_QUIET_RETURN_IF_ERROR(visitor->BeginExtensions(&extensions));
428
  // Extensions: in chronological order of being added to the format.
429
354k
  return visitor->EndExtensions();
430
354k
}
431
432
#if JXL_DEBUG_V_LEVEL >= 1
433
std::string FrameHeader::DebugString() const {
434
  std::ostringstream os;
435
  os << (encoding == FrameEncoding::kVarDCT ? "VarDCT" : "Modular");
436
  os << ",";
437
  os << (frame_type == FrameType::kRegularFrame    ? "Regular"
438
         : frame_type == FrameType::kDCFrame       ? "DC"
439
         : frame_type == FrameType::kReferenceOnly ? "Reference"
440
                                                   : "SkipProgressive");
441
  if (frame_type == FrameType::kDCFrame) {
442
    os << "(lv" << dc_level << ")";
443
  }
444
445
  if (flags) {
446
    os << ",";
447
    uint32_t remaining = flags;
448
449
#define TEST_FLAG(name)           \
450
  if (flags & Flags::k##name) {   \
451
    remaining &= ~Flags::k##name; \
452
    os << #name;                  \
453
    if (remaining) os << "|";     \
454
  }
455
    TEST_FLAG(Noise);
456
    TEST_FLAG(Patches);
457
    TEST_FLAG(Splines);
458
    TEST_FLAG(UseDcFrame);
459
    TEST_FLAG(SkipAdaptiveDCSmoothing);
460
#undef TEST_FLAG
461
  }
462
463
  os << ",";
464
  os << (color_transform == ColorTransform::kXYB     ? "XYB"
465
         : color_transform == ColorTransform::kYCbCr ? "YCbCr"
466
                                                     : "None");
467
468
  if (encoding == FrameEncoding::kModular) {
469
    os << ",shift=" << group_size_shift;
470
  } else if (color_transform == ColorTransform::kXYB) {
471
    os << ",qm=" << x_qm_scale << ";" << b_qm_scale;
472
  }
473
  if (frame_type != FrameType::kReferenceOnly) {
474
    os << "," << passes.DebugString();
475
  }
476
  if (custom_size_or_origin) {
477
    os << ",xs=" << frame_size.xsize;
478
    os << ",ys=" << frame_size.ysize;
479
    if (frame_type == FrameType::kRegularFrame ||
480
        frame_type == FrameType::kSkipProgressive) {
481
      os << ",x0=" << frame_origin.x0;
482
      os << ",y0=" << frame_origin.y0;
483
    }
484
  }
485
  if (upsampling > 1) os << ",up=" << upsampling;
486
  if (loop_filter.gab) os << ",Gaborish";
487
  if (loop_filter.epf_iters > 0) os << ",epf=" << loop_filter.epf_iters;
488
  if (animation_frame.duration > 0) os << ",dur=" << animation_frame.duration;
489
  if (frame_type == FrameType::kRegularFrame ||
490
      frame_type == FrameType::kSkipProgressive) {
491
    os << ",";
492
    os << blending_info.DebugString();
493
    for (size_t i = 0; i < extra_channel_blending_info.size(); ++i) {
494
      os << (i == 0 ? "[" : ";");
495
      os << extra_channel_blending_info[i].DebugString();
496
      if (i + 1 == extra_channel_blending_info.size()) os << "]";
497
    }
498
  }
499
  if (save_as_reference > 0) os << ",ref=" << save_as_reference;
500
  os << "," << (save_before_color_transform ? "before" : "after") << "_ct";
501
  if (is_last) os << ",last";
502
  return os.str();
503
}
504
#endif
505
506
}  // namespace jxl