// 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_PACKED_IMAGE_H_ #define LIB_EXTRAS_PACKED_IMAGE_H_ // Helper class for storing external (int or float, interleaved) images. This is // the common format used by other libraries and in the libjxl API. #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "lib/jxl/base/byte_order.h" #include "lib/jxl/base/c_callback_support.h" #include "lib/jxl/base/common.h" #include "lib/jxl/base/status.h" namespace jxl { namespace extras { // Class representing an interleaved image with a bunch of channels. class PackedImage { public: static StatusOr Create(size_t xsize, size_t ysize, const JxlPixelFormat& format) { PackedImage image(xsize, ysize, format, CalcStride(format, xsize)); if (!image.pixels()) { // TODO(szabadka): use specialized OOM error code return JXL_FAILURE("Failed to allocate memory for image"); } return image; } PackedImage Copy() const { PackedImage copy(xsize, ysize, format, CalcStride(format, xsize)); memcpy(reinterpret_cast(copy.pixels()), reinterpret_cast(pixels()), pixels_size); return copy; } // The interleaved pixels as defined in the storage format. void* pixels() const { return pixels_.get(); } uint8_t* pixels(size_t y, size_t x, size_t c) const { return (reinterpret_cast(pixels_.get()) + y * stride + x * pixel_stride_ + c * bytes_per_channel_); } const uint8_t* const_pixels(size_t y, size_t x, size_t c) const { return (reinterpret_cast(pixels_.get()) + y * stride + x * pixel_stride_ + c * bytes_per_channel_); } // The image size in pixels. size_t xsize; size_t ysize; // The number of bytes per row. size_t stride; // Pixel storage format and buffer size of the pixels_ pointer. JxlPixelFormat format; size_t pixels_size; size_t pixel_stride() const { return pixel_stride_; } static size_t BitsPerChannel(JxlDataType data_type) { switch (data_type) { case JXL_TYPE_UINT8: return 8; case JXL_TYPE_UINT16: return 16; case JXL_TYPE_FLOAT: return 32; case JXL_TYPE_FLOAT16: return 16; default: JXL_ABORT("Unhandled JxlDataType"); } } float GetPixelValue(size_t y, size_t x, size_t c) const { const uint8_t* data = const_pixels(y, x, c); switch (format.data_type) { case JXL_TYPE_UINT8: return data[0] * (1.0f / 255); case JXL_TYPE_UINT16: { uint16_t val; memcpy(&val, data, 2); return (swap_endianness_ ? JXL_BSWAP16(val) : val) * (1.0f / 65535); } case JXL_TYPE_FLOAT: { float val; memcpy(&val, data, 4); return swap_endianness_ ? BSwapFloat(val) : val; } default: JXL_ABORT("Unhandled JxlDataType"); } } void SetPixelValue(size_t y, size_t x, size_t c, float val) const { uint8_t* data = pixels(y, x, c); switch (format.data_type) { case JXL_TYPE_UINT8: data[0] = Clamp1(std::round(val * 255), 0.0f, 255.0f); break; case JXL_TYPE_UINT16: { uint16_t val16 = Clamp1(std::round(val * 65535), 0.0f, 65535.0f); if (swap_endianness_) { val16 = JXL_BSWAP16(val16); } memcpy(data, &val16, 2); break; } case JXL_TYPE_FLOAT: { if (swap_endianness_) { val = BSwapFloat(val); } memcpy(data, &val, 4); break; } default: JXL_ABORT("Unhandled JxlDataType"); } } private: PackedImage(size_t xsize, size_t ysize, const JxlPixelFormat& format, size_t stride) : xsize(xsize), ysize(ysize), stride(stride), format(format), pixels_size(ysize * stride), pixels_(malloc(std::max(1, pixels_size)), free) { bytes_per_channel_ = BitsPerChannel(format.data_type) / jxl::kBitsPerByte; pixel_stride_ = format.num_channels * bytes_per_channel_; swap_endianness_ = SwapEndianness(format.endianness); } static size_t CalcStride(const JxlPixelFormat& format, size_t xsize) { size_t stride = xsize * (BitsPerChannel(format.data_type) * format.num_channels / jxl::kBitsPerByte); if (format.align > 1) { stride = jxl::DivCeil(stride, format.align) * format.align; } return stride; } size_t bytes_per_channel_; size_t pixel_stride_; bool swap_endianness_; std::unique_ptr pixels_; }; // Helper class representing a frame, as seen from the API. Animations will have // multiple frames, but a single frame can have a color/grayscale channel and // multiple extra channels. The order of the extra channels should be the same // as all other frames in the same image. class PackedFrame { public: explicit PackedFrame(PackedImage&& image) : color(std::move(image)) {} static StatusOr Create(size_t xsize, size_t ysize, const JxlPixelFormat& format) { JXL_ASSIGN_OR_RETURN(PackedImage image, PackedImage::Create(xsize, ysize, format)); PackedFrame frame(std::move(image)); return frame; } StatusOr Copy() const { JXL_ASSIGN_OR_RETURN( PackedFrame copy, PackedFrame::Create(color.xsize, color.ysize, color.format)); copy.frame_info = frame_info; copy.name = name; copy.color = color.Copy(); for (const auto& ec : extra_channels) { copy.extra_channels.emplace_back(ec.Copy()); } return copy; } // The Frame metadata. JxlFrameHeader frame_info = {}; std::string name; // The pixel data for the color (or grayscale) channels. PackedImage color; // Extra channel image data. std::vector extra_channels; }; class ChunkedPackedFrame { public: ChunkedPackedFrame( size_t xsize, size_t ysize, std::function get_input_source) : xsize(xsize), ysize(ysize), get_input_source_(std::move(get_input_source)) { const auto input_source = get_input_source_(); input_source.get_color_channels_pixel_format(input_source.opaque, &format); } JxlChunkedFrameInputSource GetInputSource() { return get_input_source_(); } // The Frame metadata. JxlFrameHeader frame_info = {}; std::string name; size_t xsize; size_t ysize; JxlPixelFormat format; private: std::function get_input_source_; }; // Optional metadata associated with a file class PackedMetadata { public: std::vector exif; std::vector iptc; std::vector jumbf; std::vector xmp; }; // The extra channel metadata information. struct PackedExtraChannel { JxlExtraChannelInfo ec_info; size_t index; std::string name; }; // Helper class representing a JXL image file as decoded to pixels from the API. class PackedPixelFile { public: JxlBasicInfo info = {}; std::vector extra_channels_info; // Color information of the decoded pixels. // `primary_color_representation` indicates whether `color_encoding` or `icc` // is the “authoritative” encoding of the colorspace, as opposed to a fallback // encoding. For example, if `color_encoding` is the primary one, as would // occur when decoding a jxl file with such a representation, then `enc/jxl` // will use it and ignore the ICC profile, whereas `enc/png` will include the // ICC profile for compatibility. // If `icc` is the primary representation, `enc/jxl` will preserve it when // compressing losslessly, but *may* encode it as a color_encoding when // compressing lossily. enum { kColorEncodingIsPrimary, kIccIsPrimary } primary_color_representation = kColorEncodingIsPrimary; JxlColorEncoding color_encoding = {}; std::vector icc; // The icc profile of the original image. std::vector orig_icc; JxlBitDepth input_bitdepth = {JXL_BIT_DEPTH_FROM_PIXEL_FORMAT, 0, 0}; std::unique_ptr preview_frame; std::vector frames; mutable std::vector chunked_frames; PackedMetadata metadata; PackedPixelFile() { JxlEncoderInitBasicInfo(&info); }; size_t num_frames() const { return chunked_frames.empty() ? frames.size() : chunked_frames.size(); } size_t xsize() const { return info.xsize; } size_t ysize() const { return info.ysize; } }; } // namespace extras } // namespace jxl #endif // LIB_EXTRAS_PACKED_IMAGE_H_