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