xref: /aosp_15_r20/external/libultrahdr/lib/src/jpegdecoderhelper.cpp (revision 89a0ef05262152531a00a15832a2d3b1e3990773)
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 
jpegr_init_source(j_decompress_ptr cinfo)80 static void jpegr_init_source(j_decompress_ptr cinfo) {
81   jpeg_source_mgr_impl* src = static_cast<jpeg_source_mgr_impl*>(cinfo->src);
82   src->next_input_byte = static_cast<const JOCTET*>(src->mBufferPtr);
83   src->bytes_in_buffer = src->mBufferLength;
84 }
85 
jpegr_fill_input_buffer(j_decompress_ptr)86 static boolean jpegr_fill_input_buffer(j_decompress_ptr /* cinfo */) {
87   ALOGE("%s : should not reach here", __func__);
88   return FALSE;
89 }
90 
jpegr_skip_input_data(j_decompress_ptr cinfo,long num_bytes)91 static void jpegr_skip_input_data(j_decompress_ptr cinfo, long num_bytes) {
92   jpeg_source_mgr_impl* src = static_cast<jpeg_source_mgr_impl*>(cinfo->src);
93 
94   if (num_bytes > static_cast<long>(src->bytes_in_buffer)) {
95     ALOGE("jpegr_skip_input_data - num_bytes > (long)src->bytes_in_buffer");
96   } else {
97     src->next_input_byte += num_bytes;
98     src->bytes_in_buffer -= num_bytes;
99   }
100 }
101 
jpegr_term_source(j_decompress_ptr)102 static void jpegr_term_source(j_decompress_ptr /*cinfo*/) {}
103 
jpeg_source_mgr_impl(const uint8_t * ptr,size_t len)104 jpeg_source_mgr_impl::jpeg_source_mgr_impl(const uint8_t* ptr, size_t len)
105     : mBufferPtr(ptr), mBufferLength(len) {
106   init_source = jpegr_init_source;
107   fill_input_buffer = jpegr_fill_input_buffer;
108   skip_input_data = jpegr_skip_input_data;
109   resync_to_restart = jpeg_resync_to_restart;
110   term_source = jpegr_term_source;
111 }
112 
jpegrerror_exit(j_common_ptr cinfo)113 static void jpegrerror_exit(j_common_ptr cinfo) {
114   jpeg_error_mgr_impl* err = reinterpret_cast<jpeg_error_mgr_impl*>(cinfo->err);
115   longjmp(err->setjmp_buffer, 1);
116 }
117 
output_message(j_common_ptr cinfo)118 static void output_message(j_common_ptr cinfo) {
119   char buffer[JMSG_LENGTH_MAX];
120 
121   (*cinfo->err->format_message)(cinfo, buffer);
122   ALOGE("%s\n", buffer);
123 }
124 
jpeg_extract_marker_payload(const j_decompress_ptr cinfo,const uint32_t marker_code,const uint8_t * marker_fourcc_code,const uint32_t fourcc_length,std::vector<JOCTET> & destination,long & markerPayloadOffsetRelativeToSourceBuffer)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                                         long& markerPayloadOffsetRelativeToSourceBuffer) {
130   unsigned int pos = 2; /* position after reading SOI marker (0xffd8) */
131   markerPayloadOffsetRelativeToSourceBuffer = -1;
132 
133   for (jpeg_marker_struct* marker = cinfo->marker_list; marker; marker = marker->next) {
134     pos += 4; /* position after reading next marker and its size (0xFFXX, [SIZE = 2 bytes]) */
135 
136     if (marker->marker == marker_code && marker->data_length > fourcc_length &&
137         !memcmp(marker->data, marker_fourcc_code, fourcc_length)) {
138       destination.resize(marker->data_length);
139       memcpy(static_cast<void*>(destination.data()), marker->data, marker->data_length);
140       markerPayloadOffsetRelativeToSourceBuffer = pos;
141       return;
142     }
143     pos += marker->original_length; /* position after marker's payload */
144   }
145 }
146 
getOutputSamplingFormat(const j_decompress_ptr cinfo)147 static uhdr_img_fmt_t getOutputSamplingFormat(const j_decompress_ptr cinfo) {
148   if (cinfo->num_components == 1)
149     return UHDR_IMG_FMT_8bppYCbCr400;
150   else {
151     float ratios[6];
152     for (int i = 0; i < 3; i++) {
153       ratios[i * 2] = ((float)cinfo->comp_info[i].h_samp_factor) / cinfo->max_h_samp_factor;
154       ratios[i * 2 + 1] = ((float)cinfo->comp_info[i].v_samp_factor) / cinfo->max_v_samp_factor;
155     }
156     if (ratios[0] == 1 && ratios[1] == 1 && ratios[2] == ratios[4] && ratios[3] == ratios[5]) {
157       if (ratios[2] == 1 && ratios[3] == 1) {
158         return UHDR_IMG_FMT_24bppYCbCr444;
159       } else if (ratios[2] == 1 && ratios[3] == 0.5) {
160         return UHDR_IMG_FMT_16bppYCbCr440;
161       } else if (ratios[2] == 0.5 && ratios[3] == 1) {
162         return UHDR_IMG_FMT_16bppYCbCr422;
163       } else if (ratios[2] == 0.5 && ratios[3] == 0.5) {
164         return UHDR_IMG_FMT_12bppYCbCr420;
165       } else if (ratios[2] == 0.25 && ratios[3] == 1) {
166         return UHDR_IMG_FMT_12bppYCbCr411;
167       } else if (ratios[2] == 0.25 && ratios[3] == 0.5) {
168         return UHDR_IMG_FMT_10bppYCbCr410;
169       }
170     }
171   }
172   return UHDR_IMG_FMT_UNSPECIFIED;
173 }
174 
decompressImage(const void * image,size_t length,decode_mode_t mode)175 uhdr_error_info_t JpegDecoderHelper::decompressImage(const void* image, size_t length,
176                                                      decode_mode_t mode) {
177   if (image == nullptr) {
178     uhdr_error_info_t status;
179     status.error_code = UHDR_CODEC_INVALID_PARAM;
180     status.has_detail = 1;
181     snprintf(status.detail, sizeof status.detail, "received nullptr for compressed image data");
182     return status;
183   }
184   if (length <= 0) {
185     uhdr_error_info_t status;
186     status.error_code = UHDR_CODEC_INVALID_PARAM;
187     status.has_detail = 1;
188     snprintf(status.detail, sizeof status.detail, "received bad compressed image size %zd", length);
189     return status;
190   }
191 
192   // reset context
193   mResultBuffer.clear();
194   mXMPBuffer.clear();
195   mEXIFBuffer.clear();
196   mICCBuffer.clear();
197   mIsoMetadataBuffer.clear();
198   mOutFormat = UHDR_IMG_FMT_UNSPECIFIED;
199   mNumComponents = 1;
200   for (int i = 0; i < kMaxNumComponents; i++) {
201     mPlanesMCURow[i].reset();
202     mPlaneWidth[i] = 0;
203     mPlaneHeight[i] = 0;
204     mPlaneHStride[i] = 0;
205     mPlaneVStride[i] = 0;
206   }
207   mExifPayLoadOffset = -1;
208 
209   return decode(image, length, mode);
210 }
211 
decode(const void * image,size_t length,decode_mode_t mode)212 uhdr_error_info_t JpegDecoderHelper::decode(const void* image, size_t length, decode_mode_t mode) {
213   jpeg_source_mgr_impl mgr(static_cast<const uint8_t*>(image), length);
214   jpeg_decompress_struct cinfo;
215   jpeg_error_mgr_impl myerr;
216   uhdr_error_info_t status = g_no_error;
217 
218   cinfo.err = jpeg_std_error(&myerr);
219   myerr.error_exit = jpegrerror_exit;
220   myerr.output_message = output_message;
221 
222   if (0 == setjmp(myerr.setjmp_buffer)) {
223     jpeg_create_decompress(&cinfo);
224     cinfo.src = &mgr;
225     jpeg_save_markers(&cinfo, kAPP0Marker, 0xFFFF);
226     jpeg_save_markers(&cinfo, kAPP1Marker, 0xFFFF);
227     jpeg_save_markers(&cinfo, kAPP2Marker, 0xFFFF);
228     int ret_val = jpeg_read_header(&cinfo, TRUE /* require an image to be present */);
229     if (JPEG_HEADER_OK != ret_val) {
230       status.error_code = UHDR_CODEC_ERROR;
231       status.has_detail = 1;
232       snprintf(status.detail, sizeof status.detail,
233                "jpeg_read_header(...) returned %d, expected %d", ret_val, JPEG_HEADER_OK);
234       jpeg_destroy_decompress(&cinfo);
235       return status;
236     }
237     long payloadOffset = -1;
238     jpeg_extract_marker_payload(&cinfo, kAPP1Marker, kXmpNameSpace,
239                                 sizeof kXmpNameSpace / sizeof kXmpNameSpace[0], mXMPBuffer,
240                                 payloadOffset);
241     jpeg_extract_marker_payload(&cinfo, kAPP1Marker, kExifIdCode,
242                                 sizeof kExifIdCode / sizeof kExifIdCode[0], mEXIFBuffer,
243                                 mExifPayLoadOffset);
244     jpeg_extract_marker_payload(&cinfo, kAPP2Marker, kICCSig, sizeof kICCSig / sizeof kICCSig[0],
245                                 mICCBuffer, payloadOffset);
246     jpeg_extract_marker_payload(&cinfo, kAPP2Marker, kIsoMetadataNameSpace,
247                                 sizeof kIsoMetadataNameSpace / sizeof kIsoMetadataNameSpace[0],
248                                 mIsoMetadataBuffer, payloadOffset);
249 
250     if (cinfo.image_width < 1 || cinfo.image_height < 1) {
251       status.error_code = UHDR_CODEC_ERROR;
252       status.has_detail = 1;
253       snprintf(status.detail, sizeof status.detail,
254                "received bad image width or height, wd = %d, ht = %d. wd and height shall be >= 1",
255                cinfo.image_width, cinfo.image_height);
256       jpeg_destroy_decompress(&cinfo);
257       return status;
258     }
259     if ((int)cinfo.image_width > kMaxWidth || (int)cinfo.image_height > kMaxHeight) {
260       status.error_code = UHDR_CODEC_ERROR;
261       status.has_detail = 1;
262       snprintf(
263           status.detail, sizeof status.detail,
264           "max width, max supported by library are %d, %d respectively. Current image width and "
265           "height are %d, %d. Recompile library with updated max supported dimensions to proceed",
266           kMaxWidth, kMaxHeight, cinfo.image_width, cinfo.image_height);
267       jpeg_destroy_decompress(&cinfo);
268       return status;
269     }
270     if (cinfo.num_components != 1 && cinfo.num_components != 3) {
271       status.error_code = UHDR_CODEC_ERROR;
272       status.has_detail = 1;
273       snprintf(
274           status.detail, sizeof status.detail,
275           "ultrahdr primary image and supplimentary images are images encoded with 1 component "
276           "(grayscale) or 3 components (YCbCr / RGB). Unrecognized number of components %d",
277           cinfo.num_components);
278       jpeg_destroy_decompress(&cinfo);
279       return status;
280     }
281 
282     for (int i = 0, product = 0; i < cinfo.num_components; i++) {
283       if (cinfo.comp_info[i].h_samp_factor < 1 || cinfo.comp_info[i].h_samp_factor > 4) {
284         status.error_code = UHDR_CODEC_ERROR;
285         status.has_detail = 1;
286         snprintf(status.detail, sizeof status.detail,
287                  "received bad horizontal sampling factor for component index %d, sample factor h "
288                  "= %d, this is expected to be with in range [1-4]",
289                  i, cinfo.comp_info[i].h_samp_factor);
290         jpeg_destroy_decompress(&cinfo);
291         return status;
292       }
293       if (cinfo.comp_info[i].v_samp_factor < 1 || cinfo.comp_info[i].v_samp_factor > 4) {
294         status.error_code = UHDR_CODEC_ERROR;
295         status.has_detail = 1;
296         snprintf(status.detail, sizeof status.detail,
297                  "received bad vertical sampling factor for component index %d, sample factor v = "
298                  "%d, this is expected to be with in range [1-4]",
299                  i, cinfo.comp_info[i].v_samp_factor);
300         jpeg_destroy_decompress(&cinfo);
301         return status;
302       }
303       product += cinfo.comp_info[i].h_samp_factor * cinfo.comp_info[i].v_samp_factor;
304       if (product > 10) {
305         status.error_code = UHDR_CODEC_ERROR;
306         status.has_detail = 1;
307         snprintf(status.detail, sizeof status.detail,
308                  "received bad sampling factors for components, sum of product of h_samp_factor, "
309                  "v_samp_factor across all components exceeds 10");
310         jpeg_destroy_decompress(&cinfo);
311         return status;
312       }
313     }
314 
315     mNumComponents = cinfo.num_components;
316     for (int i = 0; i < cinfo.num_components; i++) {
317       mPlaneWidth[i] = std::ceil(((float)cinfo.image_width * cinfo.comp_info[i].h_samp_factor) /
318                                  cinfo.max_h_samp_factor);
319       mPlaneHStride[i] = mPlaneWidth[i];
320       mPlaneHeight[i] = std::ceil(((float)cinfo.image_height * cinfo.comp_info[i].v_samp_factor) /
321                                   cinfo.max_v_samp_factor);
322       mPlaneVStride[i] = mPlaneHeight[i];
323     }
324 
325     if (cinfo.num_components == 3) {
326       if (mPlaneWidth[1] > mPlaneWidth[0] || mPlaneHeight[2] > mPlaneHeight[0]) {
327         status.error_code = UHDR_CODEC_ERROR;
328         status.has_detail = 1;
329         snprintf(status.detail, sizeof status.detail,
330                  "cb, cr planes are upsampled wrt luma plane. luma width %d, luma height %d, cb "
331                  "width %d, cb height %d, cr width %d, cr height %d",
332                  (int)mPlaneWidth[0], (int)mPlaneHeight[0], (int)mPlaneWidth[1],
333                  (int)mPlaneHeight[1], (int)mPlaneWidth[2], (int)mPlaneHeight[2]);
334         jpeg_destroy_decompress(&cinfo);
335         return status;
336       }
337       if (mPlaneWidth[1] != mPlaneWidth[2] || mPlaneHeight[1] != mPlaneHeight[2]) {
338         status.error_code = UHDR_CODEC_ERROR;
339         status.has_detail = 1;
340         snprintf(status.detail, sizeof status.detail,
341                  "cb, cr planes are not sampled identically. cb width %d, cb height %d, cr width "
342                  "%d, cr height %d",
343                  (int)mPlaneWidth[1], (int)mPlaneHeight[1], (int)mPlaneWidth[2],
344                  (int)mPlaneHeight[2]);
345         jpeg_destroy_decompress(&cinfo);
346         return status;
347       }
348     }
349 
350     if (PARSE_STREAM == mode) {
351       jpeg_destroy_decompress(&cinfo);
352       return status;
353     }
354 
355     if (DECODE_STREAM == mode) {
356       mode = cinfo.num_components == 1 ? DECODE_TO_YCBCR_CS : DECODE_TO_RGB_CS;
357     }
358 
359     if (DECODE_TO_RGB_CS == mode) {
360       if (cinfo.jpeg_color_space != JCS_YCbCr && cinfo.jpeg_color_space != JCS_RGB) {
361         status.error_code = UHDR_CODEC_ERROR;
362         status.has_detail = 1;
363         snprintf(status.detail, sizeof status.detail,
364                  "expected input color space to be JCS_YCbCr or JCS_RGB but got %d",
365                  cinfo.jpeg_color_space);
366         jpeg_destroy_decompress(&cinfo);
367         return status;
368       }
369       mPlaneHStride[0] = cinfo.image_width;
370       mPlaneVStride[0] = cinfo.image_height;
371       for (int i = 1; i < kMaxNumComponents; i++) {
372         mPlaneHStride[i] = 0;
373         mPlaneVStride[i] = 0;
374       }
375 #ifdef JCS_ALPHA_EXTENSIONS
376       mResultBuffer.resize((size_t)mPlaneHStride[0] * mPlaneVStride[0] * 4);
377       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     } else if (DECODE_TO_YCBCR_CS == mode) {
383       if (cinfo.jpeg_color_space != JCS_YCbCr && cinfo.jpeg_color_space != JCS_GRAYSCALE) {
384         status.error_code = UHDR_CODEC_ERROR;
385         status.has_detail = 1;
386         snprintf(status.detail, sizeof status.detail,
387                  "expected input color space to be JCS_YCbCr or JCS_GRAYSCALE but got %d",
388                  cinfo.jpeg_color_space);
389         jpeg_destroy_decompress(&cinfo);
390         return status;
391       }
392       size_t size = 0;
393       for (int i = 0; i < cinfo.num_components; i++) {
394         mPlaneHStride[i] = ALIGNM(mPlaneWidth[i], cinfo.max_h_samp_factor);
395         mPlaneVStride[i] = ALIGNM(mPlaneHeight[i], cinfo.max_v_samp_factor);
396         size += (size_t)mPlaneHStride[i] * mPlaneVStride[i];
397       }
398       mResultBuffer.resize(size);
399       cinfo.out_color_space = cinfo.jpeg_color_space;
400       cinfo.raw_data_out = TRUE;
401     }
402     cinfo.dct_method = JDCT_ISLOW;
403     jpeg_start_decompress(&cinfo);
404     status = decode(&cinfo, static_cast<uint8_t*>(mResultBuffer.data()));
405     if (status.error_code != UHDR_CODEC_OK) {
406       jpeg_destroy_decompress(&cinfo);
407       return status;
408     }
409   } else {
410     status.error_code = UHDR_CODEC_ERROR;
411     status.has_detail = 1;
412     cinfo.err->format_message((j_common_ptr)&cinfo, status.detail);
413     jpeg_destroy_decompress(&cinfo);
414     return status;
415   }
416   jpeg_finish_decompress(&cinfo);
417   jpeg_destroy_decompress(&cinfo);
418   return status;
419 }
420 
decode(jpeg_decompress_struct * cinfo,uint8_t * dest)421 uhdr_error_info_t JpegDecoderHelper::decode(jpeg_decompress_struct* cinfo, uint8_t* dest) {
422   uhdr_error_info_t status = g_no_error;
423   switch (cinfo->out_color_space) {
424     case JCS_GRAYSCALE:
425       [[fallthrough]];
426     case JCS_YCbCr:
427       mOutFormat = getOutputSamplingFormat(cinfo);
428       if (mOutFormat == UHDR_IMG_FMT_UNSPECIFIED) {
429         status.error_code = UHDR_CODEC_ERROR;
430         status.has_detail = 1;
431         snprintf(status.detail, sizeof status.detail,
432                  "unrecognized subsampling format for output color space JCS_YCbCr");
433       }
434       return decodeToCSYCbCr(cinfo, dest);
435 #ifdef JCS_ALPHA_EXTENSIONS
436     case JCS_EXT_RGBA:
437       mOutFormat = UHDR_IMG_FMT_32bppRGBA8888;
438       return decodeToCSRGB(cinfo, dest);
439 #endif
440     case JCS_RGB:
441       mOutFormat = UHDR_IMG_FMT_24bppRGB888;
442       return decodeToCSRGB(cinfo, dest);
443     default:
444       status.error_code = UHDR_CODEC_ERROR;
445       status.has_detail = 1;
446       snprintf(status.detail, sizeof status.detail, "unrecognized output color space %d",
447                cinfo->out_color_space);
448   }
449   return status;
450 }
451 
decodeToCSRGB(jpeg_decompress_struct * cinfo,uint8_t * dest)452 uhdr_error_info_t JpegDecoderHelper::decodeToCSRGB(jpeg_decompress_struct* cinfo, uint8_t* dest) {
453   JSAMPLE* out = (JSAMPLE*)dest;
454 
455   while (cinfo->output_scanline < cinfo->image_height) {
456     JDIMENSION read_lines = jpeg_read_scanlines(cinfo, &out, 1);
457     if (1 != read_lines) {
458       uhdr_error_info_t status;
459       status.error_code = UHDR_CODEC_ERROR;
460       status.has_detail = 1;
461       snprintf(status.detail, sizeof status.detail, "jpeg_read_scanlines returned %d, expected %d",
462                read_lines, 1);
463       return status;
464     }
465 #ifdef JCS_ALPHA_EXTENSIONS
466     out += (size_t)mPlaneHStride[0] * 4;
467 #else
468     out += (size_t)mPlaneHStride[0] * 3;
469 #endif
470   }
471   return g_no_error;
472 }
473 
decodeToCSYCbCr(jpeg_decompress_struct * cinfo,uint8_t * dest)474 uhdr_error_info_t JpegDecoderHelper::decodeToCSYCbCr(jpeg_decompress_struct* cinfo, uint8_t* dest) {
475   JSAMPROW mcuRows[kMaxNumComponents][4 * DCTSIZE];
476   JSAMPROW mcuRowsTmp[kMaxNumComponents][4 * DCTSIZE];
477   uint8_t* planes[kMaxNumComponents]{};
478   size_t alignedPlaneWidth[kMaxNumComponents]{};
479   JSAMPARRAY subImage[kMaxNumComponents];
480 
481   for (int i = 0, plane_offset = 0; i < cinfo->num_components; i++) {
482     planes[i] = dest + plane_offset;
483     plane_offset += mPlaneHStride[i] * mPlaneVStride[i];
484     alignedPlaneWidth[i] = ALIGNM(mPlaneHStride[i], DCTSIZE);
485     if (mPlaneHStride[i] != alignedPlaneWidth[i]) {
486       mPlanesMCURow[i] = std::make_unique<uint8_t[]>(alignedPlaneWidth[i] * DCTSIZE *
487                                                      cinfo->comp_info[i].v_samp_factor);
488       uint8_t* mem = mPlanesMCURow[i].get();
489       for (int j = 0; j < DCTSIZE * cinfo->comp_info[i].v_samp_factor;
490            j++, mem += alignedPlaneWidth[i]) {
491         mcuRowsTmp[i][j] = mem;
492       }
493     } else if (mPlaneVStride[i] % DCTSIZE != 0) {
494       mPlanesMCURow[i] = std::make_unique<uint8_t[]>(alignedPlaneWidth[i]);
495     }
496     subImage[i] = mPlaneHStride[i] == alignedPlaneWidth[i] ? mcuRows[i] : mcuRowsTmp[i];
497   }
498 
499   while (cinfo->output_scanline < cinfo->image_height) {
500     JDIMENSION mcu_scanline_start[kMaxNumComponents];
501 
502     for (int i = 0; i < cinfo->num_components; i++) {
503       mcu_scanline_start[i] =
504           std::ceil(((float)cinfo->output_scanline * cinfo->comp_info[i].v_samp_factor) /
505                     cinfo->max_v_samp_factor);
506 
507       for (int j = 0; j < cinfo->comp_info[i].v_samp_factor * DCTSIZE; j++) {
508         JDIMENSION scanline = mcu_scanline_start[i] + j;
509 
510         if (scanline < mPlaneVStride[i]) {
511           mcuRows[i][j] = planes[i] + (size_t)scanline * mPlaneHStride[i];
512         } else {
513           mcuRows[i][j] = mPlanesMCURow[i].get();
514         }
515       }
516     }
517 
518     int processed = jpeg_read_raw_data(cinfo, subImage, DCTSIZE * cinfo->max_v_samp_factor);
519     if (processed != DCTSIZE * cinfo->max_v_samp_factor) {
520       uhdr_error_info_t status;
521       status.error_code = UHDR_CODEC_ERROR;
522       status.has_detail = 1;
523       snprintf(status.detail, sizeof status.detail,
524                "number of scan lines read %d does not equal requested scan lines %d ", processed,
525                DCTSIZE * cinfo->max_v_samp_factor);
526       return status;
527     }
528 
529     for (int i = 0; i < cinfo->num_components; i++) {
530       if (mPlaneHStride[i] != alignedPlaneWidth[i]) {
531         for (int j = 0; j < cinfo->comp_info[i].v_samp_factor * DCTSIZE; j++) {
532           JDIMENSION scanline = mcu_scanline_start[i] + j;
533           if (scanline < mPlaneVStride[i]) {
534             memcpy(mcuRows[i][j], mcuRowsTmp[i][j], mPlaneWidth[i]);
535           }
536         }
537       }
538     }
539   }
540   return g_no_error;
541 }
542 
getDecompressedImage()543 uhdr_raw_image_t JpegDecoderHelper::getDecompressedImage() {
544   uhdr_raw_image_t img;
545 
546   img.fmt = mOutFormat;
547   img.cg = UHDR_CG_UNSPECIFIED;
548   img.ct = UHDR_CT_UNSPECIFIED;
549   img.range = UHDR_CR_FULL_RANGE;
550   img.w = mPlaneWidth[0];
551   img.h = mPlaneHeight[0];
552   uint8_t* data = mResultBuffer.data();
553   for (int i = 0; i < 3; i++) {
554     img.planes[i] = data;
555     img.stride[i] = mPlaneHStride[i];
556     data += (size_t)mPlaneHStride[i] * mPlaneVStride[i];
557   }
558 
559   return img;
560 }
561 
562 }  // namespace ultrahdr
563