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