Coverage Report

Created: 2025-07-23 07:47

/src/libjxl/lib/extras/dec/apng.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
// Parts of this code are taken from apngdis, which has the following license:
7
/* APNG Disassembler 2.8
8
 *
9
 * Deconstructs APNG files into individual frames.
10
 *
11
 * http://apngdis.sourceforge.net
12
 *
13
 * Copyright (c) 2010-2015 Max Stepin
14
 * maxst at users.sourceforge.net
15
 *
16
 * zlib license
17
 * ------------
18
 *
19
 * This software is provided 'as-is', without any express or implied
20
 * warranty.  In no event will the authors be held liable for any damages
21
 * arising from the use of this software.
22
 *
23
 * Permission is granted to anyone to use this software for any purpose,
24
 * including commercial applications, and to alter it and redistribute it
25
 * freely, subject to the following restrictions:
26
 *
27
 * 1. The origin of this software must not be misrepresented; you must not
28
 *    claim that you wrote the original software. If you use this software
29
 *    in a product, an acknowledgment in the product documentation would be
30
 *    appreciated but is not required.
31
 * 2. Altered source versions must be plainly marked as such, and must not be
32
 *    misrepresented as being the original software.
33
 * 3. This notice may not be removed or altered from any source distribution.
34
 *
35
 */
36
37
#include "lib/extras/dec/apng.h"
38
39
// IWYU pragma: no_include <pngconf.h>
40
41
#include "lib/extras/dec/color_hints.h"
42
#include "lib/extras/packed_image.h"
43
#include "lib/extras/size_constraints.h"
44
#include "lib/jxl/base/span.h"
45
#include "lib/jxl/base/status.h"
46
47
#if !JPEGXL_ENABLE_APNG
48
49
namespace jxl {
50
namespace extras {
51
0
bool CanDecodeAPNG() { return false; }
52
Status DecodeImageAPNG(const Span<const uint8_t> bytes,
53
                       const ColorHints& color_hints, PackedPixelFile* ppf,
54
1
                       const SizeConstraints* constraints) {
55
1
  return false;
56
1
}
57
}  // namespace extras
58
}  // namespace jxl
59
60
#else  // JPEGXL_ENABLE_APNG
61
62
#include <jxl/codestream_header.h>
63
#include <jxl/color_encoding.h>
64
#include <jxl/types.h>
65
66
#include <algorithm>
67
#include <array>
68
#include <atomic>
69
#include <csetjmp>
70
#include <cstdint>
71
#include <cstring>
72
#include <limits>
73
#include <memory>
74
#include <string>
75
#include <utility>
76
#include <vector>
77
78
#include "lib/jxl/base/byte_order.h"
79
#include "lib/jxl/base/common.h"
80
#include "lib/jxl/base/compiler_specific.h"
81
#include "lib/jxl/base/printf_macros.h"  // IWYU pragma: keep
82
#include "lib/jxl/base/rect.h"
83
#include "lib/jxl/base/sanitizers.h"
84
#include "png.h" /* original (unpatched) libpng is ok */
85
86
namespace jxl {
87
namespace extras {
88
namespace {
89
90
constexpr std::array<uint8_t, 8> kPngSignature = {137,  'P',  'N', 'G',
91
                                                  '\r', '\n', 26,  '\n'};
92
93
// Returns floating-point value from the PNG encoding (times 10^5).
94
double F64FromU32(const uint32_t x) { return static_cast<int32_t>(x) * 1E-5; }
95
96
/** Extract information from 'sRGB' chunk. */
97
Status DecodeSrgbChunk(const Bytes payload, JxlColorEncoding* color_encoding) {
98
  if (payload.size() != 1) return JXL_FAILURE("Wrong sRGB size");
99
  uint8_t ri = payload[0];
100
  // (PNG uses the same values as ICC.)
101
  if (ri >= 4) return JXL_FAILURE("Invalid Rendering Intent");
102
  color_encoding->white_point = JXL_WHITE_POINT_D65;
103
  color_encoding->primaries = JXL_PRIMARIES_SRGB;
104
  color_encoding->transfer_function = JXL_TRANSFER_FUNCTION_SRGB;
105
  color_encoding->rendering_intent = static_cast<JxlRenderingIntent>(ri);
106
  return true;
107
}
108
109
/**
110
 * Extract information from 'cICP' chunk.
111
 *
112
 * If the cICP profile is not fully supported, return `false` and leave
113
 * `color_encoding` unmodified.
114
 */
115
Status DecodeCicpChunk(const Bytes payload, JxlColorEncoding* color_encoding) {
116
  if (payload.size() != 4) return JXL_FAILURE("Wrong cICP size");
117
  JxlColorEncoding color_enc = *color_encoding;
118
119
  // From https://www.itu.int/rec/T-REC-H.273-202107-I/en
120
  if (payload[0] == 1) {
121
    // IEC 61966-2-1 sRGB
122
    color_enc.primaries = JXL_PRIMARIES_SRGB;
123
    color_enc.white_point = JXL_WHITE_POINT_D65;
124
  } else if (payload[0] == 4) {
125
    // Rec. ITU-R BT.470-6 System M
126
    color_enc.primaries = JXL_PRIMARIES_CUSTOM;
127
    color_enc.primaries_red_xy[0] = 0.67;
128
    color_enc.primaries_red_xy[1] = 0.33;
129
    color_enc.primaries_green_xy[0] = 0.21;
130
    color_enc.primaries_green_xy[1] = 0.71;
131
    color_enc.primaries_blue_xy[0] = 0.14;
132
    color_enc.primaries_blue_xy[1] = 0.08;
133
    color_enc.white_point = JXL_WHITE_POINT_CUSTOM;
134
    color_enc.white_point_xy[0] = 0.310;
135
    color_enc.white_point_xy[1] = 0.316;
136
  } else if (payload[0] == 5) {
137
    // Rec. ITU-R BT.1700-0 625 PAL and 625 SECAM
138
    color_enc.primaries = JXL_PRIMARIES_CUSTOM;
139
    color_enc.primaries_red_xy[0] = 0.64;
140
    color_enc.primaries_red_xy[1] = 0.33;
141
    color_enc.primaries_green_xy[0] = 0.29;
142
    color_enc.primaries_green_xy[1] = 0.60;
143
    color_enc.primaries_blue_xy[0] = 0.15;
144
    color_enc.primaries_blue_xy[1] = 0.06;
145
    color_enc.white_point = JXL_WHITE_POINT_D65;
146
  } else if (payload[0] == 6 || payload[0] == 7) {
147
    // SMPTE ST 170 (2004) / SMPTE ST 240 (1999)
148
    color_enc.primaries = JXL_PRIMARIES_CUSTOM;
149
    color_enc.primaries_red_xy[0] = 0.630;
150
    color_enc.primaries_red_xy[1] = 0.340;
151
    color_enc.primaries_green_xy[0] = 0.310;
152
    color_enc.primaries_green_xy[1] = 0.595;
153
    color_enc.primaries_blue_xy[0] = 0.155;
154
    color_enc.primaries_blue_xy[1] = 0.070;
155
    color_enc.white_point = JXL_WHITE_POINT_D65;
156
  } else if (payload[0] == 8) {
157
    // Generic film (colour filters using Illuminant C)
158
    color_enc.primaries = JXL_PRIMARIES_CUSTOM;
159
    color_enc.primaries_red_xy[0] = 0.681;
160
    color_enc.primaries_red_xy[1] = 0.319;
161
    color_enc.primaries_green_xy[0] = 0.243;
162
    color_enc.primaries_green_xy[1] = 0.692;
163
    color_enc.primaries_blue_xy[0] = 0.145;
164
    color_enc.primaries_blue_xy[1] = 0.049;
165
    color_enc.white_point = JXL_WHITE_POINT_CUSTOM;
166
    color_enc.white_point_xy[0] = 0.310;
167
    color_enc.white_point_xy[1] = 0.316;
168
  } else if (payload[0] == 9) {
169
    // Rec. ITU-R BT.2100-2
170
    color_enc.primaries = JXL_PRIMARIES_2100;
171
    color_enc.white_point = JXL_WHITE_POINT_D65;
172
  } else if (payload[0] == 10) {
173
    // CIE 1931 XYZ
174
    color_enc.primaries = JXL_PRIMARIES_CUSTOM;
175
    color_enc.primaries_red_xy[0] = 1;
176
    color_enc.primaries_red_xy[1] = 0;
177
    color_enc.primaries_green_xy[0] = 0;
178
    color_enc.primaries_green_xy[1] = 1;
179
    color_enc.primaries_blue_xy[0] = 0;
180
    color_enc.primaries_blue_xy[1] = 0;
181
    color_enc.white_point = JXL_WHITE_POINT_E;
182
  } else if (payload[0] == 11) {
183
    // SMPTE RP 431-2 (2011)
184
    color_enc.primaries = JXL_PRIMARIES_P3;
185
    color_enc.white_point = JXL_WHITE_POINT_DCI;
186
  } else if (payload[0] == 12) {
187
    // SMPTE EG 432-1 (2010)
188
    color_enc.primaries = JXL_PRIMARIES_P3;
189
    color_enc.white_point = JXL_WHITE_POINT_D65;
190
  } else if (payload[0] == 22) {
191
    color_enc.primaries = JXL_PRIMARIES_CUSTOM;
192
    color_enc.primaries_red_xy[0] = 0.630;
193
    color_enc.primaries_red_xy[1] = 0.340;
194
    color_enc.primaries_green_xy[0] = 0.295;
195
    color_enc.primaries_green_xy[1] = 0.605;
196
    color_enc.primaries_blue_xy[0] = 0.155;
197
    color_enc.primaries_blue_xy[1] = 0.077;
198
    color_enc.white_point = JXL_WHITE_POINT_D65;
199
  } else {
200
    JXL_WARNING("Unsupported primaries specified in cICP chunk: %d",
201
                static_cast<int>(payload[0]));
202
    return false;
203
  }
204
205
  if (payload[1] == 1 || payload[1] == 6 || payload[1] == 14 ||
206
      payload[1] == 15) {
207
    // Rec. ITU-R BT.709-6
208
    color_enc.transfer_function = JXL_TRANSFER_FUNCTION_709;
209
  } else if (payload[1] == 4) {
210
    // Rec. ITU-R BT.1700-0 625 PAL and 625 SECAM
211
    color_enc.transfer_function = JXL_TRANSFER_FUNCTION_GAMMA;
212
    color_enc.gamma = 1 / 2.2;
213
  } else if (payload[1] == 5) {
214
    // Rec. ITU-R BT.470-6 System B, G
215
    color_enc.transfer_function = JXL_TRANSFER_FUNCTION_GAMMA;
216
    color_enc.gamma = 1 / 2.8;
217
  } else if (payload[1] == 8 || payload[1] == 13 || payload[1] == 16 ||
218
             payload[1] == 17 || payload[1] == 18) {
219
    // These codes all match the corresponding JXL enum values
220
    color_enc.transfer_function = static_cast<JxlTransferFunction>(payload[1]);
221
  } else {
222
    JXL_WARNING("Unsupported transfer function specified in cICP chunk: %d",
223
                static_cast<int>(payload[1]));
224
    return false;
225
  }
226
227
  if (payload[2] != 0) {
228
    JXL_WARNING("Unsupported color space specified in cICP chunk: %d",
229
                static_cast<int>(payload[2]));
230
    return false;
231
  }
232
  if (payload[3] != 1) {
233
    JXL_WARNING("Unsupported full-range flag specified in cICP chunk: %d",
234
                static_cast<int>(payload[3]));
235
    return false;
236
  }
237
  // cICP has no rendering intent, so use the default
238
  color_enc.rendering_intent = JXL_RENDERING_INTENT_RELATIVE;
239
  *color_encoding = color_enc;
240
  return true;
241
}
242
243
/** Extract information from 'gAMA' chunk. */
244
Status DecodeGamaChunk(Bytes payload, JxlColorEncoding* color_encoding) {
245
  if (payload.size() != 4) return JXL_FAILURE("Wrong gAMA size");
246
  color_encoding->transfer_function = JXL_TRANSFER_FUNCTION_GAMMA;
247
  color_encoding->gamma = F64FromU32(LoadBE32(payload.data()));
248
  return true;
249
}
250
251
/** Extract information from 'cHTM' chunk. */
252
Status DecodeChrmChunk(Bytes payload, JxlColorEncoding* color_encoding) {
253
  if (payload.size() != 32) return JXL_FAILURE("Wrong cHRM size");
254
  const uint8_t* data = payload.data();
255
  color_encoding->white_point = JXL_WHITE_POINT_CUSTOM;
256
  color_encoding->white_point_xy[0] = F64FromU32(LoadBE32(data + 0));
257
  color_encoding->white_point_xy[1] = F64FromU32(LoadBE32(data + 4));
258
259
  color_encoding->primaries = JXL_PRIMARIES_CUSTOM;
260
  color_encoding->primaries_red_xy[0] = F64FromU32(LoadBE32(data + 8));
261
  color_encoding->primaries_red_xy[1] = F64FromU32(LoadBE32(data + 12));
262
  color_encoding->primaries_green_xy[0] = F64FromU32(LoadBE32(data + 16));
263
  color_encoding->primaries_green_xy[1] = F64FromU32(LoadBE32(data + 20));
264
  color_encoding->primaries_blue_xy[0] = F64FromU32(LoadBE32(data + 24));
265
  color_encoding->primaries_blue_xy[1] = F64FromU32(LoadBE32(data + 28));
266
  return true;
267
}
268
269
/** Extracts information from 'cLLi' chunk. */
270
Status DecodeClliChunk(Bytes payload, float* max_content_light_level) {
271
  if (payload.size() != 8) return JXL_FAILURE("Wrong cLLi size");
272
  const uint8_t* data = payload.data();
273
  const uint32_t maxcll_png =
274
      Clamp1(png_get_uint_32(data), uint32_t{0}, uint32_t{10000 * 10000});
275
  // Ignore MaxFALL value.
276
  *max_content_light_level = static_cast<float>(maxcll_png) / 10000.f;
277
  return true;
278
}
279
280
/** Returns false if invalid. */
281
JXL_INLINE Status DecodeHexNibble(const char c, uint32_t* JXL_RESTRICT nibble) {
282
  if ('a' <= c && c <= 'f') {
283
    *nibble = 10 + c - 'a';
284
  } else if ('0' <= c && c <= '9') {
285
    *nibble = c - '0';
286
  } else {
287
    *nibble = 0;
288
    return JXL_FAILURE("Invalid metadata nibble");
289
  }
290
  JXL_ENSURE(*nibble < 16);
291
  return true;
292
}
293
294
/** Returns false if invalid. */
295
JXL_INLINE Status DecodeDecimal(const char** pos, const char* end,
296
                                uint32_t* JXL_RESTRICT value) {
297
  size_t len = 0;
298
  *value = 0;
299
  while (*pos < end) {
300
    char next = **pos;
301
    if (next >= '0' && next <= '9') {
302
      *value = (*value * 10) + static_cast<uint32_t>(next - '0');
303
      len++;
304
      if (len > 8) {
305
        break;
306
      }
307
    } else {
308
      // Do not consume terminator (non-decimal digit).
309
      break;
310
    }
311
    (*pos)++;
312
  }
313
  if (len == 0 || len > 8) {
314
    return JXL_FAILURE("Failed to parse decimal");
315
  }
316
  return true;
317
}
318
319
/**
320
 * Parses a PNG text chunk with key of the form "Raw profile type ####", with
321
 * #### a type.
322
 *
323
 * Returns whether it could successfully parse the content.
324
 * We trust key and encoded are null-terminated because they come from
325
 * libpng.
326
 */
327
Status MaybeDecodeBase16(const char* key, const char* encoded,
328
                         std::string* type, std::vector<uint8_t>* bytes) {
329
  const char* encoded_end = encoded + strlen(encoded);
330
331
  const char* kKey = "Raw profile type ";
332
  if (strncmp(key, kKey, strlen(kKey)) != 0) return false;
333
  *type = key + strlen(kKey);
334
  const size_t kMaxTypeLen = 20;
335
  if (type->length() > kMaxTypeLen) return false;  // Type too long
336
337
  // Header: freeform string and number of bytes
338
  // Expected format is:
339
  // \n
340
  // profile name/description\n
341
  //       40\n               (the number of bytes after hex-decoding)
342
  // 01234566789abcdef....\n  (72 bytes per line max).
343
  // 012345667\n              (last line)
344
  const char* pos = encoded;
345
346
  if (*(pos++) != '\n') return false;
347
  while (pos < encoded_end && *pos != '\n') {
348
    pos++;
349
  }
350
  if (pos == encoded_end) return false;
351
  // We parsed so far a \n, some number of non \n characters and are now
352
  // pointing at a \n.
353
  if (*(pos++) != '\n') return false;
354
  // Skip leading spaces
355
  while (pos < encoded_end && *pos == ' ') {
356
    pos++;
357
  }
358
  uint32_t bytes_to_decode = 0;
359
  JXL_RETURN_IF_ERROR(DecodeDecimal(&pos, encoded_end, &bytes_to_decode));
360
361
  // We need 2*bytes for the hex values plus 1 byte every 36 values,
362
  // plus terminal \n for length.
363
  size_t tail = static_cast<size_t>(encoded_end - pos);
364
  bool ok = ((tail / 2) >= bytes_to_decode);
365
  if (ok) tail -= 2 * static_cast<size_t>(bytes_to_decode);
366
  ok = ok && (tail == 1 + DivCeil(bytes_to_decode, 36));
367
  if (!ok) {
368
    return JXL_FAILURE("Not enough bytes to parse %d bytes in hex",
369
                       bytes_to_decode);
370
  }
371
  JXL_ENSURE(bytes->empty());
372
  bytes->reserve(bytes_to_decode);
373
374
  // Encoding: base16 with newline after 72 chars.
375
  // pos points to the \n before the first line of hex values.
376
  for (size_t i = 0; i < bytes_to_decode; ++i) {
377
    if (i % 36 == 0) {
378
      if (pos + 1 >= encoded_end) return false;  // Truncated base16 1
379
      if (*pos != '\n') return false;            // Expected newline
380
      ++pos;
381
    }
382
383
    if (pos + 2 >= encoded_end) return false;  // Truncated base16 2;
384
    uint32_t nibble0;
385
    uint32_t nibble1;
386
    JXL_RETURN_IF_ERROR(DecodeHexNibble(pos[0], &nibble0));
387
    JXL_RETURN_IF_ERROR(DecodeHexNibble(pos[1], &nibble1));
388
    bytes->push_back(static_cast<uint8_t>((nibble0 << 4) + nibble1));
389
    pos += 2;
390
  }
391
  if (pos + 1 != encoded_end) return false;  // Too many encoded bytes
392
  if (pos[0] != '\n') return false;          // Incorrect metadata terminator
393
  return true;
394
}
395
396
/** Retrieves XMP and EXIF/IPTC from itext and text. */
397
Status DecodeBlob(const png_text_struct& info, PackedMetadata* metadata) {
398
  // We trust these are properly null-terminated by libpng.
399
  const char* key = info.key;
400
  const char* value = info.text;
401
  if (strstr(key, "XML:com.adobe.xmp")) {
402
    metadata->xmp.resize(strlen(value));  // safe, see above
403
    memcpy(metadata->xmp.data(), value, metadata->xmp.size());
404
  }
405
406
  std::string type;
407
  std::vector<uint8_t> bytes;
408
409
  // Handle text chunks annotated with key "Raw profile type ####", with
410
  // #### a type, which may contain metadata.
411
  const char* kKey = "Raw profile type ";
412
  if (strncmp(key, kKey, strlen(kKey)) != 0) return false;
413
414
  if (!MaybeDecodeBase16(key, value, &type, &bytes)) {
415
    JXL_WARNING("Couldn't parse 'Raw format type' text chunk");
416
    return false;
417
  }
418
  if (type == "exif") {
419
    // Remove prefix if present.
420
    constexpr std::array<uint8_t, 6> kExifPrefix = {'E', 'x', 'i', 'f', 0, 0};
421
    if (bytes.size() >= kExifPrefix.size() &&
422
        memcmp(bytes.data(), kExifPrefix.data(), kExifPrefix.size()) == 0) {
423
      bytes.erase(bytes.begin(), bytes.begin() + kExifPrefix.size());
424
    }
425
    if (!metadata->exif.empty()) {
426
      JXL_DEBUG_V(2,
427
                  "overwriting EXIF (%" PRIuS " bytes) with base16 (%" PRIuS
428
                  " bytes)",
429
                  metadata->exif.size(), bytes.size());
430
    }
431
    metadata->exif = std::move(bytes);
432
  } else if (type == "iptc") {
433
    // TODO(jon): Deal with IPTC in some way
434
  } else if (type == "8bim") {
435
    // TODO(jon): Deal with 8bim in some way
436
  } else if (type == "xmp") {
437
    if (!metadata->xmp.empty()) {
438
      JXL_DEBUG_V(2,
439
                  "overwriting XMP (%" PRIuS " bytes) with base16 (%" PRIuS
440
                  " bytes)",
441
                  metadata->xmp.size(), bytes.size());
442
    }
443
    metadata->xmp = std::move(bytes);
444
  } else {
445
    JXL_DEBUG_V(
446
        2, "Unknown type in 'Raw format type' text chunk: %s: %" PRIuS " bytes",
447
        type.c_str(), bytes.size());
448
  }
449
  return true;
450
}
451
452
constexpr bool isAbc(char c) {
453
  return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z');
454
}
455
456
/** Wrap 4-char tag name into ID. */
457
constexpr uint32_t MakeTag(uint8_t a, uint8_t b, uint8_t c, uint8_t d) {
458
  return a | (b << 8) | (c << 16) | (d << 24);
459
}
460
461
/** Reusable image data container. */
462
struct Pixels {
463
  // Use array instead of vector to avoid memory initialization.
464
  std::unique_ptr<uint8_t[]> pixels;
465
  size_t pixels_size = 0;
466
  std::vector<uint8_t*> rows;
467
  std::atomic<uint32_t> has_error{0};
468
469
  Status Resize(size_t row_bytes, size_t num_rows) {
470
    size_t new_size = row_bytes * num_rows;  // it is assumed size is sane
471
    if (new_size > pixels_size) {
472
      pixels.reset(new uint8_t[new_size]);
473
      if (!pixels) {
474
        // TODO(szabadka): use specialized OOM error code
475
        return JXL_FAILURE("Failed to allocate memory for image buffer");
476
      }
477
      pixels_size = new_size;
478
    }
479
    rows.resize(num_rows);
480
    for (size_t y = 0; y < num_rows; y++) {
481
      rows[y] = pixels.get() + y * row_bytes;
482
    }
483
    return true;
484
  }
485
};
486
487
/**
488
 * Helper that chunks in-memory input.
489
 */
490
struct Reader {
491
  explicit Reader(Span<const uint8_t> data) : data_(data) {}
492
493
  const Span<const uint8_t> data_;
494
  size_t offset_ = 0;
495
496
  Bytes Peek(size_t len) const {
497
    size_t cap = data_.size() - offset_;
498
    size_t to_copy = std::min(cap, len);
499
    return {data_.data() + offset_, to_copy};
500
  }
501
502
  Bytes Read(size_t len) {
503
    Bytes result = Peek(len);
504
    offset_ += result.size();
505
    return result;
506
  }
507
508
  /* Returns empty Span on error. */
509
  Bytes ReadChunk() {
510
    Bytes len = Peek(4);
511
    if (len.size() != 4) {
512
      return Bytes();
513
    }
514
    const auto size = png_get_uint_32(len.data());
515
    // NB: specification allows 2^31 - 1
516
    constexpr size_t kMaxPNGChunkSize = 1u << 30;  // 1 GB
517
    // Check first, to avoid overflow.
518
    if (size > kMaxPNGChunkSize) {
519
      JXL_WARNING("APNG chunk size is too big");
520
      return Bytes();
521
    }
522
    size_t full_size = size + 12;  // size does not include itself, tag and CRC.
523
    Bytes result = Read(full_size);
524
    return (result.size() == full_size) ? result : Bytes();
525
  }
526
527
  bool Eof() const { return offset_ == data_.size(); }
528
};
529
530
void ProgressiveRead_OnInfo(png_structp png_ptr, png_infop info_ptr) {
531
  png_set_expand(png_ptr);
532
  png_set_palette_to_rgb(png_ptr);
533
  png_set_tRNS_to_alpha(png_ptr);
534
  (void)png_set_interlace_handling(png_ptr);
535
  png_read_update_info(png_ptr, info_ptr);
536
}
537
538
void ProgressiveRead_OnRow(png_structp png_ptr, png_bytep new_row,
539
                           png_uint_32 row_num, int pass) {
540
  Pixels* frame = reinterpret_cast<Pixels*>(png_get_progressive_ptr(png_ptr));
541
  if (!frame) {
542
    JXL_DEBUG_ABORT("Internal logic error");
543
    return;
544
  }
545
  if (row_num >= frame->rows.size()) {
546
    frame->has_error = 1;
547
    return;
548
  }
549
  png_progressive_combine_row(png_ptr, frame->rows[row_num], new_row);
550
}
551
552
// Holds intermediate state during parsing APNG file.
553
struct Context {
554
  ~Context() {
555
    // Make sure png memory is released in any case.
556
    ResetPngDecoder();
557
  }
558
559
  bool CreatePngDecoder() {
560
    png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr,
561
                                     nullptr);
562
    info_ptr = png_create_info_struct(png_ptr);
563
    return (png_ptr != nullptr && info_ptr != nullptr);
564
  }
565
566
  /**
567
   * Initialize PNG decoder.
568
   *
569
   * TODO(eustas): add details
570
   */
571
  bool InitPngDecoder(const std::vector<Bytes>& chunksInfo,
572
                      const RectT<uint64_t>& viewport) {
573
    ResetPngDecoder();
574
575
    png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr,
576
                                     nullptr);
577
    info_ptr = png_create_info_struct(png_ptr);
578
    if (png_ptr == nullptr || info_ptr == nullptr) {
579
      return false;
580
    }
581
582
    if (setjmp(png_jmpbuf(png_ptr))) {
583
      return false;
584
    }
585
586
    /* hIST chunk tail is not processed properly; skip this chunk completely;
587
       see https://github.com/glennrp/libpng/pull/413 */
588
    constexpr std::array<uint8_t, 5> kIgnoredChunks = {'h', 'I', 'S', 'T', 0};
589
    png_set_keep_unknown_chunks(png_ptr, 1, kIgnoredChunks.data(),
590
                                static_cast<int>(kIgnoredChunks.size() / 5));
591
592
    png_set_crc_action(png_ptr, PNG_CRC_QUIET_USE, PNG_CRC_QUIET_USE);
593
    png_set_progressive_read_fn(png_ptr, static_cast<void*>(&frameRaw),
594
                                ProgressiveRead_OnInfo, ProgressiveRead_OnRow,
595
                                nullptr);
596
597
    png_process_data(png_ptr, info_ptr,
598
                     const_cast<uint8_t*>(kPngSignature.data()),
599
                     kPngSignature.size());
600
601
    // Patch dimensions.
602
    png_save_uint_32(ihdr.data() + 8, static_cast<uint32_t>(viewport.xsize()));
603
    png_save_uint_32(ihdr.data() + 12, static_cast<uint32_t>(viewport.ysize()));
604
    png_process_data(png_ptr, info_ptr, ihdr.data(), ihdr.size());
605
606
    for (const auto& chunk : chunksInfo) {
607
      png_process_data(png_ptr, info_ptr, const_cast<uint8_t*>(chunk.data()),
608
                       chunk.size());
609
    }
610
611
    return true;
612
  }
613
614
  /**
615
   * Pass chunk to PNG decoder.
616
   */
617
  bool FeedChunks(const Bytes& chunk1, const Bytes& chunk2 = Bytes()) {
618
    // TODO(eustas): turn to DCHECK
619
    if (!png_ptr || !info_ptr) return false;
620
621
    if (setjmp(png_jmpbuf(png_ptr))) {
622
      return false;
623
    }
624
625
    for (const auto& chunk : {chunk1, chunk2}) {
626
      if (!chunk.empty()) {
627
        png_process_data(png_ptr, info_ptr, const_cast<uint8_t*>(chunk.data()),
628
                         chunk.size());
629
      }
630
    }
631
    return true;
632
  }
633
634
  bool FinalizeStream(PackedMetadata* metadata) {
635
    // TODO(eustas): turn to DCHECK
636
    if (!png_ptr || !info_ptr) return false;
637
638
    if (setjmp(png_jmpbuf(png_ptr))) {
639
      return false;
640
    }
641
642
    const std::array<uint8_t, 12> kFooter = {0,  0,  0,   0,  73, 69,
643
                                             78, 68, 174, 66, 96, 130};
644
    png_process_data(png_ptr, info_ptr, const_cast<uint8_t*>(kFooter.data()),
645
                     kFooter.size());
646
    // before destroying: check if we encountered any metadata chunks
647
    png_textp text_ptr = nullptr;
648
    int num_text = 0;
649
    if (png_get_text(png_ptr, info_ptr, &text_ptr, &num_text) != 0) {
650
      msan::UnpoisonMemory(text_ptr, sizeof(png_text_struct) * num_text);
651
      for (int i = 0; i < num_text; i++) {
652
        Status result = DecodeBlob(text_ptr[i], metadata);
653
        // Ignore unknown / malformed blob.
654
        (void)result;
655
      }
656
    }
657
658
    return true;
659
  }
660
661
  void ResetPngDecoder() {
662
    png_destroy_read_struct(&png_ptr, &info_ptr, nullptr);
663
    // Just in case. Not all versions on libpng wipe-out the pointers.
664
    png_ptr = nullptr;
665
    info_ptr = nullptr;
666
  }
667
668
  std::array<uint8_t, 25> ihdr;  // (modified) copy of file IHDR chunk
669
  png_structp png_ptr = nullptr;
670
  png_infop info_ptr = nullptr;
671
  Pixels frameRaw = {};
672
};
673
674
enum class DisposeOp : uint8_t { NONE = 0, BACKGROUND = 1, PREVIOUS = 2 };
675
676
constexpr uint8_t kLastDisposeOp = static_cast<uint32_t>(DisposeOp::PREVIOUS);
677
678
enum class BlendOp : uint8_t { SOURCE = 0, OVER = 1 };
679
680
constexpr uint8_t kLastBlendOp = static_cast<uint32_t>(BlendOp::OVER);
681
682
// fcTL
683
struct FrameControl {
684
  uint32_t delay_num;
685
  uint32_t delay_den;
686
  RectT<uint64_t> viewport;
687
  DisposeOp dispose_op;
688
  BlendOp blend_op;
689
};
690
691
struct Frame {
692
  PackedImage pixels;
693
  FrameControl metadata;
694
};
695
696
bool ValidateViewport(const RectT<uint64_t>& r) {
697
  constexpr uint32_t kMaxPngDim = 1000000UL;
698
  return (r.xsize() <= kMaxPngDim) && (r.ysize() <= kMaxPngDim);
699
}
700
701
/**
702
 * Setup #channels, bpp, colorspace, etc. from PNG values.
703
 */
704
void SetColorData(PackedPixelFile* ppf, uint8_t color_type, uint8_t bit_depth,
705
                  png_color_8p sig_bits, uint32_t has_transparency) {
706
  bool palette_used = ((color_type & 1) != 0);
707
  bool color_used = ((color_type & 2) != 0);
708
  bool alpha_channel_used = ((color_type & 4) != 0);
709
  if (palette_used) {
710
    if (!color_used || alpha_channel_used) {
711
      JXL_DEBUG_V(2, "Unexpected PNG color type");
712
    }
713
  }
714
715
  ppf->info.bits_per_sample = bit_depth;
716
717
  if (palette_used) {
718
    // palette will actually be 8-bit regardless of the index bitdepth
719
    ppf->info.bits_per_sample = 8;
720
  }
721
  if (color_used) {
722
    ppf->info.num_color_channels = 3;
723
    ppf->color_encoding.color_space = JXL_COLOR_SPACE_RGB;
724
    if (sig_bits) {
725
      if (sig_bits->red == sig_bits->green &&
726
          sig_bits->green == sig_bits->blue) {
727
        ppf->info.bits_per_sample = sig_bits->red;
728
      } else {
729
        int maxbps =
730
            std::max(sig_bits->red, std::max(sig_bits->green, sig_bits->blue));
731
        JXL_DEBUG_V(2,
732
                    "sBIT chunk: bit depths for R, G, and B are not the same "
733
                    "(%i %i %i), while in JPEG XL they have to be the same. "
734
                    "Setting RGB bit depth to %i.",
735
                    sig_bits->red, sig_bits->green, sig_bits->blue, maxbps);
736
        ppf->info.bits_per_sample = maxbps;
737
      }
738
    }
739
  } else {
740
    ppf->info.num_color_channels = 1;
741
    ppf->color_encoding.color_space = JXL_COLOR_SPACE_GRAY;
742
    if (sig_bits) ppf->info.bits_per_sample = sig_bits->gray;
743
  }
744
  if (alpha_channel_used || has_transparency) {
745
    ppf->info.alpha_bits = ppf->info.bits_per_sample;
746
    if (sig_bits && sig_bits->alpha != ppf->info.bits_per_sample) {
747
      JXL_DEBUG_V(2,
748
                  "sBIT chunk: bit depths for RGBA are inconsistent "
749
                  "(%i %i %i %i). Setting A bitdepth to %i.",
750
                  sig_bits->red, sig_bits->green, sig_bits->blue,
751
                  sig_bits->alpha, ppf->info.bits_per_sample);
752
    }
753
  } else {
754
    ppf->info.alpha_bits = 0;
755
  }
756
  ppf->color_encoding.color_space = (ppf->info.num_color_channels == 1)
757
                                        ? JXL_COLOR_SPACE_GRAY
758
                                        : JXL_COLOR_SPACE_RGB;
759
}
760
761
// Color profile chunks: cICP has the highest priority, followed by
762
// iCCP and sRGB (which shouldn't co-exist, but if they do, we use
763
// iCCP), followed finally by gAMA and cHRM.
764
enum class ColorInfoType {
765
  NONE = 0,
766
  GAMA_OR_CHRM = 1,
767
  ICCP_OR_SRGB = 2,
768
  CICP = 3
769
};
770
771
}  // namespace
772
773
bool CanDecodeAPNG() { return true; }
774
775
/**
776
 * Parse and decode PNG file.
777
 *
778
 * Useful PNG chunks:
779
 *   acTL : animation control (#frames, loop count)
780
 *   fcTL : frame control (seq#, viewport, delay, disposal blending)
781
 *   bKGD : preferable background
782
 *   IDAT : "default image"
783
 *          if single fcTL goes before IDAT, then it is also first frame
784
 *   fdAT : seq# + IDAT-like content
785
 *   PLTE : palette
786
 *   cICP : coding-independent code points for video signal type identification
787
 *   iCCP : embedded ICC profile
788
 *   sRGB : standard RGB colour space
789
 *   eXIf : exchangeable image file profile
790
 *   gAMA : image gamma
791
 *   cHRM : primary chromaticities and white point
792
 *   tRNS : transparency
793
 *
794
 * PNG chunk ordering:
795
 *  - IHDR first
796
 *  - IEND last
797
 *  - acTL, cHRM, cICP, gAMA, iCCP, sRGB, bKGD, eXIf, PLTE before IDAT
798
 *  - fdAT after IDAT
799
 *
800
 * More rules:
801
 *  - iCCP and sRGB are exclusive
802
 *  - fcTL and fdAT seq# must be in order fro 0, with no gaps or duplicates
803
 *  - fcTL before corresponding IDAT / fdAT
804
 */
805
Status DecodeImageAPNG(const Span<const uint8_t> bytes,
806
                       const ColorHints& color_hints, PackedPixelFile* ppf,
807
                       const SizeConstraints* constraints) {
808
  // Initialize output (default settings in case e.g. only gAMA is given).
809
  ppf->frames.clear();
810
  ppf->info.exponent_bits_per_sample = 0;
811
  ppf->info.alpha_exponent_bits = 0;
812
  ppf->info.orientation = JXL_ORIENT_IDENTITY;
813
  ppf->color_encoding.color_space = JXL_COLOR_SPACE_RGB;
814
  ppf->color_encoding.white_point = JXL_WHITE_POINT_D65;
815
  ppf->color_encoding.primaries = JXL_PRIMARIES_SRGB;
816
  ppf->color_encoding.transfer_function = JXL_TRANSFER_FUNCTION_SRGB;
817
  ppf->color_encoding.rendering_intent = JXL_RENDERING_INTENT_RELATIVE;
818
819
  Reader input(bytes);
820
821
  // Check signature.
822
  Bytes sig = input.Read(kPngSignature.size());
823
  if (sig.size() != 8 ||
824
      memcmp(sig.data(), kPngSignature.data(), kPngSignature.size()) != 0) {
825
    return false;  // Return silently if it is not a PNG
826
  }
827
828
  // Check IHDR chunk.
829
  Context ctx;
830
  Bytes ihdr = input.ReadChunk();
831
  if (ihdr.size() != ctx.ihdr.size()) {
832
    return JXL_FAILURE("Unexpected first chunk payload size");
833
  }
834
  memcpy(ctx.ihdr.data(), ihdr.data(), ihdr.size());
835
  uint32_t id = LoadLE32(ihdr.data() + 4);
836
  if (id != MakeTag('I', 'H', 'D', 'R')) {
837
    return JXL_FAILURE("First chunk is not IHDR");
838
  }
839
  const RectT<uint64_t> image_rect(0, 0, png_get_uint_32(ihdr.data() + 8),
840
                                   png_get_uint_32(ihdr.data() + 12));
841
  if (!ValidateViewport(image_rect)) {
842
    return JXL_FAILURE("PNG image dimensions are too large");
843
  }
844
845
  // Chunks we supply to PNG decoder for every animation frame.
846
  std::vector<Bytes> passthrough_chunks;
847
  if (!ctx.InitPngDecoder(passthrough_chunks, image_rect)) {
848
    return JXL_FAILURE("Failed to initialize PNG decoder");
849
  }
850
851
  // Marker that this PNG is animated.
852
  bool seen_actl = false;
853
  // First IDAT is a very important milestone; at this moment we freeze
854
  // gathered metadata.
855
  bool seen_idat = false;
856
  // fCTL can occur multiple times, but only once before IDAT.
857
  bool seen_fctl = false;
858
  // Logical EOF.
859
  bool seen_iend = false;
860
861
  ColorInfoType color_info_type = ColorInfoType::NONE;
862
863
  // Flag that we processed some IDAT / fDAT after image / frame start.
864
  bool seen_pixel_data = false;
865
866
  uint32_t num_channels;
867
  JxlPixelFormat format = {};
868
  size_t bytes_per_pixel = 0;
869
  std::vector<Frame> frames;
870
  FrameControl current_frame = {/*delay_num=*/1, /*delay_den=*/10, image_rect,
871
                                DisposeOp::NONE, BlendOp::SOURCE};
872
873
  // Copies frame pixels / metadata from temporary storage.
874
  // TODO(eustas): avoid copying.
875
  const auto finalize_frame = [&]() -> Status {
876
    if (!seen_pixel_data) {
877
      return JXL_FAILURE("Frame / image without fdAT / IDAT chunks");
878
    }
879
    if (!ctx.FinalizeStream(&ppf->metadata)) {
880
      return JXL_FAILURE("Failed to finalize PNG substream");
881
    }
882
    if (ctx.frameRaw.has_error) {
883
      return JXL_FAILURE("Internal error");
884
    }
885
    // Allocates the frame buffer.
886
    const RectT<uint64_t>& vp = current_frame.viewport;
887
    size_t xsize = static_cast<size_t>(vp.xsize());
888
    size_t ysize = static_cast<size_t>(vp.ysize());
889
    JXL_ASSIGN_OR_RETURN(PackedImage image,
890
                         PackedImage::Create(xsize, ysize, format));
891
    for (size_t y = 0; y < ysize; ++y) {
892
      // TODO(eustas): ensure multiplication is safe
893
      memcpy(static_cast<uint8_t*>(image.pixels()) + image.stride * y,
894
             ctx.frameRaw.rows[y], bytes_per_pixel * xsize);
895
    }
896
    frames.push_back(Frame{std::move(image), current_frame});
897
    seen_pixel_data = false;
898
    return true;
899
  };
900
901
  while (!input.Eof()) {
902
    if (seen_iend) {
903
      return JXL_FAILURE("Exuberant input after IEND chunk");
904
    }
905
    Bytes chunk = input.ReadChunk();
906
    if (chunk.empty()) {
907
      return JXL_FAILURE("Malformed chunk");
908
    }
909
    Bytes type(chunk.data() + 4, 4);
910
    id = LoadLE32(type.data());
911
    // Cut 'size' and 'type' at front and 'CRC' at the end.
912
    Bytes payload(chunk.data() + 8, chunk.size() - 12);
913
914
    if (!isAbc(type[0]) || !isAbc(type[1]) || !isAbc(type[2]) ||
915
        !isAbc(type[3])) {
916
      return JXL_FAILURE("Exotic PNG chunk");
917
    }
918
919
    switch (id) {
920
      case MakeTag('a', 'c', 'T', 'L'):
921
        if (seen_idat) {
922
          JXL_DEBUG_V(2, "aCTL after IDAT ignored");
923
          continue;
924
        }
925
        if (seen_actl) {
926
          JXL_DEBUG_V(2, "Duplicate aCTL chunk ignored");
927
          continue;
928
        }
929
        seen_actl = true;
930
        ppf->info.have_animation = JXL_TRUE;
931
        // TODO(eustas): decode from chunk?
932
        ppf->info.animation.tps_numerator = 1000;
933
        ppf->info.animation.tps_denominator = 1;
934
        continue;
935
936
      case MakeTag('I', 'E', 'N', 'D'):
937
        seen_iend = true;
938
        JXL_RETURN_IF_ERROR(finalize_frame());
939
        continue;
940
941
      case MakeTag('f', 'c', 'T', 'L'): {
942
        if (payload.size() != 26) {
943
          return JXL_FAILURE("Unexpected fcTL payload size: %u",
944
                             static_cast<uint32_t>(payload.size()));
945
        }
946
        if (seen_fctl && !seen_idat) {
947
          return JXL_FAILURE("More than one fcTL before IDAT");
948
        }
949
        if (seen_idat && !seen_actl) {
950
          return JXL_FAILURE("fcTL after IDAT, but without acTL");
951
        }
952
        seen_fctl = true;
953
954
        // TODO(eustas): check order?
955
        // sequence_number = png_get_uint_32(payload.data());
956
        RectT<uint64_t> raw_viewport(png_get_uint_32(payload.data() + 12),
957
                                     png_get_uint_32(payload.data() + 16),
958
                                     png_get_uint_32(payload.data() + 4),
959
                                     png_get_uint_32(payload.data() + 8));
960
        uint8_t dispose_op = payload[24];
961
        if (dispose_op > kLastDisposeOp) {
962
          return JXL_FAILURE("Invalid DisposeOp");
963
        }
964
        uint8_t blend_op = payload[25];
965
        if (blend_op > kLastBlendOp) {
966
          return JXL_FAILURE("Invalid BlendOp");
967
        }
968
        FrameControl next_frame = {
969
            /*delay_num=*/png_get_uint_16(payload.data() + 20),
970
            /*delay_den=*/png_get_uint_16(payload.data() + 22), raw_viewport,
971
            static_cast<DisposeOp>(dispose_op), static_cast<BlendOp>(blend_op)};
972
973
        if (!raw_viewport.Intersection(image_rect).IsSame(raw_viewport)) {
974
          // Cropping happened.
975
          return JXL_FAILURE("PNG frame is outside of image rect");
976
        }
977
978
        if (!seen_idat) {
979
          // "Default" image is the first animation frame. Its viewport must
980
          // cover the whole image area.
981
          if (!raw_viewport.IsSame(image_rect)) {
982
            return JXL_FAILURE(
983
                "If the first animation frame is default image, its viewport "
984
                "must cover full image");
985
          }
986
        } else {
987
          JXL_RETURN_IF_ERROR(finalize_frame());
988
          if (!ctx.InitPngDecoder(passthrough_chunks, next_frame.viewport)) {
989
            return JXL_FAILURE("Failed to initialize PNG decoder");
990
          }
991
        }
992
        current_frame = next_frame;
993
        continue;
994
      }
995
996
      case MakeTag('I', 'D', 'A', 'T'): {
997
        if (!frames.empty()) {
998
          return JXL_FAILURE("IDAT after default image is over");
999
        }
1000
        if (!seen_idat) {
1001
          // First IDAT means that all metadata is ready.
1002
          seen_idat = true;
1003
          JXL_ENSURE(image_rect.xsize() ==
1004
                     png_get_image_width(ctx.png_ptr, ctx.info_ptr));
1005
          JXL_ENSURE(image_rect.ysize() ==
1006
                     png_get_image_height(ctx.png_ptr, ctx.info_ptr));
1007
          JXL_RETURN_IF_ERROR(VerifyDimensions(constraints, image_rect.xsize(),
1008
                                               image_rect.ysize()));
1009
          ppf->info.xsize = image_rect.xsize();
1010
          ppf->info.ysize = image_rect.ysize();
1011
1012
          png_color_8p sig_bits = nullptr;
1013
          // Error is OK -> sig_bits remains nullptr.
1014
          png_get_sBIT(ctx.png_ptr, ctx.info_ptr, &sig_bits);
1015
          SetColorData(ppf, png_get_color_type(ctx.png_ptr, ctx.info_ptr),
1016
                       png_get_bit_depth(ctx.png_ptr, ctx.info_ptr), sig_bits,
1017
                       png_get_valid(ctx.png_ptr, ctx.info_ptr, PNG_INFO_tRNS));
1018
          num_channels =
1019
              ppf->info.num_color_channels + (ppf->info.alpha_bits ? 1 : 0);
1020
          format = {
1021
              /*num_channels=*/num_channels,
1022
              /*data_type=*/ppf->info.bits_per_sample > 8 ? JXL_TYPE_UINT16
1023
                                                          : JXL_TYPE_UINT8,
1024
              /*endianness=*/JXL_BIG_ENDIAN,
1025
              /*align=*/0,
1026
          };
1027
          bytes_per_pixel =
1028
              num_channels * (format.data_type == JXL_TYPE_UINT16 ? 2 : 1);
1029
          // TODO(eustas): ensure multiplication is safe
1030
          uint64_t row_bytes =
1031
              static_cast<uint64_t>(image_rect.xsize()) * bytes_per_pixel;
1032
          uint64_t max_rows = std::numeric_limits<size_t>::max() / row_bytes;
1033
          if (image_rect.ysize() > max_rows) {
1034
            return JXL_FAILURE("Image too big.");
1035
          }
1036
          // TODO(eustas): drop frameRaw
1037
          JXL_RETURN_IF_ERROR(
1038
              ctx.frameRaw.Resize(row_bytes, image_rect.ysize()));
1039
        }
1040
1041
        if (!ctx.FeedChunks(chunk)) {
1042
          return JXL_FAILURE("Decoding IDAT failed");
1043
        }
1044
        seen_pixel_data = true;
1045
        continue;
1046
      }
1047
1048
      case MakeTag('f', 'd', 'A', 'T'): {
1049
        if (!seen_idat) {
1050
          return JXL_FAILURE("fdAT chunk before IDAT");
1051
        }
1052
        if (!seen_actl) {
1053
          return JXL_FAILURE("fdAT chunk before acTL");
1054
        }
1055
        /* The 'fdAT' chunk has... the same structure as an 'IDAT' chunk,
1056
         * except preceded by a sequence number. */
1057
        if (payload.size() < 4) {
1058
          return JXL_FAILURE("Corrupted fdAT chunk");
1059
        }
1060
        // Turn 'fdAT' to 'IDAT' by cutting sequence number and replacing tag.
1061
        std::array<uint8_t, 8> preamble;
1062
        png_save_uint_32(preamble.data(), payload.size() - 4);
1063
        memcpy(preamble.data() + 4, "IDAT", 4);
1064
        // Cut-off 'size', 'type' and 'sequence_number'
1065
        Bytes chunk_tail(chunk.data() + 12, chunk.size() - 12);
1066
        if (!ctx.FeedChunks(Bytes(preamble), chunk_tail)) {
1067
          return JXL_FAILURE("Decoding fdAT failed");
1068
        }
1069
        seen_pixel_data = true;
1070
        continue;
1071
      }
1072
1073
      case MakeTag('c', 'I', 'C', 'P'):
1074
        if (color_info_type == ColorInfoType::CICP) {
1075
          JXL_DEBUG_V(2, "Excessive colorspace definition; cICP chunk ignored");
1076
          continue;
1077
        }
1078
        JXL_RETURN_IF_ERROR(DecodeCicpChunk(payload, &ppf->color_encoding));
1079
        ppf->icc.clear();
1080
        ppf->primary_color_representation =
1081
            PackedPixelFile::kColorEncodingIsPrimary;
1082
        color_info_type = ColorInfoType::CICP;
1083
        continue;
1084
1085
      case MakeTag('i', 'C', 'C', 'P'): {
1086
        if (color_info_type == ColorInfoType::ICCP_OR_SRGB) {
1087
          return JXL_FAILURE("Repeated iCCP / sRGB chunk");
1088
        }
1089
        if (color_info_type > ColorInfoType::ICCP_OR_SRGB) {
1090
          JXL_DEBUG_V(2, "Excessive colorspace definition; iCCP chunk ignored");
1091
          continue;
1092
        }
1093
        // Let PNG decoder deal with chunk processing.
1094
        if (!ctx.FeedChunks(chunk)) {
1095
          return JXL_FAILURE("Corrupt iCCP chunk");
1096
        }
1097
1098
        // TODO(jon): catch special case of PQ and synthesize color encoding
1099
        // in that case
1100
        int compression_type = 0;
1101
        png_bytep profile = nullptr;
1102
        png_charp name = nullptr;
1103
        png_uint_32 profile_len = 0;
1104
        png_uint_32 ok =
1105
            png_get_iCCP(ctx.png_ptr, ctx.info_ptr, &name, &compression_type,
1106
                         &profile, &profile_len);
1107
        if (!ok || !profile_len) {
1108
          return JXL_FAILURE("Malformed / incomplete iCCP chunk");
1109
        }
1110
        ppf->icc.assign(profile, profile + profile_len);
1111
        ppf->primary_color_representation = PackedPixelFile::kIccIsPrimary;
1112
        color_info_type = ColorInfoType::ICCP_OR_SRGB;
1113
        continue;
1114
      }
1115
1116
      case MakeTag('s', 'R', 'G', 'B'):
1117
        if (color_info_type == ColorInfoType::ICCP_OR_SRGB) {
1118
          return JXL_FAILURE("Repeated iCCP / sRGB chunk");
1119
        }
1120
        if (color_info_type > ColorInfoType::ICCP_OR_SRGB) {
1121
          JXL_DEBUG_V(2, "Excessive colorspace definition; sRGB chunk ignored");
1122
          continue;
1123
        }
1124
        JXL_RETURN_IF_ERROR(DecodeSrgbChunk(payload, &ppf->color_encoding));
1125
        color_info_type = ColorInfoType::ICCP_OR_SRGB;
1126
        continue;
1127
1128
      case MakeTag('g', 'A', 'M', 'A'):
1129
        if (color_info_type >= ColorInfoType::GAMA_OR_CHRM) {
1130
          JXL_DEBUG_V(2, "Excessive colorspace definition; gAMA chunk ignored");
1131
          continue;
1132
        }
1133
        JXL_RETURN_IF_ERROR(DecodeGamaChunk(payload, &ppf->color_encoding));
1134
        color_info_type = ColorInfoType::GAMA_OR_CHRM;
1135
        continue;
1136
1137
      case MakeTag('c', 'H', 'R', 'M'):
1138
        if (color_info_type >= ColorInfoType::GAMA_OR_CHRM) {
1139
          JXL_DEBUG_V(2, "Excessive colorspace definition; cHRM chunk ignored");
1140
          continue;
1141
        }
1142
        JXL_RETURN_IF_ERROR(DecodeChrmChunk(payload, &ppf->color_encoding));
1143
        color_info_type = ColorInfoType::GAMA_OR_CHRM;
1144
        continue;
1145
1146
      case MakeTag('c', 'L', 'L', 'i'):
1147
        JXL_RETURN_IF_ERROR(
1148
            DecodeClliChunk(payload, &ppf->info.intensity_target));
1149
        continue;
1150
1151
      case MakeTag('e', 'X', 'I', 'f'):
1152
        // TODO(eustas): next eXIF chunk overwrites current; is it ok?
1153
        ppf->metadata.exif.resize(payload.size());
1154
        memcpy(ppf->metadata.exif.data(), payload.data(), payload.size());
1155
        continue;
1156
1157
      default:
1158
        // We don't know what is that, just pass through.
1159
        if (!ctx.FeedChunks(chunk)) {
1160
          return JXL_FAILURE("PNG decoder failed to process chunk");
1161
        }
1162
        // If it happens before IDAT, we consider it metadata and pass to all
1163
        // sub-decoders.
1164
        if (!seen_idat) {
1165
          passthrough_chunks.push_back(chunk);
1166
        }
1167
        continue;
1168
    }
1169
  }
1170
1171
  bool color_is_already_set = (color_info_type != ColorInfoType::NONE);
1172
  bool is_gray = (ppf->info.num_color_channels == 1);
1173
  JXL_RETURN_IF_ERROR(
1174
      ApplyColorHints(color_hints, color_is_already_set, is_gray, ppf));
1175
1176
  if (ppf->color_encoding.transfer_function != JXL_TRANSFER_FUNCTION_PQ) {
1177
    // Reset intensity target, in case we set it from cLLi but TF is not PQ.
1178
    ppf->info.intensity_target = 0.f;
1179
  }
1180
1181
  bool has_nontrivial_background = false;
1182
  bool previous_frame_should_be_cleared = false;
1183
  for (size_t i = 0; i < frames.size(); i++) {
1184
    Frame& frame = frames[i];
1185
    const FrameControl& fc = frame.metadata;
1186
    const RectT<uint64_t> vp = fc.viewport;
1187
    const auto& pixels = frame.pixels;
1188
    size_t xsize = pixels.xsize;
1189
    size_t ysize = pixels.ysize;
1190
    JXL_ENSURE(xsize == vp.xsize());
1191
    JXL_ENSURE(ysize == vp.ysize());
1192
1193
    // Before encountering a DISPOSE_OP_NONE frame, the canvas is filled with
1194
    // 0, so DISPOSE_OP_BACKGROUND and DISPOSE_OP_PREVIOUS are equivalent.
1195
    if (fc.dispose_op == DisposeOp::NONE) {
1196
      has_nontrivial_background = true;
1197
    }
1198
    bool should_blend = fc.blend_op == BlendOp::OVER;
1199
    bool use_for_next_frame =
1200
        has_nontrivial_background && fc.dispose_op != DisposeOp::PREVIOUS;
1201
    size_t x0 = vp.x0();
1202
    size_t y0 = vp.y0();
1203
    if (previous_frame_should_be_cleared) {
1204
      const auto& pvp = frames[i - 1].metadata.viewport;
1205
      size_t px0 = pvp.x0();
1206
      size_t py0 = pvp.y0();
1207
      size_t pxs = pvp.xsize();
1208
      size_t pys = pvp.ysize();
1209
      if (px0 >= x0 && py0 >= y0 && px0 + pxs <= x0 + xsize &&
1210
          py0 + pys <= y0 + ysize && fc.blend_op == BlendOp::SOURCE &&
1211
          use_for_next_frame) {
1212
        // If the previous frame is entirely contained in the current frame
1213
        // and we are using BLEND_OP_SOURCE, nothing special needs to be done.
1214
        ppf->frames.emplace_back(std::move(frame.pixels));
1215
      } else if (px0 == x0 && py0 == y0 && px0 + pxs == x0 + xsize &&
1216
                 py0 + pys == y0 + ysize && use_for_next_frame) {
1217
        // If the new frame has the same size as the old one, but we are
1218
        // blending, we can instead just not blend.
1219
        should_blend = false;
1220
        ppf->frames.emplace_back(std::move(frame.pixels));
1221
      } else if (px0 <= x0 && py0 <= y0 && px0 + pxs >= x0 + xsize &&
1222
                 py0 + pys >= y0 + ysize && use_for_next_frame) {
1223
        // If the new frame is contained within the old frame, we can pad the
1224
        // new frame with zeros and not blend.
1225
        JXL_ASSIGN_OR_RETURN(PackedImage new_data,
1226
                             PackedImage::Create(pxs, pys, pixels.format));
1227
        memset(new_data.pixels(), 0, new_data.pixels_size);
1228
        for (size_t y = 0; y < ysize; y++) {
1229
          JXL_RETURN_IF_ERROR(
1230
              PackedImage::ValidateDataType(new_data.format.data_type));
1231
          size_t pixel_stride =
1232
              PackedImage::BitsPerChannel(new_data.format.data_type) *
1233
              new_data.format.num_channels / 8;
1234
          memcpy(
1235
              static_cast<uint8_t*>(new_data.pixels()) +
1236
                  new_data.stride * (y + y0 - py0) + pixel_stride * (x0 - px0),
1237
              static_cast<const uint8_t*>(pixels.pixels()) + pixels.stride * y,
1238
              xsize * pixel_stride);
1239
        }
1240
1241
        x0 = px0;
1242
        y0 = py0;
1243
        xsize = pxs;
1244
        ysize = pys;
1245
        should_blend = false;
1246
        ppf->frames.emplace_back(std::move(new_data));
1247
      } else {
1248
        // If all else fails, insert a placeholder blank frame with kReplace.
1249
        JXL_ASSIGN_OR_RETURN(PackedImage blank,
1250
                             PackedImage::Create(pxs, pys, pixels.format));
1251
        memset(blank.pixels(), 0, blank.pixels_size);
1252
        ppf->frames.emplace_back(std::move(blank));
1253
        auto& pframe = ppf->frames.back();
1254
        pframe.frame_info.layer_info.crop_x0 = px0;
1255
        pframe.frame_info.layer_info.crop_y0 = py0;
1256
        pframe.frame_info.layer_info.xsize = pxs;
1257
        pframe.frame_info.layer_info.ysize = pys;
1258
        pframe.frame_info.duration = 0;
1259
        bool is_full_size = px0 == 0 && py0 == 0 && pxs == ppf->info.xsize &&
1260
                            pys == ppf->info.ysize;
1261
        pframe.frame_info.layer_info.have_crop = is_full_size ? 0 : 1;
1262
        pframe.frame_info.layer_info.blend_info.blendmode = JXL_BLEND_REPLACE;
1263
        pframe.frame_info.layer_info.blend_info.source = 1;
1264
        pframe.frame_info.layer_info.save_as_reference = 1;
1265
        ppf->frames.emplace_back(std::move(frame.pixels));
1266
      }
1267
    } else {
1268
      ppf->frames.emplace_back(std::move(frame.pixels));
1269
    }
1270
1271
    auto& pframe = ppf->frames.back();
1272
    pframe.frame_info.layer_info.crop_x0 = x0;
1273
    pframe.frame_info.layer_info.crop_y0 = y0;
1274
    pframe.frame_info.layer_info.xsize = xsize;
1275
    pframe.frame_info.layer_info.ysize = ysize;
1276
    pframe.frame_info.duration =
1277
        fc.delay_num * 1000 / (fc.delay_den ? fc.delay_den : 100);
1278
    pframe.frame_info.layer_info.blend_info.blendmode =
1279
        should_blend ? JXL_BLEND_BLEND : JXL_BLEND_REPLACE;
1280
    bool is_full_size = x0 == 0 && y0 == 0 && xsize == ppf->info.xsize &&
1281
                        ysize == ppf->info.ysize;
1282
    pframe.frame_info.layer_info.have_crop = is_full_size ? 0 : 1;
1283
    pframe.frame_info.layer_info.blend_info.source = 1;
1284
    pframe.frame_info.layer_info.blend_info.alpha = 0;
1285
    pframe.frame_info.layer_info.save_as_reference = use_for_next_frame ? 1 : 0;
1286
1287
    previous_frame_should_be_cleared =
1288
        has_nontrivial_background && (fc.dispose_op == DisposeOp::BACKGROUND);
1289
  }
1290
1291
  if (ppf->frames.empty()) return JXL_FAILURE("No frames decoded");
1292
  ppf->frames.back().frame_info.is_last = JXL_TRUE;
1293
1294
  return true;
1295
}
1296
1297
}  // namespace extras
1298
}  // namespace jxl
1299
1300
#endif  // JPEGXL_ENABLE_APNG