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