/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 |