From 8e6c3b93a39e19111662a760ede97df55e51d39f Mon Sep 17 00:00:00 2001 From: sugoi Date: Wed, 15 Oct 2014 11:04:17 -0700 Subject: JPEG YUV Decoding Enabling JPEG YUV Decoding in Skia BUG=skia:3005, skia:1674 Review URL: https://codereview.chromium.org/399683007 --- include/core/SkImageDecoder.h | 20 +++ src/images/SkDecodingImageGenerator.cpp | 16 ++ src/images/SkImageDecoder.cpp | 5 + src/images/SkImageDecoder_libjpeg.cpp | 252 +++++++++++++++++++++++++++++++- tests/JpegTest.cpp | 49 ++++++- 5 files changed, 338 insertions(+), 4 deletions(-) diff --git a/include/core/SkImageDecoder.h b/include/core/SkImageDecoder.h index 5910d33a99..94831762de 100644 --- a/include/core/SkImageDecoder.h +++ b/include/core/SkImageDecoder.h @@ -47,6 +47,15 @@ public: */ virtual Format getFormat() const; + /** If planes or rowBytes is NULL, decodes the header and computes componentSizes + for memory allocation. + Otherwise, decodes the YUV planes into the provided image planes and + updates componentSizes to the final image size. + Returns whether the decoding was successful. + */ + bool decodeYUV8Planes(SkStream* stream, SkISize componentSizes[3], void* planes[3], + size_t rowBytes[3], SkYUVColorSpace*); + /** Return the format of the SkStreamRewindable or kUnknown_Format if it cannot be determined. Rewinds the stream before returning. */ @@ -339,6 +348,17 @@ protected: return false; } + /** If planes or rowBytes is NULL, decodes the header and computes componentSizes + for memory allocation. + Otherwise, decodes the YUV planes into the provided image planes and + updates componentSizes to the final image size. + Returns whether the decoding was successful. + */ + virtual bool onDecodeYUV8Planes(SkStream* stream, SkISize componentSizes[3], void* planes[3], + size_t rowBytes[3], SkYUVColorSpace*) { + return false; + } + /* * Crop a rectangle from the src Bitmap to the dest Bitmap. src and dst are * both sampled by sampleSize from an original Bitmap. diff --git a/src/images/SkDecodingImageGenerator.cpp b/src/images/SkDecodingImageGenerator.cpp index 3b5cb784ed..a90c1cf01f 100644 --- a/src/images/SkDecodingImageGenerator.cpp +++ b/src/images/SkDecodingImageGenerator.cpp @@ -45,6 +45,8 @@ protected: virtual bool onGetPixels(const SkImageInfo& info, void* pixels, size_t rowBytes, SkPMColor ctable[], int* ctableCount) SK_OVERRIDE; + virtual bool onGetYUV8Planes(SkISize sizes[3], void* planes[3], size_t rowBytes[3], + SkYUVColorSpace* colorSpace) SK_OVERRIDE; private: typedef SkImageGenerator INHERITED; @@ -204,6 +206,20 @@ bool DecodingImageGenerator::onGetPixels(const SkImageInfo& info, return true; } +bool DecodingImageGenerator::onGetYUV8Planes(SkISize sizes[3], void* planes[3], + size_t rowBytes[3], SkYUVColorSpace* colorSpace) { + if (!fStream->rewind()) { + return false; + } + + SkAutoTDelete decoder(SkImageDecoder::Factory(fStream)); + if (NULL == decoder.get()) { + return false; + } + + return decoder->decodeYUV8Planes(fStream, sizes, planes, rowBytes, colorSpace); +} + // A contructor-type function that returns NULL on failure. This // prevents the returned SkImageGenerator from ever being in a bad // state. Called by both Create() functions diff --git a/src/images/SkImageDecoder.cpp b/src/images/SkImageDecoder.cpp index b25e7dbbab..39dabf4477 100644 --- a/src/images/SkImageDecoder.cpp +++ b/src/images/SkImageDecoder.cpp @@ -285,3 +285,8 @@ bool SkImageDecoder::DecodeStream(SkStreamRewindable* stream, SkBitmap* bm, SkCo } return success; } + +bool SkImageDecoder::decodeYUV8Planes(SkStream* stream, SkISize componentSizes[3], void* planes[3], + size_t rowBytes[3], SkYUVColorSpace* colorSpace) { + return this->onDecodeYUV8Planes(stream, componentSizes, planes, rowBytes, colorSpace); +} diff --git a/src/images/SkImageDecoder_libjpeg.cpp b/src/images/SkImageDecoder_libjpeg.cpp index 99401e6b62..937fe903dc 100644 --- a/src/images/SkImageDecoder_libjpeg.cpp +++ b/src/images/SkImageDecoder_libjpeg.cpp @@ -239,6 +239,9 @@ protected: virtual bool onDecodeSubset(SkBitmap* bitmap, const SkIRect& rect) SK_OVERRIDE; #endif virtual bool onDecode(SkStream* stream, SkBitmap* bm, Mode) SK_OVERRIDE; + virtual bool onDecodeYUV8Planes(SkStream* stream, SkISize componentSizes[3], + void* planes[3], size_t rowBytes[3], + SkYUVColorSpace* colorSpace) SK_OVERRIDE; private: #ifdef SK_BUILD_FOR_ANDROID @@ -325,16 +328,21 @@ static bool skip_src_rows_tile(jpeg_decompress_struct* cinfo, // This guy exists just to aid in debugging, as it allows debuggers to just // set a break-point in one place to see all error exists. static bool return_false(const jpeg_decompress_struct& cinfo, - const SkBitmap& bm, const char caller[]) { + int width, int height, const char caller[]) { if (!(c_suppressJPEGImageDecoderErrors)) { char buffer[JMSG_LENGTH_MAX]; cinfo.err->format_message((const j_common_ptr)&cinfo, buffer); SkDebugf("libjpeg error %d <%s> from %s [%d %d]\n", - cinfo.err->msg_code, buffer, caller, bm.width(), bm.height()); + cinfo.err->msg_code, buffer, caller, width, height); } return false; // must always return false } +static bool return_false(const jpeg_decompress_struct& cinfo, + const SkBitmap& bm, const char caller[]) { + return return_false(cinfo, bm.width(), bm.height(), caller); +} + // Convert a scanline of CMYK samples to RGBX in place. Note that this // method moves the "scanline" pointer in its processing static void convert_CMYK_to_RGB(uint8_t* scanline, unsigned int width) { @@ -726,6 +734,246 @@ bool SkJPEGImageDecoder::onDecode(SkStream* stream, SkBitmap* bm, Mode mode) { return true; } +enum SizeType { + kSizeForMemoryAllocation_SizeType, + kActualSize_SizeType +}; + +static SkISize compute_yuv_size(const jpeg_decompress_struct& info, int component, + SizeType sizeType) { + if (sizeType == kSizeForMemoryAllocation_SizeType) { + return SkISize::Make(info.cur_comp_info[component]->width_in_blocks * DCTSIZE, + info.cur_comp_info[component]->height_in_blocks * DCTSIZE); + } + return SkISize::Make(info.cur_comp_info[component]->downsampled_width, + info.cur_comp_info[component]->downsampled_height); +} + +// Enum for YUV decoding +enum YUVSubsampling { + kUNKNOWN_YUVSubsampling, + k410_YUVSubsampling, + k411_YUVSubsampling, + k420_YUVSubsampling, + k422_YUVSubsampling, + k440_YUVSubsampling, + k444_YUVSubsampling +}; + +static YUVSubsampling yuv_subsampling(const jpeg_decompress_struct& info) { + if ((DCTSIZE == 8) + && (info.num_components == 3) + && (info.comps_in_scan >= info.num_components) + && (info.scale_denom <= 8) + && (info.cur_comp_info[0]) + && (info.cur_comp_info[1]) + && (info.cur_comp_info[2]) + && (info.cur_comp_info[1]->h_samp_factor == 1) + && (info.cur_comp_info[1]->v_samp_factor == 1) + && (info.cur_comp_info[2]->h_samp_factor == 1) + && (info.cur_comp_info[2]->v_samp_factor == 1)) + { + int h = info.cur_comp_info[0]->h_samp_factor; + int v = info.cur_comp_info[0]->v_samp_factor; + // 4:4:4 : (h == 1) && (v == 1) + // 4:4:0 : (h == 1) && (v == 2) + // 4:2:2 : (h == 2) && (v == 1) + // 4:2:0 : (h == 2) && (v == 2) + // 4:1:1 : (h == 4) && (v == 1) + // 4:1:0 : (h == 4) && (v == 2) + if (v == 1) { + switch (h) { + case 1: + return k444_YUVSubsampling; + case 2: + return k422_YUVSubsampling; + case 4: + return k411_YUVSubsampling; + default: + break; + } + } else if (v == 2) { + switch (h) { + case 1: + return k440_YUVSubsampling; + case 2: + return k420_YUVSubsampling; + case 4: + return k410_YUVSubsampling; + default: + break; + } + } + } + + return kUNKNOWN_YUVSubsampling; +} + +static void update_components_sizes(const jpeg_decompress_struct& cinfo, SkISize componentSizes[3], + SizeType sizeType) { + for (int i = 0; i < 3; ++i) { + componentSizes[i] = compute_yuv_size(cinfo, i, sizeType); + } +} + +static bool output_raw_data(jpeg_decompress_struct& cinfo, void* planes[3], size_t rowBytes[3]) { + // U size and V size have to be the same if we're calling output_raw_data() + SkISize uvSize = compute_yuv_size(cinfo, 1, kSizeForMemoryAllocation_SizeType); + SkASSERT(uvSize == compute_yuv_size(cinfo, 2, kSizeForMemoryAllocation_SizeType)); + + JSAMPARRAY bufferraw[3]; + JSAMPROW bufferraw2[32]; + bufferraw[0] = &bufferraw2[0]; // Y channel rows (8 or 16) + bufferraw[1] = &bufferraw2[16]; // U channel rows (8) + bufferraw[2] = &bufferraw2[24]; // V channel rows (8) + int yWidth = cinfo.output_width; + int yHeight = cinfo.output_height; + int yMaxH = yHeight - 1; + int v = cinfo.cur_comp_info[0]->v_samp_factor; + int uvMaxH = uvSize.height() - 1; + JSAMPROW outputY = static_cast(planes[0]); + JSAMPROW outputU = static_cast(planes[1]); + JSAMPROW outputV = static_cast(planes[2]); + size_t rowBytesY = rowBytes[0]; + size_t rowBytesU = rowBytes[1]; + size_t rowBytesV = rowBytes[2]; + + int yScanlinesToRead = DCTSIZE * v; + SkAutoMalloc lastRowStorage(yWidth * 8); + JSAMPROW yLastRow = (JSAMPROW)lastRowStorage.get(); + JSAMPROW uLastRow = yLastRow + 2 * yWidth; + JSAMPROW vLastRow = uLastRow + 2 * yWidth; + JSAMPROW dummyRow = vLastRow + 2 * yWidth; + + while (cinfo.output_scanline < cinfo.output_height) { + // Request 8 or 16 scanlines: returns 0 or more scanlines. + bool hasYLastRow(false), hasUVLastRow(false); + // Assign 8 or 16 rows of memory to read the Y channel. + for (int i = 0; i < yScanlinesToRead; ++i) { + int scanline = (cinfo.output_scanline + i); + if (scanline < yMaxH) { + bufferraw2[i] = &outputY[scanline * rowBytesY]; + } else if (scanline == yMaxH) { + bufferraw2[i] = yLastRow; + hasYLastRow = true; + } else { + bufferraw2[i] = dummyRow; + } + } + int scaledScanline = cinfo.output_scanline / v; + // Assign 8 rows of memory to read the U and V channels. + for (int i = 0; i < 8; ++i) { + int scanline = (scaledScanline + i); + if (scanline < uvMaxH) { + bufferraw2[16 + i] = &outputU[scanline * rowBytesU]; + bufferraw2[24 + i] = &outputV[scanline * rowBytesV]; + } else if (scanline == uvMaxH) { + bufferraw2[16 + i] = uLastRow; + bufferraw2[24 + i] = vLastRow; + hasUVLastRow = true; + } else { + bufferraw2[16 + i] = dummyRow; + bufferraw2[24 + i] = dummyRow; + } + } + JDIMENSION scanlinesRead = jpeg_read_raw_data(&cinfo, bufferraw, yScanlinesToRead); + + if (scanlinesRead == 0) + return false; + + if (hasYLastRow) { + memcpy(&outputY[yMaxH * rowBytesY], yLastRow, yWidth); + } + if (hasUVLastRow) { + memcpy(&outputU[uvMaxH * rowBytesU], uLastRow, uvSize.width()); + memcpy(&outputV[uvMaxH * rowBytesV], vLastRow, uvSize.width()); + } + } + + cinfo.output_scanline = SkMin32(cinfo.output_scanline, cinfo.output_height); + + return true; +} + +bool SkJPEGImageDecoder::onDecodeYUV8Planes(SkStream* stream, SkISize componentSizes[3], + void* planes[3], size_t rowBytes[3], + SkYUVColorSpace* colorSpace) { +#ifdef TIME_DECODE + SkAutoTime atm("JPEG YUV8 Decode"); +#endif + + if (this->getSampleSize() != 1) { + return false; // Resizing not supported + } + + JPEGAutoClean autoClean; + + jpeg_decompress_struct cinfo; + skjpeg_source_mgr srcManager(stream, this); + + skjpeg_error_mgr errorManager; + set_error_mgr(&cinfo, &errorManager); + + // All objects need to be instantiated before this setjmp call so that + // they will be cleaned up properly if an error occurs. + if (setjmp(errorManager.fJmpBuf)) { + return return_false(cinfo, 0, 0, "setjmp"); + } + + initialize_info(&cinfo, &srcManager); + autoClean.set(&cinfo); + + int status = jpeg_read_header(&cinfo, true); + if (status != JPEG_HEADER_OK) { + return return_false(cinfo, 0, 0, "read_header"); + } + + if (cinfo.jpeg_color_space != JCS_YCbCr) { + // It's not an error to not be encoded in YUV, so no need to use return_false() + return false; + } + + cinfo.out_color_space = JCS_YCbCr; + cinfo.raw_data_out = TRUE; + + if (!planes || !planes[0] || !rowBytes || !rowBytes[0]) { // Compute size only + update_components_sizes(cinfo, componentSizes, kSizeForMemoryAllocation_SizeType); + return true; + } + + set_dct_method(*this, &cinfo); + + SkASSERT(1 == cinfo.scale_num); + cinfo.scale_denom = 1; + + turn_off_visual_optimizations(&cinfo); + +#ifdef ANDROID_RGB + cinfo.dither_mode = JDITHER_NONE; +#endif + + /* image_width and image_height are the original dimensions, available + after jpeg_read_header(). To see the scaled dimensions, we have to call + jpeg_start_decompress(), and then read output_width and output_height. + */ + if (!jpeg_start_decompress(&cinfo)) { + return return_false(cinfo, 0, 0, "start_decompress"); + } + + if (!output_raw_data(cinfo, planes, rowBytes)) { + return return_false(cinfo, 0, 0, "output_raw_data"); + } + + update_components_sizes(cinfo, componentSizes, kActualSize_SizeType); + jpeg_finish_decompress(&cinfo); + + if (NULL != colorSpace) { + *colorSpace = kJPEG_SkYUVColorSpace; + } + + return true; +} + #ifdef SK_BUILD_FOR_ANDROID bool SkJPEGImageDecoder::onBuildTileIndex(SkStreamRewindable* stream, int *width, int *height) { diff --git a/tests/JpegTest.cpp b/tests/JpegTest.cpp index f8784a2d26..8828926ef9 100644 --- a/tests/JpegTest.cpp +++ b/tests/JpegTest.cpp @@ -6,11 +6,12 @@ */ #include "SkBitmap.h" -#include "SkData.h" +#include "SkDecodingImageGenerator.h" #include "SkForceLinking.h" -#include "SkImage.h" #include "SkImageDecoder.h" +#include "SkPixelRef.h" #include "SkStream.h" +#include "SkTemplates.h" #include "Test.h" __SK_FORCE_IMAGE_DECODER_LINKING; @@ -452,3 +453,47 @@ DEF_TEST(Jpeg, reporter) { SkASSERT(writeSuccess); #endif } + +DEF_TEST(Jpeg_YUV, reporter) { + size_t len = sizeof(goodJpegImage); + SkMemoryStream* stream = SkNEW_ARGS(SkMemoryStream, (goodJpegImage, len)); + + SkBitmap bitmap; + SkDecodingImageGenerator::Options opts; + bool pixelsInstalled = SkInstallDiscardablePixelRef( + SkDecodingImageGenerator::Create(stream, opts), &bitmap); + REPORTER_ASSERT(reporter, pixelsInstalled); + + if (!pixelsInstalled) { + return; + } + + SkPixelRef* pixelRef = bitmap.pixelRef(); + SkISize yuvSizes[3]; + bool sizesComputed = (NULL != pixelRef) && pixelRef->getYUV8Planes(yuvSizes, NULL, NULL, NULL); + REPORTER_ASSERT(reporter, sizesComputed); + + if (!sizesComputed) { + return; + } + + // Allocate the memory for YUV + size_t totalSize(0); + size_t sizes[3], rowBytes[3]; + const int32_t expected_sizes[] = {128, 64, 64}; + for (int i = 0; i < 3; ++i) { + rowBytes[i] = yuvSizes[i].fWidth; + totalSize += sizes[i] = rowBytes[i] * yuvSizes[i].fHeight; + REPORTER_ASSERT(reporter, rowBytes[i] == (size_t)expected_sizes[i]); + REPORTER_ASSERT(reporter, yuvSizes[i].fWidth == expected_sizes[i]); + REPORTER_ASSERT(reporter, yuvSizes[i].fHeight == expected_sizes[i]); + } + SkAutoMalloc storage(totalSize); + void* planes[3]; + planes[0] = storage.get(); + planes[1] = (uint8_t*)planes[0] + sizes[0]; + planes[2] = (uint8_t*)planes[1] + sizes[1]; + + // Get the YUV planes + REPORTER_ASSERT(reporter, pixelRef->getYUV8Planes(yuvSizes, planes, rowBytes, NULL)); +} -- cgit v1.2.3