summaryrefslogtreecommitdiff
path: root/lib/extras/dec
diff options
context:
space:
mode:
Diffstat (limited to 'lib/extras/dec')
-rw-r--r--lib/extras/dec/apng.cc289
-rw-r--r--lib/extras/dec/apng.h11
-rw-r--r--lib/extras/dec/color_description.cc4
-rw-r--r--lib/extras/dec/color_description.h3
-rw-r--r--lib/extras/dec/color_description_test.cc5
-rw-r--r--lib/extras/dec/color_hints.cc44
-rw-r--r--lib/extras/dec/color_hints.h2
-rw-r--r--lib/extras/dec/decode.cc180
-rw-r--r--lib/extras/dec/decode.h24
-rw-r--r--lib/extras/dec/exr.cc27
-rw-r--r--lib/extras/dec/exr.h10
-rw-r--r--lib/extras/dec/gif.cc45
-rw-r--r--lib/extras/dec/gif.h9
-rw-r--r--lib/extras/dec/jpegli.cc271
-rw-r--r--lib/extras/dec/jpegli.h41
-rw-r--r--lib/extras/dec/jpg.cc73
-rw-r--r--lib/extras/dec/jpg.h18
-rw-r--r--lib/extras/dec/jxl.cc158
-rw-r--r--lib/extras/dec/jxl.h11
-rw-r--r--lib/extras/dec/pgx.cc8
-rw-r--r--lib/extras/dec/pgx.h8
-rw-r--r--lib/extras/dec/pgx_test.cc14
-rw-r--r--lib/extras/dec/pnm.cc176
-rw-r--r--lib/extras/dec/pnm.h28
24 files changed, 1173 insertions, 286 deletions
diff --git a/lib/extras/dec/apng.cc b/lib/extras/dec/apng.cc
index 5667466..f77dab7 100644
--- a/lib/extras/dec/apng.cc
+++ b/lib/extras/dec/apng.cc
@@ -36,27 +36,34 @@
*
*/
-#include <stdio.h>
+#include <jxl/codestream_header.h>
+#include <jxl/encode.h>
#include <string.h>
#include <string>
#include <utility>
#include <vector>
-#include "jxl/codestream_header.h"
-#include "jxl/encode.h"
+#include "lib/extras/size_constraints.h"
+#include "lib/jxl/base/byte_order.h"
+#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/common.h"
#include "lib/jxl/sanitizers.h"
+#if JPEGXL_ENABLE_APNG
#include "png.h" /* original (unpatched) libpng is ok */
+#endif
namespace jxl {
namespace extras {
+#if JPEGXL_ENABLE_APNG
namespace {
+constexpr unsigned char 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[] = {
@@ -73,11 +80,145 @@ Status DecodeSRGB(const unsigned char* payload, const size_t payload_size,
if (payload_size != 1) return JXL_FAILURE("Wrong sRGB size");
// (PNG uses the same values as ICC.)
if (payload[0] >= 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]);
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");
+ JxlColorEncoding color_enc = *color_encoding;
+
+ // From https://www.itu.int/rec/T-REC-H.273-202107-I/en
+ if (payload[0] == 1) {
+ // IEC 61966-2-1 sRGB
+ color_enc.primaries = JXL_PRIMARIES_SRGB;
+ color_enc.white_point = JXL_WHITE_POINT_D65;
+ } else if (payload[0] == 4) {
+ // Rec. ITU-R BT.470-6 System M
+ color_enc.primaries = JXL_PRIMARIES_CUSTOM;
+ color_enc.primaries_red_xy[0] = 0.67;
+ color_enc.primaries_red_xy[1] = 0.33;
+ color_enc.primaries_green_xy[0] = 0.21;
+ color_enc.primaries_green_xy[1] = 0.71;
+ color_enc.primaries_blue_xy[0] = 0.14;
+ color_enc.primaries_blue_xy[1] = 0.08;
+ color_enc.white_point = JXL_WHITE_POINT_CUSTOM;
+ color_enc.white_point_xy[0] = 0.310;
+ color_enc.white_point_xy[1] = 0.316;
+ } else if (payload[0] == 5) {
+ // Rec. ITU-R BT.1700-0 625 PAL and 625 SECAM
+ color_enc.primaries = JXL_PRIMARIES_CUSTOM;
+ color_enc.primaries_red_xy[0] = 0.64;
+ color_enc.primaries_red_xy[1] = 0.33;
+ color_enc.primaries_green_xy[0] = 0.29;
+ color_enc.primaries_green_xy[1] = 0.60;
+ color_enc.primaries_blue_xy[0] = 0.15;
+ color_enc.primaries_blue_xy[1] = 0.06;
+ color_enc.white_point = JXL_WHITE_POINT_D65;
+ } else if (payload[0] == 6 || payload[0] == 7) {
+ // SMPTE ST 170 (2004) / SMPTE ST 240 (1999)
+ color_enc.primaries = JXL_PRIMARIES_CUSTOM;
+ color_enc.primaries_red_xy[0] = 0.630;
+ color_enc.primaries_red_xy[1] = 0.340;
+ color_enc.primaries_green_xy[0] = 0.310;
+ color_enc.primaries_green_xy[1] = 0.595;
+ color_enc.primaries_blue_xy[0] = 0.155;
+ color_enc.primaries_blue_xy[1] = 0.070;
+ color_enc.white_point = JXL_WHITE_POINT_D65;
+ } else if (payload[0] == 8) {
+ // Generic film (colour filters using Illuminant C)
+ color_enc.primaries = JXL_PRIMARIES_CUSTOM;
+ color_enc.primaries_red_xy[0] = 0.681;
+ color_enc.primaries_red_xy[1] = 0.319;
+ color_enc.primaries_green_xy[0] = 0.243;
+ color_enc.primaries_green_xy[1] = 0.692;
+ color_enc.primaries_blue_xy[0] = 0.145;
+ color_enc.primaries_blue_xy[1] = 0.049;
+ color_enc.white_point = JXL_WHITE_POINT_CUSTOM;
+ color_enc.white_point_xy[0] = 0.310;
+ color_enc.white_point_xy[1] = 0.316;
+ } else if (payload[0] == 9) {
+ // Rec. ITU-R BT.2100-2
+ color_enc.primaries = JXL_PRIMARIES_2100;
+ color_enc.white_point = JXL_WHITE_POINT_D65;
+ } else if (payload[0] == 10) {
+ // CIE 1931 XYZ
+ color_enc.primaries = JXL_PRIMARIES_CUSTOM;
+ color_enc.primaries_red_xy[0] = 1;
+ color_enc.primaries_red_xy[1] = 0;
+ color_enc.primaries_green_xy[0] = 0;
+ color_enc.primaries_green_xy[1] = 1;
+ color_enc.primaries_blue_xy[0] = 0;
+ color_enc.primaries_blue_xy[1] = 0;
+ color_enc.white_point = JXL_WHITE_POINT_E;
+ } else if (payload[0] == 11) {
+ // SMPTE RP 431-2 (2011)
+ color_enc.primaries = JXL_PRIMARIES_P3;
+ color_enc.white_point = JXL_WHITE_POINT_DCI;
+ } else if (payload[0] == 12) {
+ // SMPTE EG 432-1 (2010)
+ color_enc.primaries = JXL_PRIMARIES_P3;
+ color_enc.white_point = JXL_WHITE_POINT_D65;
+ } else if (payload[0] == 22) {
+ color_enc.primaries = JXL_PRIMARIES_CUSTOM;
+ color_enc.primaries_red_xy[0] = 0.630;
+ color_enc.primaries_red_xy[1] = 0.340;
+ color_enc.primaries_green_xy[0] = 0.295;
+ color_enc.primaries_green_xy[1] = 0.605;
+ color_enc.primaries_blue_xy[0] = 0.155;
+ color_enc.primaries_blue_xy[1] = 0.077;
+ color_enc.white_point = JXL_WHITE_POINT_D65;
+ } else {
+ JXL_WARNING("Unsupported primaries specified in cICP chunk: %d",
+ static_cast<int>(payload[0]));
+ return false;
+ }
+
+ if (payload[1] == 1 || payload[1] == 6 || payload[1] == 14 ||
+ payload[1] == 15) {
+ // Rec. ITU-R BT.709-6
+ color_enc.transfer_function = JXL_TRANSFER_FUNCTION_709;
+ } else if (payload[1] == 4) {
+ // Rec. ITU-R BT.1700-0 625 PAL and 625 SECAM
+ color_enc.transfer_function = JXL_TRANSFER_FUNCTION_GAMMA;
+ color_enc.gamma = 1 / 2.2;
+ } else if (payload[1] == 5) {
+ // Rec. ITU-R BT.470-6 System B, G
+ color_enc.transfer_function = JXL_TRANSFER_FUNCTION_GAMMA;
+ color_enc.gamma = 1 / 2.8;
+ } else if (payload[1] == 8 || payload[1] == 13 || payload[1] == 16 ||
+ payload[1] == 17 || payload[1] == 18) {
+ // These codes all match the corresponding JXL enum values
+ color_enc.transfer_function = static_cast<JxlTransferFunction>(payload[1]);
+ } else {
+ JXL_WARNING("Unsupported transfer function specified in cICP chunk: %d",
+ static_cast<int>(payload[1]));
+ return false;
+ }
+
+ if (payload[2] != 0) {
+ JXL_WARNING("Unsupported color space specified in cICP chunk: %d",
+ static_cast<int>(payload[2]));
+ return false;
+ }
+ if (payload[3] != 1) {
+ JXL_WARNING("Unsupported full-range flag specified in cICP chunk: %d",
+ static_cast<int>(payload[3]));
+ return false;
+ }
+ // cICP has no rendering intent, so use the default
+ color_enc.rendering_intent = JXL_RENDERING_INTENT_RELATIVE;
+ *color_encoding = color_enc;
+ 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");
@@ -129,6 +270,11 @@ class BlobsReaderPNG {
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)",
@@ -136,9 +282,9 @@ class BlobsReaderPNG {
}
metadata->exif = std::move(bytes);
} else if (type == "iptc") {
- // TODO (jon): Deal with IPTC in some way
+ // TODO(jon): Deal with IPTC in some way
} else if (type == "8bim") {
- // TODO (jon): Deal with 8bim in some way
+ // 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
@@ -228,6 +374,10 @@ class BlobsReaderPNG {
// 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));
@@ -274,6 +424,7 @@ 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;
@@ -342,6 +493,12 @@ int processing_start(png_structp& png_ptr, png_infop& info_ptr, void* frame_ptr,
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, 0);
+ // Just in case. Not all versions on libpng wipe-out the pointers.
+ png_ptr = nullptr;
+ info_ptr = nullptr;
+
png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
info_ptr = png_create_info_struct(png_ptr);
if (!png_ptr || !info_ptr) return 1;
@@ -403,11 +560,20 @@ int processing_finish(png_structp png_ptr, png_infop info_ptr,
}
} // namespace
+#endif
+
+bool CanDecodeAPNG() {
+#if JPEGXL_ENABLE_APNG
+ return true;
+#else
+ return false;
+#endif
+}
Status DecodeImageAPNG(const Span<const uint8_t> bytes,
- const ColorHints& color_hints,
- const SizeConstraints& constraints,
- PackedPixelFile* ppf) {
+ const ColorHints& color_hints, PackedPixelFile* ppf,
+ const SizeConstraints* constraints) {
+#if JPEGXL_ENABLE_APNG
Reader r;
unsigned int id, j, w, h, w0, h0, x0, y0;
unsigned int delay_num, delay_den, dop, bop, rowbytes, imagesize;
@@ -419,6 +585,7 @@ Status DecodeImageAPNG(const Span<const uint8_t> bytes,
std::vector<std::vector<uint8_t>> chunksInfo;
bool isAnimated = false;
bool hasInfo = false;
+ bool seenFctl = false;
APNGFrame frameRaw = {};
uint32_t num_channels;
JxlPixelFormat format;
@@ -457,7 +624,8 @@ Status DecodeImageAPNG(const Span<const uint8_t> bytes,
ppf->frames.clear();
- bool have_color = false, have_srgb = false;
+ bool have_color = false;
+ bool have_cicp = false, have_iccp = false, have_srgb = false;
bool errorstate = true;
if (id == kId_IHDR && chunkIHDR.size() == 25) {
x0 = 0;
@@ -478,12 +646,14 @@ Status DecodeImageAPNG(const Span<const uint8_t> bytes,
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, (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;
@@ -544,11 +714,16 @@ Status DecodeImageAPNG(const Span<const uint8_t> bytes,
}
} 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);
- ppf->info.bits_per_sample = png_get_bit_depth(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 = NULL;
png_get_sBIT(png_ptr, info_ptr, &sigbits);
if (colortype & 1) {
@@ -559,8 +734,18 @@ Status DecodeImageAPNG(const Span<const uint8_t> bytes,
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)
+ 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;
@@ -569,12 +754,12 @@ Status DecodeImageAPNG(const Span<const uint8_t> bytes,
if (colortype & 4 ||
png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) {
ppf->info.alpha_bits = ppf->info.bits_per_sample;
- if (sigbits) {
- if (sigbits->alpha &&
- sigbits->alpha != ppf->info.bits_per_sample) {
- return JXL_FAILURE("Unsupported alpha bit-depth");
- }
- ppf->info.alpha_bits = sigbits->alpha;
+ 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;
@@ -584,7 +769,7 @@ Status DecodeImageAPNG(const Span<const uint8_t> bytes,
: JXL_COLOR_SPACE_RGB);
ppf->info.xsize = w;
ppf->info.ysize = h;
- JXL_RETURN_IF_ERROR(VerifyDimensions(&constraints, w, h));
+ JXL_RETURN_IF_ERROR(VerifyDimensions(constraints, w, h));
num_channels =
ppf->info.num_color_channels + (ppf->info.alpha_bits ? 1 : 0);
format = {
@@ -594,6 +779,9 @@ Status DecodeImageAPNG(const Span<const uint8_t> bytes,
/*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);
rowbytes = w * bytes_per_pixel;
@@ -607,13 +795,26 @@ Status DecodeImageAPNG(const Span<const uint8_t> bytes,
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_iCCP) {
+ } 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();
+ }
+ } else if (!have_cicp && id == kId_iCCP) {
if (processing_data(png_ptr, info_ptr, chunk.data(), chunk.size())) {
JXL_WARNING("Corrupt iCCP chunk");
break;
@@ -630,19 +831,20 @@ Status DecodeImageAPNG(const Span<const uint8_t> bytes,
if (ok && proflen) {
ppf->icc.assign(profile, profile + proflen);
have_color = true;
+ have_iccp = true;
} else {
// TODO(eustas): JXL_WARNING?
}
- } else if (id == kId_sRGB) {
+ } 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 (id == kId_gAMA) {
+ } 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 (id == kId_cHRM) {
+ } 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;
@@ -665,12 +867,6 @@ Status DecodeImageAPNG(const Span<const uint8_t> bytes,
}
}
- if (have_srgb) {
- 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_PERCEPTUAL;
- }
JXL_RETURN_IF_ERROR(ApplyColorHints(
color_hints, have_color, ppf->info.num_color_channels == 1, ppf));
}
@@ -706,31 +902,29 @@ Status DecodeImageAPNG(const Span<const uint8_t> bytes,
size_t xsize = frame.data.xsize;
size_t ysize = frame.data.ysize;
if (previous_frame_should_be_cleared) {
- size_t xs = frame.data.xsize;
- size_t ys = frame.data.ysize;
size_t px0 = frames[i - 1].x0;
size_t py0 = frames[i - 1].y0;
size_t pxs = frames[i - 1].xsize;
size_t pys = frames[i - 1].ysize;
- if (px0 >= x0 && py0 >= y0 && px0 + pxs <= x0 + xs &&
- py0 + pys <= y0 + ys && frame.blend_op == BLEND_OP_SOURCE &&
+ if (px0 >= x0 && py0 >= y0 && px0 + pxs <= x0 + xsize &&
+ py0 + pys <= y0 + ysize && frame.blend_op == BLEND_OP_SOURCE &&
use_for_next_frame) {
// If the previous frame is entirely contained in the current frame and
// we are using BLEND_OP_SOURCE, nothing special needs to be done.
ppf->frames.emplace_back(std::move(frame.data));
- } else if (px0 == x0 && py0 == y0 && px0 + pxs == x0 + xs &&
- py0 + pys == y0 + ys && use_for_next_frame) {
+ } else if (px0 == x0 && py0 == y0 && px0 + pxs == x0 + xsize &&
+ py0 + pys == y0 + ysize && use_for_next_frame) {
// If the new frame has the same size as the old one, but we are
// blending, we can instead just not blend.
should_blend = false;
ppf->frames.emplace_back(std::move(frame.data));
- } else if (px0 <= x0 && py0 <= y0 && px0 + pxs >= x0 + xs &&
- py0 + pys >= y0 + ys && use_for_next_frame) {
+ } else if (px0 <= x0 && py0 <= y0 && px0 + pxs >= x0 + xsize &&
+ py0 + pys >= y0 + ysize && use_for_next_frame) {
// If the new frame is contained within the old frame, we can pad the
// new frame with zeros and not blend.
PackedImage new_data(pxs, pys, frame.data.format);
memset(new_data.pixels(), 0, new_data.pixels_size);
- for (size_t y = 0; y < ys; y++) {
+ for (size_t y = 0; y < ysize; y++) {
size_t bytes_per_pixel =
PackedImage::BitsPerChannel(new_data.format.data_type) *
new_data.format.num_channels / 8;
@@ -739,7 +933,7 @@ Status DecodeImageAPNG(const Span<const uint8_t> bytes,
bytes_per_pixel * (x0 - px0),
static_cast<const uint8_t*>(frame.data.pixels()) +
frame.data.stride * y,
- xs * bytes_per_pixel);
+ xsize * bytes_per_pixel);
}
x0 = px0;
@@ -749,19 +943,21 @@ Status DecodeImageAPNG(const Span<const uint8_t> bytes,
should_blend = false;
ppf->frames.emplace_back(std::move(new_data));
} else {
- // If all else fails, insert a dummy blank frame with kReplace.
+ // If all else fails, insert a placeholder blank frame with kReplace.
PackedImage blank(pxs, pys, frame.data.format);
memset(blank.pixels(), 0, blank.pixels_size);
ppf->frames.emplace_back(std::move(blank));
auto& pframe = ppf->frames.back();
pframe.frame_info.layer_info.crop_x0 = px0;
pframe.frame_info.layer_info.crop_y0 = py0;
- pframe.frame_info.layer_info.xsize = frame.xsize;
- pframe.frame_info.layer_info.ysize = frame.ysize;
+ pframe.frame_info.layer_info.xsize = pxs;
+ pframe.frame_info.layer_info.ysize = pys;
pframe.frame_info.duration = 0;
- pframe.frame_info.layer_info.have_crop = 0;
+ bool is_full_size = px0 == 0 && py0 == 0 && pxs == ppf->info.xsize &&
+ pys == ppf->info.ysize;
+ pframe.frame_info.layer_info.have_crop = is_full_size ? 0 : 1;
pframe.frame_info.layer_info.blend_info.blendmode = JXL_BLEND_REPLACE;
- pframe.frame_info.layer_info.blend_info.source = 0;
+ pframe.frame_info.layer_info.blend_info.source = 1;
pframe.frame_info.layer_info.save_as_reference = 1;
ppf->frames.emplace_back(std::move(frame.data));
}
@@ -780,7 +976,7 @@ Status DecodeImageAPNG(const Span<const uint8_t> bytes,
bool is_full_size = x0 == 0 && y0 == 0 && xsize == ppf->info.xsize &&
ysize == ppf->info.ysize;
pframe.frame_info.layer_info.have_crop = is_full_size ? 0 : 1;
- pframe.frame_info.layer_info.blend_info.source = should_blend ? 1 : 0;
+ pframe.frame_info.layer_info.blend_info.source = 1;
pframe.frame_info.layer_info.blend_info.alpha = 0;
pframe.frame_info.layer_info.save_as_reference = use_for_next_frame ? 1 : 0;
@@ -791,6 +987,9 @@ Status DecodeImageAPNG(const Span<const uint8_t> bytes,
ppf->frames.back().frame_info.is_last = true;
return true;
+#else
+ return false;
+#endif
}
} // namespace extras
diff --git a/lib/extras/dec/apng.h b/lib/extras/dec/apng.h
index a68f6f8..d91364b 100644
--- a/lib/extras/dec/apng.h
+++ b/lib/extras/dec/apng.h
@@ -13,18 +13,21 @@
#include "lib/extras/dec/color_hints.h"
#include "lib/extras/packed_image.h"
#include "lib/jxl/base/data_parallel.h"
-#include "lib/jxl/base/padded_bytes.h"
#include "lib/jxl/base/span.h"
#include "lib/jxl/base/status.h"
-#include "lib/jxl/codec_in_out.h"
namespace jxl {
+
+struct SizeConstraints;
+
namespace extras {
+bool CanDecodeAPNG();
+
// Decodes `bytes` into `ppf`.
Status DecodeImageAPNG(Span<const uint8_t> bytes, const ColorHints& color_hints,
- const SizeConstraints& constraints,
- PackedPixelFile* ppf);
+ PackedPixelFile* ppf,
+ const SizeConstraints* constraints = nullptr);
} // namespace extras
} // namespace jxl
diff --git a/lib/extras/dec/color_description.cc b/lib/extras/dec/color_description.cc
index 2325b50..54f6aa4 100644
--- a/lib/extras/dec/color_description.cc
+++ b/lib/extras/dec/color_description.cc
@@ -69,9 +69,9 @@ Status ParseEnum(const std::string& token, const EnumName<T>* enum_values,
}
return false;
}
-#define ARRAYSIZE(X) (sizeof(X) / sizeof((X)[0]))
+#define ARRAY_SIZE(X) (sizeof(X) / sizeof((X)[0]))
#define PARSE_ENUM(type, token, value) \
- ParseEnum<type>(token, k##type##Names, ARRAYSIZE(k##type##Names), value)
+ ParseEnum<type>(token, k##type##Names, ARRAY_SIZE(k##type##Names), value)
class Tokenizer {
public:
diff --git a/lib/extras/dec/color_description.h b/lib/extras/dec/color_description.h
index 989d591..23680ff 100644
--- a/lib/extras/dec/color_description.h
+++ b/lib/extras/dec/color_description.h
@@ -6,9 +6,10 @@
#ifndef LIB_EXTRAS_COLOR_DESCRIPTION_H_
#define LIB_EXTRAS_COLOR_DESCRIPTION_H_
+#include <jxl/color_encoding.h>
+
#include <string>
-#include "jxl/color_encoding.h"
#include "lib/jxl/base/status.h"
namespace jxl {
diff --git a/lib/extras/dec/color_description_test.cc b/lib/extras/dec/color_description_test.cc
index 8ae9e5d..e6e34f0 100644
--- a/lib/extras/dec/color_description_test.cc
+++ b/lib/extras/dec/color_description_test.cc
@@ -5,9 +5,9 @@
#include "lib/extras/dec/color_description.h"
-#include "gtest/gtest.h"
#include "lib/jxl/color_encoding_internal.h"
#include "lib/jxl/test_utils.h"
+#include "lib/jxl/testing.h"
namespace jxl {
@@ -21,8 +21,7 @@ TEST(ColorDescriptionTest, RoundTripAll) {
JxlColorEncoding c_external = {};
EXPECT_TRUE(ParseDescription(description, &c_external));
ColorEncoding c_internal;
- EXPECT_TRUE(
- ConvertExternalToInternalColorEncoding(c_external, &c_internal));
+ EXPECT_TRUE(c_internal.FromExternal(c_external));
EXPECT_TRUE(c_original.SameColorEncoding(c_internal))
<< "Where c_original=" << c_original
<< " and c_internal=" << c_internal;
diff --git a/lib/extras/dec/color_hints.cc b/lib/extras/dec/color_hints.cc
index cf7d3e3..5c6d7b8 100644
--- a/lib/extras/dec/color_hints.cc
+++ b/lib/extras/dec/color_hints.cc
@@ -5,9 +5,12 @@
#include "lib/extras/dec/color_hints.h"
-#include "jxl/encode.h"
+#include <jxl/encode.h>
+
+#include <vector>
+
#include "lib/extras/dec/color_description.h"
-#include "lib/jxl/base/file_io.h"
+#include "lib/jxl/base/status.h"
namespace jxl {
namespace extras {
@@ -15,19 +18,15 @@ namespace extras {
Status ApplyColorHints(const ColorHints& color_hints,
const bool color_already_set, const bool is_gray,
PackedPixelFile* ppf) {
- if (color_already_set) {
- return color_hints.Foreach(
- [](const std::string& key, const std::string& /*value*/) {
- JXL_WARNING("Decoder ignoring %s hint", key.c_str());
- return true;
- });
- }
-
- bool got_color_space = false;
+ bool got_color_space = color_already_set;
JXL_RETURN_IF_ERROR(color_hints.Foreach(
- [is_gray, ppf, &got_color_space](const std::string& key,
- const std::string& value) -> Status {
+ [color_already_set, is_gray, ppf, &got_color_space](
+ const std::string& key, const std::string& value) -> Status {
+ if (color_already_set && (key == "color_space" || key == "icc")) {
+ JXL_WARNING("Decoder ignoring %s hint", key.c_str());
+ return true;
+ }
if (key == "color_space") {
JxlColorEncoding c_original_external;
if (!ParseDescription(value, &c_original_external)) {
@@ -41,9 +40,23 @@ Status ApplyColorHints(const ColorHints& color_hints,
}
got_color_space = true;
- } else if (key == "icc_pathname") {
- JXL_RETURN_IF_ERROR(ReadFile(value, &ppf->icc));
+ } else if (key == "icc") {
+ const uint8_t* data = reinterpret_cast<const uint8_t*>(value.data());
+ std::vector<uint8_t> icc(data, data + value.size());
+ ppf->icc.swap(icc);
got_color_space = true;
+ } else if (key == "exif") {
+ const uint8_t* data = reinterpret_cast<const uint8_t*>(value.data());
+ std::vector<uint8_t> blob(data, data + value.size());
+ ppf->metadata.exif.swap(blob);
+ } else if (key == "xmp") {
+ const uint8_t* data = reinterpret_cast<const uint8_t*>(value.data());
+ std::vector<uint8_t> blob(data, data + value.size());
+ ppf->metadata.xmp.swap(blob);
+ } else if (key == "jumbf") {
+ const uint8_t* data = reinterpret_cast<const uint8_t*>(value.data());
+ std::vector<uint8_t> blob(data, data + value.size());
+ ppf->metadata.jumbf.swap(blob);
} else {
JXL_WARNING("Ignoring %s hint", key.c_str());
}
@@ -51,7 +64,6 @@ Status ApplyColorHints(const ColorHints& color_hints,
}));
if (!got_color_space) {
- JXL_WARNING("No color_space/icc_pathname given, assuming sRGB");
ppf->color_encoding.color_space =
is_gray ? JXL_COLOR_SPACE_GRAY : JXL_COLOR_SPACE_RGB;
ppf->color_encoding.white_point = JXL_WHITE_POINT_D65;
diff --git a/lib/extras/dec/color_hints.h b/lib/extras/dec/color_hints.h
index 9c7de88..036f203 100644
--- a/lib/extras/dec/color_hints.h
+++ b/lib/extras/dec/color_hints.h
@@ -10,6 +10,8 @@
// information into the file, and those that support it may not have it.
// To allow attaching color information to those file formats the caller can
// define these color hints.
+// Besides color space information, 'ColorHints' may also include other
+// additional information such as Exif, XMP and JUMBF metadata.
#include <stddef.h>
#include <stdint.h>
diff --git a/lib/extras/dec/decode.cc b/lib/extras/dec/decode.cc
index 8712e03..9149208 100644
--- a/lib/extras/dec/decode.cc
+++ b/lib/extras/dec/decode.cc
@@ -7,18 +7,11 @@
#include <locale>
-#if JPEGXL_ENABLE_APNG
#include "lib/extras/dec/apng.h"
-#endif
-#if JPEGXL_ENABLE_EXR
#include "lib/extras/dec/exr.h"
-#endif
-#if JPEGXL_ENABLE_GIF
#include "lib/extras/dec/gif.h"
-#endif
-#if JPEGXL_ENABLE_JPEG
#include "lib/extras/dec/jpg.h"
-#endif
+#include "lib/extras/dec/jxl.h"
#include "lib/extras/dec/pgx.h"
#include "lib/extras/dec/pnm.h"
@@ -29,59 +22,89 @@ namespace {
// Any valid encoding is larger (ensures codecs can read the first few bytes)
constexpr size_t kMinBytes = 9;
-} // namespace
-
-std::vector<Codec> AvailableCodecs() {
- std::vector<Codec> out;
-#if JPEGXL_ENABLE_APNG
- out.push_back(Codec::kPNG);
-#endif
-#if JPEGXL_ENABLE_EXR
- out.push_back(Codec::kEXR);
-#endif
-#if JPEGXL_ENABLE_GIF
- out.push_back(Codec::kGIF);
-#endif
-#if JPEGXL_ENABLE_JPEG
- out.push_back(Codec::kJPG);
-#endif
- out.push_back(Codec::kPGX);
- out.push_back(Codec::kPNM);
- return out;
-}
+void BasenameAndExtension(const std::string& path, std::string* filename,
+ std::string* extension) {
+ // Pattern: "png:name" or "png:-"
+ size_t pos = path.find_first_of(':');
+ if (pos != std::string::npos) {
+ *extension = "." + path.substr(0, pos);
+ *filename = path.substr(pos + 1);
+ //+ ((path.length() == pos + 2 && path.substr(pos + 1, 1) == "-") ? "" :
+ //*extension);
+ return;
+ }
-Codec CodecFromExtension(std::string extension,
- size_t* JXL_RESTRICT bits_per_sample) {
- std::transform(
- extension.begin(), extension.end(), extension.begin(),
- [](char c) { return std::tolower(c, std::locale::classic()); });
- if (extension == ".png") return Codec::kPNG;
+ // Pattern: "name.png"
+ pos = path.find_last_of('.');
+ if (pos != std::string::npos) {
+ *extension = path.substr(pos);
+ *filename = path;
+ return;
+ }
- if (extension == ".jpg") return Codec::kJPG;
- if (extension == ".jpeg") return Codec::kJPG;
+ // Extension not found
+ *filename = path;
+ *extension = "";
+}
- if (extension == ".pgx") return Codec::kPGX;
+} // namespace
- if (extension == ".pam") return Codec::kPNM;
- if (extension == ".pnm") return Codec::kPNM;
- if (extension == ".pgm") return Codec::kPNM;
- if (extension == ".ppm") return Codec::kPNM;
- if (extension == ".pfm") {
+Codec CodecFromPath(std::string path, size_t* JXL_RESTRICT bits_per_sample,
+ std::string* filename, std::string* extension) {
+ std::string base;
+ std::string ext;
+ BasenameAndExtension(path, &base, &ext);
+ if (filename) *filename = base;
+ if (extension) *extension = ext;
+
+ std::transform(ext.begin(), ext.end(), ext.begin(), [](char c) {
+ return std::tolower(c, std::locale::classic());
+ });
+ if (ext == ".png") return Codec::kPNG;
+
+ if (ext == ".jpg") return Codec::kJPG;
+ if (ext == ".jpeg") return Codec::kJPG;
+
+ if (ext == ".pgx") return Codec::kPGX;
+
+ if (ext == ".pam") return Codec::kPNM;
+ if (ext == ".pnm") return Codec::kPNM;
+ if (ext == ".pgm") return Codec::kPNM;
+ if (ext == ".ppm") return Codec::kPNM;
+ if (ext == ".pfm") {
if (bits_per_sample != nullptr) *bits_per_sample = 32;
return Codec::kPNM;
}
- if (extension == ".gif") return Codec::kGIF;
+ if (ext == ".gif") return Codec::kGIF;
- if (extension == ".exr") return Codec::kEXR;
+ if (ext == ".exr") return Codec::kEXR;
return Codec::kUnknown;
}
+bool CanDecode(Codec codec) {
+ switch (codec) {
+ case Codec::kEXR:
+ return CanDecodeEXR();
+ case Codec::kGIF:
+ return CanDecodeGIF();
+ case Codec::kJPG:
+ return CanDecodeJPG();
+ case Codec::kPNG:
+ return CanDecodeAPNG();
+ case Codec::kPNM:
+ case Codec::kPGX:
+ case Codec::kJXL:
+ return true;
+ default:
+ return false;
+ }
+}
+
Status DecodeBytes(const Span<const uint8_t> bytes,
- const ColorHints& color_hints,
- const SizeConstraints& constraints,
- extras::PackedPixelFile* ppf, Codec* orig_codec) {
+ const ColorHints& color_hints, extras::PackedPixelFile* ppf,
+ const SizeConstraints* constraints, Codec* orig_codec) {
if (bytes.size() < kMinBytes) return JXL_FAILURE("Too few bytes");
*ppf = extras::PackedPixelFile();
@@ -90,33 +113,42 @@ Status DecodeBytes(const Span<const uint8_t> bytes,
ppf->info.uses_original_profile = true;
ppf->info.orientation = JXL_ORIENT_IDENTITY;
- Codec codec;
-#if JPEGXL_ENABLE_APNG
- if (DecodeImageAPNG(bytes, color_hints, constraints, ppf)) {
- codec = Codec::kPNG;
- } else
-#endif
- if (DecodeImagePGX(bytes, color_hints, constraints, ppf)) {
- codec = Codec::kPGX;
- } else if (DecodeImagePNM(bytes, color_hints, constraints, ppf)) {
- codec = Codec::kPNM;
- }
-#if JPEGXL_ENABLE_GIF
- else if (DecodeImageGIF(bytes, color_hints, constraints, ppf)) {
- codec = Codec::kGIF;
- }
-#endif
-#if JPEGXL_ENABLE_JPEG
- else if (DecodeImageJPG(bytes, color_hints, constraints, ppf)) {
- codec = Codec::kJPG;
- }
-#endif
-#if JPEGXL_ENABLE_EXR
- else if (DecodeImageEXR(bytes, color_hints, constraints, ppf)) {
- codec = Codec::kEXR;
- }
-#endif
- else {
+ const auto choose_codec = [&]() -> Codec {
+ if (DecodeImageAPNG(bytes, color_hints, ppf, constraints)) {
+ return Codec::kPNG;
+ }
+ if (DecodeImagePGX(bytes, color_hints, ppf, constraints)) {
+ return Codec::kPGX;
+ }
+ if (DecodeImagePNM(bytes, color_hints, ppf, constraints)) {
+ return Codec::kPNM;
+ }
+ JXLDecompressParams dparams = {};
+ for (const uint32_t num_channels : {1, 2, 3, 4}) {
+ dparams.accepted_formats.push_back(
+ {num_channels, JXL_TYPE_FLOAT, JXL_LITTLE_ENDIAN, /*align=*/0});
+ }
+ size_t decoded_bytes;
+ if (DecodeImageJXL(bytes.data(), bytes.size(), dparams, &decoded_bytes,
+ ppf) &&
+ ApplyColorHints(color_hints, true, ppf->info.num_color_channels == 1,
+ ppf)) {
+ return Codec::kJXL;
+ }
+ if (DecodeImageGIF(bytes, color_hints, ppf, constraints)) {
+ return Codec::kGIF;
+ }
+ if (DecodeImageJPG(bytes, color_hints, ppf, constraints)) {
+ return Codec::kJPG;
+ }
+ if (DecodeImageEXR(bytes, color_hints, ppf, constraints)) {
+ return Codec::kEXR;
+ }
+ return Codec::kUnknown;
+ };
+
+ Codec codec = choose_codec();
+ if (codec == Codec::kUnknown) {
return JXL_FAILURE("Codecs failed to decode");
}
if (orig_codec) *orig_codec = codec;
diff --git a/lib/extras/dec/decode.h b/lib/extras/dec/decode.h
index 7f0ff70..0f864dd 100644
--- a/lib/extras/dec/decode.h
+++ b/lib/extras/dec/decode.h
@@ -17,34 +17,40 @@
#include "lib/extras/dec/color_hints.h"
#include "lib/jxl/base/span.h"
#include "lib/jxl/base/status.h"
-#include "lib/jxl/codec_in_out.h"
namespace jxl {
+
+struct SizeConstraints;
+
namespace extras {
-// Codecs supported by CodecInOut::Encode.
+// Codecs supported by DecodeBytes.
enum class Codec : uint32_t {
- kUnknown, // for CodecFromExtension
+ kUnknown, // for CodecFromPath
kPNG,
kPNM,
kPGX,
kJPG,
kGIF,
- kEXR
+ kEXR,
+ kJXL
};
-std::vector<Codec> AvailableCodecs();
+bool CanDecode(Codec codec);
// If and only if extension is ".pfm", *bits_per_sample is updated to 32 so
// that Encode() would encode to PFM instead of PPM.
-Codec CodecFromExtension(std::string extension,
- size_t* JXL_RESTRICT bits_per_sample = nullptr);
+Codec CodecFromPath(std::string path,
+ size_t* JXL_RESTRICT bits_per_sample = nullptr,
+ std::string* filename = nullptr,
+ std::string* extension = nullptr);
// Decodes "bytes" info *ppf.
// color_space_hint may specify the color space, otherwise, defaults to sRGB.
Status DecodeBytes(Span<const uint8_t> bytes, const ColorHints& color_hints,
- const SizeConstraints& constraints,
- extras::PackedPixelFile* ppf, Codec* orig_codec = nullptr);
+ extras::PackedPixelFile* ppf,
+ const SizeConstraints* constraints = nullptr,
+ Codec* orig_codec = nullptr);
} // namespace extras
} // namespace jxl
diff --git a/lib/extras/dec/exr.cc b/lib/extras/dec/exr.cc
index ddb6d53..821e0f4 100644
--- a/lib/extras/dec/exr.cc
+++ b/lib/extras/dec/exr.cc
@@ -5,20 +5,22 @@
#include "lib/extras/dec/exr.h"
+#if JPEGXL_ENABLE_EXR
#include <ImfChromaticitiesAttribute.h>
#include <ImfIO.h>
#include <ImfRgbaFile.h>
#include <ImfStandardAttributes.h>
+#endif
#include <vector>
namespace jxl {
namespace extras {
+#if JPEGXL_ENABLE_EXR
namespace {
namespace OpenEXR = OPENEXR_IMF_NAMESPACE;
-namespace Imath = IMATH_NAMESPACE;
// OpenEXR::Int64 is deprecated in favor of using uint64_t directly, but using
// uint64_t as recommended causes build failures with previous OpenEXR versions
@@ -60,10 +62,20 @@ class InMemoryIStream : public OpenEXR::IStream {
};
} // namespace
+#endif
+
+bool CanDecodeEXR() {
+#if JPEGXL_ENABLE_EXR
+ return true;
+#else
+ return false;
+#endif
+}
Status DecodeImageEXR(Span<const uint8_t> bytes, const ColorHints& color_hints,
- const SizeConstraints& constraints,
- PackedPixelFile* ppf) {
+ PackedPixelFile* ppf,
+ const SizeConstraints* constraints) {
+#if JPEGXL_ENABLE_EXR
InMemoryIStream is(bytes);
#ifdef __EXCEPTIONS
@@ -71,7 +83,8 @@ Status DecodeImageEXR(Span<const uint8_t> bytes, const ColorHints& color_hints,
try {
input_ptr.reset(new OpenEXR::RgbaInputFile(is));
} catch (...) {
- return JXL_FAILURE("OpenEXR failed to parse input");
+ // silently return false if it is not an EXR file
+ return false;
}
OpenEXR::RgbaInputFile& input = *input_ptr;
#else
@@ -87,7 +100,7 @@ Status DecodeImageEXR(Span<const uint8_t> bytes, const ColorHints& color_hints,
const float intensity_target = OpenEXR::hasWhiteLuminance(input.header())
? OpenEXR::whiteLuminance(input.header())
- : kDefaultIntensityTarget;
+ : 0;
auto image_size = input.displayWindow().size();
// Size is computed as max - min, but both bounds are inclusive.
@@ -144,6 +157,7 @@ Status DecodeImageEXR(Span<const uint8_t> bytes, const ColorHints& color_hints,
std::min(input.dataWindow().max.x, input.displayWindow().max.x);
++exr_x) {
const int image_x = exr_x - input.displayWindow().min.x;
+ // TODO(eustas): UB: OpenEXR::Rgba is not TriviallyCopyable
memcpy(row + image_x * pixel_size,
input_row + (exr_x - input.dataWindow().min.x), pixel_size);
}
@@ -178,6 +192,9 @@ Status DecodeImageEXR(Span<const uint8_t> bytes, const ColorHints& color_hints,
}
ppf->info.intensity_target = intensity_target;
return true;
+#else
+ return false;
+#endif
}
} // namespace extras
diff --git a/lib/extras/dec/exr.h b/lib/extras/dec/exr.h
index 6af4e6b..0605cbb 100644
--- a/lib/extras/dec/exr.h
+++ b/lib/extras/dec/exr.h
@@ -11,17 +11,21 @@
#include "lib/extras/dec/color_hints.h"
#include "lib/extras/packed_image.h"
#include "lib/jxl/base/data_parallel.h"
-#include "lib/jxl/base/padded_bytes.h"
#include "lib/jxl/base/span.h"
#include "lib/jxl/base/status.h"
-#include "lib/jxl/codec_in_out.h"
namespace jxl {
+
+struct SizeConstraints;
+
namespace extras {
+bool CanDecodeEXR();
+
// Decodes `bytes` into `ppf`. color_hints are ignored.
Status DecodeImageEXR(Span<const uint8_t> bytes, const ColorHints& color_hints,
- const SizeConstraints& constraints, PackedPixelFile* ppf);
+ PackedPixelFile* ppf,
+ const SizeConstraints* constraints = nullptr);
} // namespace extras
} // namespace jxl
diff --git a/lib/extras/dec/gif.cc b/lib/extras/dec/gif.cc
index 5167bf5..3d96394 100644
--- a/lib/extras/dec/gif.cc
+++ b/lib/extras/dec/gif.cc
@@ -5,20 +5,24 @@
#include "lib/extras/dec/gif.h"
+#if JPEGXL_ENABLE_GIF
#include <gif_lib.h>
+#endif
+#include <jxl/codestream_header.h>
#include <string.h>
#include <memory>
#include <utility>
#include <vector>
-#include "jxl/codestream_header.h"
+#include "lib/extras/size_constraints.h"
#include "lib/jxl/base/compiler_specific.h"
#include "lib/jxl/sanitizers.h"
namespace jxl {
namespace extras {
+#if JPEGXL_ENABLE_GIF
namespace {
struct ReadState {
@@ -38,21 +42,6 @@ struct PackedRgb {
uint8_t r, g, b;
};
-// Gif does not support partial transparency, so this considers any nonzero
-// alpha channel value as opaque.
-bool AllOpaque(const PackedImage& color) {
- for (size_t y = 0; y < color.ysize; ++y) {
- const PackedRgba* const JXL_RESTRICT row =
- static_cast<const PackedRgba*>(color.pixels()) + y * color.xsize;
- for (size_t x = 0; x < color.xsize; ++x) {
- if (row[x].a == 0) {
- return false;
- }
- }
- }
- return true;
-}
-
void ensure_have_alpha(PackedFrame* frame) {
if (!frame->extra_channels.empty()) return;
const JxlPixelFormat alpha_format{
@@ -67,12 +56,21 @@ void ensure_have_alpha(PackedFrame* frame) {
std::fill_n(static_cast<uint8_t*>(frame->extra_channels[0].pixels()),
frame->color.xsize * frame->color.ysize, 255u);
}
-
} // namespace
+#endif
+
+bool CanDecodeGIF() {
+#if JPEGXL_ENABLE_GIF
+ return true;
+#else
+ return false;
+#endif
+}
Status DecodeImageGIF(Span<const uint8_t> bytes, const ColorHints& color_hints,
- const SizeConstraints& constraints,
- PackedPixelFile* ppf) {
+ PackedPixelFile* ppf,
+ const SizeConstraints* constraints) {
+#if JPEGXL_ENABLE_GIF
int error = GIF_OK;
ReadState state = {bytes};
const auto ReadFromSpan = [](GifFileType* const gif, GifByteType* const bytes,
@@ -111,20 +109,20 @@ Status DecodeImageGIF(Span<const uint8_t> bytes, const ColorHints& color_hints,
sizeof(*gif->SavedImages) * gif->ImageCount);
JXL_RETURN_IF_ERROR(
- VerifyDimensions<uint32_t>(&constraints, gif->SWidth, gif->SHeight));
+ VerifyDimensions<uint32_t>(constraints, gif->SWidth, gif->SHeight));
uint64_t total_pixel_count =
static_cast<uint64_t>(gif->SWidth) * gif->SHeight;
for (int i = 0; i < gif->ImageCount; ++i) {
const SavedImage& image = gif->SavedImages[i];
uint32_t w = image.ImageDesc.Width;
uint32_t h = image.ImageDesc.Height;
- JXL_RETURN_IF_ERROR(VerifyDimensions<uint32_t>(&constraints, w, h));
+ JXL_RETURN_IF_ERROR(VerifyDimensions<uint32_t>(constraints, w, h));
uint64_t pixel_count = static_cast<uint64_t>(w) * h;
if (total_pixel_count + pixel_count < total_pixel_count) {
return JXL_FAILURE("Image too big");
}
total_pixel_count += pixel_count;
- if (total_pixel_count > constraints.dec_max_pixels) {
+ if (constraints && (total_pixel_count > constraints->dec_max_pixels)) {
return JXL_FAILURE("Image too big");
}
}
@@ -408,6 +406,9 @@ Status DecodeImageGIF(Span<const uint8_t> bytes, const ColorHints& color_hints,
}
}
return true;
+#else
+ return false;
+#endif
}
} // namespace extras
diff --git a/lib/extras/dec/gif.h b/lib/extras/dec/gif.h
index b359517..4d5be86 100644
--- a/lib/extras/dec/gif.h
+++ b/lib/extras/dec/gif.h
@@ -15,14 +15,19 @@
#include "lib/jxl/base/data_parallel.h"
#include "lib/jxl/base/span.h"
#include "lib/jxl/base/status.h"
-#include "lib/jxl/codec_in_out.h"
namespace jxl {
+
+struct SizeConstraints;
+
namespace extras {
+bool CanDecodeGIF();
+
// Decodes `bytes` into `ppf`. color_hints are ignored.
Status DecodeImageGIF(Span<const uint8_t> bytes, const ColorHints& color_hints,
- const SizeConstraints& constraints, PackedPixelFile* ppf);
+ PackedPixelFile* ppf,
+ const SizeConstraints* constraints = nullptr);
} // namespace extras
} // namespace jxl
diff --git a/lib/extras/dec/jpegli.cc b/lib/extras/dec/jpegli.cc
new file mode 100644
index 0000000..ffa1b79
--- /dev/null
+++ b/lib/extras/dec/jpegli.cc
@@ -0,0 +1,271 @@
+// Copyright (c) the JPEG XL Project Authors. All rights reserved.
+//
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+#include "lib/extras/dec/jpegli.h"
+
+#include <setjmp.h>
+#include <stdint.h>
+
+#include <algorithm>
+#include <numeric>
+#include <utility>
+#include <vector>
+
+#include "lib/jpegli/decode.h"
+#include "lib/jxl/base/status.h"
+#include "lib/jxl/sanitizers.h"
+
+namespace jxl {
+namespace extras {
+
+namespace {
+
+constexpr unsigned char kExifSignature[6] = {0x45, 0x78, 0x69,
+ 0x66, 0x00, 0x00};
+constexpr int kExifMarker = JPEG_APP0 + 1;
+constexpr int kICCMarker = JPEG_APP0 + 2;
+
+static inline bool IsJPG(const std::vector<uint8_t>& bytes) {
+ if (bytes.size() < 2) return false;
+ if (bytes[0] != 0xFF || bytes[1] != 0xD8) return false;
+ return true;
+}
+
+bool MarkerIsExif(const jpeg_saved_marker_ptr marker) {
+ return marker->marker == kExifMarker &&
+ marker->data_length >= sizeof kExifSignature + 2 &&
+ std::equal(std::begin(kExifSignature), std::end(kExifSignature),
+ marker->data);
+}
+
+Status ReadICCProfile(jpeg_decompress_struct* const cinfo,
+ std::vector<uint8_t>* const icc) {
+ uint8_t* icc_data_ptr;
+ unsigned int icc_data_len;
+ if (jpegli_read_icc_profile(cinfo, &icc_data_ptr, &icc_data_len)) {
+ icc->assign(icc_data_ptr, icc_data_ptr + icc_data_len);
+ free(icc_data_ptr);
+ return true;
+ }
+ return false;
+}
+
+void ReadExif(jpeg_decompress_struct* const cinfo,
+ std::vector<uint8_t>* const exif) {
+ constexpr size_t kExifSignatureSize = sizeof kExifSignature;
+ for (jpeg_saved_marker_ptr marker = cinfo->marker_list; marker != nullptr;
+ marker = marker->next) {
+ // marker is initialized by libjpeg, which we are not instrumenting with
+ // msan.
+ msan::UnpoisonMemory(marker, sizeof(*marker));
+ msan::UnpoisonMemory(marker->data, marker->data_length);
+ if (!MarkerIsExif(marker)) continue;
+ size_t marker_length = marker->data_length - kExifSignatureSize;
+ exif->resize(marker_length);
+ std::copy_n(marker->data + kExifSignatureSize, marker_length, exif->data());
+ return;
+ }
+}
+
+JpegliDataType ConvertDataType(JxlDataType type) {
+ switch (type) {
+ case JXL_TYPE_UINT8:
+ return JPEGLI_TYPE_UINT8;
+ case JXL_TYPE_UINT16:
+ return JPEGLI_TYPE_UINT16;
+ case JXL_TYPE_FLOAT:
+ return JPEGLI_TYPE_FLOAT;
+ default:
+ return JPEGLI_TYPE_UINT8;
+ }
+}
+
+JpegliEndianness ConvertEndianness(JxlEndianness type) {
+ switch (type) {
+ case JXL_NATIVE_ENDIAN:
+ return JPEGLI_NATIVE_ENDIAN;
+ case JXL_BIG_ENDIAN:
+ return JPEGLI_BIG_ENDIAN;
+ case JXL_LITTLE_ENDIAN:
+ return JPEGLI_LITTLE_ENDIAN;
+ default:
+ return JPEGLI_NATIVE_ENDIAN;
+ }
+}
+
+JxlColorSpace ConvertColorSpace(J_COLOR_SPACE colorspace) {
+ switch (colorspace) {
+ case JCS_GRAYSCALE:
+ return JXL_COLOR_SPACE_GRAY;
+ case JCS_RGB:
+ return JXL_COLOR_SPACE_RGB;
+ default:
+ return JXL_COLOR_SPACE_UNKNOWN;
+ }
+}
+
+void MyErrorExit(j_common_ptr cinfo) {
+ jmp_buf* env = static_cast<jmp_buf*>(cinfo->client_data);
+ (*cinfo->err->output_message)(cinfo);
+ jpegli_destroy_decompress(reinterpret_cast<j_decompress_ptr>(cinfo));
+ longjmp(*env, 1);
+}
+
+void MyOutputMessage(j_common_ptr cinfo) {
+#if JXL_DEBUG_WARNING == 1
+ char buf[JMSG_LENGTH_MAX + 1];
+ (*cinfo->err->format_message)(cinfo, buf);
+ buf[JMSG_LENGTH_MAX] = 0;
+ JXL_WARNING("%s", buf);
+#endif
+}
+
+void UnmapColors(uint8_t* row, size_t xsize, int components,
+ JSAMPARRAY colormap, size_t num_colors) {
+ JXL_CHECK(colormap != nullptr);
+ std::vector<uint8_t> tmp(xsize * components);
+ for (size_t x = 0; x < xsize; ++x) {
+ JXL_CHECK(row[x] < num_colors);
+ for (int c = 0; c < components; ++c) {
+ tmp[x * components + c] = colormap[c][row[x]];
+ }
+ }
+ memcpy(row, tmp.data(), tmp.size());
+}
+
+} // namespace
+
+Status DecodeJpeg(const std::vector<uint8_t>& compressed,
+ const JpegDecompressParams& dparams, ThreadPool* pool,
+ PackedPixelFile* ppf) {
+ // Don't do anything for non-JPEG files (no need to report an error)
+ if (!IsJPG(compressed)) return false;
+
+ // TODO(veluca): use JPEGData also for pixels?
+
+ // We need to declare all the non-trivial destructor local variables before
+ // the call to setjmp().
+ std::unique_ptr<JSAMPLE[]> row;
+
+ jpeg_decompress_struct cinfo;
+ const auto try_catch_block = [&]() -> bool {
+ // Setup error handling in jpeg library so we can deal with broken jpegs in
+ // the fuzzer.
+ jpeg_error_mgr jerr;
+ jmp_buf env;
+ cinfo.err = jpegli_std_error(&jerr);
+ jerr.error_exit = &MyErrorExit;
+ jerr.output_message = &MyOutputMessage;
+ if (setjmp(env)) {
+ return false;
+ }
+ cinfo.client_data = static_cast<void*>(&env);
+
+ jpegli_create_decompress(&cinfo);
+ jpegli_mem_src(&cinfo,
+ reinterpret_cast<const unsigned char*>(compressed.data()),
+ compressed.size());
+ jpegli_save_markers(&cinfo, kICCMarker, 0xFFFF);
+ jpegli_save_markers(&cinfo, kExifMarker, 0xFFFF);
+ const auto failure = [&cinfo](const char* str) -> Status {
+ jpegli_abort_decompress(&cinfo);
+ jpegli_destroy_decompress(&cinfo);
+ return JXL_FAILURE("%s", str);
+ };
+ jpegli_read_header(&cinfo, TRUE);
+ // Might cause CPU-zip bomb.
+ if (cinfo.arith_code) {
+ return failure("arithmetic code JPEGs are not supported");
+ }
+ int nbcomp = cinfo.num_components;
+ if (nbcomp != 1 && nbcomp != 3) {
+ return failure("unsupported number of components in JPEG");
+ }
+ if (dparams.force_rgb) {
+ cinfo.out_color_space = JCS_RGB;
+ } else if (dparams.force_grayscale) {
+ cinfo.out_color_space = JCS_GRAYSCALE;
+ }
+ if (!ReadICCProfile(&cinfo, &ppf->icc)) {
+ ppf->icc.clear();
+ // Default to SRGB
+ ppf->color_encoding.color_space =
+ ConvertColorSpace(cinfo.out_color_space);
+ 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_PERCEPTUAL;
+ }
+ ReadExif(&cinfo, &ppf->metadata.exif);
+
+ ppf->info.xsize = cinfo.image_width;
+ ppf->info.ysize = cinfo.image_height;
+ if (dparams.output_data_type == JXL_TYPE_UINT8) {
+ ppf->info.bits_per_sample = 8;
+ ppf->info.exponent_bits_per_sample = 0;
+ } else if (dparams.output_data_type == JXL_TYPE_UINT16) {
+ ppf->info.bits_per_sample = 16;
+ ppf->info.exponent_bits_per_sample = 0;
+ } else if (dparams.output_data_type == JXL_TYPE_FLOAT) {
+ ppf->info.bits_per_sample = 32;
+ ppf->info.exponent_bits_per_sample = 8;
+ } else {
+ return failure("unsupported data type");
+ }
+ ppf->info.uses_original_profile = true;
+
+ // No alpha in JPG
+ ppf->info.alpha_bits = 0;
+ ppf->info.alpha_exponent_bits = 0;
+ ppf->info.orientation = JXL_ORIENT_IDENTITY;
+
+ jpegli_set_output_format(&cinfo, ConvertDataType(dparams.output_data_type),
+ ConvertEndianness(dparams.output_endianness));
+
+ if (dparams.num_colors > 0) {
+ cinfo.quantize_colors = TRUE;
+ cinfo.desired_number_of_colors = dparams.num_colors;
+ cinfo.two_pass_quantize = dparams.two_pass_quant;
+ cinfo.dither_mode = (J_DITHER_MODE)dparams.dither_mode;
+ }
+
+ jpegli_start_decompress(&cinfo);
+
+ ppf->info.num_color_channels = cinfo.out_color_components;
+ const JxlPixelFormat format{
+ /*num_channels=*/static_cast<uint32_t>(cinfo.out_color_components),
+ dparams.output_data_type,
+ dparams.output_endianness,
+ /*align=*/0,
+ };
+ ppf->frames.clear();
+ // Allocates the frame buffer.
+ ppf->frames.emplace_back(cinfo.image_width, cinfo.image_height, format);
+ const auto& frame = ppf->frames.back();
+ JXL_ASSERT(sizeof(JSAMPLE) * cinfo.out_color_components *
+ cinfo.image_width <=
+ frame.color.stride);
+
+ for (size_t y = 0; y < cinfo.image_height; ++y) {
+ JSAMPROW rows[] = {reinterpret_cast<JSAMPLE*>(
+ static_cast<uint8_t*>(frame.color.pixels()) +
+ frame.color.stride * y)};
+ jpegli_read_scanlines(&cinfo, rows, 1);
+ if (dparams.num_colors > 0) {
+ UnmapColors(rows[0], cinfo.output_width, cinfo.out_color_components,
+ cinfo.colormap, cinfo.actual_number_of_colors);
+ }
+ }
+
+ jpegli_finish_decompress(&cinfo);
+ return true;
+ };
+ bool success = try_catch_block();
+ jpegli_destroy_decompress(&cinfo);
+ return success;
+}
+
+} // namespace extras
+} // namespace jxl
diff --git a/lib/extras/dec/jpegli.h b/lib/extras/dec/jpegli.h
new file mode 100644
index 0000000..574df54
--- /dev/null
+++ b/lib/extras/dec/jpegli.h
@@ -0,0 +1,41 @@
+// Copyright (c) the JPEG XL Project Authors. All rights reserved.
+//
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+#ifndef LIB_EXTRAS_DEC_JPEGLI_H_
+#define LIB_EXTRAS_DEC_JPEGLI_H_
+
+// Decodes JPG pixels and metadata in memory using the libjpegli library.
+
+#include <jxl/types.h>
+#include <stdint.h>
+
+#include <vector>
+
+#include "lib/extras/packed_image.h"
+#include "lib/jxl/base/data_parallel.h"
+#include "lib/jxl/base/status.h"
+
+namespace jxl {
+namespace extras {
+
+struct JpegDecompressParams {
+ JxlDataType output_data_type = JXL_TYPE_UINT8;
+ JxlEndianness output_endianness = JXL_NATIVE_ENDIAN;
+ bool force_rgb = false;
+ bool force_grayscale = false;
+ int num_colors = 0;
+ bool two_pass_quant = true;
+ // 0 = none, 1 = ordered, 2 = Floyd-Steinberg
+ int dither_mode = 2;
+};
+
+Status DecodeJpeg(const std::vector<uint8_t>& compressed,
+ const JpegDecompressParams& dparams, ThreadPool* pool,
+ PackedPixelFile* ppf);
+
+} // namespace extras
+} // namespace jxl
+
+#endif // LIB_EXTRAS_DEC_JPEGLI_H_
diff --git a/lib/extras/dec/jpg.cc b/lib/extras/dec/jpg.cc
index 6b92f4a..3c8a4bc 100644
--- a/lib/extras/dec/jpg.cc
+++ b/lib/extras/dec/jpg.cc
@@ -5,8 +5,10 @@
#include "lib/extras/dec/jpg.h"
+#if JPEGXL_ENABLE_JPEG
#include <jpeglib.h>
#include <setjmp.h>
+#endif
#include <stdint.h>
#include <algorithm>
@@ -14,12 +16,14 @@
#include <utility>
#include <vector>
+#include "lib/extras/size_constraints.h"
#include "lib/jxl/base/status.h"
#include "lib/jxl/sanitizers.h"
namespace jxl {
namespace extras {
+#if JPEGXL_ENABLE_JPEG
namespace {
constexpr unsigned char kICCSignature[12] = {
@@ -160,12 +164,35 @@ void MyOutputMessage(j_common_ptr cinfo) {
#endif
}
+void UnmapColors(uint8_t* row, size_t xsize, int components,
+ JSAMPARRAY colormap, size_t num_colors) {
+ JXL_CHECK(colormap != nullptr);
+ std::vector<uint8_t> tmp(xsize * components);
+ for (size_t x = 0; x < xsize; ++x) {
+ JXL_CHECK(row[x] < num_colors);
+ for (int c = 0; c < components; ++c) {
+ tmp[x * components + c] = colormap[c][row[x]];
+ }
+ }
+ memcpy(row, tmp.data(), tmp.size());
+}
+
} // namespace
+#endif
+
+bool CanDecodeJPG() {
+#if JPEGXL_ENABLE_JPEG
+ return true;
+#else
+ return false;
+#endif
+}
Status DecodeImageJPG(const Span<const uint8_t> bytes,
- const ColorHints& color_hints,
- const SizeConstraints& constraints,
- PackedPixelFile* ppf) {
+ const ColorHints& color_hints, PackedPixelFile* ppf,
+ const SizeConstraints* constraints,
+ const JPGDecompressParams* dparams) {
+#if JPEGXL_ENABLE_JPEG
// Don't do anything for non-JPEG files (no need to report an error)
if (!IsJPG(bytes)) return false;
@@ -176,10 +203,7 @@ Status DecodeImageJPG(const Span<const uint8_t> bytes,
std::unique_ptr<JSAMPLE[]> row;
const auto try_catch_block = [&]() -> bool {
- jpeg_decompress_struct cinfo;
- // cinfo is initialized by libjpeg, which we are not instrumenting with
- // msan, therefore we need to initialize cinfo here.
- msan::UnpoisonMemory(&cinfo, sizeof(cinfo));
+ jpeg_decompress_struct cinfo = {};
// Setup error handling in jpeg library so we can deal with broken jpegs in
// the fuzzer.
jpeg_error_mgr jerr;
@@ -207,8 +231,7 @@ Status DecodeImageJPG(const Span<const uint8_t> bytes,
if (read_header_result == JPEG_SUSPENDED) {
return failure("truncated JPEG input");
}
- if (!VerifyDimensions(&constraints, cinfo.image_width,
- cinfo.image_height)) {
+ if (!VerifyDimensions(constraints, cinfo.image_width, cinfo.image_height)) {
return failure("image too big");
}
// Might cause CPU-zip bomb.
@@ -252,12 +275,21 @@ Status DecodeImageJPG(const Span<const uint8_t> bytes,
ppf->info.num_color_channels = nbcomp;
ppf->info.orientation = JXL_ORIENT_IDENTITY;
+ if (dparams && dparams->num_colors > 0) {
+ cinfo.quantize_colors = TRUE;
+ cinfo.desired_number_of_colors = dparams->num_colors;
+ cinfo.two_pass_quantize = dparams->two_pass_quant;
+ cinfo.dither_mode = (J_DITHER_MODE)dparams->dither_mode;
+ }
+
jpeg_start_decompress(&cinfo);
- JXL_ASSERT(cinfo.output_components == nbcomp);
+ JXL_ASSERT(cinfo.out_color_components == nbcomp);
+ JxlDataType data_type =
+ ppf->info.bits_per_sample <= 8 ? JXL_TYPE_UINT8 : JXL_TYPE_UINT16;
const JxlPixelFormat format{
/*num_channels=*/static_cast<uint32_t>(nbcomp),
- /*data_type=*/BITS_IN_JSAMPLE == 8 ? JXL_TYPE_UINT8 : JXL_TYPE_UINT16,
+ data_type,
/*endianness=*/JXL_NATIVE_ENDIAN,
/*align=*/0,
};
@@ -265,9 +297,19 @@ Status DecodeImageJPG(const Span<const uint8_t> bytes,
// Allocates the frame buffer.
ppf->frames.emplace_back(cinfo.image_width, cinfo.image_height, format);
const auto& frame = ppf->frames.back();
- JXL_ASSERT(sizeof(JSAMPLE) * cinfo.output_components * cinfo.image_width <=
+ JXL_ASSERT(sizeof(JSAMPLE) * cinfo.out_color_components *
+ cinfo.image_width <=
frame.color.stride);
+ if (cinfo.quantize_colors) {
+ jxl::msan::UnpoisonMemory(cinfo.colormap, cinfo.out_color_components *
+ sizeof(cinfo.colormap[0]));
+ for (int c = 0; c < cinfo.out_color_components; ++c) {
+ jxl::msan::UnpoisonMemory(
+ cinfo.colormap[c],
+ cinfo.actual_number_of_colors * sizeof(cinfo.colormap[c][0]));
+ }
+ }
for (size_t y = 0; y < cinfo.image_height; ++y) {
JSAMPROW rows[] = {reinterpret_cast<JSAMPLE*>(
static_cast<uint8_t*>(frame.color.pixels()) +
@@ -275,6 +317,10 @@ Status DecodeImageJPG(const Span<const uint8_t> bytes,
jpeg_read_scanlines(&cinfo, rows, 1);
msan::UnpoisonMemory(rows[0], sizeof(JSAMPLE) * cinfo.output_components *
cinfo.image_width);
+ if (dparams && dparams->num_colors > 0) {
+ UnmapColors(rows[0], cinfo.output_width, cinfo.out_color_components,
+ cinfo.colormap, cinfo.actual_number_of_colors);
+ }
}
jpeg_finish_decompress(&cinfo);
@@ -283,6 +329,9 @@ Status DecodeImageJPG(const Span<const uint8_t> bytes,
};
return try_catch_block();
+#else
+ return false;
+#endif
}
} // namespace extras
diff --git a/lib/extras/dec/jpg.h b/lib/extras/dec/jpg.h
index 66b3452..6e7b2f7 100644
--- a/lib/extras/dec/jpg.h
+++ b/lib/extras/dec/jpg.h
@@ -13,19 +13,31 @@
#include "lib/extras/codec.h"
#include "lib/extras/dec/color_hints.h"
#include "lib/jxl/base/data_parallel.h"
-#include "lib/jxl/base/padded_bytes.h"
#include "lib/jxl/base/span.h"
#include "lib/jxl/base/status.h"
-#include "lib/jxl/codec_in_out.h"
namespace jxl {
+
+struct SizeConstraints;
+
namespace extras {
+bool CanDecodeJPG();
+
+struct JPGDecompressParams {
+ int num_colors = 0;
+ bool two_pass_quant = false;
+ // 0 = none, 1 = ordered, 2 = Floyd-Steinberg
+ int dither_mode = 0;
+};
+
// Decodes `bytes` into `ppf`. color_hints are ignored.
// `elapsed_deinterleave`, if non-null, will be set to the time (in seconds)
// that it took to deinterleave the raw JSAMPLEs to planar floats.
Status DecodeImageJPG(Span<const uint8_t> bytes, const ColorHints& color_hints,
- const SizeConstraints& constraints, PackedPixelFile* ppf);
+ PackedPixelFile* ppf,
+ const SizeConstraints* constraints = nullptr,
+ const JPGDecompressParams* dparams = nullptr);
} // namespace extras
} // namespace jxl
diff --git a/lib/extras/dec/jxl.cc b/lib/extras/dec/jxl.cc
index 0e10356..1f3a3ff 100644
--- a/lib/extras/dec/jxl.cc
+++ b/lib/extras/dec/jxl.cc
@@ -5,12 +5,15 @@
#include "lib/extras/dec/jxl.h"
-#include "jxl/decode.h"
-#include "jxl/decode_cxx.h"
-#include "jxl/types.h"
+#include <jxl/cms.h>
+#include <jxl/decode.h>
+#include <jxl/decode_cxx.h>
+#include <jxl/types.h>
+
+#include "lib/extras/common.h"
#include "lib/extras/dec/color_description.h"
-#include "lib/extras/enc/encode.h"
#include "lib/jxl/base/printf_macros.h"
+#include "lib/jxl/exif.h"
namespace jxl {
namespace extras {
@@ -68,11 +71,48 @@ struct BoxProcessor {
}
};
+void SetBitDepthFromDataType(JxlDataType data_type, uint32_t* bits_per_sample,
+ uint32_t* exponent_bits_per_sample) {
+ switch (data_type) {
+ case JXL_TYPE_UINT8:
+ *bits_per_sample = 8;
+ *exponent_bits_per_sample = 0;
+ break;
+ case JXL_TYPE_UINT16:
+ *bits_per_sample = 16;
+ *exponent_bits_per_sample = 0;
+ break;
+ case JXL_TYPE_FLOAT16:
+ *bits_per_sample = 16;
+ *exponent_bits_per_sample = 5;
+ break;
+ case JXL_TYPE_FLOAT:
+ *bits_per_sample = 32;
+ *exponent_bits_per_sample = 8;
+ break;
+ }
+}
+
+template <typename T>
+void UpdateBitDepth(JxlBitDepth bit_depth, JxlDataType data_type, T* info) {
+ if (bit_depth.type == JXL_BIT_DEPTH_FROM_PIXEL_FORMAT) {
+ SetBitDepthFromDataType(data_type, &info->bits_per_sample,
+ &info->exponent_bits_per_sample);
+ } else if (bit_depth.type == JXL_BIT_DEPTH_CUSTOM) {
+ info->bits_per_sample = bit_depth.bits_per_sample;
+ info->exponent_bits_per_sample = bit_depth.exponent_bits_per_sample;
+ }
+}
+
} // namespace
bool DecodeImageJXL(const uint8_t* bytes, size_t bytes_size,
const JXLDecompressParams& dparams, size_t* decoded_bytes,
PackedPixelFile* ppf, std::vector<uint8_t>* jpeg_bytes) {
+ JxlSignature sig = JxlSignatureCheck(bytes, bytes_size);
+ // silently return false if this is not a JXL file
+ if (sig == JXL_SIG_INVALID) return false;
+
auto decoder = JxlDecoderMake(/*memory_manager=*/nullptr);
JxlDecoder* dec = decoder.get();
ppf->frames.clear();
@@ -86,12 +126,7 @@ bool DecodeImageJXL(const uint8_t* bytes, size_t bytes_size,
JxlPixelFormat format;
std::vector<JxlPixelFormat> accepted_formats = dparams.accepted_formats;
- if (accepted_formats.empty()) {
- for (const uint32_t num_channels : {1, 2, 3, 4}) {
- accepted_formats.push_back(
- {num_channels, JXL_TYPE_FLOAT, JXL_LITTLE_ENDIAN, /*align=*/0});
- }
- }
+
JxlColorEncoding color_encoding;
size_t num_color_channels = 0;
if (!dparams.color_space.empty()) {
@@ -107,7 +142,9 @@ bool DecodeImageJXL(const uint8_t* bytes, size_t bytes_size,
bool can_reconstruct_jpeg = false;
std::vector<uint8_t> jpeg_data_chunk;
if (jpeg_bytes != nullptr) {
- jpeg_data_chunk.resize(16384);
+ // This bound is very likely to be enough to hold the entire
+ // reconstructed JPEG, to avoid having to do expensive retries.
+ jpeg_data_chunk.resize(bytes_size * 3 / 2 + 1024);
jpeg_bytes->resize(0);
}
@@ -128,6 +165,10 @@ bool DecodeImageJXL(const uint8_t* bytes, size_t bytes_size,
} else {
events |= (JXL_DEC_COLOR_ENCODING | JXL_DEC_FRAME | JXL_DEC_PREVIEW_IMAGE |
JXL_DEC_BOX);
+ if (accepted_formats.empty()) {
+ // decoding just the metadata, not the pixel data
+ events ^= (JXL_DEC_FULL_IMAGE | JXL_DEC_PREVIEW_IMAGE);
+ }
}
if (JXL_DEC_SUCCESS != JxlDecoderSubscribeEvents(dec, events)) {
fprintf(stderr, "JxlDecoderSubscribeEvents failed\n");
@@ -165,7 +206,7 @@ bool DecodeImageJXL(const uint8_t* bytes, size_t bytes_size,
return false;
}
uint32_t progression_index = 0;
- bool codestream_done = false;
+ bool codestream_done = accepted_formats.empty();
BoxProcessor boxes(dec);
for (;;) {
JxlDecoderStatus status = JxlDecoderProcessInput(dec);
@@ -185,8 +226,12 @@ bool DecodeImageJXL(const uint8_t* bytes, size_t bytes_size,
}
break;
}
+ size_t released_size = JxlDecoderReleaseInput(dec);
fprintf(stderr,
- "Input file is truncated and allow_partial_input was disabled.");
+ "Input file is truncated (total bytes: %" PRIuS
+ ", processed bytes: %" PRIuS
+ ") and --allow_partial_files is not present.\n",
+ bytes_size, bytes_size - released_size);
return false;
} else if (status == JXL_DEC_BOX) {
boxes.FinalizeOutput();
@@ -240,11 +285,16 @@ bool DecodeImageJXL(const uint8_t* bytes, size_t bytes_size,
fprintf(stderr, "JxlDecoderGetBasicInfo failed\n");
return false;
}
+ if (accepted_formats.empty()) continue;
if (num_color_channels != 0) {
// Mark the change in number of color channels due to the requested
// color space.
ppf->info.num_color_channels = num_color_channels;
}
+ if (dparams.output_bitdepth.type == JXL_BIT_DEPTH_CUSTOM) {
+ // Select format based on custom bits per sample.
+ ppf->info.bits_per_sample = dparams.output_bitdepth.bits_per_sample;
+ }
// Select format according to accepted formats.
if (!jxl::extras::SelectFormat(accepted_formats, ppf->info, &format)) {
fprintf(stderr, "SelectFormat failed\n");
@@ -254,9 +304,11 @@ bool DecodeImageJXL(const uint8_t* bytes, size_t bytes_size,
if (!have_alpha) {
// Mark in the basic info that alpha channel was dropped.
ppf->info.alpha_bits = 0;
- } else if (dparams.unpremultiply_alpha) {
- // Mark in the basic info that alpha was unpremultiplied.
- ppf->info.alpha_premultiplied = false;
+ } else {
+ if (dparams.unpremultiply_alpha) {
+ // Mark in the basic info that alpha was unpremultiplied.
+ ppf->info.alpha_premultiplied = false;
+ }
}
bool alpha_found = false;
for (uint32_t i = 0; i < ppf->info.num_extra_channels; ++i) {
@@ -287,6 +339,7 @@ bool DecodeImageJXL(const uint8_t* bytes, size_t bytes_size,
"Warning: --color_space ignored because the image is "
"not XYB encoded.\n");
} else {
+ JxlDecoderSetCms(dec, *JxlGetDefaultCms());
if (JXL_DEC_SUCCESS !=
JxlDecoderSetPreferredColorProfile(dec, &color_encoding)) {
fprintf(stderr, "Failed to set color space.\n");
@@ -296,34 +349,35 @@ bool DecodeImageJXL(const uint8_t* bytes, size_t bytes_size,
}
size_t icc_size = 0;
JxlColorProfileTarget target = JXL_COLOR_PROFILE_TARGET_DATA;
- if (JXL_DEC_SUCCESS !=
- JxlDecoderGetICCProfileSize(dec, nullptr, target, &icc_size)) {
- fprintf(stderr, "JxlDecoderGetICCProfileSize failed\n");
- }
- if (icc_size != 0) {
- ppf->icc.resize(icc_size);
+ ppf->color_encoding.color_space = JXL_COLOR_SPACE_UNKNOWN;
+ if (JXL_DEC_SUCCESS != JxlDecoderGetColorAsEncodedProfile(
+ dec, target, &ppf->color_encoding) ||
+ dparams.need_icc) {
+ // only get ICC if it is not an Enum color encoding
if (JXL_DEC_SUCCESS !=
- JxlDecoderGetColorAsICCProfile(dec, nullptr, target,
- ppf->icc.data(), icc_size)) {
- fprintf(stderr, "JxlDecoderGetColorAsICCProfile failed\n");
- return false;
+ JxlDecoderGetICCProfileSize(dec, target, &icc_size)) {
+ fprintf(stderr, "JxlDecoderGetICCProfileSize failed\n");
+ }
+ if (icc_size != 0) {
+ ppf->icc.resize(icc_size);
+ if (JXL_DEC_SUCCESS != JxlDecoderGetColorAsICCProfile(
+ dec, target, ppf->icc.data(), icc_size)) {
+ fprintf(stderr, "JxlDecoderGetColorAsICCProfile failed\n");
+ return false;
+ }
}
- }
- if (JXL_DEC_SUCCESS != JxlDecoderGetColorAsEncodedProfile(
- dec, nullptr, target, &ppf->color_encoding)) {
- ppf->color_encoding.color_space = JXL_COLOR_SPACE_UNKNOWN;
}
icc_size = 0;
target = JXL_COLOR_PROFILE_TARGET_ORIGINAL;
if (JXL_DEC_SUCCESS !=
- JxlDecoderGetICCProfileSize(dec, nullptr, target, &icc_size)) {
+ JxlDecoderGetICCProfileSize(dec, target, &icc_size)) {
fprintf(stderr, "JxlDecoderGetICCProfileSize failed\n");
}
if (icc_size != 0) {
ppf->orig_icc.resize(icc_size);
if (JXL_DEC_SUCCESS !=
- JxlDecoderGetColorAsICCProfile(dec, nullptr, target,
- ppf->orig_icc.data(), icc_size)) {
+ JxlDecoderGetColorAsICCProfile(dec, target, ppf->orig_icc.data(),
+ icc_size)) {
fprintf(stderr, "JxlDecoderGetColorAsICCProfile failed\n");
return false;
}
@@ -421,9 +475,21 @@ bool DecodeImageJXL(const uint8_t* bytes, size_t bytes_size,
return false;
}
}
+ if (JXL_DEC_SUCCESS !=
+ JxlDecoderSetImageOutBitDepth(dec, &dparams.output_bitdepth)) {
+ fprintf(stderr, "JxlDecoderSetImageOutBitDepth failed\n");
+ return false;
+ }
+ UpdateBitDepth(dparams.output_bitdepth, format.data_type, &ppf->info);
+ bool have_alpha = (format.num_channels == 2 || format.num_channels == 4);
+ if (have_alpha) {
+ // Interleaved alpha channels has the same bit depth as color channels.
+ ppf->info.alpha_bits = ppf->info.bits_per_sample;
+ ppf->info.alpha_exponent_bits = ppf->info.exponent_bits_per_sample;
+ }
JxlPixelFormat ec_format = format;
ec_format.num_channels = 1;
- for (const auto& eci : ppf->extra_channels_info) {
+ for (auto& eci : ppf->extra_channels_info) {
frame.extra_channels.emplace_back(jxl::extras::PackedImage(
ppf->info.xsize, ppf->info.ysize, ec_format));
auto& ec = frame.extra_channels.back();
@@ -446,6 +512,8 @@ bool DecodeImageJXL(const uint8_t* bytes, size_t bytes_size,
fprintf(stderr, "JxlDecoderSetExtraChannelBuffer failed\n");
return false;
}
+ UpdateBitDepth(dparams.output_bitdepth, ec_format.data_type,
+ &eci.ec_info);
}
} else if (status == JXL_DEC_SUCCESS) {
// Decoding finished successfully.
@@ -463,6 +531,28 @@ bool DecodeImageJXL(const uint8_t* bytes, size_t bytes_size,
}
}
boxes.FinalizeOutput();
+ if (!ppf->metadata.exif.empty()) {
+ // Verify that Exif box has a valid TIFF header at the specified offset.
+ // Discard bytes preceding the header.
+ if (ppf->metadata.exif.size() >= 4) {
+ uint32_t offset = LoadBE32(ppf->metadata.exif.data());
+ if (offset <= ppf->metadata.exif.size() - 8) {
+ std::vector<uint8_t> exif(ppf->metadata.exif.begin() + 4 + offset,
+ ppf->metadata.exif.end());
+ bool bigendian;
+ if (IsExif(exif, &bigendian)) {
+ ppf->metadata.exif = std::move(exif);
+ } else {
+ fprintf(stderr, "Warning: invalid TIFF header in Exif\n");
+ }
+ } else {
+ fprintf(stderr, "Warning: invalid Exif offset: %" PRIu32 "\n", offset);
+ }
+ } else {
+ fprintf(stderr, "Warning: invalid Exif length: %" PRIuS "\n",
+ ppf->metadata.exif.size());
+ }
+ }
if (jpeg_bytes != nullptr) {
if (!can_reconstruct_jpeg) return false;
size_t used_jpeg_output =
diff --git a/lib/extras/dec/jxl.h b/lib/extras/dec/jxl.h
index c462fa4..cbada1f 100644
--- a/lib/extras/dec/jxl.h
+++ b/lib/extras/dec/jxl.h
@@ -8,14 +8,14 @@
// Decodes JPEG XL images in memory.
+#include <jxl/parallel_runner.h>
+#include <jxl/types.h>
#include <stdint.h>
#include <limits>
#include <string>
#include <vector>
-#include "jxl/parallel_runner.h"
-#include "jxl/types.h"
#include "lib/extras/packed_image.h"
namespace jxl {
@@ -41,6 +41,10 @@ struct JXLDecompressParams {
// Whether truncated input should be treated as an error.
bool allow_partial_input = false;
+ // Set to true if an ICC profile has to be synthesized for Enum color
+ // encodings
+ bool need_icc = false;
+
// How many passes to decode at most. By default, decode everything.
uint32_t max_passes = std::numeric_limits<uint32_t>::max();
@@ -53,6 +57,9 @@ struct JXLDecompressParams {
bool use_image_callback = true;
// Whether to unpremultiply colors for associated alpha channels.
bool unpremultiply_alpha = false;
+
+ // Controls the effective bit depth of the output pixels.
+ JxlBitDepth output_bitdepth = {JXL_BIT_DEPTH_FROM_PIXEL_FORMAT, 0, 0};
};
bool DecodeImageJXL(const uint8_t* bytes, size_t bytes_size,
diff --git a/lib/extras/dec/pgx.cc b/lib/extras/dec/pgx.cc
index 1417348..a99eb0f 100644
--- a/lib/extras/dec/pgx.cc
+++ b/lib/extras/dec/pgx.cc
@@ -7,6 +7,7 @@
#include <string.h>
+#include "lib/extras/size_constraints.h"
#include "lib/jxl/base/bits.h"
#include "lib/jxl/base/compiler_specific.h"
@@ -145,15 +146,14 @@ class Parser {
} // namespace
Status DecodeImagePGX(const Span<const uint8_t> bytes,
- const ColorHints& color_hints,
- const SizeConstraints& constraints,
- PackedPixelFile* ppf) {
+ const ColorHints& color_hints, PackedPixelFile* ppf,
+ const SizeConstraints* constraints) {
Parser parser(bytes);
HeaderPGX header = {};
const uint8_t* pos;
if (!parser.ParseHeader(&header, &pos)) return false;
JXL_RETURN_IF_ERROR(
- VerifyDimensions(&constraints, header.xsize, header.ysize));
+ VerifyDimensions(constraints, header.xsize, header.ysize));
if (header.bits_per_sample == 0 || header.bits_per_sample > 32) {
return JXL_FAILURE("PGX: bits_per_sample invalid");
}
diff --git a/lib/extras/dec/pgx.h b/lib/extras/dec/pgx.h
index 38aedf5..ce852e6 100644
--- a/lib/extras/dec/pgx.h
+++ b/lib/extras/dec/pgx.h
@@ -14,17 +14,19 @@
#include "lib/extras/dec/color_hints.h"
#include "lib/extras/packed_image.h"
#include "lib/jxl/base/data_parallel.h"
-#include "lib/jxl/base/padded_bytes.h"
#include "lib/jxl/base/span.h"
#include "lib/jxl/base/status.h"
-#include "lib/jxl/codec_in_out.h"
namespace jxl {
+
+struct SizeConstraints;
+
namespace extras {
// Decodes `bytes` into `ppf`.
Status DecodeImagePGX(Span<const uint8_t> bytes, const ColorHints& color_hints,
- const SizeConstraints& constraints, PackedPixelFile* ppf);
+ PackedPixelFile* ppf,
+ const SizeConstraints* constraints = nullptr);
} // namespace extras
} // namespace jxl
diff --git a/lib/extras/dec/pgx_test.cc b/lib/extras/dec/pgx_test.cc
index 41e6bf8..5dbc314 100644
--- a/lib/extras/dec/pgx_test.cc
+++ b/lib/extras/dec/pgx_test.cc
@@ -5,16 +5,18 @@
#include "lib/extras/dec/pgx.h"
-#include "gtest/gtest.h"
+#include <cstring>
+
#include "lib/extras/packed_image_convert.h"
+#include "lib/jxl/image_bundle.h"
+#include "lib/jxl/testing.h"
namespace jxl {
namespace extras {
namespace {
Span<const uint8_t> MakeSpan(const char* str) {
- return Span<const uint8_t>(reinterpret_cast<const uint8_t*>(str),
- strlen(str));
+ return Bytes(reinterpret_cast<const uint8_t*>(str), strlen(str));
}
TEST(CodecPGXTest, Test8bits) {
@@ -23,8 +25,7 @@ TEST(CodecPGXTest, Test8bits) {
PackedPixelFile ppf;
ThreadPool* pool = nullptr;
- EXPECT_TRUE(DecodeImagePGX(MakeSpan(pgx.c_str()), ColorHints(),
- SizeConstraints(), &ppf));
+ EXPECT_TRUE(DecodeImagePGX(MakeSpan(pgx.c_str()), ColorHints(), &ppf));
CodecInOut io;
EXPECT_TRUE(ConvertPackedPixelFileToCodecInOut(ppf, pool, &io));
@@ -51,8 +52,7 @@ TEST(CodecPGXTest, Test16bits) {
PackedPixelFile ppf;
ThreadPool* pool = nullptr;
- EXPECT_TRUE(DecodeImagePGX(MakeSpan(pgx.c_str()), ColorHints(),
- SizeConstraints(), &ppf));
+ EXPECT_TRUE(DecodeImagePGX(MakeSpan(pgx.c_str()), ColorHints(), &ppf));
CodecInOut io;
EXPECT_TRUE(ConvertPackedPixelFileToCodecInOut(ppf, pool, &io));
diff --git a/lib/extras/dec/pnm.cc b/lib/extras/dec/pnm.cc
index 03aecef..c576385 100644
--- a/lib/extras/dec/pnm.cc
+++ b/lib/extras/dec/pnm.cc
@@ -8,6 +8,9 @@
#include <stdlib.h>
#include <string.h>
+#include <cmath>
+
+#include "lib/extras/size_constraints.h"
#include "lib/jxl/base/bits.h"
#include "lib/jxl/base/compiler_specific.h"
#include "lib/jxl/base/status.h"
@@ -16,16 +19,6 @@ namespace jxl {
namespace extras {
namespace {
-struct HeaderPNM {
- size_t xsize;
- size_t ysize;
- bool is_gray; // PGM
- bool has_alpha; // PAM
- size_t bits_per_sample;
- bool floating_point;
- bool big_endian;
-};
-
class Parser {
public:
explicit Parser(const Span<const uint8_t> bytes)
@@ -183,16 +176,20 @@ class Parser {
Status ParseHeaderPAM(HeaderPNM* header, const uint8_t** pos) {
size_t depth = 3;
size_t max_val = 255;
+ JXL_RETURN_IF_ERROR(SkipWhitespace());
while (!MatchString("ENDHDR", /*skipws=*/false)) {
- JXL_RETURN_IF_ERROR(SkipWhitespace());
if (MatchString("WIDTH")) {
JXL_RETURN_IF_ERROR(ParseUnsigned(&header->xsize));
+ JXL_RETURN_IF_ERROR(SkipWhitespace());
} else if (MatchString("HEIGHT")) {
JXL_RETURN_IF_ERROR(ParseUnsigned(&header->ysize));
+ JXL_RETURN_IF_ERROR(SkipWhitespace());
} else if (MatchString("DEPTH")) {
JXL_RETURN_IF_ERROR(ParseUnsigned(&depth));
+ JXL_RETURN_IF_ERROR(SkipWhitespace());
} else if (MatchString("MAXVAL")) {
JXL_RETURN_IF_ERROR(ParseUnsigned(&max_val));
+ JXL_RETURN_IF_ERROR(SkipWhitespace());
} else if (MatchString("TUPLTYPE")) {
if (MatchString("RGB_ALPHA")) {
header->has_alpha = true;
@@ -209,6 +206,20 @@ class Parser {
} else if (MatchString("BLACKANDWHITE")) {
header->is_gray = true;
max_val = 1;
+ } else if (MatchString("Alpha")) {
+ header->ec_types.push_back(JXL_CHANNEL_ALPHA);
+ } else if (MatchString("Depth")) {
+ header->ec_types.push_back(JXL_CHANNEL_DEPTH);
+ } else if (MatchString("SpotColor")) {
+ header->ec_types.push_back(JXL_CHANNEL_SPOT_COLOR);
+ } else if (MatchString("SelectionMask")) {
+ header->ec_types.push_back(JXL_CHANNEL_SELECTION_MASK);
+ } else if (MatchString("Black")) {
+ header->ec_types.push_back(JXL_CHANNEL_BLACK);
+ } else if (MatchString("CFA")) {
+ header->ec_types.push_back(JXL_CHANNEL_CFA);
+ } else if (MatchString("Thermal")) {
+ header->ec_types.push_back(JXL_CHANNEL_THERMAL);
} else {
return JXL_FAILURE("PAM: unknown TUPLTYPE");
}
@@ -223,13 +234,13 @@ class Parser {
}
size_t num_channels = header->is_gray ? 1 : 3;
if (header->has_alpha) num_channels++;
- if (num_channels != depth) {
+ if (num_channels + header->ec_types.size() != depth) {
return JXL_FAILURE("PAM: bad DEPTH");
}
if (max_val == 0 || max_val >= 65536) {
return JXL_FAILURE("PAM: bad MAXVAL");
}
- // e.g When `max_val` is 1 , we want 1 bit:
+ // e.g. When `max_val` is 1 , we want 1 bit:
header->bits_per_sample = FloorLog2Nonzero(max_val) + 1;
if ((1u << header->bits_per_sample) - 1 != max_val)
return JXL_FAILURE("PNM: unsupported MaxVal (expected 2^n - 1)");
@@ -298,30 +309,98 @@ class Parser {
};
Span<const uint8_t> MakeSpan(const char* str) {
- return Span<const uint8_t>(reinterpret_cast<const uint8_t*>(str),
- strlen(str));
+ return Bytes(reinterpret_cast<const uint8_t*>(str), strlen(str));
+}
+
+void ReadLinePNM(void* opaque, size_t xpos, size_t ypos, size_t xsize,
+ uint8_t* buffer, size_t len) {
+ ChunkedPNMDecoder* dec = reinterpret_cast<ChunkedPNMDecoder*>(opaque);
+ const size_t bytes_per_channel =
+ DivCeil(dec->header.bits_per_sample, jxl::kBitsPerByte);
+ const size_t pixel_offset = ypos * dec->header.xsize + xpos;
+ const size_t num_channels = dec->header.is_gray ? 1 : 3;
+ const size_t offset = pixel_offset * num_channels * bytes_per_channel;
+ const size_t num_bytes = xsize * num_channels * bytes_per_channel;
+ if (fseek(dec->f, dec->data_start + offset, SEEK_SET) != 0) {
+ return;
+ }
+ JXL_ASSERT(num_bytes == len);
+ if (num_bytes != fread(buffer, 1, num_bytes, dec->f)) {
+ JXL_WARNING("Failed to read from PNM file\n");
+ }
}
} // namespace
-Status DecodeImagePNM(const Span<const uint8_t> bytes,
- const ColorHints& color_hints,
- const SizeConstraints& constraints,
+Status DecodeImagePNM(ChunkedPNMDecoder* dec, const ColorHints& color_hints,
PackedPixelFile* ppf) {
+ std::vector<uint8_t> buffer(10 * 1024);
+ const size_t bytes_read = fread(buffer.data(), 1, buffer.size(), dec->f);
+ if (ferror(dec->f) || bytes_read > buffer.size()) {
+ return false;
+ }
+ Span<const uint8_t> span(buffer);
+ Parser parser(span);
+ HeaderPNM& header = dec->header;
+ const uint8_t* pos = nullptr;
+ if (!parser.ParseHeader(&header, &pos)) {
+ return false;
+ }
+ dec->data_start = pos - &buffer[0];
+
+ if (header.bits_per_sample == 0 || header.bits_per_sample > 16) {
+ return JXL_FAILURE("Invalid bits_per_sample");
+ }
+ if (header.has_alpha || !header.ec_types.empty() || header.floating_point) {
+ return JXL_FAILURE("Only PGM and PPM inputs are supported");
+ }
+
+ // PPM specifies that in the raster, the sample values are "nonlinear"
+ // (BP.709, with gamma number of 2.2). Deviate from the specification and
+ // assume `sRGB` in our implementation.
+ JXL_RETURN_IF_ERROR(ApplyColorHints(color_hints, /*color_already_set=*/false,
+ header.is_gray, ppf));
+
+ ppf->info.xsize = header.xsize;
+ ppf->info.ysize = header.ysize;
+ ppf->info.bits_per_sample = header.bits_per_sample;
+ ppf->info.exponent_bits_per_sample = 0;
+ ppf->info.orientation = JXL_ORIENT_IDENTITY;
+ ppf->info.alpha_bits = 0;
+ ppf->info.alpha_exponent_bits = 0;
+ ppf->info.num_color_channels = (header.is_gray ? 1 : 3);
+ ppf->info.num_extra_channels = 0;
+
+ const JxlDataType data_type =
+ header.bits_per_sample > 8 ? JXL_TYPE_UINT16 : JXL_TYPE_UINT8;
+ const JxlPixelFormat format{
+ /*num_channels=*/ppf->info.num_color_channels,
+ /*data_type=*/data_type,
+ /*endianness=*/header.big_endian ? JXL_BIG_ENDIAN : JXL_LITTLE_ENDIAN,
+ /*align=*/0,
+ };
+ ppf->chunked_frames.emplace_back(header.xsize, header.ysize, format, dec,
+ ReadLinePNM);
+ return true;
+}
+
+Status DecodeImagePNM(const Span<const uint8_t> bytes,
+ const ColorHints& color_hints, PackedPixelFile* ppf,
+ const SizeConstraints* constraints) {
Parser parser(bytes);
HeaderPNM header = {};
const uint8_t* pos = nullptr;
if (!parser.ParseHeader(&header, &pos)) return false;
JXL_RETURN_IF_ERROR(
- VerifyDimensions(&constraints, header.xsize, header.ysize));
+ VerifyDimensions(constraints, header.xsize, header.ysize));
if (header.bits_per_sample == 0 || header.bits_per_sample > 32) {
return JXL_FAILURE("PNM: bits_per_sample invalid");
}
- // PPM specify that in the raster, the sample values are "nonlinear" (BP.709,
- // with gamma number of 2.2). Deviate from the specification and assume
- // `sRGB` in our implementation.
+ // PPM specifies that in the raster, the sample values are "nonlinear"
+ // (BP.709, with gamma number of 2.2). Deviate from the specification and
+ // assume `sRGB` in our implementation.
JXL_RETURN_IF_ERROR(ApplyColorHints(color_hints, /*color_already_set=*/false,
header.is_gray, ppf));
@@ -341,7 +420,17 @@ Status DecodeImagePNM(const Span<const uint8_t> bytes,
ppf->info.alpha_bits = (header.has_alpha ? ppf->info.bits_per_sample : 0);
ppf->info.alpha_exponent_bits = 0;
ppf->info.num_color_channels = (header.is_gray ? 1 : 3);
- ppf->info.num_extra_channels = (header.has_alpha ? 1 : 0);
+ uint32_t num_alpha_channels = (header.has_alpha ? 1 : 0);
+ uint32_t num_interleaved_channels =
+ ppf->info.num_color_channels + num_alpha_channels;
+ ppf->info.num_extra_channels = num_alpha_channels + header.ec_types.size();
+
+ for (auto type : header.ec_types) {
+ PackedExtraChannel pec;
+ pec.ec_info.bits_per_sample = ppf->info.bits_per_sample;
+ pec.ec_info.type = type;
+ ppf->extra_channels_info.emplace_back(std::move(pec));
+ }
JxlDataType data_type;
if (header.floating_point) {
@@ -356,27 +445,50 @@ Status DecodeImagePNM(const Span<const uint8_t> bytes,
}
const JxlPixelFormat format{
- /*num_channels=*/ppf->info.num_color_channels +
- ppf->info.num_extra_channels,
+ /*num_channels=*/num_interleaved_channels,
/*data_type=*/data_type,
/*endianness=*/header.big_endian ? JXL_BIG_ENDIAN : JXL_LITTLE_ENDIAN,
/*align=*/0,
};
+ const JxlPixelFormat ec_format{1, format.data_type, format.endianness, 0};
ppf->frames.clear();
ppf->frames.emplace_back(header.xsize, header.ysize, format);
auto* frame = &ppf->frames.back();
-
+ for (size_t i = 0; i < header.ec_types.size(); ++i) {
+ frame->extra_channels.emplace_back(header.xsize, header.ysize, ec_format);
+ }
size_t pnm_remaining_size = bytes.data() + bytes.size() - pos;
if (pnm_remaining_size < frame->color.pixels_size) {
return JXL_FAILURE("PNM file too small");
}
- const bool flipped_y = header.bits_per_sample == 32; // PFMs are flipped
+
uint8_t* out = reinterpret_cast<uint8_t*>(frame->color.pixels());
- for (size_t y = 0; y < header.ysize; ++y) {
- size_t y_in = flipped_y ? header.ysize - 1 - y : y;
- const uint8_t* row_in = &pos[y_in * frame->color.stride];
- uint8_t* row_out = &out[y * frame->color.stride];
- memcpy(row_out, row_in, frame->color.stride);
+ std::vector<uint8_t*> ec_out(header.ec_types.size());
+ for (size_t i = 0; i < ec_out.size(); ++i) {
+ ec_out[i] = reinterpret_cast<uint8_t*>(frame->extra_channels[i].pixels());
+ }
+ if (ec_out.empty()) {
+ const bool flipped_y = header.bits_per_sample == 32; // PFMs are flipped
+ for (size_t y = 0; y < header.ysize; ++y) {
+ size_t y_in = flipped_y ? header.ysize - 1 - y : y;
+ const uint8_t* row_in = &pos[y_in * frame->color.stride];
+ uint8_t* row_out = &out[y * frame->color.stride];
+ memcpy(row_out, row_in, frame->color.stride);
+ }
+ } else {
+ size_t pwidth = PackedImage::BitsPerChannel(data_type) / 8;
+ for (size_t y = 0; y < header.ysize; ++y) {
+ for (size_t x = 0; x < header.xsize; ++x) {
+ memcpy(out, pos, frame->color.pixel_stride());
+ out += frame->color.pixel_stride();
+ pos += frame->color.pixel_stride();
+ for (auto& p : ec_out) {
+ memcpy(p, pos, pwidth);
+ pos += pwidth;
+ p += pwidth;
+ }
+ }
+ }
}
return true;
}
diff --git a/lib/extras/dec/pnm.h b/lib/extras/dec/pnm.h
index f637483..9b68e56 100644
--- a/lib/extras/dec/pnm.h
+++ b/lib/extras/dec/pnm.h
@@ -17,21 +17,43 @@
#include "lib/extras/dec/color_hints.h"
#include "lib/extras/packed_image.h"
#include "lib/jxl/base/data_parallel.h"
-#include "lib/jxl/base/padded_bytes.h"
#include "lib/jxl/base/span.h"
#include "lib/jxl/base/status.h"
-#include "lib/jxl/codec_in_out.h"
namespace jxl {
+
+struct SizeConstraints;
+
namespace extras {
// Decodes `bytes` into `ppf`. color_hints may specify "color_space", which
// defaults to sRGB.
Status DecodeImagePNM(Span<const uint8_t> bytes, const ColorHints& color_hints,
- const SizeConstraints& constraints, PackedPixelFile* ppf);
+ PackedPixelFile* ppf,
+ const SizeConstraints* constraints = nullptr);
void TestCodecPNM();
+struct HeaderPNM {
+ size_t xsize;
+ size_t ysize;
+ bool is_gray; // PGM
+ bool has_alpha; // PAM
+ size_t bits_per_sample;
+ bool floating_point;
+ bool big_endian;
+ std::vector<JxlExtraChannelType> ec_types; // PAM
+};
+
+struct ChunkedPNMDecoder {
+ FILE* f;
+ HeaderPNM header = {};
+ size_t data_start;
+};
+
+Status DecodeImagePNM(ChunkedPNMDecoder* dec, const ColorHints& color_hints,
+ PackedPixelFile* ppf);
+
} // namespace extras
} // namespace jxl