Coverage Report

Created: 2025-06-13 06:37

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