packed_image.h (9381B)
1 // Copyright (c) the JPEG XL Project Authors. All rights reserved. 2 // 3 // Use of this source code is governed by a BSD-style 4 // license that can be found in the LICENSE file. 5 6 #ifndef LIB_EXTRAS_PACKED_IMAGE_H_ 7 #define LIB_EXTRAS_PACKED_IMAGE_H_ 8 9 // Helper class for storing external (int or float, interleaved) images. This is 10 // the common format used by other libraries and in the libjxl API. 11 12 #include <jxl/codestream_header.h> 13 #include <jxl/encode.h> 14 #include <jxl/types.h> 15 16 #include <algorithm> 17 #include <cmath> 18 #include <cstddef> 19 #include <cstdint> 20 #include <cstdlib> 21 #include <cstring> 22 #include <functional> 23 #include <memory> 24 #include <string> 25 #include <vector> 26 27 #include "lib/jxl/base/byte_order.h" 28 #include "lib/jxl/base/common.h" 29 #include "lib/jxl/base/status.h" 30 31 namespace jxl { 32 namespace extras { 33 34 // Class representing an interleaved image with a bunch of channels. 35 class PackedImage { 36 public: 37 static StatusOr<PackedImage> Create(size_t xsize, size_t ysize, 38 const JxlPixelFormat& format) { 39 PackedImage image(xsize, ysize, format, CalcStride(format, xsize)); 40 if (!image.pixels()) { 41 // TODO(szabadka): use specialized OOM error code 42 return JXL_FAILURE("Failed to allocate memory for image"); 43 } 44 return image; 45 } 46 47 PackedImage Copy() const { 48 PackedImage copy(xsize, ysize, format, CalcStride(format, xsize)); 49 memcpy(reinterpret_cast<uint8_t*>(copy.pixels()), 50 reinterpret_cast<const uint8_t*>(pixels()), pixels_size); 51 return copy; 52 } 53 54 // The interleaved pixels as defined in the storage format. 55 void* pixels() const { return pixels_.get(); } 56 57 uint8_t* pixels(size_t y, size_t x, size_t c) const { 58 return (reinterpret_cast<uint8_t*>(pixels_.get()) + y * stride + 59 x * pixel_stride_ + c * bytes_per_channel_); 60 } 61 62 const uint8_t* const_pixels(size_t y, size_t x, size_t c) const { 63 return (reinterpret_cast<const uint8_t*>(pixels_.get()) + y * stride + 64 x * pixel_stride_ + c * bytes_per_channel_); 65 } 66 67 // The image size in pixels. 68 size_t xsize; 69 size_t ysize; 70 71 // The number of bytes per row. 72 size_t stride; 73 74 // Pixel storage format and buffer size of the pixels_ pointer. 75 JxlPixelFormat format; 76 size_t pixels_size; 77 78 size_t pixel_stride() const { return pixel_stride_; } 79 80 static Status ValidateDataType(JxlDataType data_type) { 81 if ((data_type != JXL_TYPE_UINT8) && (data_type != JXL_TYPE_UINT16) && 82 (data_type != JXL_TYPE_FLOAT) && (data_type != JXL_TYPE_FLOAT16)) { 83 return JXL_FAILURE("Unhandled data type: %d", 84 static_cast<int>(data_type)); 85 } 86 return true; 87 } 88 89 static size_t BitsPerChannel(JxlDataType data_type) { 90 switch (data_type) { 91 case JXL_TYPE_UINT8: 92 return 8; 93 case JXL_TYPE_UINT16: 94 return 16; 95 case JXL_TYPE_FLOAT: 96 return 32; 97 case JXL_TYPE_FLOAT16: 98 return 16; 99 default: 100 JXL_DEBUG_ABORT("Unreachable"); 101 return 0; 102 } 103 } 104 105 float GetPixelValue(size_t y, size_t x, size_t c) const { 106 const uint8_t* data = const_pixels(y, x, c); 107 switch (format.data_type) { 108 case JXL_TYPE_UINT8: 109 return data[0] * (1.0f / 255); 110 case JXL_TYPE_UINT16: { 111 uint16_t val; 112 memcpy(&val, data, 2); 113 return (swap_endianness_ ? JXL_BSWAP16(val) : val) * (1.0f / 65535); 114 } 115 case JXL_TYPE_FLOAT: { 116 float val; 117 memcpy(&val, data, 4); 118 return swap_endianness_ ? BSwapFloat(val) : val; 119 } 120 default: 121 JXL_DEBUG_ABORT("Unreachable"); 122 return 0.0f; 123 } 124 } 125 126 void SetPixelValue(size_t y, size_t x, size_t c, float val) const { 127 uint8_t* data = pixels(y, x, c); 128 switch (format.data_type) { 129 case JXL_TYPE_UINT8: 130 data[0] = Clamp1(std::round(val * 255), 0.0f, 255.0f); 131 break; 132 case JXL_TYPE_UINT16: { 133 uint16_t val16 = Clamp1(std::round(val * 65535), 0.0f, 65535.0f); 134 if (swap_endianness_) { 135 val16 = JXL_BSWAP16(val16); 136 } 137 memcpy(data, &val16, 2); 138 break; 139 } 140 case JXL_TYPE_FLOAT: { 141 if (swap_endianness_) { 142 val = BSwapFloat(val); 143 } 144 memcpy(data, &val, 4); 145 break; 146 } 147 default: 148 JXL_DEBUG_ABORT("Unreachable"); 149 } 150 } 151 152 private: 153 PackedImage(size_t xsize, size_t ysize, const JxlPixelFormat& format, 154 size_t stride) 155 : xsize(xsize), 156 ysize(ysize), 157 stride(stride), 158 format(format), 159 pixels_size(ysize * stride), 160 pixels_(malloc(std::max<size_t>(1, pixels_size)), free) { 161 bytes_per_channel_ = BitsPerChannel(format.data_type) / jxl::kBitsPerByte; 162 pixel_stride_ = format.num_channels * bytes_per_channel_; 163 swap_endianness_ = SwapEndianness(format.endianness); 164 } 165 166 static size_t CalcStride(const JxlPixelFormat& format, size_t xsize) { 167 size_t stride = xsize * (BitsPerChannel(format.data_type) * 168 format.num_channels / jxl::kBitsPerByte); 169 if (format.align > 1) { 170 stride = jxl::DivCeil(stride, format.align) * format.align; 171 } 172 return stride; 173 } 174 175 size_t bytes_per_channel_; 176 size_t pixel_stride_; 177 bool swap_endianness_; 178 std::unique_ptr<void, decltype(free)*> pixels_; 179 }; 180 181 // Helper class representing a frame, as seen from the API. Animations will have 182 // multiple frames, but a single frame can have a color/grayscale channel and 183 // multiple extra channels. The order of the extra channels should be the same 184 // as all other frames in the same image. 185 class PackedFrame { 186 public: 187 explicit PackedFrame(PackedImage&& image) : color(std::move(image)) {} 188 189 static StatusOr<PackedFrame> Create(size_t xsize, size_t ysize, 190 const JxlPixelFormat& format) { 191 JXL_ASSIGN_OR_RETURN(PackedImage image, 192 PackedImage::Create(xsize, ysize, format)); 193 PackedFrame frame(std::move(image)); 194 return frame; 195 } 196 197 StatusOr<PackedFrame> Copy() const { 198 JXL_ASSIGN_OR_RETURN( 199 PackedFrame copy, 200 PackedFrame::Create(color.xsize, color.ysize, color.format)); 201 copy.frame_info = frame_info; 202 copy.name = name; 203 copy.color = color.Copy(); 204 for (const auto& ec : extra_channels) { 205 copy.extra_channels.emplace_back(ec.Copy()); 206 } 207 return copy; 208 } 209 210 // The Frame metadata. 211 JxlFrameHeader frame_info = {}; 212 std::string name; 213 214 // The pixel data for the color (or grayscale) channels. 215 PackedImage color; 216 // Extra channel image data. 217 std::vector<PackedImage> extra_channels; 218 }; 219 220 class ChunkedPackedFrame { 221 public: 222 ChunkedPackedFrame( 223 size_t xsize, size_t ysize, 224 std::function<JxlChunkedFrameInputSource()> get_input_source) 225 : xsize(xsize), 226 ysize(ysize), 227 get_input_source_(std::move(get_input_source)) { 228 const auto input_source = get_input_source_(); 229 input_source.get_color_channels_pixel_format(input_source.opaque, &format); 230 } 231 232 JxlChunkedFrameInputSource GetInputSource() { return get_input_source_(); } 233 234 // The Frame metadata. 235 JxlFrameHeader frame_info = {}; 236 std::string name; 237 238 size_t xsize; 239 size_t ysize; 240 JxlPixelFormat format; 241 242 private: 243 std::function<JxlChunkedFrameInputSource()> get_input_source_; 244 }; 245 246 // Optional metadata associated with a file 247 class PackedMetadata { 248 public: 249 std::vector<uint8_t> exif; 250 std::vector<uint8_t> iptc; 251 std::vector<uint8_t> jhgm; 252 std::vector<uint8_t> jumbf; 253 std::vector<uint8_t> xmp; 254 }; 255 256 // The extra channel metadata information. 257 struct PackedExtraChannel { 258 JxlExtraChannelInfo ec_info; 259 size_t index; 260 std::string name; 261 }; 262 263 // Helper class representing a JXL image file as decoded to pixels from the API. 264 class PackedPixelFile { 265 public: 266 JxlBasicInfo info = {}; 267 268 std::vector<PackedExtraChannel> extra_channels_info; 269 270 // Color information of the decoded pixels. 271 // `primary_color_representation` indicates whether `color_encoding` or `icc` 272 // is the “authoritative” encoding of the colorspace, as opposed to a fallback 273 // encoding. For example, if `color_encoding` is the primary one, as would 274 // occur when decoding a jxl file with such a representation, then `enc/jxl` 275 // will use it and ignore the ICC profile, whereas `enc/png` will include the 276 // ICC profile for compatibility. 277 // If `icc` is the primary representation, `enc/jxl` will preserve it when 278 // compressing losslessly, but *may* encode it as a color_encoding when 279 // compressing lossily. 280 enum { 281 kColorEncodingIsPrimary, 282 kIccIsPrimary 283 } primary_color_representation = kColorEncodingIsPrimary; 284 JxlColorEncoding color_encoding = {}; 285 std::vector<uint8_t> icc; 286 // The icc profile of the original image. 287 std::vector<uint8_t> orig_icc; 288 289 JxlBitDepth input_bitdepth = {JXL_BIT_DEPTH_FROM_PIXEL_FORMAT, 0, 0}; 290 291 std::unique_ptr<PackedFrame> preview_frame; 292 std::vector<PackedFrame> frames; 293 mutable std::vector<ChunkedPackedFrame> chunked_frames; 294 295 PackedMetadata metadata; 296 PackedPixelFile() { JxlEncoderInitBasicInfo(&info); }; 297 298 size_t num_frames() const { 299 return chunked_frames.empty() ? frames.size() : chunked_frames.size(); 300 } 301 size_t xsize() const { return info.xsize; } 302 size_t ysize() const { return info.ysize; } 303 }; 304 305 } // namespace extras 306 } // namespace jxl 307 308 #endif // LIB_EXTRAS_PACKED_IMAGE_H_