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