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