• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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/ultrahdr.h"
25 #include "ultrahdr/jpegdecoderhelper.h"
26 
27 using namespace std;
28 
29 namespace ultrahdr {
30 
31 static const uint32_t kAPP0Marker = JPEG_APP0;      // JFIF
32 static const uint32_t kAPP1Marker = JPEG_APP0 + 1;  // EXIF, XMP
33 static const uint32_t kAPP2Marker = JPEG_APP0 + 2;  // ICC, ISO Metadata
34 
35 static constexpr uint8_t kICCSig[] = {
36     'I', 'C', 'C', '_', 'P', 'R', 'O', 'F', 'I', 'L', 'E', '\0',
37 };
38 
39 static constexpr uint8_t kXmpNameSpace[] = {
40     'h', 't', 't', 'p', ':', '/', '/', 'n', 's', '.', 'a', 'd', 'o', 'b', 'e',
41     '.', 'c', 'o', 'm', '/', 'x', 'a', 'p', '/', '1', '.', '0', '/', '\0',
42 };
43 
44 static constexpr uint8_t kExifIdCode[] = {
45     'E', 'x', 'i', 'f', '\0', '\0',
46 };
47 
48 static constexpr uint8_t kIsoMetadataNameSpace[] = {
49     'u', 'r', 'n', ':', 'i', 's', 'o', ':', 's', 't', 'd', ':', 'i', 's',
50     'o', ':', 't', 's', ':', '2', '1', '4', '9', '6', ':', '-', '1', '\0',
51 };
52 
53 /*!\brief module for managing input */
54 struct jpeg_source_mgr_impl : jpeg_source_mgr {
55   jpeg_source_mgr_impl(const uint8_t* ptr, int len);
56   ~jpeg_source_mgr_impl() = default;
57 
58   const uint8_t* mBufferPtr;
59   size_t mBufferLength;
60 };
61 
62 /*!\brief module for managing error */
63 struct jpeg_error_mgr_impl : jpeg_error_mgr {
64   jmp_buf setjmp_buffer;
65 };
66 
jpegr_init_source(j_decompress_ptr cinfo)67 static void jpegr_init_source(j_decompress_ptr cinfo) {
68   jpeg_source_mgr_impl* src = static_cast<jpeg_source_mgr_impl*>(cinfo->src);
69   src->next_input_byte = static_cast<const JOCTET*>(src->mBufferPtr);
70   src->bytes_in_buffer = src->mBufferLength;
71 }
72 
jpegr_fill_input_buffer(j_decompress_ptr)73 static boolean jpegr_fill_input_buffer(j_decompress_ptr /* cinfo */) {
74   ALOGE("%s : should not reach here", __func__);
75   return FALSE;
76 }
77 
jpegr_skip_input_data(j_decompress_ptr cinfo,long num_bytes)78 static void jpegr_skip_input_data(j_decompress_ptr cinfo, long num_bytes) {
79   jpeg_source_mgr_impl* src = static_cast<jpeg_source_mgr_impl*>(cinfo->src);
80 
81   if (num_bytes > static_cast<long>(src->bytes_in_buffer)) {
82     ALOGE("jpegr_skip_input_data - num_bytes > (long)src->bytes_in_buffer");
83   } else {
84     src->next_input_byte += num_bytes;
85     src->bytes_in_buffer -= num_bytes;
86   }
87 }
88 
jpegr_term_source(j_decompress_ptr)89 static void jpegr_term_source(j_decompress_ptr /*cinfo*/) {}
90 
jpeg_source_mgr_impl(const uint8_t * ptr,int len)91 jpeg_source_mgr_impl::jpeg_source_mgr_impl(const uint8_t* ptr, int len)
92     : mBufferPtr(ptr), mBufferLength(len) {
93   init_source = jpegr_init_source;
94   fill_input_buffer = jpegr_fill_input_buffer;
95   skip_input_data = jpegr_skip_input_data;
96   resync_to_restart = jpeg_resync_to_restart;
97   term_source = jpegr_term_source;
98 }
99 
jpegrerror_exit(j_common_ptr cinfo)100 static void jpegrerror_exit(j_common_ptr cinfo) {
101   jpeg_error_mgr_impl* err = reinterpret_cast<jpeg_error_mgr_impl*>(cinfo->err);
102   longjmp(err->setjmp_buffer, 1);
103 }
104 
output_message(j_common_ptr cinfo)105 static void output_message(j_common_ptr cinfo) {
106   char buffer[JMSG_LENGTH_MAX];
107 
108   (*cinfo->err->format_message)(cinfo, buffer);
109   ALOGE("%s\n", buffer);
110 }
111 
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,int & markerPayloadOffsetRelativeToSourceBuffer)112 static void jpeg_extract_marker_payload(const j_decompress_ptr cinfo, const uint32_t marker_code,
113                                         const uint8_t* marker_fourcc_code,
114                                         const uint32_t fourcc_length,
115                                         std::vector<JOCTET>& destination,
116                                         int& markerPayloadOffsetRelativeToSourceBuffer) {
117   size_t pos = 2; /* position after reading SOI marker (0xffd8) */
118   markerPayloadOffsetRelativeToSourceBuffer = -1;
119 
120   for (jpeg_marker_struct* marker = cinfo->marker_list; marker; marker = marker->next) {
121     pos += 4; /* position after reading next marker and its size (0xFFXX, [SIZE = 2 bytes]) */
122 
123     if (marker->marker == marker_code && marker->data_length > fourcc_length &&
124         !memcmp(marker->data, marker_fourcc_code, fourcc_length)) {
125       destination.resize(marker->data_length);
126       memcpy(static_cast<void*>(destination.data()), marker->data, marker->data_length);
127       markerPayloadOffsetRelativeToSourceBuffer = pos;
128       return;
129     }
130     pos += marker->original_length; /* position after marker's payload */
131   }
132 }
133 
getOutputSamplingFormat(const j_decompress_ptr cinfo)134 static uhdr_img_fmt_t getOutputSamplingFormat(const j_decompress_ptr cinfo) {
135   if (cinfo->num_components == 1)
136     return UHDR_IMG_FMT_8bppYCbCr400;
137   else {
138     int a = cinfo->max_h_samp_factor / cinfo->comp_info[1].h_samp_factor;
139     int b = cinfo->max_v_samp_factor / cinfo->comp_info[1].v_samp_factor;
140     if (a == 1 && b == 1)
141       return UHDR_IMG_FMT_24bppYCbCr444;
142     else if (a == 1 && b == 2)
143       return UHDR_IMG_FMT_16bppYCbCr440;
144     else if (a == 2 && b == 1)
145       return UHDR_IMG_FMT_16bppYCbCr422;
146     else if (a == 2 && b == 2)
147       return UHDR_IMG_FMT_12bppYCbCr420;
148     else if (a == 4 && b == 1)
149       return UHDR_IMG_FMT_12bppYCbCr411;
150     else if (a == 4 && b == 2)
151       return UHDR_IMG_FMT_10bppYCbCr410;
152   }
153   return UHDR_IMG_FMT_UNSPECIFIED;
154 }
155 
decompressImage(const void * image,int length,decode_mode_t mode)156 bool JpegDecoderHelper::decompressImage(const void* image, int length, decode_mode_t mode) {
157   if (image == nullptr) {
158     ALOGE("received nullptr for compressed image data");
159     return false;
160   }
161   if (length <= 0) {
162     ALOGE("received bad compressed image size %d", length);
163     return false;
164   }
165 
166   // reset context
167   mResultBuffer.clear();
168   mXMPBuffer.clear();
169   mEXIFBuffer.clear();
170   mICCBuffer.clear();
171   mIsoMetadataBuffer.clear();
172   mOutFormat = UHDR_IMG_FMT_UNSPECIFIED;
173   for (int i = 0; i < kMaxNumComponents; i++) {
174     mPlanesMCURow[i].reset();
175     mPlaneWidth[i] = 0;
176     mPlaneHeight[i] = 0;
177   }
178   mExifPayLoadOffset = -1;
179 
180   return decode(image, length, mode);
181 }
182 
decode(const void * image,int length,decode_mode_t mode)183 bool JpegDecoderHelper::decode(const void* image, int length, decode_mode_t mode) {
184   jpeg_source_mgr_impl mgr(static_cast<const uint8_t*>(image), length);
185   jpeg_decompress_struct cinfo;
186   jpeg_error_mgr_impl myerr;
187 
188   cinfo.err = jpeg_std_error(&myerr);
189   myerr.error_exit = jpegrerror_exit;
190   myerr.output_message = output_message;
191 
192   if (0 == setjmp(myerr.setjmp_buffer)) {
193     jpeg_create_decompress(&cinfo);
194     cinfo.src = &mgr;
195     jpeg_save_markers(&cinfo, kAPP0Marker, 0xFFFF);
196     jpeg_save_markers(&cinfo, kAPP1Marker, 0xFFFF);
197     jpeg_save_markers(&cinfo, kAPP2Marker, 0xFFFF);
198     int ret_val = jpeg_read_header(&cinfo, TRUE /* require an image to be present */);
199     if (JPEG_HEADER_OK != ret_val) {
200       ALOGE("jpeg_read_header(...) returned %d, expected %d", ret_val, JPEG_HEADER_OK);
201       jpeg_destroy_decompress(&cinfo);
202       return false;
203     }
204     int payloadOffset = -1;
205     jpeg_extract_marker_payload(&cinfo, kAPP1Marker, kXmpNameSpace,
206                                 sizeof kXmpNameSpace / sizeof kXmpNameSpace[0], mXMPBuffer,
207                                 payloadOffset);
208     jpeg_extract_marker_payload(&cinfo, kAPP1Marker, kExifIdCode,
209                                 sizeof kExifIdCode / sizeof kExifIdCode[0], mEXIFBuffer,
210                                 mExifPayLoadOffset);
211     jpeg_extract_marker_payload(&cinfo, kAPP2Marker, kICCSig, sizeof kICCSig / sizeof kICCSig[0],
212                                 mICCBuffer, payloadOffset);
213     jpeg_extract_marker_payload(&cinfo, kAPP2Marker, kIsoMetadataNameSpace,
214                                 sizeof kIsoMetadataNameSpace / sizeof kIsoMetadataNameSpace[0],
215                                 mIsoMetadataBuffer, payloadOffset);
216 
217     if (cinfo.image_width < 1 || cinfo.image_height < 1) {
218       ALOGE("received bad image width or height, wd = %d, ht = %d. wd and height shall be >= 1",
219             cinfo.image_width, cinfo.image_height);
220       jpeg_destroy_decompress(&cinfo);
221       return false;
222     }
223     if (cinfo.image_width > kMaxWidth || cinfo.image_height > kMaxHeight) {
224       ALOGE(
225           "max width, max supported by library are %d, %d respectively. Current image width and "
226           "height are %d, %d. Recompile library with updated max supported dimensions to proceed",
227           kMaxWidth, kMaxHeight, cinfo.image_width, cinfo.image_height);
228       jpeg_destroy_decompress(&cinfo);
229       return false;
230     }
231     if (cinfo.num_components != 1 && cinfo.num_components != 3) {
232       ALOGE(
233           "ultrahdr primary image and supplimentary images are images encoded with 1 component "
234           "(grayscale) or 3 components (YCbCr / RGB). Unrecognized number of components %d",
235           cinfo.num_components);
236       jpeg_destroy_decompress(&cinfo);
237       return false;
238     }
239 
240     for (int i = 0, product = 0; i < cinfo.num_components; i++) {
241       if (cinfo.comp_info[i].h_samp_factor < 1 || cinfo.comp_info[i].h_samp_factor > 4) {
242         ALOGE(
243             "received bad horizontal sampling factor for component index %d, sample factor h = %d, "
244             "this is expected to be with in range [1-4]",
245             i, cinfo.comp_info[i].h_samp_factor);
246         jpeg_destroy_decompress(&cinfo);
247         return false;
248       }
249       if (cinfo.comp_info[i].v_samp_factor < 1 || cinfo.comp_info[i].v_samp_factor > 4) {
250         ALOGE(
251             "received bad vertical sampling factor for component index %d, sample factor v = %d, "
252             "this is expected to be with in range [1-4]",
253             i, cinfo.comp_info[i].v_samp_factor);
254         jpeg_destroy_decompress(&cinfo);
255         return false;
256       }
257       product += cinfo.comp_info[i].h_samp_factor * cinfo.comp_info[i].v_samp_factor;
258       if (product > 10) {
259         ALOGE(
260             "received bad sampling factors for components, sum of product of h_samp_factor, "
261             "v_samp_factor across all components exceeds 10");
262         jpeg_destroy_decompress(&cinfo);
263         return false;
264       }
265     }
266 
267     for (int i = 0; i < cinfo.num_components; i++) {
268       mPlaneWidth[i] = std::ceil(((float)cinfo.image_width * cinfo.comp_info[i].h_samp_factor) /
269                                  cinfo.max_h_samp_factor);
270       mPlaneHeight[i] = std::ceil(((float)cinfo.image_height * cinfo.comp_info[i].v_samp_factor) /
271                                   cinfo.max_v_samp_factor);
272     }
273 
274     if (cinfo.num_components == 3 &&
275         (mPlaneWidth[1] != mPlaneWidth[2] || mPlaneHeight[1] != mPlaneHeight[2])) {
276       ALOGE(
277           "cb, cr planes are not sampled identically. cb width %d, cb height %d, cr width %d, cr "
278           "height %d",
279           (int)mPlaneWidth[1], (int)mPlaneWidth[2], (int)mPlaneHeight[1], (int)mPlaneHeight[2]);
280       jpeg_destroy_decompress(&cinfo);
281       return false;
282     }
283 
284     if (PARSE_STREAM == mode) {
285       jpeg_destroy_decompress(&cinfo);
286       return true;
287     }
288 
289     if (DECODE_STREAM == mode) {
290       mode = cinfo.num_components == 1 ? DECODE_TO_YCBCR_CS : DECODE_TO_RGB_CS;
291     }
292 
293     if (DECODE_TO_RGB_CS == mode) {
294       if (cinfo.jpeg_color_space != JCS_YCbCr && cinfo.jpeg_color_space != JCS_RGB) {
295         ALOGE("expected input color space to be JCS_YCbCr or JCS_RGB but got %d",
296               cinfo.jpeg_color_space);
297         jpeg_destroy_decompress(&cinfo);
298         return false;
299       }
300 #ifdef JCS_ALPHA_EXTENSIONS
301       mResultBuffer.resize(cinfo.image_width * cinfo.image_height * 4);
302       cinfo.out_color_space = JCS_EXT_RGBA;
303 #else
304       mResultBuffer.resize(cinfo.image_width * cinfo.image_height * 3);
305       cinfo.out_color_space = JCS_RGB;
306 #endif
307     } else if (DECODE_TO_YCBCR_CS == mode) {
308       if (cinfo.jpeg_color_space != JCS_YCbCr && cinfo.jpeg_color_space != JCS_GRAYSCALE) {
309         ALOGE("expected input color space to be JCS_YCbCr or JCS_GRAYSCALE but got %d",
310               cinfo.jpeg_color_space);
311         jpeg_destroy_decompress(&cinfo);
312         return false;
313       }
314       if (cinfo.jpeg_color_space == JCS_YCbCr) {
315         if (cinfo.comp_info[0].h_samp_factor != 2 || cinfo.comp_info[0].v_samp_factor != 2 ||
316             cinfo.comp_info[1].h_samp_factor != 1 || cinfo.comp_info[1].v_samp_factor != 1 ||
317             cinfo.comp_info[2].h_samp_factor != 1 || cinfo.comp_info[2].v_samp_factor != 1) {
318           ALOGE("apply gainmap supports only 4:2:0 sub sampling format, stopping image decode");
319           jpeg_destroy_decompress(&cinfo);
320           return false;
321         }
322       }
323       int size = 0;
324       for (int i = 0; i < cinfo.num_components; i++) {
325         size += mPlaneWidth[i] * mPlaneHeight[i];
326       }
327       mResultBuffer.resize(size);
328       cinfo.out_color_space = cinfo.jpeg_color_space;
329       cinfo.raw_data_out = TRUE;
330     }
331     cinfo.dct_method = JDCT_ISLOW;
332     jpeg_start_decompress(&cinfo);
333     if (!decode(&cinfo, static_cast<uint8_t*>(mResultBuffer.data()))) {
334       jpeg_destroy_decompress(&cinfo);
335       return false;
336     }
337   } else {
338     cinfo.err->output_message((j_common_ptr)&cinfo);
339     jpeg_destroy_decompress(&cinfo);
340     return false;
341   }
342   jpeg_finish_decompress(&cinfo);
343   jpeg_destroy_decompress(&cinfo);
344   return true;
345 }
346 
decode(jpeg_decompress_struct * cinfo,uint8_t * dest)347 bool JpegDecoderHelper::decode(jpeg_decompress_struct* cinfo, uint8_t* dest) {
348   switch (cinfo->out_color_space) {
349     case JCS_GRAYSCALE:
350       [[fallthrough]];
351     case JCS_YCbCr:
352       mOutFormat = getOutputSamplingFormat(cinfo);
353       return decodeToCSYCbCr(cinfo, dest);
354 #ifdef JCS_ALPHA_EXTENSIONS
355     case JCS_EXT_RGBA:
356       mOutFormat = UHDR_IMG_FMT_32bppRGBA8888;
357       return decodeToCSRGB(cinfo, dest);
358 #endif
359     case JCS_RGB:
360       mOutFormat = UHDR_IMG_FMT_24bppRGB888;
361       return decodeToCSRGB(cinfo, dest);
362     default:
363       ALOGE("unrecognized output color space %d", cinfo->out_color_space);
364   }
365   return false;
366 }
367 
decodeToCSRGB(jpeg_decompress_struct * cinfo,uint8_t * dest)368 bool JpegDecoderHelper::decodeToCSRGB(jpeg_decompress_struct* cinfo, uint8_t* dest) {
369   JSAMPLE* out = (JSAMPLE*)dest;
370 
371   while (cinfo->output_scanline < cinfo->image_height) {
372     JDIMENSION read_lines = jpeg_read_scanlines(cinfo, &out, 1);
373     if (1 != read_lines) {
374       ALOGE("jpeg_read_scanlines returned %d, expected %d", read_lines, 1);
375       return false;
376     }
377 #ifdef JCS_ALPHA_EXTENSIONS
378     out += cinfo->image_width * 4;
379 #else
380     out += cinfo->image_width * 3;
381 #endif
382   }
383   return true;
384 }
385 
decodeToCSYCbCr(jpeg_decompress_struct * cinfo,uint8_t * dest)386 bool JpegDecoderHelper::decodeToCSYCbCr(jpeg_decompress_struct* cinfo, uint8_t* dest) {
387   JSAMPROW mcuRows[kMaxNumComponents][4 * DCTSIZE];
388   JSAMPROW mcuRowsTmp[kMaxNumComponents][4 * DCTSIZE];
389   uint8_t* planes[kMaxNumComponents]{};
390   size_t alignedPlaneWidth[kMaxNumComponents]{};
391   JSAMPARRAY subImage[kMaxNumComponents];
392 
393   for (int i = 0, plane_offset = 0; i < cinfo->num_components; i++) {
394     planes[i] = dest + plane_offset;
395     plane_offset += mPlaneWidth[i] * mPlaneHeight[i];
396     alignedPlaneWidth[i] = ALIGNM(mPlaneWidth[i], DCTSIZE);
397     if (mPlaneWidth[i] != alignedPlaneWidth[i]) {
398       mPlanesMCURow[i] = std::make_unique<uint8_t[]>(alignedPlaneWidth[i] * DCTSIZE *
399                                                      cinfo->comp_info[i].v_samp_factor);
400       uint8_t* mem = mPlanesMCURow[i].get();
401       for (int j = 0; j < DCTSIZE * cinfo->comp_info[i].v_samp_factor;
402            j++, mem += alignedPlaneWidth[i]) {
403         mcuRowsTmp[i][j] = mem;
404       }
405     } else if (mPlaneHeight[i] % DCTSIZE != 0) {
406       mPlanesMCURow[i] = std::make_unique<uint8_t[]>(alignedPlaneWidth[i]);
407     }
408     subImage[i] = mPlaneWidth[i] == alignedPlaneWidth[i] ? mcuRows[i] : mcuRowsTmp[i];
409   }
410 
411   while (cinfo->output_scanline < cinfo->image_height) {
412     JDIMENSION mcu_scanline_start[kMaxNumComponents];
413 
414     for (int i = 0; i < cinfo->num_components; i++) {
415       mcu_scanline_start[i] =
416           std::ceil(((float)cinfo->output_scanline * cinfo->comp_info[i].v_samp_factor) /
417                     cinfo->max_v_samp_factor);
418 
419       for (int j = 0; j < cinfo->comp_info[i].v_samp_factor * DCTSIZE; j++) {
420         JDIMENSION scanline = mcu_scanline_start[i] + j;
421 
422         if (scanline < mPlaneHeight[i]) {
423           mcuRows[i][j] = planes[i] + scanline * mPlaneWidth[i];
424         } else {
425           mcuRows[i][j] = mPlanesMCURow[i].get();
426         }
427       }
428     }
429 
430     int processed = jpeg_read_raw_data(cinfo, subImage, DCTSIZE * cinfo->max_v_samp_factor);
431     if (processed != DCTSIZE * cinfo->max_v_samp_factor) {
432       ALOGE("number of scan lines read %d does not equal requested scan lines %d ", processed,
433             DCTSIZE * cinfo->max_v_samp_factor);
434       return false;
435     }
436 
437     for (int i = 0; i < cinfo->num_components; i++) {
438       if (mPlaneWidth[i] != alignedPlaneWidth[i]) {
439         for (int j = 0; j < cinfo->comp_info[i].v_samp_factor * DCTSIZE; j++) {
440           JDIMENSION scanline = mcu_scanline_start[i] + j;
441           if (scanline < mPlaneHeight[i]) {
442             memcpy(mcuRows[i][j], mcuRowsTmp[i][j], mPlaneWidth[i]);
443           }
444         }
445       }
446     }
447   }
448   return true;
449 }
450 
451 }  // namespace ultrahdr
452