Coverage Report

Created: 2026-05-16 07:22

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libjxl/lib/jxl/enc_external_image.cc
Line
Count
Source
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
#include "lib/jxl/enc_external_image.h"
7
8
#include <jxl/memory_manager.h>
9
#include <jxl/types.h>
10
11
#include <cstdint>
12
#include <cstring>
13
#include <utility>
14
15
#include "lib/jxl/base/byte_order.h"
16
#include "lib/jxl/base/common.h"
17
#include "lib/jxl/base/compiler_specific.h"
18
#include "lib/jxl/base/data_parallel.h"
19
#include "lib/jxl/base/float.h"
20
#include "lib/jxl/base/printf_macros.h"
21
#include "lib/jxl/base/span.h"
22
#include "lib/jxl/base/status.h"
23
#include "lib/jxl/color_encoding_internal.h"
24
#include "lib/jxl/image.h"
25
#include "lib/jxl/image_bundle.h"
26
#include "lib/jxl/image_ops.h"
27
28
namespace jxl {
29
namespace {
30
31
11.4k
size_t JxlDataTypeBytes(JxlDataType data_type) {
32
11.4k
  switch (data_type) {
33
4.15k
    case JXL_TYPE_UINT8:
34
4.15k
      return 1;
35
3.43k
    case JXL_TYPE_UINT16:
36
3.43k
      return 2;
37
0
    case JXL_TYPE_FLOAT16:
38
0
      return 2;
39
3.89k
    case JXL_TYPE_FLOAT:
40
3.89k
      return 4;
41
0
    default:
42
0
      return 0;
43
11.4k
  }
44
11.4k
}
45
46
}  // namespace
47
48
Status ConvertFromExternalNoSizeCheck(const uint8_t* data, size_t xsize,
49
                                      size_t ysize, size_t stride,
50
                                      size_t bits_per_sample,
51
                                      JxlPixelFormat format, size_t c,
52
11.4k
                                      ThreadPool* pool, ImageF* channel) {
53
11.4k
  if (format.data_type == JXL_TYPE_UINT8) {
54
4.15k
    JXL_RETURN_IF_ERROR(bits_per_sample > 0 && bits_per_sample <= 8);
55
7.33k
  } else if (format.data_type == JXL_TYPE_UINT16) {
56
3.43k
    JXL_RETURN_IF_ERROR(bits_per_sample > 8 && bits_per_sample <= 16);
57
3.89k
  } else if (format.data_type != JXL_TYPE_FLOAT16 &&
58
3.89k
             format.data_type != JXL_TYPE_FLOAT) {
59
0
    return JXL_FAILURE("unsupported pixel format data type %d",
60
0
                       format.data_type);
61
0
  }
62
63
11.4k
  JXL_ENSURE(channel->xsize() == xsize);
64
11.4k
  JXL_ENSURE(channel->ysize() == ysize);
65
66
11.4k
  size_t bytes_per_channel = JxlDataTypeBytes(format.data_type);
67
11.4k
  size_t bytes_per_pixel = format.num_channels * bytes_per_channel;
68
11.4k
  size_t pixel_offset = c * bytes_per_channel;
69
  // Only for uint8/16.
70
11.4k
  float scale = 1.0f / ((1ull << bits_per_sample) - 1);
71
72
11.4k
  const bool little_endian =
73
11.4k
      format.endianness == JXL_LITTLE_ENDIAN ||
74
11.4k
      (format.endianness == JXL_NATIVE_ENDIAN && IsLittleEndian());
75
76
11.4k
  const auto convert_row = [&](const uint32_t task,
77
2.49M
                               size_t /*thread*/) -> Status {
78
2.49M
    const size_t y = task;
79
2.49M
    size_t offset = y * stride + pixel_offset;
80
2.49M
    float* JXL_RESTRICT row_out = channel->Row(y);
81
927M
    const auto save_value = [&](size_t index, float value) {
82
927M
      row_out[index] = value;
83
927M
    };
84
2.49M
    JXL_RETURN_IF_ERROR(LoadFloatRow(data + offset, xsize, bytes_per_pixel,
85
2.49M
                                     format.data_type, little_endian, scale,
86
2.49M
                                     save_value));
87
2.49M
    return true;
88
2.49M
  };
89
11.4k
  JXL_RETURN_IF_ERROR(RunOnPool(pool, 0, static_cast<uint32_t>(ysize),
90
11.4k
                                ThreadPool::NoInit, convert_row,
91
11.4k
                                "ConvertExtraChannel"));
92
11.4k
  return true;
93
11.4k
}
94
95
Status ConvertFromExternalNoSizeCheck(const uint8_t* data, size_t xsize,
96
                                      size_t ysize, size_t stride,
97
                                      const ColorEncoding& c_current,
98
                                      size_t color_channels,
99
                                      size_t bits_per_sample,
100
                                      JxlPixelFormat format, ThreadPool* pool,
101
0
                                      ImageBundle* ib) {
102
0
  JxlMemoryManager* memory_manager = ib->memory_manager();
103
0
  bool has_alpha = format.num_channels == 2 || format.num_channels == 4;
104
0
  if (format.num_channels < color_channels) {
105
0
    return JXL_FAILURE("Expected %" PRIuS
106
0
                       " color channels, received only %u channels",
107
0
                       color_channels, format.num_channels);
108
0
  }
109
110
0
  JXL_ASSIGN_OR_RETURN(Image3F color,
111
0
                       Image3F::Create(memory_manager, xsize, ysize));
112
0
  for (size_t c = 0; c < color_channels; ++c) {
113
0
    JXL_RETURN_IF_ERROR(ConvertFromExternalNoSizeCheck(
114
0
        data, xsize, ysize, stride, bits_per_sample, format, c, pool,
115
0
        &color.Plane(c)));
116
0
  }
117
0
  if (color_channels == 1) {
118
0
    JXL_RETURN_IF_ERROR(CopyImageTo(color.Plane(0), &color.Plane(1)));
119
0
    JXL_RETURN_IF_ERROR(CopyImageTo(color.Plane(0), &color.Plane(2)));
120
0
  }
121
0
  JXL_RETURN_IF_ERROR(ib->SetFromImage(std::move(color), c_current));
122
123
  // Passing an interleaved image with an alpha channel to an image that doesn't
124
  // have alpha channel just discards the passed alpha channel.
125
0
  if (has_alpha && ib->HasAlpha()) {
126
0
    JXL_ASSIGN_OR_RETURN(ImageF alpha,
127
0
                         ImageF::Create(memory_manager, xsize, ysize));
128
0
    JXL_RETURN_IF_ERROR(ConvertFromExternalNoSizeCheck(
129
0
        data, xsize, ysize, stride, bits_per_sample, format,
130
0
        format.num_channels - 1, pool, &alpha));
131
0
    JXL_RETURN_IF_ERROR(ib->SetAlpha(std::move(alpha)));
132
0
  } else if (!has_alpha && ib->HasAlpha()) {
133
    // if alpha is not passed, but it is expected, then assume
134
    // it is all-opaque
135
0
    JXL_ASSIGN_OR_RETURN(ImageF alpha,
136
0
                         ImageF::Create(memory_manager, xsize, ysize));
137
0
    FillImage(1.0f, &alpha);
138
0
    JXL_RETURN_IF_ERROR(ib->SetAlpha(std::move(alpha)));
139
0
  }
140
141
0
  return true;
142
0
}
143
144
Status ConvertFromExternal(const uint8_t* data, size_t size, size_t xsize,
145
                           size_t ysize, size_t bits_per_sample,
146
                           JxlPixelFormat format, size_t c, ThreadPool* pool,
147
0
                           ImageF* channel) {
148
0
  size_t bytes_per_channel = JxlDataTypeBytes(format.data_type);
149
0
  size_t bytes_per_pixel;
150
0
  if (!SafeMul(static_cast<size_t>(format.num_channels), bytes_per_channel,
151
0
               bytes_per_pixel)) {
152
0
    return JXL_FAILURE("Invalid format");
153
0
  }
154
0
  size_t last_row_size;
155
0
  if (!SafeMul(xsize, bytes_per_pixel, last_row_size)) {
156
0
    return JXL_FAILURE("Image dimensions are too large");
157
0
  }
158
0
  const size_t align = format.align;
159
0
  size_t row_size = last_row_size;
160
0
  if (align > 1) {
161
0
    row_size = jxl::DivCeil(last_row_size, align) * align;
162
0
  }
163
0
  if (xsize == 0 || ysize == 0) return JXL_FAILURE("Empty image");
164
0
  size_t bytes_to_read;
165
0
  if (!SafeMul(row_size, ysize - 1, bytes_to_read) ||
166
0
      !SafeAdd(bytes_to_read, last_row_size, bytes_to_read)) {
167
0
    return JXL_FAILURE("Image dimensions are too large");
168
0
  }
169
0
  if (size > 0 && size < bytes_to_read) {
170
0
    return JXL_FAILURE("Buffer size is too small, expected: %" PRIuS
171
0
                       " got: %" PRIuS " (Image: %" PRIuS "x%" PRIuS
172
0
                       "x%u, bytes_per_channel: %" PRIuS ")",
173
0
                       bytes_to_read, size, xsize, ysize, format.num_channels,
174
0
                       bytes_per_channel);
175
0
  }
176
  // Too large buffer is likely an application bug, so also fail for that.
177
  // Do allow padding to stride in last row though.
178
0
  if (size > row_size * ysize) {
179
0
    return JXL_FAILURE("Buffer size is too large");
180
0
  }
181
0
  return ConvertFromExternalNoSizeCheck(
182
0
      data, xsize, ysize, row_size, bits_per_sample, format, c, pool, channel);
183
0
}
184
Status ConvertFromExternal(Span<const uint8_t> bytes, size_t xsize,
185
                           size_t ysize, const ColorEncoding& c_current,
186
                           size_t color_channels, size_t bits_per_sample,
187
                           JxlPixelFormat format, ThreadPool* pool,
188
0
                           ImageBundle* ib) {
189
0
  JxlMemoryManager* memory_manager = ib->memory_manager();
190
0
  bool has_alpha = format.num_channels == 2 || format.num_channels == 4;
191
0
  if (format.num_channels < color_channels) {
192
0
    return JXL_FAILURE("Expected %" PRIuS
193
0
                       " color channels, received only %u channels",
194
0
                       color_channels, format.num_channels);
195
0
  }
196
197
0
  JXL_ASSIGN_OR_RETURN(Image3F color,
198
0
                       Image3F::Create(memory_manager, xsize, ysize));
199
0
  for (size_t c = 0; c < color_channels; ++c) {
200
0
    JXL_RETURN_IF_ERROR(ConvertFromExternal(bytes.data(), bytes.size(), xsize,
201
0
                                            ysize, bits_per_sample, format, c,
202
0
                                            pool, &color.Plane(c)));
203
0
  }
204
0
  if (color_channels == 1) {
205
0
    JXL_RETURN_IF_ERROR(CopyImageTo(color.Plane(0), &color.Plane(1)));
206
0
    JXL_RETURN_IF_ERROR(CopyImageTo(color.Plane(0), &color.Plane(2)));
207
0
  }
208
0
  JXL_RETURN_IF_ERROR(ib->SetFromImage(std::move(color), c_current));
209
210
  // Passing an interleaved image with an alpha channel to an image that doesn't
211
  // have alpha channel just discards the passed alpha channel.
212
0
  if (has_alpha && ib->HasAlpha()) {
213
0
    JXL_ASSIGN_OR_RETURN(ImageF alpha,
214
0
                         ImageF::Create(memory_manager, xsize, ysize));
215
0
    JXL_RETURN_IF_ERROR(ConvertFromExternal(
216
0
        bytes.data(), bytes.size(), xsize, ysize, bits_per_sample, format,
217
0
        format.num_channels - 1, pool, &alpha));
218
0
    JXL_RETURN_IF_ERROR(ib->SetAlpha(std::move(alpha)));
219
0
  } else if (!has_alpha && ib->HasAlpha()) {
220
    // if alpha is not passed, but it is expected, then assume
221
    // it is all-opaque
222
0
    JXL_ASSIGN_OR_RETURN(ImageF alpha,
223
0
                         ImageF::Create(memory_manager, xsize, ysize));
224
0
    FillImage(1.0f, &alpha);
225
0
    JXL_RETURN_IF_ERROR(ib->SetAlpha(std::move(alpha)));
226
0
  }
227
228
0
  return true;
229
0
}
230
231
Status ConvertFromExternal(Span<const uint8_t> bytes, size_t xsize,
232
                           size_t ysize, const ColorEncoding& c_current,
233
                           size_t bits_per_sample, JxlPixelFormat format,
234
0
                           ThreadPool* pool, ImageBundle* ib) {
235
0
  return ConvertFromExternal(bytes, xsize, ysize, c_current,
236
0
                             c_current.Channels(), bits_per_sample, format,
237
0
                             pool, ib);
238
0
}
239
240
Status BufferToImageF(const JxlPixelFormat& pixel_format, size_t xsize,
241
                      size_t ysize, const void* buffer, size_t size,
242
0
                      ThreadPool* pool, ImageF* channel) {
243
0
  size_t bitdepth = JxlDataTypeBytes(pixel_format.data_type) * kBitsPerByte;
244
0
  return ConvertFromExternal(reinterpret_cast<const uint8_t*>(buffer), size,
245
0
                             xsize, ysize, bitdepth, pixel_format, 0, pool,
246
0
                             channel);
247
0
}
248
249
Status BufferToImageBundle(const JxlPixelFormat& pixel_format, uint32_t xsize,
250
                           uint32_t ysize, const void* buffer, size_t size,
251
                           jxl::ThreadPool* pool,
252
                           const jxl::ColorEncoding& c_current,
253
0
                           jxl::ImageBundle* ib) {
254
0
  size_t bitdepth = JxlDataTypeBytes(pixel_format.data_type) * kBitsPerByte;
255
0
  JXL_RETURN_IF_ERROR(ConvertFromExternal(
256
0
      jxl::Bytes(static_cast<const uint8_t*>(buffer), size), xsize, ysize,
257
0
      c_current, bitdepth, pixel_format, pool, ib));
258
0
  JXL_RETURN_IF_ERROR(ib->VerifyMetadata());
259
260
0
  return true;
261
0
}
262
263
}  // namespace jxl