• 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 #include <map>
23 #include <memory>
24 #include <string>
25 
26 #include "ultrahdr/ultrahdrcommon.h"
27 #include "ultrahdr/ultrahdr.h"
28 #include "ultrahdr/jpegencoderhelper.h"
29 
30 namespace ultrahdr {
31 
32 /*!\brief map of sub sampling format and jpeg h_samp_factor, v_samp_factor */
33 std::map<uhdr_img_fmt_t, std::vector<int>> sample_factors = {
34     {UHDR_IMG_FMT_8bppYCbCr400,
35      {1 /*h0*/, 1 /*v0*/, 0 /*h1*/, 0 /*v1*/, 0 /*h2*/, 0 /*v2*/, 1 /*maxh*/, 1 /*maxv*/}},
36     {UHDR_IMG_FMT_24bppYCbCr444,
37      {1 /*h0*/, 1 /*v0*/, 1 /*h1*/, 1 /*v1*/, 1 /*h2*/, 1 /*v2*/, 1 /*maxh*/, 1 /*maxv*/}},
38     {UHDR_IMG_FMT_16bppYCbCr440,
39      {1 /*h0*/, 2 /*v0*/, 1 /*h1*/, 1 /*v1*/, 1 /*h2*/, 1 /*v2*/, 1 /*maxh*/, 2 /*maxv*/}},
40     {UHDR_IMG_FMT_16bppYCbCr422,
41      {2 /*h0*/, 1 /*v0*/, 1 /*h1*/, 1 /*v1*/, 1 /*h2*/, 1 /*v2*/, 2 /*maxh*/, 1 /*maxv*/}},
42     {UHDR_IMG_FMT_12bppYCbCr420,
43      {2 /*h0*/, 2 /*v0*/, 1 /*h1*/, 1 /*v1*/, 1 /*h2*/, 1 /*v2*/, 2 /*maxh*/, 2 /*maxv*/}},
44     {UHDR_IMG_FMT_12bppYCbCr411,
45      {4 /*h0*/, 1 /*v0*/, 1 /*h1*/, 1 /*v1*/, 1 /*h2*/, 1 /*v2*/, 4 /*maxh*/, 1 /*maxv*/}},
46     {UHDR_IMG_FMT_10bppYCbCr410,
47      {4 /*h0*/, 2 /*v0*/, 1 /*h1*/, 1 /*v1*/, 1 /*h2*/, 1 /*v2*/, 4 /*maxh*/, 2 /*maxv*/}},
48     {UHDR_IMG_FMT_24bppRGB888,
49      {1 /*h0*/, 1 /*v0*/, 1 /*h1*/, 1 /*v1*/, 1 /*h2*/, 1 /*v2*/, 1 /*maxh*/, 1 /*maxv*/}},
50 };
51 
52 /*!\brief jpeg encoder library destination manager callback functions implementation */
53 
54 /*!\brief  called by jpeg_start_compress() before any data is actually written. This function is
55  * expected to initialize fields next_output_byte (place to write encoded output) and
56  * free_in_buffer (size of the buffer supplied) of jpeg destination manager. free_in_buffer must
57  * be initialized to a positive value.*/
initDestination(j_compress_ptr cinfo)58 static void initDestination(j_compress_ptr cinfo) {
59   destination_mgr_impl* dest = reinterpret_cast<destination_mgr_impl*>(cinfo->dest);
60   std::vector<JOCTET>& buffer = dest->mResultBuffer;
61   buffer.resize(dest->kBlockSize);
62   dest->next_output_byte = &buffer[0];
63   dest->free_in_buffer = buffer.size();
64 }
65 
66 /*!\brief  called if buffer provided for storing encoded data is exhausted during encoding. This
67  * function is expected to consume the encoded output and provide fresh buffer to continue
68  * encoding. */
emptyOutputBuffer(j_compress_ptr cinfo)69 static boolean emptyOutputBuffer(j_compress_ptr cinfo) {
70   destination_mgr_impl* dest = reinterpret_cast<destination_mgr_impl*>(cinfo->dest);
71   std::vector<JOCTET>& buffer = dest->mResultBuffer;
72   size_t oldsize = buffer.size();
73   buffer.resize(oldsize + dest->kBlockSize);
74   dest->next_output_byte = &buffer[oldsize];
75   dest->free_in_buffer = dest->kBlockSize;
76   return true;
77 }
78 
79 /*!\brief  called by jpeg_finish_compress() to flush out all the remaining encoded data. client
80  * can use either next_output_byte or free_in_buffer to determine how much data is in the buffer.
81  */
terminateDestination(j_compress_ptr cinfo)82 static void terminateDestination(j_compress_ptr cinfo) {
83   destination_mgr_impl* dest = reinterpret_cast<destination_mgr_impl*>(cinfo->dest);
84   std::vector<JOCTET>& buffer = dest->mResultBuffer;
85   buffer.resize(buffer.size() - dest->free_in_buffer);
86 }
87 
88 /*!\brief module for managing error */
89 struct jpeg_error_mgr_impl : jpeg_error_mgr {
90   jmp_buf setjmp_buffer;
91 };
92 
93 /*!\brief jpeg encoder library error manager callback function implementations */
jpegrerror_exit(j_common_ptr cinfo)94 static void jpegrerror_exit(j_common_ptr cinfo) {
95   jpeg_error_mgr_impl* err = reinterpret_cast<jpeg_error_mgr_impl*>(cinfo->err);
96   longjmp(err->setjmp_buffer, 1);
97 }
98 
99 /* receive most recent jpeg error message and print */
outputErrorMessage(j_common_ptr cinfo)100 static void outputErrorMessage(j_common_ptr cinfo) {
101   char buffer[JMSG_LENGTH_MAX];
102 
103   /* Create the message */
104   (*cinfo->err->format_message)(cinfo, buffer);
105   ALOGE("%s\n", buffer);
106 }
107 
compressImage(const uint8_t * planes[3],const size_t strides[3],const int width,const int height,const uhdr_img_fmt_t format,const int qfactor,const void * iccBuffer,const unsigned int iccSize)108 bool JpegEncoderHelper::compressImage(const uint8_t* planes[3], const size_t strides[3],
109                                       const int width, const int height, const uhdr_img_fmt_t format,
110                                       const int qfactor, const void* iccBuffer,
111                                       const unsigned int iccSize) {
112   return encode(planes, strides, width, height, format, qfactor, iccBuffer, iccSize);
113 }
114 
encode(const uint8_t * planes[3],const size_t strides[3],const int width,const int height,const uhdr_img_fmt_t format,const int qfactor,const void * iccBuffer,const unsigned int iccSize)115 bool JpegEncoderHelper::encode(const uint8_t* planes[3], const size_t strides[3], const int width,
116                                const int height, const uhdr_img_fmt_t format, const int qfactor,
117                                const void* iccBuffer, const unsigned int iccSize) {
118   jpeg_compress_struct cinfo;
119   jpeg_error_mgr_impl myerr;
120 
121   if (sample_factors.find(format) == sample_factors.end()) {
122     ALOGE("unrecognized format %d", format);
123     return false;
124   }
125   std::vector<int>& factors = sample_factors.find(format)->second;
126 
127   cinfo.err = jpeg_std_error(&myerr);
128   myerr.error_exit = jpegrerror_exit;
129   myerr.output_message = outputErrorMessage;
130 
131   if (0 == setjmp(myerr.setjmp_buffer)) {
132     jpeg_create_compress(&cinfo);
133 
134     // initialize destination manager
135     mDestMgr.init_destination = &initDestination;
136     mDestMgr.empty_output_buffer = &emptyOutputBuffer;
137     mDestMgr.term_destination = &terminateDestination;
138     mDestMgr.mResultBuffer.clear();
139     cinfo.dest = reinterpret_cast<struct jpeg_destination_mgr*>(&mDestMgr);
140 
141     // initialize configuration parameters
142     cinfo.image_width = width;
143     cinfo.image_height = height;
144     if (format == UHDR_IMG_FMT_24bppRGB888) {
145       cinfo.input_components = 3;
146       cinfo.in_color_space = JCS_RGB;
147     } else {
148       if (format == UHDR_IMG_FMT_8bppYCbCr400) {
149         cinfo.input_components = 1;
150         cinfo.in_color_space = JCS_GRAYSCALE;
151       } else {
152         cinfo.input_components = 3;
153         cinfo.in_color_space = JCS_YCbCr;
154       }
155     }
156     jpeg_set_defaults(&cinfo);
157     jpeg_set_quality(&cinfo, qfactor, TRUE);
158     for (int i = 0; i < cinfo.num_components; i++) {
159       cinfo.comp_info[i].h_samp_factor = factors[i * 2];
160       cinfo.comp_info[i].v_samp_factor = factors[i * 2 + 1];
161       mPlaneWidth[i] =
162           std::ceil(((float)cinfo.image_width * cinfo.comp_info[i].h_samp_factor) / factors[6]);
163       mPlaneHeight[i] =
164           std::ceil(((float)cinfo.image_height * cinfo.comp_info[i].v_samp_factor) / factors[7]);
165     }
166     if (format != UHDR_IMG_FMT_24bppRGB888) cinfo.raw_data_in = TRUE;
167     cinfo.dct_method = JDCT_ISLOW;
168 
169     // start compress
170     jpeg_start_compress(&cinfo, TRUE);
171     if (iccBuffer != nullptr && iccSize > 0) {
172       jpeg_write_marker(&cinfo, JPEG_APP0 + 2, static_cast<const JOCTET*>(iccBuffer), iccSize);
173     }
174     if (format == UHDR_IMG_FMT_24bppRGB888) {
175       while (cinfo.next_scanline < cinfo.image_height) {
176         JSAMPROW row_pointer[]{const_cast<JSAMPROW>(&planes[0][cinfo.next_scanline * strides[0]])};
177         JDIMENSION processed = jpeg_write_scanlines(&cinfo, row_pointer, 1);
178         if (1 != processed) {
179           ALOGE("jpeg_read_scanlines returned %d, expected %d", processed, 1);
180           jpeg_destroy_compress(&cinfo);
181           return false;
182         }
183       }
184     } else {
185       if (!compressYCbCr(&cinfo, planes, strides)) {
186         jpeg_destroy_compress(&cinfo);
187         return false;
188       }
189     }
190   } else {
191     cinfo.err->output_message((j_common_ptr)&cinfo);
192     jpeg_destroy_compress(&cinfo);
193     return false;
194   }
195 
196   jpeg_finish_compress(&cinfo);
197   jpeg_destroy_compress(&cinfo);
198   return true;
199 }
200 
compressYCbCr(jpeg_compress_struct * cinfo,const uint8_t * planes[3],const size_t strides[3])201 bool JpegEncoderHelper::compressYCbCr(jpeg_compress_struct* cinfo, const uint8_t* planes[3],
202                                       const size_t strides[3]) {
203   JSAMPROW mcuRows[kMaxNumComponents][2 * DCTSIZE];
204   JSAMPROW mcuRowsTmp[kMaxNumComponents][2 * DCTSIZE];
205   size_t alignedPlaneWidth[kMaxNumComponents]{};
206   JSAMPARRAY subImage[kMaxNumComponents];
207 
208   for (int i = 0; i < cinfo->num_components; i++) {
209     alignedPlaneWidth[i] = ALIGNM(mPlaneWidth[i], DCTSIZE);
210     if (strides[i] < alignedPlaneWidth[i]) {
211       mPlanesMCURow[i] = std::make_unique<uint8_t[]>(alignedPlaneWidth[i] * DCTSIZE *
212                                                      cinfo->comp_info[i].v_samp_factor);
213       uint8_t* mem = mPlanesMCURow[i].get();
214       for (int j = 0; j < DCTSIZE * cinfo->comp_info[i].v_samp_factor;
215            j++, mem += alignedPlaneWidth[i]) {
216         mcuRowsTmp[i][j] = mem;
217         if (i > 0) {
218           memset(mem + mPlaneWidth[i], 128, alignedPlaneWidth[i] - mPlaneWidth[i]);
219         }
220       }
221     } else if (mPlaneHeight[i] % DCTSIZE != 0) {
222       mPlanesMCURow[i] = std::make_unique<uint8_t[]>(alignedPlaneWidth[i]);
223       if (i > 0) {
224         memset(mPlanesMCURow[i].get(), 128, alignedPlaneWidth[i]);
225       }
226     }
227     subImage[i] = strides[i] < alignedPlaneWidth[i] ? mcuRowsTmp[i] : mcuRows[i];
228   }
229 
230   while (cinfo->next_scanline < cinfo->image_height) {
231     JDIMENSION mcu_scanline_start[kMaxNumComponents];
232 
233     for (int i = 0; i < cinfo->num_components; i++) {
234       mcu_scanline_start[i] =
235           std::ceil(((float)cinfo->next_scanline * cinfo->comp_info[i].v_samp_factor) /
236                     cinfo->max_v_samp_factor);
237 
238       for (int j = 0; j < cinfo->comp_info[i].v_samp_factor * DCTSIZE; j++) {
239         JDIMENSION scanline = mcu_scanline_start[i] + j;
240 
241         if (scanline < mPlaneHeight[i]) {
242           mcuRows[i][j] = const_cast<uint8_t*>(planes[i] + scanline * strides[i]);
243           if (strides[i] < alignedPlaneWidth[i]) {
244             memcpy(mcuRowsTmp[i][j], mcuRows[i][j], mPlaneWidth[i]);
245           }
246         } else {
247           mcuRows[i][j] = mPlanesMCURow[i].get();
248         }
249       }
250     }
251     int processed = jpeg_write_raw_data(cinfo, subImage, DCTSIZE * cinfo->max_v_samp_factor);
252     if (processed != DCTSIZE * cinfo->max_v_samp_factor) {
253       ALOGE("number of scan lines processed %d does not equal requested scan lines %d ", processed,
254             DCTSIZE * cinfo->max_v_samp_factor);
255       return false;
256     }
257   }
258   return true;
259 }
260 
261 }  // namespace ultrahdr
262