Coverage Report

Created: 2022-08-24 06:33

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