Coverage Report

Created: 2025-11-16 07:22

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