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:
parent
62372bcc6a
commit
b227e37eae
@ -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.
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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) {
|
||||
|
||||
|
@ -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));
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user