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