summaryrefslogtreecommitdiffstats
path: root/third_party/jpeg-xl/lib/extras/dec/apng.cc
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-06-12 05:43:14 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-06-12 05:43:14 +0000
commit8dd16259287f58f9273002717ec4d27e97127719 (patch)
tree3863e62a53829a84037444beab3abd4ed9dfc7d0 /third_party/jpeg-xl/lib/extras/dec/apng.cc
parentReleasing progress-linux version 126.0.1-1~progress7.99u1. (diff)
downloadfirefox-8dd16259287f58f9273002717ec4d27e97127719.tar.xz
firefox-8dd16259287f58f9273002717ec4d27e97127719.zip
Merging upstream version 127.0.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'third_party/jpeg-xl/lib/extras/dec/apng.cc')
-rw-r--r--third_party/jpeg-xl/lib/extras/dec/apng.cc1226
1 files changed, 632 insertions, 594 deletions
diff --git a/third_party/jpeg-xl/lib/extras/dec/apng.cc b/third_party/jpeg-xl/lib/extras/dec/apng.cc
index c607a71d08..824a6f47ee 100644
--- a/third_party/jpeg-xl/lib/extras/dec/apng.cc
+++ b/third_party/jpeg-xl/lib/extras/dec/apng.cc
@@ -38,8 +38,10 @@
#include <jxl/codestream_header.h>
#include <jxl/encode.h>
-#include <string.h>
+#include <array>
+#include <cstdint>
+#include <cstring>
#include <string>
#include <utility>
#include <vector>
@@ -49,8 +51,7 @@
#include "lib/jxl/base/common.h"
#include "lib/jxl/base/compiler_specific.h"
#include "lib/jxl/base/printf_macros.h"
-#include "lib/jxl/base/scope_guard.h"
-#include "lib/jxl/sanitizers.h"
+#include "lib/jxl/base/span.h"
#if JPEGXL_ENABLE_APNG
#include "png.h" /* original (unpatched) libpng is ok */
#endif
@@ -58,39 +59,49 @@
namespace jxl {
namespace extras {
-#if JPEGXL_ENABLE_APNG
+#if !JPEGXL_ENABLE_APNG
+
+bool CanDecodeAPNG() { return false; }
+Status DecodeImageAPNG(const Span<const uint8_t> bytes,
+ const ColorHints& color_hints, PackedPixelFile* ppf,
+ const SizeConstraints* constraints) {
+ return false;
+}
+
+#else // JPEGXL_ENABLE_APNG
+
namespace {
-constexpr unsigned char kExifSignature[6] = {0x45, 0x78, 0x69,
- 0x66, 0x00, 0x00};
+constexpr uint8_t kExifSignature[6] = {0x45, 0x78, 0x69, 0x66, 0x00, 0x00};
/* hIST chunk tail is not proccesed properly; skip this chunk completely;
see https://github.com/glennrp/libpng/pull/413 */
-const png_byte kIgnoredPngChunks[] = {
- 104, 73, 83, 84, '\0' /* hIST */
-};
+const uint8_t kIgnoredPngChunks[] = {'h', 'I', 'S', 'T', '\0'};
// Returns floating-point value from the PNG encoding (times 10^5).
double F64FromU32(const uint32_t x) { return static_cast<int32_t>(x) * 1E-5; }
-Status DecodeSRGB(const unsigned char* payload, const size_t payload_size,
- JxlColorEncoding* color_encoding) {
- if (payload_size != 1) return JXL_FAILURE("Wrong sRGB size");
+/** Extract information from 'sRGB' chunk. */
+Status DecodeSrgbChunk(const Bytes payload, JxlColorEncoding* color_encoding) {
+ if (payload.size() != 1) return JXL_FAILURE("Wrong sRGB size");
+ uint8_t ri = payload[0];
// (PNG uses the same values as ICC.)
- if (payload[0] >= 4) return JXL_FAILURE("Invalid Rendering Intent");
+ if (ri >= 4) return JXL_FAILURE("Invalid Rendering Intent");
color_encoding->white_point = JXL_WHITE_POINT_D65;
color_encoding->primaries = JXL_PRIMARIES_SRGB;
color_encoding->transfer_function = JXL_TRANSFER_FUNCTION_SRGB;
- color_encoding->rendering_intent =
- static_cast<JxlRenderingIntent>(payload[0]);
+ color_encoding->rendering_intent = static_cast<JxlRenderingIntent>(ri);
return true;
}
-// If the cICP profile is not fully supported, return false and leave
-// color_encoding unmodified.
-Status DecodeCICP(const unsigned char* payload, const size_t payload_size,
- JxlColorEncoding* color_encoding) {
- if (payload_size != 4) return JXL_FAILURE("Wrong cICP size");
+/**
+ * Extract information from 'cICP' chunk.
+ *
+ * If the cICP profile is not fully supported, return `false` and leave
+ * `color_encoding` unmodified.
+ */
+Status DecodeCicpChunk(const Bytes payload, JxlColorEncoding* color_encoding) {
+ if (payload.size() != 4) return JXL_FAILURE("Wrong cICP size");
JxlColorEncoding color_enc = *color_encoding;
// From https://www.itu.int/rec/T-REC-H.273-202107-I/en
@@ -217,257 +228,279 @@ Status DecodeCICP(const unsigned char* payload, const size_t payload_size,
return true;
}
-Status DecodeGAMA(const unsigned char* payload, const size_t payload_size,
- JxlColorEncoding* color_encoding) {
- if (payload_size != 4) return JXL_FAILURE("Wrong gAMA size");
+/** Extract information from 'gAMA' chunk. */
+Status DecodeGamaChunk(Bytes payload, JxlColorEncoding* color_encoding) {
+ if (payload.size() != 4) return JXL_FAILURE("Wrong gAMA size");
color_encoding->transfer_function = JXL_TRANSFER_FUNCTION_GAMMA;
- color_encoding->gamma = F64FromU32(LoadBE32(payload));
+ color_encoding->gamma = F64FromU32(LoadBE32(payload.data()));
return true;
}
-Status DecodeCHRM(const unsigned char* payload, const size_t payload_size,
- JxlColorEncoding* color_encoding) {
- if (payload_size != 32) return JXL_FAILURE("Wrong cHRM size");
-
+/** Extract information from 'cHTM' chunk. */
+Status DecodeChrmChunk(Bytes payload, JxlColorEncoding* color_encoding) {
+ if (payload.size() != 32) return JXL_FAILURE("Wrong cHRM size");
+ const uint8_t* data = payload.data();
color_encoding->white_point = JXL_WHITE_POINT_CUSTOM;
- color_encoding->white_point_xy[0] = F64FromU32(LoadBE32(payload + 0));
- color_encoding->white_point_xy[1] = F64FromU32(LoadBE32(payload + 4));
+ color_encoding->white_point_xy[0] = F64FromU32(LoadBE32(data + 0));
+ color_encoding->white_point_xy[1] = F64FromU32(LoadBE32(data + 4));
color_encoding->primaries = JXL_PRIMARIES_CUSTOM;
- color_encoding->primaries_red_xy[0] = F64FromU32(LoadBE32(payload + 8));
- color_encoding->primaries_red_xy[1] = F64FromU32(LoadBE32(payload + 12));
- color_encoding->primaries_green_xy[0] = F64FromU32(LoadBE32(payload + 16));
- color_encoding->primaries_green_xy[1] = F64FromU32(LoadBE32(payload + 20));
- color_encoding->primaries_blue_xy[0] = F64FromU32(LoadBE32(payload + 24));
- color_encoding->primaries_blue_xy[1] = F64FromU32(LoadBE32(payload + 28));
+ color_encoding->primaries_red_xy[0] = F64FromU32(LoadBE32(data + 8));
+ color_encoding->primaries_red_xy[1] = F64FromU32(LoadBE32(data + 12));
+ color_encoding->primaries_green_xy[0] = F64FromU32(LoadBE32(data + 16));
+ color_encoding->primaries_green_xy[1] = F64FromU32(LoadBE32(data + 20));
+ color_encoding->primaries_blue_xy[0] = F64FromU32(LoadBE32(data + 24));
+ color_encoding->primaries_blue_xy[1] = F64FromU32(LoadBE32(data + 28));
return true;
}
-// Retrieves XMP and EXIF/IPTC from itext and text.
-class BlobsReaderPNG {
- public:
- static Status Decode(const png_text_struct& info, PackedMetadata* metadata) {
- // We trust these are properly null-terminated by libpng.
- const char* key = info.key;
- const char* value = info.text;
- if (strstr(key, "XML:com.adobe.xmp")) {
- metadata->xmp.resize(strlen(value)); // safe, see above
- memcpy(metadata->xmp.data(), value, metadata->xmp.size());
- }
-
- std::string type;
- std::vector<uint8_t> bytes;
-
- // Handle text chunks annotated with key "Raw profile type ####", with
- // #### a type, which may contain metadata.
- const char* kKey = "Raw profile type ";
- if (strncmp(key, kKey, strlen(kKey)) != 0) return false;
+/** Returns false if invalid. */
+JXL_INLINE Status DecodeHexNibble(const char c, uint32_t* JXL_RESTRICT nibble) {
+ if ('a' <= c && c <= 'f') {
+ *nibble = 10 + c - 'a';
+ } else if ('0' <= c && c <= '9') {
+ *nibble = c - '0';
+ } else {
+ *nibble = 0;
+ return JXL_FAILURE("Invalid metadata nibble");
+ }
+ JXL_ASSERT(*nibble < 16);
+ return true;
+}
- if (!MaybeDecodeBase16(key, value, &type, &bytes)) {
- JXL_WARNING("Couldn't parse 'Raw format type' text chunk");
- return false;
- }
- if (type == "exif") {
- // Remove "Exif\0\0" prefix if present
- if (bytes.size() >= sizeof kExifSignature &&
- memcmp(bytes.data(), kExifSignature, sizeof kExifSignature) == 0) {
- bytes.erase(bytes.begin(), bytes.begin() + sizeof kExifSignature);
- }
- if (!metadata->exif.empty()) {
- JXL_WARNING("overwriting EXIF (%" PRIuS " bytes) with base16 (%" PRIuS
- " bytes)",
- metadata->exif.size(), bytes.size());
- }
- metadata->exif = std::move(bytes);
- } else if (type == "iptc") {
- // TODO(jon): Deal with IPTC in some way
- } else if (type == "8bim") {
- // TODO(jon): Deal with 8bim in some way
- } else if (type == "xmp") {
- if (!metadata->xmp.empty()) {
- JXL_WARNING("overwriting XMP (%" PRIuS " bytes) with base16 (%" PRIuS
- " bytes)",
- metadata->xmp.size(), bytes.size());
+/** Returns false if invalid. */
+JXL_INLINE Status DecodeDecimal(const char** pos, const char* end,
+ uint32_t* JXL_RESTRICT value) {
+ size_t len = 0;
+ *value = 0;
+ while (*pos < end) {
+ char next = **pos;
+ if (next >= '0' && next <= '9') {
+ *value = (*value * 10) + static_cast<uint32_t>(next - '0');
+ len++;
+ if (len > 8) {
+ break;
}
- metadata->xmp = std::move(bytes);
} else {
- JXL_WARNING("Unknown type in 'Raw format type' text chunk: %s: %" PRIuS
- " bytes",
- type.c_str(), bytes.size());
+ // Do not consume terminator (non-decimal digit).
+ break;
}
- return true;
+ (*pos)++;
+ }
+ if (len == 0 || len > 8) {
+ return JXL_FAILURE("Failed to parse decimal");
}
+ return true;
+}
- private:
- // Returns false if invalid.
- static JXL_INLINE Status DecodeNibble(const char c,
- uint32_t* JXL_RESTRICT nibble) {
- if ('a' <= c && c <= 'f') {
- *nibble = 10 + c - 'a';
- } else if ('0' <= c && c <= '9') {
- *nibble = c - '0';
- } else {
- *nibble = 0;
- return JXL_FAILURE("Invalid metadata nibble");
+/**
+ * Parses a PNG text chunk with key of the form "Raw profile type ####", with
+ * #### a type.
+ *
+ * Returns whether it could successfully parse the content.
+ * We trust key and encoded are null-terminated because they come from
+ * libpng.
+ */
+Status MaybeDecodeBase16(const char* key, const char* encoded,
+ std::string* type, std::vector<uint8_t>* bytes) {
+ const char* encoded_end = encoded + strlen(encoded);
+
+ const char* kKey = "Raw profile type ";
+ if (strncmp(key, kKey, strlen(kKey)) != 0) return false;
+ *type = key + strlen(kKey);
+ const size_t kMaxTypeLen = 20;
+ if (type->length() > kMaxTypeLen) return false; // Type too long
+
+ // Header: freeform string and number of bytes
+ // Expected format is:
+ // \n
+ // profile name/description\n
+ // 40\n (the number of bytes after hex-decoding)
+ // 01234566789abcdef....\n (72 bytes per line max).
+ // 012345667\n (last line)
+ const char* pos = encoded;
+
+ if (*(pos++) != '\n') return false;
+ while (pos < encoded_end && *pos != '\n') {
+ pos++;
+ }
+ if (pos == encoded_end) return false;
+ // We parsed so far a \n, some number of non \n characters and are now
+ // pointing at a \n.
+ if (*(pos++) != '\n') return false;
+ // Skip leading spaces
+ while (pos < encoded_end && *pos == ' ') {
+ pos++;
+ }
+ uint32_t bytes_to_decode = 0;
+ JXL_RETURN_IF_ERROR(DecodeDecimal(&pos, encoded_end, &bytes_to_decode));
+
+ // We need 2*bytes for the hex values plus 1 byte every 36 values,
+ // plus terminal \n for length.
+ size_t tail = static_cast<size_t>(encoded_end - pos);
+ bool ok = ((tail / 2) >= bytes_to_decode);
+ if (ok) tail -= 2 * static_cast<size_t>(bytes_to_decode);
+ ok = ok && (tail == 1 + DivCeil(bytes_to_decode, 36));
+ if (!ok) {
+ return JXL_FAILURE("Not enough bytes to parse %d bytes in hex",
+ bytes_to_decode);
+ }
+ JXL_ASSERT(bytes->empty());
+ bytes->reserve(bytes_to_decode);
+
+ // Encoding: base16 with newline after 72 chars.
+ // pos points to the \n before the first line of hex values.
+ for (size_t i = 0; i < bytes_to_decode; ++i) {
+ if (i % 36 == 0) {
+ if (pos + 1 >= encoded_end) return false; // Truncated base16 1
+ if (*pos != '\n') return false; // Expected newline
+ ++pos;
}
- JXL_ASSERT(*nibble < 16);
- return true;
+
+ if (pos + 2 >= encoded_end) return false; // Truncated base16 2;
+ uint32_t nibble0;
+ uint32_t nibble1;
+ JXL_RETURN_IF_ERROR(DecodeHexNibble(pos[0], &nibble0));
+ JXL_RETURN_IF_ERROR(DecodeHexNibble(pos[1], &nibble1));
+ bytes->push_back(static_cast<uint8_t>((nibble0 << 4) + nibble1));
+ pos += 2;
}
+ if (pos + 1 != encoded_end) return false; // Too many encoded bytes
+ if (pos[0] != '\n') return false; // Incorrect metadata terminator
+ return true;
+}
- // Returns false if invalid.
- static JXL_INLINE Status DecodeDecimal(const char** pos, const char* end,
- uint32_t* JXL_RESTRICT value) {
- size_t len = 0;
- *value = 0;
- while (*pos < end) {
- char next = **pos;
- if (next >= '0' && next <= '9') {
- *value = (*value * 10) + static_cast<uint32_t>(next - '0');
- len++;
- if (len > 8) {
- break;
- }
- } else {
- // Do not consume terminator (non-decimal digit).
- break;
- }
- (*pos)++;
- }
- if (len == 0 || len > 8) {
- return JXL_FAILURE("Failed to parse decimal");
- }
- return true;
+/** Retrieves XMP and EXIF/IPTC from itext and text. */
+Status DecodeBlob(const png_text_struct& info, PackedMetadata* metadata) {
+ // We trust these are properly null-terminated by libpng.
+ const char* key = info.key;
+ const char* value = info.text;
+ if (strstr(key, "XML:com.adobe.xmp")) {
+ metadata->xmp.resize(strlen(value)); // safe, see above
+ memcpy(metadata->xmp.data(), value, metadata->xmp.size());
}
- // Parses a PNG text chunk with key of the form "Raw profile type ####", with
- // #### a type.
- // Returns whether it could successfully parse the content.
- // We trust key and encoded are null-terminated because they come from
- // libpng.
- static Status MaybeDecodeBase16(const char* key, const char* encoded,
- std::string* type,
- std::vector<uint8_t>* bytes) {
- const char* encoded_end = encoded + strlen(encoded);
-
- const char* kKey = "Raw profile type ";
- if (strncmp(key, kKey, strlen(kKey)) != 0) return false;
- *type = key + strlen(kKey);
- const size_t kMaxTypeLen = 20;
- if (type->length() > kMaxTypeLen) return false; // Type too long
-
- // Header: freeform string and number of bytes
- // Expected format is:
- // \n
- // profile name/description\n
- // 40\n (the number of bytes after hex-decoding)
- // 01234566789abcdef....\n (72 bytes per line max).
- // 012345667\n (last line)
- const char* pos = encoded;
-
- if (*(pos++) != '\n') return false;
- while (pos < encoded_end && *pos != '\n') {
- pos++;
- }
- if (pos == encoded_end) return false;
- // We parsed so far a \n, some number of non \n characters and are now
- // pointing at a \n.
- if (*(pos++) != '\n') return false;
- // Skip leading spaces
- while (pos < encoded_end && *pos == ' ') {
- pos++;
+ std::string type;
+ std::vector<uint8_t> bytes;
+
+ // Handle text chunks annotated with key "Raw profile type ####", with
+ // #### a type, which may contain metadata.
+ const char* kKey = "Raw profile type ";
+ if (strncmp(key, kKey, strlen(kKey)) != 0) return false;
+
+ if (!MaybeDecodeBase16(key, value, &type, &bytes)) {
+ JXL_WARNING("Couldn't parse 'Raw format type' text chunk");
+ return false;
+ }
+ if (type == "exif") {
+ // Remove "Exif\0\0" prefix if present
+ if (bytes.size() >= sizeof kExifSignature &&
+ memcmp(bytes.data(), kExifSignature, sizeof kExifSignature) == 0) {
+ bytes.erase(bytes.begin(), bytes.begin() + sizeof kExifSignature);
}
- uint32_t bytes_to_decode = 0;
- JXL_RETURN_IF_ERROR(DecodeDecimal(&pos, encoded_end, &bytes_to_decode));
-
- // We need 2*bytes for the hex values plus 1 byte every 36 values,
- // plus terminal \n for length.
- size_t tail = static_cast<size_t>(encoded_end - pos);
- bool ok = ((tail / 2) >= bytes_to_decode);
- if (ok) tail -= 2 * static_cast<size_t>(bytes_to_decode);
- ok = ok && (tail == 1 + DivCeil(bytes_to_decode, 36));
- if (!ok) {
- return JXL_FAILURE("Not enough bytes to parse %d bytes in hex",
- bytes_to_decode);
+ if (!metadata->exif.empty()) {
+ JXL_WARNING("overwriting EXIF (%" PRIuS " bytes) with base16 (%" PRIuS
+ " bytes)",
+ metadata->exif.size(), bytes.size());
}
- JXL_ASSERT(bytes->empty());
- bytes->reserve(bytes_to_decode);
-
- // Encoding: base16 with newline after 72 chars.
- // pos points to the \n before the first line of hex values.
- for (size_t i = 0; i < bytes_to_decode; ++i) {
- if (i % 36 == 0) {
- if (pos + 1 >= encoded_end) return false; // Truncated base16 1
- if (*pos != '\n') return false; // Expected newline
- ++pos;
- }
-
- if (pos + 2 >= encoded_end) return false; // Truncated base16 2;
- uint32_t nibble0;
- uint32_t nibble1;
- JXL_RETURN_IF_ERROR(DecodeNibble(pos[0], &nibble0));
- JXL_RETURN_IF_ERROR(DecodeNibble(pos[1], &nibble1));
- bytes->push_back(static_cast<uint8_t>((nibble0 << 4) + nibble1));
- pos += 2;
+ metadata->exif = std::move(bytes);
+ } else if (type == "iptc") {
+ // TODO(jon): Deal with IPTC in some way
+ } else if (type == "8bim") {
+ // TODO(jon): Deal with 8bim in some way
+ } else if (type == "xmp") {
+ if (!metadata->xmp.empty()) {
+ JXL_WARNING("overwriting XMP (%" PRIuS " bytes) with base16 (%" PRIuS
+ " bytes)",
+ metadata->xmp.size(), bytes.size());
}
- if (pos + 1 != encoded_end) return false; // Too many encoded bytes
- if (pos[0] != '\n') return false; // Incorrect metadata terminator
- return true;
+ metadata->xmp = std::move(bytes);
+ } else {
+ JXL_WARNING("Unknown type in 'Raw format type' text chunk: %s: %" PRIuS
+ " bytes",
+ type.c_str(), bytes.size());
}
-};
+ return true;
+}
constexpr bool isAbc(char c) {
return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z');
}
-constexpr uint32_t kId_IHDR = 0x52444849;
-constexpr uint32_t kId_acTL = 0x4C546361;
-constexpr uint32_t kId_fcTL = 0x4C546366;
-constexpr uint32_t kId_IDAT = 0x54414449;
-constexpr uint32_t kId_fdAT = 0x54416466;
-constexpr uint32_t kId_IEND = 0x444E4549;
-constexpr uint32_t kId_cICP = 0x50434963;
-constexpr uint32_t kId_iCCP = 0x50434369;
-constexpr uint32_t kId_sRGB = 0x42475273;
-constexpr uint32_t kId_gAMA = 0x414D4167;
-constexpr uint32_t kId_cHRM = 0x4D524863;
-constexpr uint32_t kId_eXIf = 0x66495865;
-
-struct APNGFrame {
- APNGFrame() : pixels(nullptr, free) {}
- std::unique_ptr<void, decltype(free)*> pixels;
+/** Wrap 4-char tag name into ID. */
+constexpr uint32_t MakeTag(uint8_t a, uint8_t b, uint8_t c, uint8_t d) {
+ return a | (b << 8) | (c << 16) | (d << 24);
+}
+
+/** Reusable image data container. */
+struct Pixels {
+ // Use array instead of vector to avoid memory initialization.
+ std::unique_ptr<uint8_t[]> pixels;
size_t pixels_size = 0;
std::vector<uint8_t*> rows;
- unsigned int w, h, delay_num, delay_den;
- Status Resize(size_t new_size) {
+
+ Status Resize(size_t row_bytes, size_t num_rows) {
+ size_t new_size = row_bytes * num_rows; // it is assumed size is sane
if (new_size > pixels_size) {
- pixels.reset(malloc(new_size));
+ pixels.reset(new uint8_t[new_size]);
if (!pixels) {
// TODO(szabadka): use specialized OOM error code
return JXL_FAILURE("Failed to allocate memory for image buffer");
}
pixels_size = new_size;
}
+ rows.resize(num_rows);
+ for (size_t y = 0; y < num_rows; y++) {
+ rows[y] = pixels.get() + y * row_bytes;
+ }
return true;
}
};
+/**
+ * Helper that chunks in-memory input.
+ */
struct Reader {
- const uint8_t* next;
- const uint8_t* last;
- bool Read(void* data, size_t len) {
- size_t cap = last - next;
+ explicit Reader(Span<const uint8_t> data) : data_(data) {}
+
+ const Span<const uint8_t> data_;
+ size_t offset_ = 0;
+
+ Bytes Peek(size_t len) const {
+ size_t cap = data_.size() - offset_;
size_t to_copy = std::min(cap, len);
- memcpy(data, next, to_copy);
- next += to_copy;
- return (len == to_copy);
+ return {data_.data() + offset_, to_copy};
}
- bool Eof() const { return next == last; }
-};
-const uint32_t cMaxPNGSize = 1000000UL;
-const size_t kMaxPNGChunkSize = 1lu << 30; // 1 GB
+ Bytes Read(size_t len) {
+ Bytes result = Peek(len);
+ offset_ += result.size();
+ return result;
+ }
-void info_fn(png_structp png_ptr, png_infop info_ptr) {
+ /* Returns empty Span on errror. */
+ Bytes ReadChunk() {
+ Bytes len = Peek(4);
+ if (len.size() != 4) {
+ return Bytes();
+ }
+ const auto size = png_get_uint_32(len.data());
+ // NB: specification allows 2^31 - 1
+ constexpr size_t kMaxPNGChunkSize = 1u << 30; // 1 GB
+ // Check first, to avoid overflow.
+ if (size > kMaxPNGChunkSize) {
+ JXL_WARNING("APNG chunk size is too big");
+ return Bytes();
+ }
+ size_t full_size = size + 12; // size does not include itself, tag and CRC.
+ Bytes result = Read(full_size);
+ return (result.size() == full_size) ? result : Bytes();
+ }
+
+ bool Eof() const { return offset_ == data_.size(); }
+};
+
+void ProgressiveRead_OnInfo(png_structp png_ptr, png_infop info_ptr) {
png_set_expand(png_ptr);
png_set_palette_to_rgb(png_ptr);
png_set_tRNS_to_alpha(png_ptr);
@@ -475,432 +508,437 @@ void info_fn(png_structp png_ptr, png_infop info_ptr) {
png_read_update_info(png_ptr, info_ptr);
}
-void row_fn(png_structp png_ptr, png_bytep new_row, png_uint_32 row_num,
- int pass) {
- APNGFrame* frame =
- reinterpret_cast<APNGFrame*>(png_get_progressive_ptr(png_ptr));
+void ProgressiveRead_OnRow(png_structp png_ptr, png_bytep new_row,
+ png_uint_32 row_num, int pass) {
+ Pixels* frame = reinterpret_cast<Pixels*>(png_get_progressive_ptr(png_ptr));
JXL_CHECK(frame);
JXL_CHECK(row_num < frame->rows.size());
JXL_CHECK(frame->rows[row_num] < frame->rows[0] + frame->pixels_size);
png_progressive_combine_row(png_ptr, frame->rows[row_num], new_row);
}
-inline unsigned int read_chunk(Reader* r, std::vector<uint8_t>* pChunk) {
- unsigned char len[4];
- if (r->Read(&len, 4)) {
- const auto size = png_get_uint_32(len);
- // Check first, to avoid overflow.
- if (size > kMaxPNGChunkSize) {
- JXL_WARNING("APNG chunk size is too big");
- return 0;
- }
- pChunk->resize(size + 12);
- memcpy(pChunk->data(), len, 4);
- if (r->Read(pChunk->data() + 4, pChunk->size() - 4)) {
- return LoadLE32(pChunk->data() + 4);
- }
+// Holds intermediate state during parsing APNG file.
+struct Context {
+ ~Context() {
+ // Make sure png memory is released in any case.
+ ResetPngDecoder();
}
- return 0;
-}
-
-int processing_start(png_structp& png_ptr, png_infop& info_ptr, void* frame_ptr,
- bool hasInfo, std::vector<uint8_t>& chunkIHDR,
- std::vector<std::vector<uint8_t>>& chunksInfo) {
- unsigned char header[8] = {137, 80, 78, 71, 13, 10, 26, 10};
- // Cleanup prior decoder, if any.
- png_destroy_read_struct(&png_ptr, &info_ptr, nullptr);
- // Just in case. Not all versions on libpng wipe-out the pointers.
- png_ptr = nullptr;
- info_ptr = nullptr;
+ bool CreatePngDecoder() {
+ png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr,
+ nullptr);
+ info_ptr = png_create_info_struct(png_ptr);
+ return (png_ptr != nullptr && info_ptr != nullptr);
+ }
- png_ptr =
- png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr);
- info_ptr = png_create_info_struct(png_ptr);
- if (!png_ptr || !info_ptr) return 1;
+ /**
+ * Initialize PNG decoder.
+ *
+ * TODO(eustas): add details
+ */
+ bool InitPngDecoder(bool hasInfo, std::vector<Bytes>& chunksInfo) {
+ ResetPngDecoder();
+
+ png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr,
+ nullptr);
+ info_ptr = png_create_info_struct(png_ptr);
+ if (png_ptr == nullptr || info_ptr == nullptr) {
+ return false;
+ }
- if (setjmp(png_jmpbuf(png_ptr))) {
- return 1;
- }
+ if (setjmp(png_jmpbuf(png_ptr))) {
+ return false;
+ }
- png_set_keep_unknown_chunks(png_ptr, 1, kIgnoredPngChunks,
- static_cast<int>(sizeof(kIgnoredPngChunks) / 5));
+ png_set_keep_unknown_chunks(
+ png_ptr, 1, kIgnoredPngChunks,
+ static_cast<int>(sizeof(kIgnoredPngChunks) / 5));
- png_set_crc_action(png_ptr, PNG_CRC_QUIET_USE, PNG_CRC_QUIET_USE);
- png_set_progressive_read_fn(png_ptr, frame_ptr, info_fn, row_fn, nullptr);
+ png_set_crc_action(png_ptr, PNG_CRC_QUIET_USE, PNG_CRC_QUIET_USE);
+ png_set_progressive_read_fn(png_ptr, static_cast<void*>(&frameRaw),
+ ProgressiveRead_OnInfo, ProgressiveRead_OnRow,
+ nullptr);
- png_process_data(png_ptr, info_ptr, header, 8);
- png_process_data(png_ptr, info_ptr, chunkIHDR.data(), chunkIHDR.size());
+ std::array<uint8_t, 8> header = {137, 80, 78, 71, 13, 10, 26, 10};
+ png_process_data(png_ptr, info_ptr, header.data(), header.size());
+ png_process_data(png_ptr, info_ptr, ihdr.data(), ihdr.size());
- if (hasInfo) {
- for (auto& chunk : chunksInfo) {
- png_process_data(png_ptr, info_ptr, chunk.data(), chunk.size());
+ if (hasInfo) {
+ for (auto& chunk : chunksInfo) {
+ png_process_data(png_ptr, info_ptr, const_cast<uint8_t*>(chunk.data()),
+ chunk.size());
+ }
}
+ return true;
}
- return 0;
-}
-int processing_data(png_structp png_ptr, png_infop info_ptr, unsigned char* p,
- unsigned int size) {
- if (!png_ptr || !info_ptr) return 1;
+ /**
+ * Pass chunk to PNG decoder.
+ */
+ bool FeedChunks(const Bytes& chunk1, const Bytes& chunk2 = Bytes()) {
+ // TODO(eustas): turn to DCHECK
+ if (!png_ptr || !info_ptr) return false;
+
+ if (setjmp(png_jmpbuf(png_ptr))) {
+ return false;
+ }
- if (setjmp(png_jmpbuf(png_ptr))) {
- return 1;
+ for (const auto& chunk : {chunk1, chunk2}) {
+ if (!chunk.empty()) {
+ png_process_data(png_ptr, info_ptr, const_cast<uint8_t*>(chunk.data()),
+ chunk.size());
+ }
+ }
+ return true;
}
- png_process_data(png_ptr, info_ptr, p, size);
- return 0;
-}
+ bool FinalizeStream(PackedMetadata* metadata) {
+ // TODO(eustas): turn to DCHECK
+ if (!png_ptr || !info_ptr) return false;
-int processing_finish(png_structp png_ptr, png_infop info_ptr,
- PackedMetadata* metadata) {
- unsigned char footer[12] = {0, 0, 0, 0, 73, 69, 78, 68, 174, 66, 96, 130};
+ if (setjmp(png_jmpbuf(png_ptr))) {
+ return false;
+ }
- if (!png_ptr || !info_ptr) return 1;
+ const std::array<uint8_t, 12> kFooter = {0, 0, 0, 0, 73, 69,
+ 78, 68, 174, 66, 96, 130};
+ png_process_data(png_ptr, info_ptr, const_cast<uint8_t*>(kFooter.data()),
+ kFooter.size());
+ // before destroying: check if we encountered any metadata chunks
+ png_textp text_ptr;
+ int num_text;
+ png_get_text(png_ptr, info_ptr, &text_ptr, &num_text);
+ for (int i = 0; i < num_text; i++) {
+ Status result = DecodeBlob(text_ptr[i], metadata);
+ // Ignore unknown / malformed blob.
+ (void)result;
+ }
- if (setjmp(png_jmpbuf(png_ptr))) {
- return 1;
+ return true;
}
- png_process_data(png_ptr, info_ptr, footer, 12);
- // before destroying: check if we encountered any metadata chunks
- png_textp text_ptr;
- int num_text;
- png_get_text(png_ptr, info_ptr, &text_ptr, &num_text);
- for (int i = 0; i < num_text; i++) {
- (void)BlobsReaderPNG::Decode(text_ptr[i], metadata);
+ void ResetPngDecoder() {
+ png_destroy_read_struct(&png_ptr, &info_ptr, nullptr);
+ // Just in case. Not all versions on libpng wipe-out the pointers.
+ png_ptr = nullptr;
+ info_ptr = nullptr;
}
- return 0;
-}
+ std::vector<uint8_t> ihdr; // (modified) copy of file IHDR chunk
+ png_structp png_ptr = nullptr;
+ png_infop info_ptr = nullptr;
+ Pixels frameRaw = {};
+};
+
+constexpr uint32_t kMaxPNGSize = 1000000UL;
+
+struct FrameInfo {
+ PackedImage data;
+ uint32_t duration;
+ size_t x0, xsize;
+ size_t y0, ysize;
+ uint32_t dispose_op;
+ uint32_t blend_op;
+};
} // namespace
-#endif
-bool CanDecodeAPNG() {
-#if JPEGXL_ENABLE_APNG
- return true;
-#else
- return false;
-#endif
-}
+bool CanDecodeAPNG() { return true; }
Status DecodeImageAPNG(const Span<const uint8_t> bytes,
const ColorHints& color_hints, PackedPixelFile* ppf,
const SizeConstraints* constraints) {
-#if JPEGXL_ENABLE_APNG
- Reader r;
- unsigned char sig[8];
- png_structp png_ptr = nullptr;
- png_infop info_ptr = nullptr;
- std::vector<uint8_t> chunk;
- std::vector<uint8_t> chunkIHDR;
- std::vector<std::vector<uint8_t>> chunksInfo;
- bool isAnimated = false;
- bool hasInfo = false;
- bool seenFctl = false;
- APNGFrame frameRaw = {};
- uint32_t num_channels;
- JxlPixelFormat format = {};
- unsigned int bytes_per_pixel = 0;
-
- struct FrameInfo {
- PackedImage data;
- uint32_t duration;
- size_t x0, xsize;
- size_t y0, ysize;
- uint32_t dispose_op;
- uint32_t blend_op;
- };
-
- std::vector<FrameInfo> frames;
+ // Initialize output (default settings in case e.g. only gAMA is given).
+ ppf->frames.clear();
+ ppf->info.exponent_bits_per_sample = 0;
+ ppf->info.alpha_exponent_bits = 0;
+ ppf->info.orientation = JXL_ORIENT_IDENTITY;
+ ppf->color_encoding.color_space = JXL_COLOR_SPACE_RGB;
+ ppf->color_encoding.white_point = JXL_WHITE_POINT_D65;
+ ppf->color_encoding.primaries = JXL_PRIMARIES_SRGB;
+ ppf->color_encoding.transfer_function = JXL_TRANSFER_FUNCTION_SRGB;
+ ppf->color_encoding.rendering_intent = JXL_RENDERING_INTENT_RELATIVE;
- // Make sure png memory is released in any case.
- auto scope_guard = MakeScopeGuard([&]() {
- png_destroy_read_struct(&png_ptr, &info_ptr, nullptr);
- // Just in case. Not all versions on libpng wipe-out the pointers.
- png_ptr = nullptr;
- info_ptr = nullptr;
- });
+ Reader input(bytes);
- r = {bytes.data(), bytes.data() + bytes.size()};
// Not a PNG => not an error
unsigned char png_signature[8] = {137, 80, 78, 71, 13, 10, 26, 10};
- if (!r.Read(sig, 8) || memcmp(sig, png_signature, 8) != 0) {
+ Bytes sig = input.Read(8);
+ if (sig.size() != 8 || memcmp(sig.data(), png_signature, 8) != 0) {
return false;
}
- unsigned int id = read_chunk(&r, &chunkIHDR);
- ppf->info.exponent_bits_per_sample = 0;
- ppf->info.alpha_exponent_bits = 0;
- ppf->info.orientation = JXL_ORIENT_IDENTITY;
+ Bytes chunk_ihdr = input.ReadChunk();
+ if (chunk_ihdr.empty()) {
+ return false;
+ }
+ uint32_t id = LoadLE32(chunk_ihdr.data() + 4);
+ if (id != MakeTag('I', 'H', 'D', 'R') || chunk_ihdr.size() != 25) {
+ return false;
+ }
+ const uint32_t w = png_get_uint_32(chunk_ihdr.data() + 8);
+ const uint32_t h = png_get_uint_32(chunk_ihdr.data() + 12);
+ if (w > kMaxPNGSize || h > kMaxPNGSize) {
+ return false;
+ }
- ppf->frames.clear();
+ Context ctx;
+ ctx.ihdr = chunk_ihdr.Copy();
+
+ std::vector<Bytes> chunksInfo;
+ if (!ctx.InitPngDecoder(false, chunksInfo)) {
+ return false;
+ }
+ bool isAnimated = false;
+ bool hasInfo = false;
+ uint32_t num_channels;
+ JxlPixelFormat format = {};
+ unsigned int bytes_per_pixel = 0;
+ std::vector<FrameInfo> frames;
bool have_color = false;
bool have_cicp = false;
bool have_iccp = false;
bool have_srgb = false;
- bool errorstate = true;
- if (id == kId_IHDR && chunkIHDR.size() == 25) {
- uint32_t x0 = 0;
- uint32_t y0 = 0;
- uint32_t delay_num = 1;
- uint32_t delay_den = 10;
- uint32_t dop = 0;
- uint32_t bop = 0;
-
- uint32_t w = png_get_uint_32(chunkIHDR.data() + 8);
- uint32_t h = png_get_uint_32(chunkIHDR.data() + 12);
- uint32_t w0 = w;
- uint32_t h0 = h;
- if (w > cMaxPNGSize || h > cMaxPNGSize) {
+ uint32_t x0 = 0;
+ uint32_t y0 = 0;
+ uint32_t delay_num = 1;
+ uint32_t delay_den = 10;
+ uint32_t dop = 0;
+ uint32_t bop = 0;
+ uint32_t w0 = w;
+ uint32_t h0 = h;
+
+ while (!input.Eof()) {
+ Bytes chunk = input.ReadChunk();
+ if (chunk.empty()) {
return false;
}
-
- // default settings in case e.g. only gAMA is given
- ppf->color_encoding.color_space = JXL_COLOR_SPACE_RGB;
- ppf->color_encoding.white_point = JXL_WHITE_POINT_D65;
- ppf->color_encoding.primaries = JXL_PRIMARIES_SRGB;
- ppf->color_encoding.transfer_function = JXL_TRANSFER_FUNCTION_SRGB;
- ppf->color_encoding.rendering_intent = JXL_RENDERING_INTENT_RELATIVE;
-
- if (!processing_start(png_ptr, info_ptr, static_cast<void*>(&frameRaw),
- hasInfo, chunkIHDR, chunksInfo)) {
- while (!r.Eof()) {
- id = read_chunk(&r, &chunk);
- if (!id) break;
- seenFctl |= (id == kId_fcTL);
-
- if (id == kId_acTL && !hasInfo && !isAnimated) {
- isAnimated = true;
- ppf->info.have_animation = JXL_TRUE;
- ppf->info.animation.tps_numerator = 1000;
- ppf->info.animation.tps_denominator = 1;
- } else if (id == kId_IEND ||
- (id == kId_fcTL && (!hasInfo || isAnimated))) {
- if (hasInfo) {
- if (!processing_finish(png_ptr, info_ptr, &ppf->metadata)) {
- // Allocates the frame buffer.
- uint32_t duration = delay_num * 1000 / delay_den;
- JXL_ASSIGN_OR_RETURN(PackedImage image,
- PackedImage::Create(w0, h0, format));
- frames.push_back(FrameInfo{std::move(image), duration, x0, w0, y0,
- h0, dop, bop});
- auto& frame = frames.back().data;
- for (size_t y = 0; y < h0; ++y) {
- memcpy(static_cast<uint8_t*>(frame.pixels()) + frame.stride * y,
- frameRaw.rows[y], bytes_per_pixel * w0);
- }
- } else {
- break;
- }
+ id = LoadLE32(chunk.data() + 4);
+
+ if (id == MakeTag('a', 'c', 'T', 'L') && !hasInfo && !isAnimated) {
+ isAnimated = true;
+ ppf->info.have_animation = JXL_TRUE;
+ ppf->info.animation.tps_numerator = 1000;
+ ppf->info.animation.tps_denominator = 1;
+ } else if (id == MakeTag('I', 'E', 'N', 'D') ||
+ (id == MakeTag('f', 'c', 'T', 'L') &&
+ (!hasInfo || isAnimated))) {
+ if (hasInfo) {
+ if (ctx.FinalizeStream(&ppf->metadata)) {
+ // Allocates the frame buffer.
+ uint32_t duration = delay_num * 1000 / delay_den;
+ JXL_ASSIGN_OR_RETURN(PackedImage image,
+ PackedImage::Create(w0, h0, format));
+ frames.push_back(
+ FrameInfo{std::move(image), duration, x0, w0, y0, h0, dop, bop});
+ auto& frame = frames.back().data;
+ for (size_t y = 0; y < h0; ++y) {
+ // TODO(eustas): ensure multiplication is safe
+ memcpy(static_cast<uint8_t*>(frame.pixels()) + frame.stride * y,
+ ctx.frameRaw.rows[y], bytes_per_pixel * w0);
}
+ } else {
+ return false;
+ }
+ }
- if (id == kId_IEND) {
- errorstate = false;
- break;
- }
- if (chunk.size() < 34) {
- return JXL_FAILURE("Received a chunk that is too small (%" PRIuS
- "B)",
- chunk.size());
- }
- // At this point the old frame is done. Let's start a new one.
- w0 = png_get_uint_32(chunk.data() + 12);
- h0 = png_get_uint_32(chunk.data() + 16);
- x0 = png_get_uint_32(chunk.data() + 20);
- y0 = png_get_uint_32(chunk.data() + 24);
- delay_num = png_get_uint_16(chunk.data() + 28);
- delay_den = png_get_uint_16(chunk.data() + 30);
- dop = chunk[32];
- bop = chunk[33];
-
- if (!delay_den) delay_den = 100;
-
- if (w0 > cMaxPNGSize || h0 > cMaxPNGSize || x0 > cMaxPNGSize ||
- y0 > cMaxPNGSize || x0 + w0 > w || y0 + h0 > h || dop > 2 ||
- bop > 1) {
- break;
- }
+ if (id == MakeTag('I', 'E', 'N', 'D')) {
+ break;
+ }
+ if (chunk.size() < 34) {
+ return JXL_FAILURE("Received a chunk that is too small (%" PRIuS "B)",
+ chunk.size());
+ }
+ // At this point the old frame is done. Let's start a new one.
+ w0 = png_get_uint_32(chunk.data() + 12);
+ h0 = png_get_uint_32(chunk.data() + 16);
+ x0 = png_get_uint_32(chunk.data() + 20);
+ y0 = png_get_uint_32(chunk.data() + 24);
+ delay_num = png_get_uint_16(chunk.data() + 28);
+ delay_den = png_get_uint_16(chunk.data() + 30);
+ dop = chunk[32];
+ bop = chunk[33];
+
+ if (!delay_den) delay_den = 100;
+
+ if (w0 > kMaxPNGSize || h0 > kMaxPNGSize || x0 > kMaxPNGSize ||
+ y0 > kMaxPNGSize || x0 + w0 > w || y0 + h0 > h || dop > 2 ||
+ bop > 1) {
+ return false;
+ }
- if (hasInfo) {
- memcpy(chunkIHDR.data() + 8, chunk.data() + 12, 8);
- if (processing_start(png_ptr, info_ptr,
- static_cast<void*>(&frameRaw), hasInfo,
- chunkIHDR, chunksInfo)) {
- break;
- }
- }
- } else if (id == kId_IDAT) {
- // First IDAT chunk means we now have all header info
- if (seenFctl) {
- // `fcTL` chunk must appear after all `IDAT` chunks
- return JXL_FAILURE("IDAT chunk after fcTL chunk");
- }
- hasInfo = true;
- JXL_CHECK(w == png_get_image_width(png_ptr, info_ptr));
- JXL_CHECK(h == png_get_image_height(png_ptr, info_ptr));
- int colortype = png_get_color_type(png_ptr, info_ptr);
- int png_bit_depth = png_get_bit_depth(png_ptr, info_ptr);
- ppf->info.bits_per_sample = png_bit_depth;
- png_color_8p sigbits = nullptr;
- png_get_sBIT(png_ptr, info_ptr, &sigbits);
- if (colortype & 1) {
- // palette will actually be 8-bit regardless of the index bitdepth
- ppf->info.bits_per_sample = 8;
- }
- if (colortype & 2) {
- ppf->info.num_color_channels = 3;
- ppf->color_encoding.color_space = JXL_COLOR_SPACE_RGB;
- if (sigbits && sigbits->red == sigbits->green &&
- sigbits->green == sigbits->blue) {
- ppf->info.bits_per_sample = sigbits->red;
- } else if (sigbits) {
- int maxbps = std::max(sigbits->red,
- std::max(sigbits->green, sigbits->blue));
- JXL_WARNING(
- "sBIT chunk: bit depths for R, G, and B are not the same (%i "
- "%i %i), while in JPEG XL they have to be the same. Setting "
- "RGB bit depth to %i.",
- sigbits->red, sigbits->green, sigbits->blue, maxbps);
- ppf->info.bits_per_sample = maxbps;
- }
- } else {
- ppf->info.num_color_channels = 1;
- ppf->color_encoding.color_space = JXL_COLOR_SPACE_GRAY;
- if (sigbits) ppf->info.bits_per_sample = sigbits->gray;
- }
- if (colortype & 4 ||
- png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) {
- ppf->info.alpha_bits = ppf->info.bits_per_sample;
- if (sigbits && sigbits->alpha != ppf->info.bits_per_sample) {
- JXL_WARNING(
- "sBIT chunk: bit depths for RGBA are inconsistent "
- "(%i %i %i %i). Setting A bitdepth to %i.",
- sigbits->red, sigbits->green, sigbits->blue, sigbits->alpha,
- ppf->info.bits_per_sample);
- }
- } else {
- ppf->info.alpha_bits = 0;
- }
- ppf->color_encoding.color_space =
- (ppf->info.num_color_channels == 1 ? JXL_COLOR_SPACE_GRAY
- : JXL_COLOR_SPACE_RGB);
- ppf->info.xsize = w;
- ppf->info.ysize = h;
- JXL_RETURN_IF_ERROR(VerifyDimensions(constraints, w, h));
- num_channels =
- ppf->info.num_color_channels + (ppf->info.alpha_bits ? 1 : 0);
- format = {
- /*num_channels=*/num_channels,
- /*data_type=*/ppf->info.bits_per_sample > 8 ? JXL_TYPE_UINT16
- : JXL_TYPE_UINT8,
- /*endianness=*/JXL_BIG_ENDIAN,
- /*align=*/0,
- };
- if (png_bit_depth > 8 && format.data_type == JXL_TYPE_UINT8) {
- png_set_strip_16(png_ptr);
- }
- bytes_per_pixel =
- num_channels * (format.data_type == JXL_TYPE_UINT16 ? 2 : 1);
- size_t rowbytes = w * bytes_per_pixel;
- if (h > std::numeric_limits<size_t>::max() / rowbytes) {
- return JXL_FAILURE("Image too big.");
- }
- size_t imagesize = h * rowbytes;
- JXL_RETURN_IF_ERROR(frameRaw.Resize(imagesize));
- frameRaw.rows.resize(h);
- for (size_t j = 0; j < h; j++) {
- frameRaw.rows[j] =
- reinterpret_cast<uint8_t*>(frameRaw.pixels.get()) +
- j * rowbytes;
- }
+ if (hasInfo) {
+ // Copy dimensions.
+ memcpy(ctx.ihdr.data() + 8, chunk.data() + 12, 8);
+ if (!ctx.InitPngDecoder(hasInfo, chunksInfo)) {
+ return false;
+ }
+ }
+ } else if (id == MakeTag('I', 'D', 'A', 'T')) {
+ // First IDAT chunk means we now have all header info
+ hasInfo = true;
+ JXL_CHECK(w == png_get_image_width(ctx.png_ptr, ctx.info_ptr));
+ JXL_CHECK(h == png_get_image_height(ctx.png_ptr, ctx.info_ptr));
+ int colortype = png_get_color_type(ctx.png_ptr, ctx.info_ptr);
+ int png_bit_depth = png_get_bit_depth(ctx.png_ptr, ctx.info_ptr);
+ ppf->info.bits_per_sample = png_bit_depth;
+ png_color_8p sigbits = nullptr;
+ png_get_sBIT(ctx.png_ptr, ctx.info_ptr, &sigbits);
+ if (colortype & 1) {
+ // palette will actually be 8-bit regardless of the index bitdepth
+ ppf->info.bits_per_sample = 8;
+ }
+ if (colortype & 2) {
+ ppf->info.num_color_channels = 3;
+ ppf->color_encoding.color_space = JXL_COLOR_SPACE_RGB;
+ if (sigbits && sigbits->red == sigbits->green &&
+ sigbits->green == sigbits->blue) {
+ ppf->info.bits_per_sample = sigbits->red;
+ } else if (sigbits) {
+ int maxbps =
+ std::max(sigbits->red, std::max(sigbits->green, sigbits->blue));
+ JXL_WARNING(
+ "sBIT chunk: bit depths for R, G, and B are not the same (%i "
+ "%i %i), while in JPEG XL they have to be the same. Setting "
+ "RGB bit depth to %i.",
+ sigbits->red, sigbits->green, sigbits->blue, maxbps);
+ ppf->info.bits_per_sample = maxbps;
+ }
+ } else {
+ ppf->info.num_color_channels = 1;
+ ppf->color_encoding.color_space = JXL_COLOR_SPACE_GRAY;
+ if (sigbits) ppf->info.bits_per_sample = sigbits->gray;
+ }
+ if (colortype & 4 ||
+ png_get_valid(ctx.png_ptr, ctx.info_ptr, PNG_INFO_tRNS)) {
+ ppf->info.alpha_bits = ppf->info.bits_per_sample;
+ if (sigbits && sigbits->alpha != ppf->info.bits_per_sample) {
+ JXL_WARNING(
+ "sBIT chunk: bit depths for RGBA are inconsistent "
+ "(%i %i %i %i). Setting A bitdepth to %i.",
+ sigbits->red, sigbits->green, sigbits->blue, sigbits->alpha,
+ ppf->info.bits_per_sample);
+ }
+ } else {
+ ppf->info.alpha_bits = 0;
+ }
+ ppf->color_encoding.color_space =
+ (ppf->info.num_color_channels == 1 ? JXL_COLOR_SPACE_GRAY
+ : JXL_COLOR_SPACE_RGB);
+ ppf->info.xsize = w;
+ ppf->info.ysize = h;
+ JXL_RETURN_IF_ERROR(VerifyDimensions(constraints, w, h));
+ num_channels =
+ ppf->info.num_color_channels + (ppf->info.alpha_bits ? 1 : 0);
+ format = {
+ /*num_channels=*/num_channels,
+ /*data_type=*/ppf->info.bits_per_sample > 8 ? JXL_TYPE_UINT16
+ : JXL_TYPE_UINT8,
+ /*endianness=*/JXL_BIG_ENDIAN,
+ /*align=*/0,
+ };
+ if (png_bit_depth > 8 && format.data_type == JXL_TYPE_UINT8) {
+ png_set_strip_16(ctx.png_ptr);
+ }
+ bytes_per_pixel =
+ num_channels * (format.data_type == JXL_TYPE_UINT16 ? 2 : 1);
+ // TODO(eustas): ensure multiplication is safe
+ size_t rowbytes = w * bytes_per_pixel;
+ if (h > std::numeric_limits<size_t>::max() / rowbytes) {
+ return JXL_FAILURE("Image too big.");
+ }
+ JXL_RETURN_IF_ERROR(ctx.frameRaw.Resize(rowbytes, h));
- if (processing_data(png_ptr, info_ptr, chunk.data(), chunk.size())) {
- break;
- }
- } else if (id == kId_fdAT && isAnimated) {
- if (!hasInfo) {
- return JXL_FAILURE("fDAT chunk before iDAT");
- }
- png_save_uint_32(chunk.data() + 4, chunk.size() - 16);
- memcpy(chunk.data() + 8, "IDAT", 4);
- if (processing_data(png_ptr, info_ptr, chunk.data() + 4,
- chunk.size() - 4)) {
- break;
- }
- } else if (id == kId_cICP) {
- // Color profile chunks: cICP has the highest priority, followed by
- // iCCP and sRGB (which shouldn't co-exist, but if they do, we use
- // iCCP), followed finally by gAMA and cHRM.
- if (DecodeCICP(chunk.data() + 8, chunk.size() - 12,
- &ppf->color_encoding)) {
- have_cicp = true;
- have_color = true;
- ppf->icc.clear();
- ppf->primary_color_representation =
- PackedPixelFile::kColorEncodingIsPrimary;
- }
- } else if (!have_cicp && id == kId_iCCP) {
- if (processing_data(png_ptr, info_ptr, chunk.data(), chunk.size())) {
- JXL_WARNING("Corrupt iCCP chunk");
- break;
- }
+ if (!ctx.FeedChunks(chunk)) {
+ return false;
+ }
+ } else if (id == MakeTag('f', 'd', 'A', 'T') && isAnimated) {
+ if (!hasInfo) {
+ return JXL_FAILURE("fdAT chunk before IDAT");
+ }
+ /* The 'fdAT' chunk has... the same structure as an 'IDAT' chunk,
+ * except preceded by a sequence number. */
+ size_t payload_size = chunk.size() - 12;
+ if (payload_size < 4) {
+ return JXL_FAILURE("Corrupted fdAT chunk");
+ }
+ // Turn 'fdAT' to 'IDAT' by cutting sequence number and replacing tag.
+ std::array<uint8_t, 8> preamble;
+ png_save_uint_32(preamble.data(), payload_size - 4);
+ memcpy(preamble.data() + 4, "IDAT", 4);
+ if (!ctx.FeedChunks(Bytes(preamble),
+ Bytes(chunk.data() + 12, chunk.size() - 12))) {
+ return false;
+ }
+ } else if (id == MakeTag('c', 'I', 'C', 'P')) {
+ // Color profile chunks: cICP has the highest priority, followed by
+ // iCCP and sRGB (which shouldn't co-exist, but if they do, we use
+ // iCCP), followed finally by gAMA and cHRM.
+ if (DecodeCicpChunk(Bytes(chunk.data() + 8, chunk.size() - 12),
+ &ppf->color_encoding)) {
+ have_cicp = true;
+ have_color = true;
+ ppf->icc.clear();
+ ppf->primary_color_representation =
+ PackedPixelFile::kColorEncodingIsPrimary;
+ }
+ } else if (!have_cicp && id == MakeTag('i', 'C', 'C', 'P')) {
+ if (!ctx.FeedChunks(chunk)) {
+ JXL_WARNING("Corrupt iCCP chunk");
+ return false;
+ }
- // TODO(jon): catch special case of PQ and synthesize color encoding
- // in that case
- int compression_type;
- png_bytep profile;
- png_charp name;
- png_uint_32 proflen = 0;
- auto ok = png_get_iCCP(png_ptr, info_ptr, &name, &compression_type,
- &profile, &proflen);
- if (ok && proflen) {
- ppf->icc.assign(profile, profile + proflen);
- ppf->primary_color_representation = PackedPixelFile::kIccIsPrimary;
- have_color = true;
- have_iccp = true;
- } else {
- // TODO(eustas): JXL_WARNING?
- }
- } else if (!have_cicp && !have_iccp && id == kId_sRGB) {
- JXL_RETURN_IF_ERROR(DecodeSRGB(chunk.data() + 8, chunk.size() - 12,
- &ppf->color_encoding));
- have_srgb = true;
- have_color = true;
- } else if (!have_cicp && !have_srgb && !have_iccp && id == kId_gAMA) {
- JXL_RETURN_IF_ERROR(DecodeGAMA(chunk.data() + 8, chunk.size() - 12,
- &ppf->color_encoding));
- have_color = true;
- } else if (!have_cicp && !have_srgb && !have_iccp && id == kId_cHRM) {
- JXL_RETURN_IF_ERROR(DecodeCHRM(chunk.data() + 8, chunk.size() - 12,
- &ppf->color_encoding));
- have_color = true;
- } else if (id == kId_eXIf) {
- ppf->metadata.exif.resize(chunk.size() - 12);
- memcpy(ppf->metadata.exif.data(), chunk.data() + 8,
- chunk.size() - 12);
- } else if (!isAbc(chunk[4]) || !isAbc(chunk[5]) || !isAbc(chunk[6]) ||
- !isAbc(chunk[7])) {
- break;
- } else {
- if (processing_data(png_ptr, info_ptr, chunk.data(), chunk.size())) {
- break;
- }
- if (!hasInfo) {
- chunksInfo.push_back(chunk);
- continue;
- }
- }
+ // TODO(jon): catch special case of PQ and synthesize color encoding
+ // in that case
+ int compression_type;
+ png_bytep profile;
+ png_charp name;
+ png_uint_32 proflen = 0;
+ auto ok = png_get_iCCP(ctx.png_ptr, ctx.info_ptr, &name,
+ &compression_type, &profile, &proflen);
+ if (ok && proflen) {
+ ppf->icc.assign(profile, profile + proflen);
+ ppf->primary_color_representation = PackedPixelFile::kIccIsPrimary;
+ have_color = true;
+ have_iccp = true;
+ } else {
+ // TODO(eustas): JXL_WARNING?
+ }
+ } else if (!have_cicp && !have_iccp && id == MakeTag('s', 'R', 'G', 'B')) {
+ JXL_RETURN_IF_ERROR(DecodeSrgbChunk(
+ Bytes(chunk.data() + 8, chunk.size() - 12), &ppf->color_encoding));
+ have_srgb = true;
+ have_color = true;
+ } else if (!have_cicp && !have_srgb && !have_iccp &&
+ id == MakeTag('g', 'A', 'M', 'A')) {
+ JXL_RETURN_IF_ERROR(DecodeGamaChunk(
+ Bytes(chunk.data() + 8, chunk.size() - 12), &ppf->color_encoding));
+ have_color = true;
+ } else if (!have_cicp && !have_srgb && !have_iccp &&
+ id == MakeTag('c', 'H', 'R', 'M')) {
+ JXL_RETURN_IF_ERROR(DecodeChrmChunk(
+ Bytes(chunk.data() + 8, chunk.size() - 12), &ppf->color_encoding));
+ have_color = true;
+ } else if (id == MakeTag('e', 'X', 'I', 'f')) {
+ ppf->metadata.exif.resize(chunk.size() - 12);
+ memcpy(ppf->metadata.exif.data(), chunk.data() + 8, chunk.size() - 12);
+ } else if (!isAbc(chunk[4]) || !isAbc(chunk[5]) || !isAbc(chunk[6]) ||
+ !isAbc(chunk[7])) {
+ return false;
+ } else {
+ if (!ctx.FeedChunks(chunk)) {
+ return false;
+ }
+ if (!hasInfo) {
+ chunksInfo.push_back(chunk);
+ continue;
}
}
-
- JXL_RETURN_IF_ERROR(ApplyColorHints(
- color_hints, have_color, ppf->info.num_color_channels == 1, ppf));
}
- if (errorstate) return false;
+ JXL_RETURN_IF_ERROR(ApplyColorHints(color_hints, have_color,
+ ppf->info.num_color_channels == 1, ppf));
bool has_nontrivial_background = false;
bool previous_frame_should_be_cleared = false;
@@ -1014,14 +1052,14 @@ Status DecodeImageAPNG(const Span<const uint8_t> bytes,
previous_frame_should_be_cleared =
has_nontrivial_background && frame.dispose_op == DISPOSE_OP_BACKGROUND;
}
+
if (ppf->frames.empty()) return JXL_FAILURE("No frames decoded");
ppf->frames.back().frame_info.is_last = JXL_TRUE;
return true;
-#else
- return false;
-#endif
}
+#endif // JPEGXL_ENABLE_APNG
+
} // namespace extras
} // namespace jxl