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