Coverage Report

Created: 2025-06-22 08:04

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