• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 #undef LOG_TAG
2 #define LOG_TAG "YuvToJpegEncoder"
3 
4 #include "CreateJavaOutputStreamAdaptor.h"
5 #include "SkStream.h"
6 #include "YuvToJpegEncoder.h"
7 #include <ui/PixelFormat.h>
8 #include <hardware/hardware.h>
9 
10 #include "graphics_jni_helpers.h"
11 
12 #include <csetjmp>
13 
14 extern "C" {
15     // We need to include stdio.h before jpeg because jpeg does not include it, but uses FILE
16     // See https://github.com/libjpeg-turbo/libjpeg-turbo/issues/17
17     #include <stdio.h>
18     #include "jpeglib.h"
19     #include "jerror.h"
20     #include "jmorecfg.h"
21 }
22 
create(int format,int * strides)23 YuvToJpegEncoder* YuvToJpegEncoder::create(int format, int* strides) {
24     // Only ImageFormat.NV21 and ImageFormat.YUY2 are supported
25     // for now.
26     if (format == HAL_PIXEL_FORMAT_YCrCb_420_SP) {
27         return new Yuv420SpToJpegEncoder(strides);
28     } else if (format == HAL_PIXEL_FORMAT_YCbCr_422_I) {
29         return new Yuv422IToJpegEncoder(strides);
30     } else {
31       return NULL;
32     }
33 }
34 
YuvToJpegEncoder(int * strides)35 YuvToJpegEncoder::YuvToJpegEncoder(int* strides) : fStrides(strides) {
36 }
37 
38 struct ErrorMgr {
39     struct jpeg_error_mgr pub;
40     jmp_buf jmp;
41 };
42 
error_exit(j_common_ptr cinfo)43 void error_exit(j_common_ptr cinfo) {
44     ErrorMgr* err = (ErrorMgr*) cinfo->err;
45     (*cinfo->err->output_message) (cinfo);
46     longjmp(err->jmp, 1);
47 }
48 
49 /*
50  * Destination struct for directing decompressed pixels to a SkStream.
51  */
52 static constexpr size_t kMgrBufferSize = 1024;
53 struct skstream_destination_mgr : jpeg_destination_mgr {
54     skstream_destination_mgr(SkWStream* stream);
55 
56     SkWStream* const fStream;
57 
58     uint8_t fBuffer[kMgrBufferSize];
59 };
60 
sk_init_destination(j_compress_ptr cinfo)61 static void sk_init_destination(j_compress_ptr cinfo) {
62     skstream_destination_mgr* dest = (skstream_destination_mgr*)cinfo->dest;
63 
64     dest->next_output_byte = dest->fBuffer;
65     dest->free_in_buffer = kMgrBufferSize;
66 }
67 
sk_empty_output_buffer(j_compress_ptr cinfo)68 static boolean sk_empty_output_buffer(j_compress_ptr cinfo) {
69     skstream_destination_mgr* dest = (skstream_destination_mgr*)cinfo->dest;
70 
71     if (!dest->fStream->write(dest->fBuffer, kMgrBufferSize)) {
72         ERREXIT(cinfo, JERR_FILE_WRITE);
73         return FALSE;
74     }
75 
76     dest->next_output_byte = dest->fBuffer;
77     dest->free_in_buffer = kMgrBufferSize;
78     return TRUE;
79 }
80 
sk_term_destination(j_compress_ptr cinfo)81 static void sk_term_destination(j_compress_ptr cinfo) {
82     skstream_destination_mgr* dest = (skstream_destination_mgr*)cinfo->dest;
83 
84     size_t size = kMgrBufferSize - dest->free_in_buffer;
85     if (size > 0) {
86         if (!dest->fStream->write(dest->fBuffer, size)) {
87             ERREXIT(cinfo, JERR_FILE_WRITE);
88             return;
89         }
90     }
91 
92     dest->fStream->flush();
93 }
94 
skstream_destination_mgr(SkWStream * stream)95 skstream_destination_mgr::skstream_destination_mgr(SkWStream* stream)
96         : fStream(stream) {
97     this->init_destination = sk_init_destination;
98     this->empty_output_buffer = sk_empty_output_buffer;
99     this->term_destination = sk_term_destination;
100 }
101 
encode(SkWStream * stream,void * inYuv,int width,int height,int * offsets,int jpegQuality)102 bool YuvToJpegEncoder::encode(SkWStream* stream, void* inYuv, int width,
103         int height, int* offsets, int jpegQuality) {
104     jpeg_compress_struct      cinfo;
105     ErrorMgr                  err;
106     skstream_destination_mgr  sk_wstream(stream);
107 
108     cinfo.err = jpeg_std_error(&err.pub);
109     err.pub.error_exit = error_exit;
110 
111     if (setjmp(err.jmp)) {
112         jpeg_destroy_compress(&cinfo);
113         return false;
114     }
115     jpeg_create_compress(&cinfo);
116 
117     cinfo.dest = &sk_wstream;
118 
119     setJpegCompressStruct(&cinfo, width, height, jpegQuality);
120 
121     jpeg_start_compress(&cinfo, TRUE);
122 
123     compress(&cinfo, (uint8_t*) inYuv, offsets);
124 
125     jpeg_finish_compress(&cinfo);
126 
127     jpeg_destroy_compress(&cinfo);
128 
129     return true;
130 }
131 
setJpegCompressStruct(jpeg_compress_struct * cinfo,int width,int height,int quality)132 void YuvToJpegEncoder::setJpegCompressStruct(jpeg_compress_struct* cinfo,
133         int width, int height, int quality) {
134     cinfo->image_width = width;
135     cinfo->image_height = height;
136     cinfo->input_components = 3;
137     cinfo->in_color_space = JCS_YCbCr;
138     jpeg_set_defaults(cinfo);
139 
140     jpeg_set_quality(cinfo, quality, TRUE);
141     jpeg_set_colorspace(cinfo, JCS_YCbCr);
142     cinfo->raw_data_in = TRUE;
143     cinfo->dct_method = JDCT_IFAST;
144     configSamplingFactors(cinfo);
145 }
146 
147 ///////////////////////////////////////////////////////////////////
Yuv420SpToJpegEncoder(int * strides)148 Yuv420SpToJpegEncoder::Yuv420SpToJpegEncoder(int* strides) :
149         YuvToJpegEncoder(strides) {
150     fNumPlanes = 2;
151 }
152 
compress(jpeg_compress_struct * cinfo,uint8_t * yuv,int * offsets)153 void Yuv420SpToJpegEncoder::compress(jpeg_compress_struct* cinfo,
154         uint8_t* yuv, int* offsets) {
155     ALOGD("onFlyCompress");
156     JSAMPROW y[16];
157     JSAMPROW cb[8];
158     JSAMPROW cr[8];
159     JSAMPARRAY planes[3];
160     planes[0] = y;
161     planes[1] = cb;
162     planes[2] = cr;
163 
164     int width = cinfo->image_width;
165     int height = cinfo->image_height;
166     uint8_t* yPlanar = yuv + offsets[0];
167     uint8_t* vuPlanar = yuv + offsets[1]; //width * height;
168     uint8_t* uRows = new uint8_t [8 * (width >> 1)];
169     uint8_t* vRows = new uint8_t [8 * (width >> 1)];
170 
171 
172     // process 16 lines of Y and 8 lines of U/V each time.
173     while (cinfo->next_scanline < cinfo->image_height) {
174         //deitnerleave u and v
175         deinterleave(vuPlanar, uRows, vRows, cinfo->next_scanline, width, height);
176 
177         // Jpeg library ignores the rows whose indices are greater than height.
178         for (int i = 0; i < 16; i++) {
179             // y row
180             y[i] = yPlanar + (cinfo->next_scanline + i) * fStrides[0];
181 
182             // construct u row and v row
183             if ((i & 1) == 0) {
184                 // height and width are both halved because of downsampling
185                 int offset = (i >> 1) * (width >> 1);
186                 cb[i/2] = uRows + offset;
187                 cr[i/2] = vRows + offset;
188             }
189           }
190         jpeg_write_raw_data(cinfo, planes, 16);
191     }
192     delete [] uRows;
193     delete [] vRows;
194 
195 }
196 
deinterleave(uint8_t * vuPlanar,uint8_t * uRows,uint8_t * vRows,int rowIndex,int width,int height)197 void Yuv420SpToJpegEncoder::deinterleave(uint8_t* vuPlanar, uint8_t* uRows,
198         uint8_t* vRows, int rowIndex, int width, int height) {
199     int numRows = (height - rowIndex) / 2;
200     if (numRows > 8) numRows = 8;
201     for (int row = 0; row < numRows; ++row) {
202         int offset = ((rowIndex >> 1) + row) * fStrides[1];
203         uint8_t* vu = vuPlanar + offset;
204         for (int i = 0; i < (width >> 1); ++i) {
205             int index = row * (width >> 1) + i;
206             uRows[index] = vu[1];
207             vRows[index] = vu[0];
208             vu += 2;
209         }
210     }
211 }
212 
configSamplingFactors(jpeg_compress_struct * cinfo)213 void Yuv420SpToJpegEncoder::configSamplingFactors(jpeg_compress_struct* cinfo) {
214     // cb and cr are horizontally downsampled and vertically downsampled as well.
215     cinfo->comp_info[0].h_samp_factor = 2;
216     cinfo->comp_info[0].v_samp_factor = 2;
217     cinfo->comp_info[1].h_samp_factor = 1;
218     cinfo->comp_info[1].v_samp_factor = 1;
219     cinfo->comp_info[2].h_samp_factor = 1;
220     cinfo->comp_info[2].v_samp_factor = 1;
221 }
222 
223 ///////////////////////////////////////////////////////////////////////////////
Yuv422IToJpegEncoder(int * strides)224 Yuv422IToJpegEncoder::Yuv422IToJpegEncoder(int* strides) :
225         YuvToJpegEncoder(strides) {
226     fNumPlanes = 1;
227 }
228 
compress(jpeg_compress_struct * cinfo,uint8_t * yuv,int * offsets)229 void Yuv422IToJpegEncoder::compress(jpeg_compress_struct* cinfo,
230         uint8_t* yuv, int* offsets) {
231     ALOGD("onFlyCompress_422");
232     JSAMPROW y[16];
233     JSAMPROW cb[16];
234     JSAMPROW cr[16];
235     JSAMPARRAY planes[3];
236     planes[0] = y;
237     planes[1] = cb;
238     planes[2] = cr;
239 
240     int width = cinfo->image_width;
241     int height = cinfo->image_height;
242     uint8_t* yRows = new uint8_t [16 * width];
243     uint8_t* uRows = new uint8_t [16 * (width >> 1)];
244     uint8_t* vRows = new uint8_t [16 * (width >> 1)];
245 
246     uint8_t* yuvOffset = yuv + offsets[0];
247 
248     // process 16 lines of Y and 16 lines of U/V each time.
249     while (cinfo->next_scanline < cinfo->image_height) {
250         deinterleave(yuvOffset, yRows, uRows, vRows, cinfo->next_scanline, width, height);
251 
252         // Jpeg library ignores the rows whose indices are greater than height.
253         for (int i = 0; i < 16; i++) {
254             // y row
255             y[i] = yRows + i * width;
256 
257             // construct u row and v row
258             // width is halved because of downsampling
259             int offset = i * (width >> 1);
260             cb[i] = uRows + offset;
261             cr[i] = vRows + offset;
262         }
263 
264         jpeg_write_raw_data(cinfo, planes, 16);
265     }
266     delete [] yRows;
267     delete [] uRows;
268     delete [] vRows;
269 }
270 
271 
deinterleave(uint8_t * yuv,uint8_t * yRows,uint8_t * uRows,uint8_t * vRows,int rowIndex,int width,int height)272 void Yuv422IToJpegEncoder::deinterleave(uint8_t* yuv, uint8_t* yRows, uint8_t* uRows,
273         uint8_t* vRows, int rowIndex, int width, int height) {
274     int numRows = height - rowIndex;
275     if (numRows > 16) numRows = 16;
276     for (int row = 0; row < numRows; ++row) {
277         uint8_t* yuvSeg = yuv + (rowIndex + row) * fStrides[0];
278         for (int i = 0; i < (width >> 1); ++i) {
279             int indexY = row * width + (i << 1);
280             int indexU = row * (width >> 1) + i;
281             yRows[indexY] = yuvSeg[0];
282             yRows[indexY + 1] = yuvSeg[2];
283             uRows[indexU] = yuvSeg[1];
284             vRows[indexU] = yuvSeg[3];
285             yuvSeg += 4;
286         }
287     }
288 }
289 
configSamplingFactors(jpeg_compress_struct * cinfo)290 void Yuv422IToJpegEncoder::configSamplingFactors(jpeg_compress_struct* cinfo) {
291     // cb and cr are horizontally downsampled and vertically downsampled as well.
292     cinfo->comp_info[0].h_samp_factor = 2;
293     cinfo->comp_info[0].v_samp_factor = 2;
294     cinfo->comp_info[1].h_samp_factor = 1;
295     cinfo->comp_info[1].v_samp_factor = 2;
296     cinfo->comp_info[2].h_samp_factor = 1;
297     cinfo->comp_info[2].v_samp_factor = 2;
298 }
299 ///////////////////////////////////////////////////////////////////////////////
300 
301 using namespace android::ultrahdr;
302 
findColorGamut(JNIEnv * env,int aDataSpace)303 ultrahdr_color_gamut P010Yuv420ToJpegREncoder::findColorGamut(JNIEnv* env, int aDataSpace) {
304     switch (aDataSpace & ADataSpace::STANDARD_MASK) {
305         case ADataSpace::STANDARD_BT709:
306             return ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT709;
307         case ADataSpace::STANDARD_DCI_P3:
308             return ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_P3;
309         case ADataSpace::STANDARD_BT2020:
310             return ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100;
311         default:
312             jclass IllegalArgumentException = env->FindClass("java/lang/IllegalArgumentException");
313             env->ThrowNew(IllegalArgumentException,
314                     "The requested color gamut is not supported by JPEG/R.");
315     }
316 
317     return ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_UNSPECIFIED;
318 }
319 
findHdrTransferFunction(JNIEnv * env,int aDataSpace)320 ultrahdr_transfer_function P010Yuv420ToJpegREncoder::findHdrTransferFunction(JNIEnv* env,
321         int aDataSpace) {
322     switch (aDataSpace & ADataSpace::TRANSFER_MASK) {
323         case ADataSpace::TRANSFER_ST2084:
324             return ultrahdr_transfer_function::ULTRAHDR_TF_PQ;
325         case ADataSpace::TRANSFER_HLG:
326             return ultrahdr_transfer_function::ULTRAHDR_TF_HLG;
327         default:
328             jclass IllegalArgumentException = env->FindClass("java/lang/IllegalArgumentException");
329             env->ThrowNew(IllegalArgumentException,
330                     "The requested HDR transfer function is not supported by JPEG/R.");
331     }
332 
333     return ultrahdr_transfer_function::ULTRAHDR_TF_UNSPECIFIED;
334 }
335 
encode(JNIEnv * env,SkWStream * stream,void * hdr,int hdrColorSpace,void * sdr,int sdrColorSpace,int width,int height,int jpegQuality)336 bool P010Yuv420ToJpegREncoder::encode(JNIEnv* env,
337         SkWStream* stream, void* hdr, int hdrColorSpace, void* sdr, int sdrColorSpace,
338         int width, int height, int jpegQuality) {
339     // Check SDR color space. Now we only support SRGB transfer function
340     if ((sdrColorSpace & ADataSpace::TRANSFER_MASK) !=  ADataSpace::TRANSFER_SRGB) {
341         jclass IllegalArgumentException = env->FindClass("java/lang/IllegalArgumentException");
342         env->ThrowNew(IllegalArgumentException,
343             "The requested SDR color space is not supported. Transfer function must be SRGB");
344         return false;
345     }
346 
347     ultrahdr_color_gamut hdrColorGamut = findColorGamut(env, hdrColorSpace);
348     ultrahdr_color_gamut sdrColorGamut = findColorGamut(env, sdrColorSpace);
349     ultrahdr_transfer_function hdrTransferFunction = findHdrTransferFunction(env, hdrColorSpace);
350 
351     if (hdrColorGamut == ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_UNSPECIFIED
352             || sdrColorGamut == ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_UNSPECIFIED
353             || hdrTransferFunction == ultrahdr_transfer_function::ULTRAHDR_TF_UNSPECIFIED) {
354         return false;
355     }
356 
357     JpegR jpegREncoder;
358 
359     jpegr_uncompressed_struct p010;
360     p010.data = hdr;
361     p010.width = width;
362     p010.height = height;
363     p010.colorGamut = hdrColorGamut;
364 
365     jpegr_uncompressed_struct yuv420;
366     yuv420.data = sdr;
367     yuv420.width = width;
368     yuv420.height = height;
369     yuv420.colorGamut = sdrColorGamut;
370 
371     jpegr_compressed_struct jpegR;
372     jpegR.maxLength = width * height * sizeof(uint8_t);
373 
374     std::unique_ptr<uint8_t[]> jpegr_data = std::make_unique<uint8_t[]>(jpegR.maxLength);
375     jpegR.data = jpegr_data.get();
376 
377     if (int success = jpegREncoder.encodeJPEGR(&p010, &yuv420,
378             hdrTransferFunction,
379             &jpegR, jpegQuality, nullptr); success != android::OK) {
380         ALOGW("Encode JPEG/R failed, error code: %d.", success);
381         return false;
382     }
383 
384     if (!stream->write(jpegR.data, jpegR.length)) {
385         ALOGW("Writing JPEG/R to stream failed.");
386         return false;
387     }
388 
389     return true;
390 }
391 
392 ///////////////////////////////////////////////////////////////////////////////
393 
YuvImage_compressToJpeg(JNIEnv * env,jobject,jbyteArray inYuv,jint format,jint width,jint height,jintArray offsets,jintArray strides,jint jpegQuality,jobject jstream,jbyteArray jstorage)394 static jboolean YuvImage_compressToJpeg(JNIEnv* env, jobject, jbyteArray inYuv,
395         jint format, jint width, jint height, jintArray offsets,
396         jintArray strides, jint jpegQuality, jobject jstream,
397         jbyteArray jstorage) {
398     jbyte* yuv = env->GetByteArrayElements(inYuv, NULL);
399     SkWStream* strm = CreateJavaOutputStreamAdaptor(env, jstream, jstorage);
400 
401     jint* imgOffsets = env->GetIntArrayElements(offsets, NULL);
402     jint* imgStrides = env->GetIntArrayElements(strides, NULL);
403     YuvToJpegEncoder* encoder = YuvToJpegEncoder::create(format, imgStrides);
404     jboolean result = JNI_FALSE;
405     if (encoder != NULL) {
406         encoder->encode(strm, yuv, width, height, imgOffsets, jpegQuality);
407         delete encoder;
408         result = JNI_TRUE;
409     }
410 
411     env->ReleaseByteArrayElements(inYuv, yuv, 0);
412     env->ReleaseIntArrayElements(offsets, imgOffsets, 0);
413     env->ReleaseIntArrayElements(strides, imgStrides, 0);
414     delete strm;
415     return result;
416 }
417 
YuvImage_compressToJpegR(JNIEnv * env,jobject,jbyteArray inHdr,jint hdrColorSpace,jbyteArray inSdr,jint sdrColorSpace,jint width,jint height,jint quality,jobject jstream,jbyteArray jstorage)418 static jboolean YuvImage_compressToJpegR(JNIEnv* env, jobject, jbyteArray inHdr,
419         jint hdrColorSpace, jbyteArray inSdr, jint sdrColorSpace,
420         jint width, jint height, jint quality, jobject jstream,
421         jbyteArray jstorage) {
422     jbyte* hdr = env->GetByteArrayElements(inHdr, NULL);
423     jbyte* sdr = env->GetByteArrayElements(inSdr, NULL);
424     SkWStream* strm = CreateJavaOutputStreamAdaptor(env, jstream, jstorage);
425     P010Yuv420ToJpegREncoder encoder;
426 
427     jboolean result = JNI_FALSE;
428     if (encoder.encode(env, strm, hdr, hdrColorSpace, sdr, sdrColorSpace,
429                        width, height, quality)) {
430         result = JNI_TRUE;
431     }
432 
433     env->ReleaseByteArrayElements(inHdr, hdr, 0);
434     env->ReleaseByteArrayElements(inSdr, sdr, 0);
435     delete strm;
436     return result;
437 }
438 ///////////////////////////////////////////////////////////////////////////////
439 
440 static const JNINativeMethod gYuvImageMethods[] = {
441     {   "nativeCompressToJpeg",  "([BIII[I[IILjava/io/OutputStream;[B)Z",
442         (void*)YuvImage_compressToJpeg },
443     {   "nativeCompressToJpegR",  "([BI[BIIIILjava/io/OutputStream;[B)Z",
444         (void*)YuvImage_compressToJpegR }
445 };
446 
register_android_graphics_YuvImage(JNIEnv * env)447 int register_android_graphics_YuvImage(JNIEnv* env)
448 {
449     return android::RegisterMethodsOrDie(env, "android/graphics/YuvImage", gYuvImageMethods,
450                                          NELEM(gYuvImageMethods));
451 }
452