blob: f0f41550848c3bebf795ad21b2ef36364b9191a5 [file] [log] [blame]
Dichen Zhang3b2c0ce2022-12-14 19:58:55 +00001#undef LOG_TAG
2#define LOG_TAG "YuvToJpegEncoder"
3
Wei-Ta Chenbca2d612009-11-30 17:52:05 +08004#include "CreateJavaOutputStreamAdaptor.h"
Matt Sarett79ad7be2016-03-25 14:28:40 -04005#include "SkJPEGWriteUtility.h"
Kevin Lubick1175dc02022-02-28 12:41:27 -05006#include "SkStream.h"
7#include "SkTypes.h"
Wei-Ta Chenbca2d612009-11-30 17:52:05 +08008#include "YuvToJpegEncoder.h"
Mathias Agopian8f2423e2010-02-16 17:33:37 -08009#include <ui/PixelFormat.h>
10#include <hardware/hardware.h>
Wei-Ta Chenbca2d612009-11-30 17:52:05 +080011
Derek Sollenbergerc5882c42019-10-25 11:11:32 -040012#include "graphics_jni_helpers.h"
Wei-Ta Chenbca2d612009-11-30 17:52:05 +080013
Kevin Lubickba253602022-08-09 21:13:39 +000014#include <csetjmp>
15
Wei-Ta Chenbca2d612009-11-30 17:52:05 +080016YuvToJpegEncoder* YuvToJpegEncoder::create(int format, int* strides) {
Mathias Agopiana696f5d2010-02-17 17:53:09 -080017 // Only ImageFormat.NV21 and ImageFormat.YUY2 are supported
Wei-Ta Chenbca2d612009-11-30 17:52:05 +080018 // for now.
Mathias Agopiana696f5d2010-02-17 17:53:09 -080019 if (format == HAL_PIXEL_FORMAT_YCrCb_420_SP) {
Wei-Ta Chenbca2d612009-11-30 17:52:05 +080020 return new Yuv420SpToJpegEncoder(strides);
Mathias Agopian8f2423e2010-02-16 17:33:37 -080021 } else if (format == HAL_PIXEL_FORMAT_YCbCr_422_I) {
Wei-Ta Chenbca2d612009-11-30 17:52:05 +080022 return new Yuv422IToJpegEncoder(strides);
23 } else {
24 return NULL;
25 }
26}
27
28YuvToJpegEncoder::YuvToJpegEncoder(int* strides) : fStrides(strides) {
29}
30
Leon Scroggins III1c3ded392018-02-28 13:23:32 -050031struct ErrorMgr {
32 struct jpeg_error_mgr pub;
33 jmp_buf jmp;
34};
35
36void error_exit(j_common_ptr cinfo) {
37 ErrorMgr* err = (ErrorMgr*) cinfo->err;
38 (*cinfo->err->output_message) (cinfo);
39 longjmp(err->jmp, 1);
40}
41
Wei-Ta Chenbca2d612009-11-30 17:52:05 +080042bool YuvToJpegEncoder::encode(SkWStream* stream, void* inYuv, int width,
43 int height, int* offsets, int jpegQuality) {
44 jpeg_compress_struct cinfo;
Leon Scroggins III1c3ded392018-02-28 13:23:32 -050045 ErrorMgr err;
Wei-Ta Chenbca2d612009-11-30 17:52:05 +080046 skjpeg_destination_mgr sk_wstream(stream);
47
Leon Scroggins III1c3ded392018-02-28 13:23:32 -050048 cinfo.err = jpeg_std_error(&err.pub);
49 err.pub.error_exit = error_exit;
50
51 if (setjmp(err.jmp)) {
52 jpeg_destroy_compress(&cinfo);
Wei-Ta Chenbca2d612009-11-30 17:52:05 +080053 return false;
54 }
55 jpeg_create_compress(&cinfo);
56
57 cinfo.dest = &sk_wstream;
58
59 setJpegCompressStruct(&cinfo, width, height, jpegQuality);
60
61 jpeg_start_compress(&cinfo, TRUE);
62
63 compress(&cinfo, (uint8_t*) inYuv, offsets);
64
65 jpeg_finish_compress(&cinfo);
66
Leon Scroggins III1c3ded392018-02-28 13:23:32 -050067 jpeg_destroy_compress(&cinfo);
68
Wei-Ta Chenbca2d612009-11-30 17:52:05 +080069 return true;
70}
71
72void YuvToJpegEncoder::setJpegCompressStruct(jpeg_compress_struct* cinfo,
73 int width, int height, int quality) {
Wei-Ta Chenbca2d612009-11-30 17:52:05 +080074 cinfo->image_width = width;
75 cinfo->image_height = height;
Wei-Ta Chenbca2d612009-11-30 17:52:05 +080076 cinfo->input_components = 3;
77 cinfo->in_color_space = JCS_YCbCr;
78 jpeg_set_defaults(cinfo);
Chia-chi Yehaa868592010-03-10 17:08:58 +080079
80 jpeg_set_quality(cinfo, quality, TRUE);
Wei-Ta Chenbca2d612009-11-30 17:52:05 +080081 jpeg_set_colorspace(cinfo, JCS_YCbCr);
82 cinfo->raw_data_in = TRUE;
Wei-Ta Chenbca2d612009-11-30 17:52:05 +080083 cinfo->dct_method = JDCT_IFAST;
Wei-Ta Chenbca2d612009-11-30 17:52:05 +080084 configSamplingFactors(cinfo);
85}
86
87///////////////////////////////////////////////////////////////////
88Yuv420SpToJpegEncoder::Yuv420SpToJpegEncoder(int* strides) :
89 YuvToJpegEncoder(strides) {
90 fNumPlanes = 2;
91}
92
93void Yuv420SpToJpegEncoder::compress(jpeg_compress_struct* cinfo,
94 uint8_t* yuv, int* offsets) {
Sally Qi7e3f93b2021-07-15 00:00:54 +000095 ALOGD("onFlyCompress");
Wei-Ta Chenbca2d612009-11-30 17:52:05 +080096 JSAMPROW y[16];
97 JSAMPROW cb[8];
98 JSAMPROW cr[8];
99 JSAMPARRAY planes[3];
100 planes[0] = y;
101 planes[1] = cb;
102 planes[2] = cr;
103
104 int width = cinfo->image_width;
105 int height = cinfo->image_height;
106 uint8_t* yPlanar = yuv + offsets[0];
107 uint8_t* vuPlanar = yuv + offsets[1]; //width * height;
108 uint8_t* uRows = new uint8_t [8 * (width >> 1)];
109 uint8_t* vRows = new uint8_t [8 * (width >> 1)];
110
111
112 // process 16 lines of Y and 8 lines of U/V each time.
113 while (cinfo->next_scanline < cinfo->image_height) {
114 //deitnerleave u and v
Wu-cheng Li4b63f142012-10-16 23:40:03 +0800115 deinterleave(vuPlanar, uRows, vRows, cinfo->next_scanline, width, height);
Wei-Ta Chenbca2d612009-11-30 17:52:05 +0800116
Wu-cheng Li4b63f142012-10-16 23:40:03 +0800117 // Jpeg library ignores the rows whose indices are greater than height.
Wei-Ta Chenbca2d612009-11-30 17:52:05 +0800118 for (int i = 0; i < 16; i++) {
119 // y row
120 y[i] = yPlanar + (cinfo->next_scanline + i) * fStrides[0];
121
122 // construct u row and v row
123 if ((i & 1) == 0) {
124 // height and width are both halved because of downsampling
125 int offset = (i >> 1) * (width >> 1);
126 cb[i/2] = uRows + offset;
127 cr[i/2] = vRows + offset;
128 }
129 }
130 jpeg_write_raw_data(cinfo, planes, 16);
131 }
132 delete [] uRows;
133 delete [] vRows;
134
135}
136
137void Yuv420SpToJpegEncoder::deinterleave(uint8_t* vuPlanar, uint8_t* uRows,
Wu-cheng Li4b63f142012-10-16 23:40:03 +0800138 uint8_t* vRows, int rowIndex, int width, int height) {
139 int numRows = (height - rowIndex) / 2;
140 if (numRows > 8) numRows = 8;
141 for (int row = 0; row < numRows; ++row) {
Wei-Ta Chenbca2d612009-11-30 17:52:05 +0800142 int offset = ((rowIndex >> 1) + row) * fStrides[1];
143 uint8_t* vu = vuPlanar + offset;
144 for (int i = 0; i < (width >> 1); ++i) {
145 int index = row * (width >> 1) + i;
146 uRows[index] = vu[1];
147 vRows[index] = vu[0];
148 vu += 2;
149 }
150 }
151}
152
153void Yuv420SpToJpegEncoder::configSamplingFactors(jpeg_compress_struct* cinfo) {
154 // cb and cr are horizontally downsampled and vertically downsampled as well.
155 cinfo->comp_info[0].h_samp_factor = 2;
156 cinfo->comp_info[0].v_samp_factor = 2;
157 cinfo->comp_info[1].h_samp_factor = 1;
158 cinfo->comp_info[1].v_samp_factor = 1;
159 cinfo->comp_info[2].h_samp_factor = 1;
160 cinfo->comp_info[2].v_samp_factor = 1;
161}
162
163///////////////////////////////////////////////////////////////////////////////
164Yuv422IToJpegEncoder::Yuv422IToJpegEncoder(int* strides) :
165 YuvToJpegEncoder(strides) {
166 fNumPlanes = 1;
167}
168
169void Yuv422IToJpegEncoder::compress(jpeg_compress_struct* cinfo,
170 uint8_t* yuv, int* offsets) {
Sally Qi7e3f93b2021-07-15 00:00:54 +0000171 ALOGD("onFlyCompress_422");
Wei-Ta Chenbca2d612009-11-30 17:52:05 +0800172 JSAMPROW y[16];
173 JSAMPROW cb[16];
174 JSAMPROW cr[16];
175 JSAMPARRAY planes[3];
176 planes[0] = y;
177 planes[1] = cb;
178 planes[2] = cr;
179
180 int width = cinfo->image_width;
181 int height = cinfo->image_height;
182 uint8_t* yRows = new uint8_t [16 * width];
183 uint8_t* uRows = new uint8_t [16 * (width >> 1)];
184 uint8_t* vRows = new uint8_t [16 * (width >> 1)];
185
186 uint8_t* yuvOffset = yuv + offsets[0];
187
188 // process 16 lines of Y and 16 lines of U/V each time.
189 while (cinfo->next_scanline < cinfo->image_height) {
190 deinterleave(yuvOffset, yRows, uRows, vRows, cinfo->next_scanline, width, height);
191
Wu-cheng Li4b63f142012-10-16 23:40:03 +0800192 // Jpeg library ignores the rows whose indices are greater than height.
Wei-Ta Chenbca2d612009-11-30 17:52:05 +0800193 for (int i = 0; i < 16; i++) {
194 // y row
195 y[i] = yRows + i * width;
196
197 // construct u row and v row
198 // width is halved because of downsampling
199 int offset = i * (width >> 1);
200 cb[i] = uRows + offset;
201 cr[i] = vRows + offset;
202 }
203
204 jpeg_write_raw_data(cinfo, planes, 16);
205 }
206 delete [] yRows;
207 delete [] uRows;
208 delete [] vRows;
209}
210
211
212void Yuv422IToJpegEncoder::deinterleave(uint8_t* yuv, uint8_t* yRows, uint8_t* uRows,
213 uint8_t* vRows, int rowIndex, int width, int height) {
Wu-cheng Li4b63f142012-10-16 23:40:03 +0800214 int numRows = height - rowIndex;
215 if (numRows > 16) numRows = 16;
216 for (int row = 0; row < numRows; ++row) {
Wei-Ta Chenbca2d612009-11-30 17:52:05 +0800217 uint8_t* yuvSeg = yuv + (rowIndex + row) * fStrides[0];
218 for (int i = 0; i < (width >> 1); ++i) {
219 int indexY = row * width + (i << 1);
220 int indexU = row * (width >> 1) + i;
221 yRows[indexY] = yuvSeg[0];
222 yRows[indexY + 1] = yuvSeg[2];
223 uRows[indexU] = yuvSeg[1];
224 vRows[indexU] = yuvSeg[3];
225 yuvSeg += 4;
226 }
227 }
228}
229
230void Yuv422IToJpegEncoder::configSamplingFactors(jpeg_compress_struct* cinfo) {
231 // cb and cr are horizontally downsampled and vertically downsampled as well.
232 cinfo->comp_info[0].h_samp_factor = 2;
233 cinfo->comp_info[0].v_samp_factor = 2;
234 cinfo->comp_info[1].h_samp_factor = 1;
235 cinfo->comp_info[1].v_samp_factor = 2;
236 cinfo->comp_info[2].h_samp_factor = 1;
237 cinfo->comp_info[2].v_samp_factor = 2;
238}
239///////////////////////////////////////////////////////////////////////////////
240
Dichen Zhang3b2c0ce2022-12-14 19:58:55 +0000241using namespace android::recoverymap;
242
243jpegr_color_gamut P010Yuv420ToJpegREncoder::findColorGamut(JNIEnv* env, int aDataSpace) {
244 switch (aDataSpace & ADataSpace::STANDARD_MASK) {
245 case ADataSpace::STANDARD_BT709:
246 return jpegr_color_gamut::JPEGR_COLORGAMUT_BT709;
247 case ADataSpace::STANDARD_DCI_P3:
248 return jpegr_color_gamut::JPEGR_COLORGAMUT_P3;
249 case ADataSpace::STANDARD_BT2020:
250 return jpegr_color_gamut::JPEGR_COLORGAMUT_BT2100;
251 default:
252 jclass IllegalArgumentException = env->FindClass("java/lang/IllegalArgumentException");
253 env->ThrowNew(IllegalArgumentException,
254 "The requested color gamut is not supported by JPEG/R.");
255 }
256
257 return jpegr_color_gamut::JPEGR_COLORGAMUT_UNSPECIFIED;
258}
259
260jpegr_transfer_function P010Yuv420ToJpegREncoder::findHdrTransferFunction(JNIEnv* env,
261 int aDataSpace) {
262 switch (aDataSpace & ADataSpace::TRANSFER_MASK) {
263 case ADataSpace::TRANSFER_ST2084:
264 return jpegr_transfer_function::JPEGR_TF_PQ;
265 case ADataSpace::TRANSFER_HLG:
266 return jpegr_transfer_function::JPEGR_TF_HLG;
267 default:
268 jclass IllegalArgumentException = env->FindClass("java/lang/IllegalArgumentException");
269 env->ThrowNew(IllegalArgumentException,
270 "The requested HDR transfer function is not supported by JPEG/R.");
271 }
272
273 return jpegr_transfer_function::JPEGR_TF_UNSPECIFIED;
274}
275
276bool P010Yuv420ToJpegREncoder::encode(JNIEnv* env,
277 SkWStream* stream, void* hdr, int hdrColorSpace, void* sdr, int sdrColorSpace,
278 int width, int height, int jpegQuality) {
279 // Check SDR color space. Now we only support SRGB transfer function
280 if ((sdrColorSpace & ADataSpace::TRANSFER_MASK) != ADataSpace::TRANSFER_SRGB) {
281 jclass IllegalArgumentException = env->FindClass("java/lang/IllegalArgumentException");
282 env->ThrowNew(IllegalArgumentException,
283 "The requested SDR color space is not supported. Transfer function must be SRGB");
284 return false;
285 }
286
287 jpegr_color_gamut hdrColorGamut = findColorGamut(env, hdrColorSpace);
288 jpegr_color_gamut sdrColorGamut = findColorGamut(env, sdrColorSpace);
289 jpegr_transfer_function hdrTransferFunction = findHdrTransferFunction(env, hdrColorSpace);
290
291 if (hdrColorGamut == jpegr_color_gamut::JPEGR_COLORGAMUT_UNSPECIFIED
292 || sdrColorGamut == jpegr_color_gamut::JPEGR_COLORGAMUT_UNSPECIFIED
293 || hdrTransferFunction == jpegr_transfer_function::JPEGR_TF_UNSPECIFIED) {
294 return false;
295 }
296
Dichen Zhangf84ed402023-02-10 22:41:46 +0000297 JpegR jpegREncoder;
Dichen Zhang3b2c0ce2022-12-14 19:58:55 +0000298
299 jpegr_uncompressed_struct p010;
300 p010.data = hdr;
301 p010.width = width;
302 p010.height = height;
303 p010.colorGamut = hdrColorGamut;
304
305 jpegr_uncompressed_struct yuv420;
306 yuv420.data = sdr;
307 yuv420.width = width;
308 yuv420.height = height;
309 yuv420.colorGamut = sdrColorGamut;
310
311 jpegr_compressed_struct jpegR;
312 jpegR.maxLength = width * height * sizeof(uint8_t);
313
314 std::unique_ptr<uint8_t[]> jpegr_data = std::make_unique<uint8_t[]>(jpegR.maxLength);
315 jpegR.data = jpegr_data.get();
316
Dichen Zhangf84ed402023-02-10 22:41:46 +0000317 if (int success = jpegREncoder.encodeJPEGR(&p010, &yuv420,
Dichen Zhang3b2c0ce2022-12-14 19:58:55 +0000318 hdrTransferFunction,
319 &jpegR, jpegQuality, nullptr); success != android::OK) {
320 ALOGW("Encode JPEG/R failed, error code: %d.", success);
321 return false;
322 }
323
324 if (!stream->write(jpegR.data, jpegR.length)) {
325 ALOGW("Writing JPEG/R to stream failed.");
326 return false;
327 }
328
329 return true;
330}
331
332///////////////////////////////////////////////////////////////////////////////
333
Wei-Ta Chenbca2d612009-11-30 17:52:05 +0800334static jboolean YuvImage_compressToJpeg(JNIEnv* env, jobject, jbyteArray inYuv,
Ashok Bhat39029b22014-01-10 16:24:38 +0000335 jint format, jint width, jint height, jintArray offsets,
336 jintArray strides, jint jpegQuality, jobject jstream,
Wei-Ta Chenbca2d612009-11-30 17:52:05 +0800337 jbyteArray jstorage) {
338 jbyte* yuv = env->GetByteArrayElements(inYuv, NULL);
339 SkWStream* strm = CreateJavaOutputStreamAdaptor(env, jstream, jstorage);
340
341 jint* imgOffsets = env->GetIntArrayElements(offsets, NULL);
342 jint* imgStrides = env->GetIntArrayElements(strides, NULL);
343 YuvToJpegEncoder* encoder = YuvToJpegEncoder::create(format, imgStrides);
Pawel Augustyn7c68a4072012-07-16 11:23:54 +0200344 jboolean result = JNI_FALSE;
345 if (encoder != NULL) {
346 encoder->encode(strm, yuv, width, height, imgOffsets, jpegQuality);
347 delete encoder;
348 result = JNI_TRUE;
Wei-Ta Chenbca2d612009-11-30 17:52:05 +0800349 }
Wei-Ta Chenbca2d612009-11-30 17:52:05 +0800350
Wei-Ta Chenbca2d612009-11-30 17:52:05 +0800351 env->ReleaseByteArrayElements(inYuv, yuv, 0);
352 env->ReleaseIntArrayElements(offsets, imgOffsets, 0);
353 env->ReleaseIntArrayElements(strides, imgStrides, 0);
Martin Wallgrend8659002014-08-20 14:58:58 +0200354 delete strm;
Pawel Augustyn7c68a4072012-07-16 11:23:54 +0200355 return result;
Wei-Ta Chenbca2d612009-11-30 17:52:05 +0800356}
Dichen Zhang3b2c0ce2022-12-14 19:58:55 +0000357
358static jboolean YuvImage_compressToJpegR(JNIEnv* env, jobject, jbyteArray inHdr,
359 jint hdrColorSpace, jbyteArray inSdr, jint sdrColorSpace,
360 jint width, jint height, jint quality, jobject jstream,
361 jbyteArray jstorage) {
362 jbyte* hdr = env->GetByteArrayElements(inHdr, NULL);
363 jbyte* sdr = env->GetByteArrayElements(inSdr, NULL);
364 SkWStream* strm = CreateJavaOutputStreamAdaptor(env, jstream, jstorage);
365 P010Yuv420ToJpegREncoder encoder;
366
367 jboolean result = JNI_FALSE;
368 if (encoder.encode(env, strm, hdr, hdrColorSpace, sdr, sdrColorSpace,
369 width, height, quality)) {
370 result = JNI_TRUE;
371 }
372
373 env->ReleaseByteArrayElements(inHdr, hdr, 0);
374 env->ReleaseByteArrayElements(inSdr, sdr, 0);
375 delete strm;
376 return result;
377}
Wei-Ta Chenbca2d612009-11-30 17:52:05 +0800378///////////////////////////////////////////////////////////////////////////////
379
Daniel Micay76f6a862015-09-19 17:31:01 -0400380static const JNINativeMethod gYuvImageMethods[] = {
Wei-Ta Chenbca2d612009-11-30 17:52:05 +0800381 { "nativeCompressToJpeg", "([BIII[I[IILjava/io/OutputStream;[B)Z",
Dichen Zhang3b2c0ce2022-12-14 19:58:55 +0000382 (void*)YuvImage_compressToJpeg },
383 { "nativeCompressToJpegR", "([BI[BIIIILjava/io/OutputStream;[B)Z",
384 (void*)YuvImage_compressToJpegR }
Wei-Ta Chenbca2d612009-11-30 17:52:05 +0800385};
386
Wei-Ta Chenbca2d612009-11-30 17:52:05 +0800387int register_android_graphics_YuvImage(JNIEnv* env)
388{
Andreas Gampeed6b9df2014-11-20 22:02:20 -0800389 return android::RegisterMethodsOrDie(env, "android/graphics/YuvImage", gYuvImageMethods,
390 NELEM(gYuvImageMethods));
Wei-Ta Chenbca2d612009-11-30 17:52:05 +0800391}