Coverage Report

Created: 2024-05-21 06:41

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