Coverage Report

Created: 2024-09-08 07:14

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