JPEG YUV Decoding

Enabling JPEG YUV Decoding in Skia

BUG=skia:3005, skia:1674, skia:3029

Committed: https://skia.googlesource.com/skia/+/8e6c3b93a39e19111662a760ede97df55e51d39f

Review URL: https://codereview.chromium.org/399683007
This commit is contained in:
sugoi 2014-10-16 13:10:57 -07:00 committed by Commit bot
parent 62372bcc6a
commit b227e37eae
5 changed files with 341 additions and 4 deletions

View File

@ -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.

View File

@ -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<SkImageDecoder> 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

View File

@ -285,3 +285,11 @@ 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) {
// we reset this to false before calling onDecodeYUV8Planes
fShouldCancelDecode = false;
return this->onDecodeYUV8Planes(stream, componentSizes, planes, rowBytes, colorSpace);
}

View File

@ -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<JSAMPROW>(planes[0]);
JSAMPROW outputU = static_cast<JSAMPROW>(planes[1]);
JSAMPROW outputV = static_cast<JSAMPROW>(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 YUV8");
}
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 YUV8");
}
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 YUV8");
}
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) {

View File

@ -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));
}