/src/libultrahdr/lib/src/jpegdecoderhelper.cpp
Line | Count | Source |
1 | | /* |
2 | | * Copyright 2022 The Android Open Source Project |
3 | | * |
4 | | * Licensed under the Apache License, Version 2.0 (the "License"); |
5 | | * you may not use this file except in compliance with the License. |
6 | | * You may obtain a copy of the License at |
7 | | * |
8 | | * http://www.apache.org/licenses/LICENSE-2.0 |
9 | | * |
10 | | * Unless required by applicable law or agreed to in writing, software |
11 | | * distributed under the License is distributed on an "AS IS" BASIS, |
12 | | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
13 | | * See the License for the specific language governing permissions and |
14 | | * limitations under the License. |
15 | | */ |
16 | | |
17 | | #include <errno.h> |
18 | | #include <setjmp.h> |
19 | | |
20 | | #include <cmath> |
21 | | #include <cstring> |
22 | | |
23 | | #include "ultrahdr/ultrahdrcommon.h" |
24 | | #include "ultrahdr/jpegdecoderhelper.h" |
25 | | |
26 | | using namespace std; |
27 | | |
28 | | namespace ultrahdr { |
29 | | |
30 | | static const uint32_t kAPP0Marker = JPEG_APP0; // JFIF |
31 | | static const uint32_t kAPP1Marker = JPEG_APP0 + 1; // EXIF, XMP |
32 | | static const uint32_t kAPP2Marker = JPEG_APP0 + 2; // ICC, ISO Metadata |
33 | | |
34 | | static constexpr uint8_t kICCSig[] = { |
35 | | 'I', 'C', 'C', '_', 'P', 'R', 'O', 'F', 'I', 'L', 'E', '\0', |
36 | | }; |
37 | | |
38 | | static constexpr uint8_t kXmpNameSpace[] = { |
39 | | 'h', 't', 't', 'p', ':', '/', '/', 'n', 's', '.', 'a', 'd', 'o', 'b', 'e', |
40 | | '.', 'c', 'o', 'm', '/', 'x', 'a', 'p', '/', '1', '.', '0', '/', '\0', |
41 | | }; |
42 | | |
43 | | static constexpr uint8_t kExifIdCode[] = { |
44 | | 'E', 'x', 'i', 'f', '\0', '\0', |
45 | | }; |
46 | | |
47 | | static constexpr uint8_t kIsoMetadataNameSpace[] = { |
48 | | 'u', 'r', 'n', ':', 'i', 's', 'o', ':', 's', 't', 'd', ':', 'i', 's', |
49 | | 'o', ':', 't', 's', ':', '2', '1', '4', '9', '6', ':', '-', '1', '\0', |
50 | | }; |
51 | | |
52 | | const int kMinWidth = 8; |
53 | | const int kMinHeight = 8; |
54 | | |
55 | | // if max dimension is not defined, default to 8k resolution |
56 | | #ifndef UHDR_MAX_DIMENSION |
57 | | #define UHDR_MAX_DIMENSION 8192 |
58 | | #endif |
59 | | static_assert(UHDR_MAX_DIMENSION >= (std::max)(kMinHeight, kMinWidth), |
60 | | "configured UHDR_MAX_DIMENSION must be atleast max(minWidth, minHeight)"); |
61 | | static_assert(UHDR_MAX_DIMENSION <= JPEG_MAX_DIMENSION, |
62 | | "configured UHDR_MAX_DIMENSION must be <= JPEG_MAX_DIMENSION"); |
63 | | const int kMaxWidth = UHDR_MAX_DIMENSION; |
64 | | const int kMaxHeight = UHDR_MAX_DIMENSION; |
65 | | |
66 | | /*!\brief module for managing input */ |
67 | | struct jpeg_source_mgr_impl : jpeg_source_mgr { |
68 | | jpeg_source_mgr_impl(const uint8_t* ptr, size_t len); |
69 | | ~jpeg_source_mgr_impl() = default; |
70 | | |
71 | | const uint8_t* mBufferPtr; |
72 | | size_t mBufferLength; |
73 | | }; |
74 | | |
75 | | /*!\brief module for managing error */ |
76 | | struct jpeg_error_mgr_impl : jpeg_error_mgr { |
77 | | jmp_buf setjmp_buffer; |
78 | | }; |
79 | | |
80 | 51.1k | static void jpegr_init_source(j_decompress_ptr cinfo) { |
81 | 51.1k | jpeg_source_mgr_impl* src = static_cast<jpeg_source_mgr_impl*>(cinfo->src); |
82 | 51.1k | src->next_input_byte = static_cast<const JOCTET*>(src->mBufferPtr); |
83 | 51.1k | src->bytes_in_buffer = src->mBufferLength; |
84 | 51.1k | } |
85 | | |
86 | 1.11k | static boolean jpegr_fill_input_buffer(j_decompress_ptr /* cinfo */) { |
87 | 1.11k | ALOGE("%s : should not reach here", __func__); |
88 | 1.11k | return FALSE; |
89 | 1.11k | } |
90 | | |
91 | 8.77k | static void jpegr_skip_input_data(j_decompress_ptr cinfo, long num_bytes) { |
92 | 8.77k | jpeg_source_mgr_impl* src = static_cast<jpeg_source_mgr_impl*>(cinfo->src); |
93 | | |
94 | 8.77k | if (num_bytes > static_cast<long>(src->bytes_in_buffer)) { |
95 | 3.87k | ALOGE("jpegr_skip_input_data - num_bytes > (long)src->bytes_in_buffer"); |
96 | 4.89k | } else { |
97 | 4.89k | src->next_input_byte += num_bytes; |
98 | 4.89k | src->bytes_in_buffer -= num_bytes; |
99 | 4.89k | } |
100 | 8.77k | } |
101 | | |
102 | 7.71k | static void jpegr_term_source(j_decompress_ptr /*cinfo*/) {} |
103 | | |
104 | | jpeg_source_mgr_impl::jpeg_source_mgr_impl(const uint8_t* ptr, size_t len) |
105 | 51.1k | : mBufferPtr(ptr), mBufferLength(len) { |
106 | 51.1k | init_source = jpegr_init_source; |
107 | 51.1k | fill_input_buffer = jpegr_fill_input_buffer; |
108 | 51.1k | skip_input_data = jpegr_skip_input_data; |
109 | 51.1k | resync_to_restart = jpeg_resync_to_restart; |
110 | 51.1k | term_source = jpegr_term_source; |
111 | 51.1k | } |
112 | | |
113 | 5.00k | static void jpegrerror_exit(j_common_ptr cinfo) { |
114 | 5.00k | jpeg_error_mgr_impl* err = reinterpret_cast<jpeg_error_mgr_impl*>(cinfo->err); |
115 | 5.00k | longjmp(err->setjmp_buffer, 1); |
116 | 5.00k | } |
117 | | |
118 | 24.7k | static void output_message(j_common_ptr cinfo) { |
119 | 24.7k | char buffer[JMSG_LENGTH_MAX]; |
120 | | |
121 | 24.7k | (*cinfo->err->format_message)(cinfo, buffer); |
122 | 24.7k | ALOGE("%s\n", buffer); |
123 | 24.7k | } |
124 | | |
125 | | static void jpeg_extract_marker_payload(const j_decompress_ptr cinfo, const uint32_t marker_code, |
126 | | const uint8_t* marker_fourcc_code, |
127 | | const uint32_t fourcc_length, |
128 | | std::vector<JOCTET>& destination, |
129 | 194k | long& markerPayloadOffsetRelativeToSourceBuffer) { |
130 | 194k | unsigned int pos = 2; /* position after reading SOI marker (0xffd8) */ |
131 | 194k | markerPayloadOffsetRelativeToSourceBuffer = -1; |
132 | | |
133 | 599k | for (jpeg_marker_struct* marker = cinfo->marker_list; marker; marker = marker->next) { |
134 | 452k | pos += 4; /* position after reading next marker and its size (0xFFXX, [SIZE = 2 bytes]) */ |
135 | | |
136 | 452k | if (marker->marker == marker_code && marker->data_length > fourcc_length && |
137 | 103k | !memcmp(marker->data, marker_fourcc_code, fourcc_length)) { |
138 | 47.7k | destination.resize(marker->data_length); |
139 | 47.7k | memcpy(static_cast<void*>(destination.data()), marker->data, marker->data_length); |
140 | 47.7k | markerPayloadOffsetRelativeToSourceBuffer = pos; |
141 | 47.7k | return; |
142 | 47.7k | } |
143 | 404k | pos += marker->original_length; /* position after marker's payload */ |
144 | 404k | } |
145 | 194k | } |
146 | | |
147 | 5.69k | static uhdr_img_fmt_t getOutputSamplingFormat(const j_decompress_ptr cinfo) { |
148 | 5.69k | if (cinfo->num_components == 1) |
149 | 3.93k | return UHDR_IMG_FMT_8bppYCbCr400; |
150 | 1.75k | else { |
151 | 1.75k | float ratios[6]; |
152 | 7.02k | for (int i = 0; i < 3; i++) { |
153 | 5.26k | ratios[i * 2] = ((float)cinfo->comp_info[i].h_samp_factor) / cinfo->max_h_samp_factor; |
154 | 5.26k | ratios[i * 2 + 1] = ((float)cinfo->comp_info[i].v_samp_factor) / cinfo->max_v_samp_factor; |
155 | 5.26k | } |
156 | 1.75k | if (ratios[0] == 1 && ratios[1] == 1 && ratios[2] == ratios[4] && ratios[3] == ratios[5]) { |
157 | 1.59k | if (ratios[2] == 1 && ratios[3] == 1) { |
158 | 103 | return UHDR_IMG_FMT_24bppYCbCr444; |
159 | 1.49k | } else if (ratios[2] == 1 && ratios[3] == 0.5) { |
160 | 22 | return UHDR_IMG_FMT_16bppYCbCr440; |
161 | 1.47k | } else if (ratios[2] == 0.5 && ratios[3] == 1) { |
162 | 233 | return UHDR_IMG_FMT_16bppYCbCr422; |
163 | 1.24k | } else if (ratios[2] == 0.5 && ratios[3] == 0.5) { |
164 | 1.14k | return UHDR_IMG_FMT_12bppYCbCr420; |
165 | 1.14k | } else if (ratios[2] == 0.25 && ratios[3] == 1) { |
166 | 8 | return UHDR_IMG_FMT_12bppYCbCr411; |
167 | 90 | } else if (ratios[2] == 0.25 && ratios[3] == 0.5) { |
168 | 1 | return UHDR_IMG_FMT_10bppYCbCr410; |
169 | 1 | } |
170 | 1.59k | } |
171 | 1.75k | } |
172 | 246 | return UHDR_IMG_FMT_UNSPECIFIED; |
173 | 5.69k | } |
174 | | |
175 | | uhdr_error_info_t JpegDecoderHelper::decompressImage(const void* image, size_t length, |
176 | 51.1k | decode_mode_t mode) { |
177 | 51.1k | if (image == nullptr) { |
178 | 0 | uhdr_error_info_t status; |
179 | 0 | status.error_code = UHDR_CODEC_INVALID_PARAM; |
180 | 0 | status.has_detail = 1; |
181 | 0 | snprintf(status.detail, sizeof status.detail, "received nullptr for compressed image data"); |
182 | 0 | return status; |
183 | 0 | } |
184 | 51.1k | if (length <= 0) { |
185 | 0 | uhdr_error_info_t status; |
186 | 0 | status.error_code = UHDR_CODEC_INVALID_PARAM; |
187 | 0 | status.has_detail = 1; |
188 | 0 | snprintf(status.detail, sizeof status.detail, "received bad compressed image size %zd", length); |
189 | 0 | return status; |
190 | 0 | } |
191 | | |
192 | | // reset context |
193 | 51.1k | mResultBuffer.clear(); |
194 | 51.1k | mXMPBuffer.clear(); |
195 | 51.1k | mEXIFBuffer.clear(); |
196 | 51.1k | mICCBuffer.clear(); |
197 | 51.1k | mIsoMetadataBuffer.clear(); |
198 | 51.1k | mOutFormat = UHDR_IMG_FMT_UNSPECIFIED; |
199 | 51.1k | mNumComponents = 1; |
200 | 204k | for (int i = 0; i < kMaxNumComponents; i++) { |
201 | 153k | mPlanesMCURow[i].reset(); |
202 | 153k | mPlaneWidth[i] = 0; |
203 | 153k | mPlaneHeight[i] = 0; |
204 | 153k | mPlaneHStride[i] = 0; |
205 | 153k | mPlaneVStride[i] = 0; |
206 | 153k | } |
207 | 51.1k | mExifPayLoadOffset = -1; |
208 | | |
209 | 51.1k | return decode(image, length, mode); |
210 | 51.1k | } |
211 | | |
212 | 51.1k | uhdr_error_info_t JpegDecoderHelper::decode(const void* image, size_t length, decode_mode_t mode) { |
213 | 51.1k | jpeg_source_mgr_impl mgr(static_cast<const uint8_t*>(image), length); |
214 | 51.1k | jpeg_decompress_struct cinfo; |
215 | 51.1k | jpeg_error_mgr_impl myerr; |
216 | 51.1k | uhdr_error_info_t status = g_no_error; |
217 | | |
218 | 51.1k | cinfo.err = jpeg_std_error(&myerr); |
219 | 51.1k | myerr.error_exit = jpegrerror_exit; |
220 | 51.1k | myerr.output_message = output_message; |
221 | | |
222 | 51.1k | if (0 == setjmp(myerr.setjmp_buffer)) { |
223 | 51.1k | jpeg_create_decompress(&cinfo); |
224 | 51.1k | cinfo.src = &mgr; |
225 | 51.1k | jpeg_save_markers(&cinfo, kAPP0Marker, 0xFFFF); |
226 | 51.1k | jpeg_save_markers(&cinfo, kAPP1Marker, 0xFFFF); |
227 | 51.1k | jpeg_save_markers(&cinfo, kAPP2Marker, 0xFFFF); |
228 | 51.1k | int ret_val = jpeg_read_header(&cinfo, TRUE /* require an image to be present */); |
229 | 51.1k | if (JPEG_HEADER_OK != ret_val) { |
230 | 910 | status.error_code = UHDR_CODEC_ERROR; |
231 | 910 | status.has_detail = 1; |
232 | 910 | snprintf(status.detail, sizeof status.detail, |
233 | 910 | "jpeg_read_header(...) returned %d, expected %d", ret_val, JPEG_HEADER_OK); |
234 | 910 | jpeg_destroy_decompress(&cinfo); |
235 | 910 | return status; |
236 | 910 | } |
237 | 50.2k | long payloadOffset = -1; |
238 | 50.2k | jpeg_extract_marker_payload(&cinfo, kAPP1Marker, kXmpNameSpace, |
239 | 50.2k | sizeof kXmpNameSpace / sizeof kXmpNameSpace[0], mXMPBuffer, |
240 | 50.2k | payloadOffset); |
241 | 50.2k | jpeg_extract_marker_payload(&cinfo, kAPP1Marker, kExifIdCode, |
242 | 50.2k | sizeof kExifIdCode / sizeof kExifIdCode[0], mEXIFBuffer, |
243 | 50.2k | mExifPayLoadOffset); |
244 | 50.2k | jpeg_extract_marker_payload(&cinfo, kAPP2Marker, kICCSig, sizeof kICCSig / sizeof kICCSig[0], |
245 | 50.2k | mICCBuffer, payloadOffset); |
246 | 50.2k | jpeg_extract_marker_payload(&cinfo, kAPP2Marker, kIsoMetadataNameSpace, |
247 | 50.2k | sizeof kIsoMetadataNameSpace / sizeof kIsoMetadataNameSpace[0], |
248 | 50.2k | mIsoMetadataBuffer, payloadOffset); |
249 | | |
250 | 50.2k | if (cinfo.image_width < 1 || cinfo.image_height < 1) { |
251 | 0 | status.error_code = UHDR_CODEC_ERROR; |
252 | 0 | status.has_detail = 1; |
253 | 0 | snprintf(status.detail, sizeof status.detail, |
254 | 0 | "received bad image width or height, wd = %d, ht = %d. wd and height shall be >= 1", |
255 | 0 | cinfo.image_width, cinfo.image_height); |
256 | 0 | jpeg_destroy_decompress(&cinfo); |
257 | 0 | return status; |
258 | 0 | } |
259 | 50.2k | if ((int)cinfo.image_width > kMaxWidth || (int)cinfo.image_height > kMaxHeight) { |
260 | 256 | status.error_code = UHDR_CODEC_ERROR; |
261 | 256 | status.has_detail = 1; |
262 | 256 | snprintf( |
263 | 256 | status.detail, sizeof status.detail, |
264 | 256 | "max width, max supported by library are %d, %d respectively. Current image width and " |
265 | 256 | "height are %d, %d. Recompile library with updated max supported dimensions to proceed", |
266 | 256 | kMaxWidth, kMaxHeight, cinfo.image_width, cinfo.image_height); |
267 | 256 | jpeg_destroy_decompress(&cinfo); |
268 | 256 | return status; |
269 | 256 | } |
270 | 50.0k | if (cinfo.num_components != 1 && cinfo.num_components != 3) { |
271 | 6 | status.error_code = UHDR_CODEC_ERROR; |
272 | 6 | status.has_detail = 1; |
273 | 6 | snprintf( |
274 | 6 | status.detail, sizeof status.detail, |
275 | 6 | "ultrahdr primary image and supplimentary images are images encoded with 1 component " |
276 | 6 | "(grayscale) or 3 components (YCbCr / RGB). Unrecognized number of components %d", |
277 | 6 | cinfo.num_components); |
278 | 6 | jpeg_destroy_decompress(&cinfo); |
279 | 6 | return status; |
280 | 6 | } |
281 | | |
282 | 151k | for (int i = 0, product = 0; i < cinfo.num_components; i++) { |
283 | 101k | if (cinfo.comp_info[i].h_samp_factor < 1 || cinfo.comp_info[i].h_samp_factor > 4) { |
284 | 0 | status.error_code = UHDR_CODEC_ERROR; |
285 | 0 | status.has_detail = 1; |
286 | 0 | snprintf(status.detail, sizeof status.detail, |
287 | 0 | "received bad horizontal sampling factor for component index %d, sample factor h " |
288 | 0 | "= %d, this is expected to be with in range [1-4]", |
289 | 0 | i, cinfo.comp_info[i].h_samp_factor); |
290 | 0 | jpeg_destroy_decompress(&cinfo); |
291 | 0 | return status; |
292 | 0 | } |
293 | 101k | if (cinfo.comp_info[i].v_samp_factor < 1 || cinfo.comp_info[i].v_samp_factor > 4) { |
294 | 0 | status.error_code = UHDR_CODEC_ERROR; |
295 | 0 | status.has_detail = 1; |
296 | 0 | snprintf(status.detail, sizeof status.detail, |
297 | 0 | "received bad vertical sampling factor for component index %d, sample factor v = " |
298 | 0 | "%d, this is expected to be with in range [1-4]", |
299 | 0 | i, cinfo.comp_info[i].v_samp_factor); |
300 | 0 | jpeg_destroy_decompress(&cinfo); |
301 | 0 | return status; |
302 | 0 | } |
303 | 101k | product += cinfo.comp_info[i].h_samp_factor * cinfo.comp_info[i].v_samp_factor; |
304 | 101k | if (product > 10) { |
305 | 24 | status.error_code = UHDR_CODEC_ERROR; |
306 | 24 | status.has_detail = 1; |
307 | 24 | snprintf(status.detail, sizeof status.detail, |
308 | 24 | "received bad sampling factors for components, sum of product of h_samp_factor, " |
309 | 24 | "v_samp_factor across all components exceeds 10"); |
310 | 24 | jpeg_destroy_decompress(&cinfo); |
311 | 24 | return status; |
312 | 24 | } |
313 | 101k | } |
314 | | |
315 | 49.9k | mNumComponents = cinfo.num_components; |
316 | 151k | for (int i = 0; i < cinfo.num_components; i++) { |
317 | 101k | mPlaneWidth[i] = std::ceil(((float)cinfo.image_width * cinfo.comp_info[i].h_samp_factor) / |
318 | 101k | cinfo.max_h_samp_factor); |
319 | 101k | mPlaneHStride[i] = mPlaneWidth[i]; |
320 | 101k | mPlaneHeight[i] = std::ceil(((float)cinfo.image_height * cinfo.comp_info[i].v_samp_factor) / |
321 | 101k | cinfo.max_v_samp_factor); |
322 | 101k | mPlaneVStride[i] = mPlaneHeight[i]; |
323 | 101k | } |
324 | | |
325 | 49.9k | if (cinfo.num_components == 3) { |
326 | 26.3k | if (mPlaneWidth[1] > mPlaneWidth[0] || mPlaneHeight[2] > mPlaneHeight[0]) { |
327 | 36 | status.error_code = UHDR_CODEC_ERROR; |
328 | 36 | status.has_detail = 1; |
329 | 36 | snprintf(status.detail, sizeof status.detail, |
330 | 36 | "cb, cr planes are upsampled wrt luma plane. luma width %d, luma height %d, cb " |
331 | 36 | "width %d, cb height %d, cr width %d, cr height %d", |
332 | 36 | (int)mPlaneWidth[0], (int)mPlaneHeight[0], (int)mPlaneWidth[1], |
333 | 36 | (int)mPlaneHeight[1], (int)mPlaneWidth[2], (int)mPlaneHeight[2]); |
334 | 36 | jpeg_destroy_decompress(&cinfo); |
335 | 36 | return status; |
336 | 36 | } |
337 | 26.3k | if (mPlaneWidth[1] != mPlaneWidth[2] || mPlaneHeight[1] != mPlaneHeight[2]) { |
338 | 62 | status.error_code = UHDR_CODEC_ERROR; |
339 | 62 | status.has_detail = 1; |
340 | 62 | snprintf(status.detail, sizeof status.detail, |
341 | 62 | "cb, cr planes are not sampled identically. cb width %d, cb height %d, cr width " |
342 | 62 | "%d, cr height %d", |
343 | 62 | (int)mPlaneWidth[1], (int)mPlaneHeight[1], (int)mPlaneWidth[2], |
344 | 62 | (int)mPlaneHeight[2]); |
345 | 62 | jpeg_destroy_decompress(&cinfo); |
346 | 62 | return status; |
347 | 62 | } |
348 | 26.3k | } |
349 | | |
350 | 49.8k | if (PARSE_STREAM == mode) { |
351 | 36.9k | jpeg_destroy_decompress(&cinfo); |
352 | 36.9k | return status; |
353 | 36.9k | } |
354 | | |
355 | 12.8k | if (DECODE_STREAM == mode) { |
356 | 3.99k | mode = cinfo.num_components == 1 ? DECODE_TO_YCBCR_CS : DECODE_TO_RGB_CS; |
357 | 3.99k | } |
358 | | |
359 | 12.8k | if (DECODE_TO_RGB_CS == mode) { |
360 | 5.03k | if (cinfo.jpeg_color_space != JCS_YCbCr && cinfo.jpeg_color_space != JCS_RGB) { |
361 | 10 | status.error_code = UHDR_CODEC_ERROR; |
362 | 10 | status.has_detail = 1; |
363 | 10 | snprintf(status.detail, sizeof status.detail, |
364 | 10 | "expected input color space to be JCS_YCbCr or JCS_RGB but got %d", |
365 | 10 | cinfo.jpeg_color_space); |
366 | 10 | jpeg_destroy_decompress(&cinfo); |
367 | 10 | return status; |
368 | 10 | } |
369 | 5.02k | mPlaneHStride[0] = cinfo.image_width; |
370 | 5.02k | mPlaneVStride[0] = cinfo.image_height; |
371 | 15.0k | for (int i = 1; i < kMaxNumComponents; i++) { |
372 | 10.0k | mPlaneHStride[i] = 0; |
373 | 10.0k | mPlaneVStride[i] = 0; |
374 | 10.0k | } |
375 | 5.02k | #ifdef JCS_ALPHA_EXTENSIONS |
376 | 5.02k | mResultBuffer.resize((size_t)mPlaneHStride[0] * mPlaneVStride[0] * 4); |
377 | 5.02k | cinfo.out_color_space = JCS_EXT_RGBA; |
378 | | #else |
379 | | mResultBuffer.resize((size_t)mPlaneHStride[0] * mPlaneVStride[0] * 3); |
380 | | cinfo.out_color_space = JCS_RGB; |
381 | | #endif |
382 | 7.86k | } else if (DECODE_TO_YCBCR_CS == mode) { |
383 | 6.19k | if (cinfo.jpeg_color_space != JCS_YCbCr && cinfo.jpeg_color_space != JCS_GRAYSCALE) { |
384 | 53 | status.error_code = UHDR_CODEC_ERROR; |
385 | 53 | status.has_detail = 1; |
386 | 53 | snprintf(status.detail, sizeof status.detail, |
387 | 53 | "expected input color space to be JCS_YCbCr or JCS_GRAYSCALE but got %d", |
388 | 53 | cinfo.jpeg_color_space); |
389 | 53 | jpeg_destroy_decompress(&cinfo); |
390 | 53 | return status; |
391 | 53 | } |
392 | 6.14k | size_t size = 0; |
393 | 16.2k | for (int i = 0; i < cinfo.num_components; i++) { |
394 | 10.0k | mPlaneHStride[i] = ALIGNM(mPlaneWidth[i], cinfo.max_h_samp_factor); |
395 | 10.0k | mPlaneVStride[i] = ALIGNM(mPlaneHeight[i], cinfo.max_v_samp_factor); |
396 | 10.0k | size += (size_t)mPlaneHStride[i] * mPlaneVStride[i]; |
397 | 10.0k | } |
398 | 6.14k | mResultBuffer.resize(size); |
399 | 6.14k | cinfo.out_color_space = cinfo.jpeg_color_space; |
400 | 6.14k | cinfo.raw_data_out = TRUE; |
401 | 6.14k | } |
402 | 12.8k | cinfo.dct_method = JDCT_ISLOW; |
403 | 12.8k | jpeg_start_decompress(&cinfo); |
404 | 12.8k | status = decode(&cinfo, static_cast<uint8_t*>(mResultBuffer.data())); |
405 | 12.8k | if (status.error_code != UHDR_CODEC_OK) { |
406 | 4 | jpeg_destroy_decompress(&cinfo); |
407 | 4 | return status; |
408 | 4 | } |
409 | 12.8k | } else { |
410 | 0 | status.error_code = UHDR_CODEC_ERROR; |
411 | 0 | status.has_detail = 1; |
412 | 0 | cinfo.err->format_message((j_common_ptr)&cinfo, status.detail); |
413 | 0 | jpeg_destroy_decompress(&cinfo); |
414 | 0 | return status; |
415 | 0 | } |
416 | 12.8k | jpeg_finish_decompress(&cinfo); |
417 | 12.8k | jpeg_destroy_decompress(&cinfo); |
418 | 12.8k | return status; |
419 | 51.1k | } |
420 | | |
421 | 10.1k | uhdr_error_info_t JpegDecoderHelper::decode(jpeg_decompress_struct* cinfo, uint8_t* dest) { |
422 | 10.1k | uhdr_error_info_t status = g_no_error; |
423 | 10.1k | switch (cinfo->out_color_space) { |
424 | 3.93k | case JCS_GRAYSCALE: |
425 | 3.93k | [[fallthrough]]; |
426 | 5.69k | case JCS_YCbCr: |
427 | 5.69k | mOutFormat = getOutputSamplingFormat(cinfo); |
428 | 5.69k | if (mOutFormat == UHDR_IMG_FMT_UNSPECIFIED) { |
429 | 246 | status.error_code = UHDR_CODEC_ERROR; |
430 | 246 | status.has_detail = 1; |
431 | 246 | snprintf(status.detail, sizeof status.detail, |
432 | 246 | "unrecognized subsampling format for output color space JCS_YCbCr"); |
433 | 246 | } |
434 | 5.69k | return decodeToCSYCbCr(cinfo, dest); |
435 | 0 | #ifdef JCS_ALPHA_EXTENSIONS |
436 | 4.45k | case JCS_EXT_RGBA: |
437 | 4.45k | mOutFormat = UHDR_IMG_FMT_32bppRGBA8888; |
438 | 4.45k | return decodeToCSRGB(cinfo, dest); |
439 | 0 | #endif |
440 | 0 | case JCS_RGB: |
441 | 0 | mOutFormat = UHDR_IMG_FMT_24bppRGB888; |
442 | 0 | return decodeToCSRGB(cinfo, dest); |
443 | 0 | default: |
444 | 0 | status.error_code = UHDR_CODEC_ERROR; |
445 | 0 | status.has_detail = 1; |
446 | 0 | snprintf(status.detail, sizeof status.detail, "unrecognized output color space %d", |
447 | 0 | cinfo->out_color_space); |
448 | 10.1k | } |
449 | 0 | return status; |
450 | 10.1k | } |
451 | | |
452 | 4.45k | uhdr_error_info_t JpegDecoderHelper::decodeToCSRGB(jpeg_decompress_struct* cinfo, uint8_t* dest) { |
453 | 4.45k | JSAMPLE* out = (JSAMPLE*)dest; |
454 | | |
455 | 1.44M | while (cinfo->output_scanline < cinfo->image_height) { |
456 | 1.44M | JDIMENSION read_lines = jpeg_read_scanlines(cinfo, &out, 1); |
457 | 1.44M | if (1 != read_lines) { |
458 | 2 | uhdr_error_info_t status; |
459 | 2 | status.error_code = UHDR_CODEC_ERROR; |
460 | 2 | status.has_detail = 1; |
461 | 2 | snprintf(status.detail, sizeof status.detail, "jpeg_read_scanlines returned %d, expected %d", |
462 | 2 | read_lines, 1); |
463 | 2 | return status; |
464 | 2 | } |
465 | 1.44M | #ifdef JCS_ALPHA_EXTENSIONS |
466 | 1.44M | out += (size_t)mPlaneHStride[0] * 4; |
467 | | #else |
468 | | out += (size_t)mPlaneHStride[0] * 3; |
469 | | #endif |
470 | 1.44M | } |
471 | 4.45k | return g_no_error; |
472 | 4.45k | } |
473 | | |
474 | 5.69k | uhdr_error_info_t JpegDecoderHelper::decodeToCSYCbCr(jpeg_decompress_struct* cinfo, uint8_t* dest) { |
475 | 5.69k | JSAMPROW mcuRows[kMaxNumComponents][4 * DCTSIZE]; |
476 | 5.69k | JSAMPROW mcuRowsTmp[kMaxNumComponents][4 * DCTSIZE]; |
477 | 5.69k | uint8_t* planes[kMaxNumComponents]{}; |
478 | 5.69k | size_t alignedPlaneWidth[kMaxNumComponents]{}; |
479 | 5.69k | JSAMPARRAY subImage[kMaxNumComponents]; |
480 | | |
481 | 14.9k | for (int i = 0, plane_offset = 0; i < cinfo->num_components; i++) { |
482 | 9.20k | planes[i] = dest + plane_offset; |
483 | 9.20k | plane_offset += mPlaneHStride[i] * mPlaneVStride[i]; |
484 | 9.20k | alignedPlaneWidth[i] = ALIGNM(mPlaneHStride[i], DCTSIZE); |
485 | 9.20k | if (mPlaneHStride[i] != alignedPlaneWidth[i]) { |
486 | 7.35k | mPlanesMCURow[i] = std::make_unique<uint8_t[]>(alignedPlaneWidth[i] * DCTSIZE * |
487 | 7.35k | cinfo->comp_info[i].v_samp_factor); |
488 | 7.35k | uint8_t* mem = mPlanesMCURow[i].get(); |
489 | 97.5k | for (int j = 0; j < DCTSIZE * cinfo->comp_info[i].v_samp_factor; |
490 | 90.1k | j++, mem += alignedPlaneWidth[i]) { |
491 | 90.1k | mcuRowsTmp[i][j] = mem; |
492 | 90.1k | } |
493 | 7.35k | } else if (mPlaneVStride[i] % DCTSIZE != 0) { |
494 | 1.49k | mPlanesMCURow[i] = std::make_unique<uint8_t[]>(alignedPlaneWidth[i]); |
495 | 1.49k | } |
496 | 9.20k | subImage[i] = mPlaneHStride[i] == alignedPlaneWidth[i] ? mcuRows[i] : mcuRowsTmp[i]; |
497 | 9.20k | } |
498 | | |
499 | 187k | while (cinfo->output_scanline < cinfo->image_height) { |
500 | 181k | JDIMENSION mcu_scanline_start[kMaxNumComponents]; |
501 | | |
502 | 532k | for (int i = 0; i < cinfo->num_components; i++) { |
503 | 351k | mcu_scanline_start[i] = |
504 | 351k | std::ceil(((float)cinfo->output_scanline * cinfo->comp_info[i].v_samp_factor) / |
505 | 351k | cinfo->max_v_samp_factor); |
506 | | |
507 | 4.15M | for (int j = 0; j < cinfo->comp_info[i].v_samp_factor * DCTSIZE; j++) { |
508 | 3.80M | JDIMENSION scanline = mcu_scanline_start[i] + j; |
509 | | |
510 | 3.80M | if (scanline < mPlaneVStride[i]) { |
511 | 3.75M | mcuRows[i][j] = planes[i] + (size_t)scanline * mPlaneHStride[i]; |
512 | 3.75M | } else { |
513 | 51.8k | mcuRows[i][j] = mPlanesMCURow[i].get(); |
514 | 51.8k | } |
515 | 3.80M | } |
516 | 351k | } |
517 | | |
518 | 181k | int processed = jpeg_read_raw_data(cinfo, subImage, DCTSIZE * cinfo->max_v_samp_factor); |
519 | 181k | if (processed != DCTSIZE * cinfo->max_v_samp_factor) { |
520 | 2 | uhdr_error_info_t status; |
521 | 2 | status.error_code = UHDR_CODEC_ERROR; |
522 | 2 | status.has_detail = 1; |
523 | 2 | snprintf(status.detail, sizeof status.detail, |
524 | 2 | "number of scan lines read %d does not equal requested scan lines %d ", processed, |
525 | 2 | DCTSIZE * cinfo->max_v_samp_factor); |
526 | 2 | return status; |
527 | 2 | } |
528 | | |
529 | 532k | for (int i = 0; i < cinfo->num_components; i++) { |
530 | 350k | if (mPlaneHStride[i] != alignedPlaneWidth[i]) { |
531 | 3.40M | for (int j = 0; j < cinfo->comp_info[i].v_samp_factor * DCTSIZE; j++) { |
532 | 3.11M | JDIMENSION scanline = mcu_scanline_start[i] + j; |
533 | 3.11M | if (scanline < mPlaneVStride[i]) { |
534 | 3.07M | memcpy(mcuRows[i][j], mcuRowsTmp[i][j], mPlaneWidth[i]); |
535 | 3.07M | } |
536 | 3.11M | } |
537 | 295k | } |
538 | 350k | } |
539 | 181k | } |
540 | 5.69k | return g_no_error; |
541 | 5.69k | } |
542 | | |
543 | 7.66k | uhdr_raw_image_t JpegDecoderHelper::getDecompressedImage() { |
544 | 7.66k | uhdr_raw_image_t img; |
545 | | |
546 | 7.66k | img.fmt = mOutFormat; |
547 | 7.66k | img.cg = UHDR_CG_UNSPECIFIED; |
548 | 7.66k | img.ct = UHDR_CT_UNSPECIFIED; |
549 | 7.66k | img.range = UHDR_CR_FULL_RANGE; |
550 | 7.66k | img.w = mPlaneWidth[0]; |
551 | 7.66k | img.h = mPlaneHeight[0]; |
552 | 7.66k | uint8_t* data = mResultBuffer.data(); |
553 | 30.6k | for (int i = 0; i < 3; i++) { |
554 | 23.0k | img.planes[i] = data; |
555 | 23.0k | img.stride[i] = mPlaneHStride[i]; |
556 | 23.0k | data += (size_t)mPlaneHStride[i] * mPlaneVStride[i]; |
557 | 23.0k | } |
558 | | |
559 | 7.66k | return img; |
560 | 7.66k | } |
561 | | |
562 | | } // namespace ultrahdr |