virtual bool onBuildTileIndex(SkStreamRewindable *stream, int *width, int *height) SK_OVERRIDE;
virtual bool onDecodeSubset(SkBitmap* bitmap, const SkIRect& rect) SK_OVERRIDE;
#endif
- virtual bool onDecode(SkStream* stream, SkBitmap* bm, Mode) SK_OVERRIDE;
+ virtual Result 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
}
#endif
+///////////////////////////////////////////////////////////////////////////////
+
// 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[]) {
+static void print_jpeg_decoder_errors(const jpeg_decompress_struct& cinfo,
+ 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 char caller[]) {
+ print_jpeg_decoder_errors(cinfo, 0, 0, caller);
+ return false;
+}
+
+#ifdef SK_BUILD_FOR_ANDROID
+static bool return_false(const jpeg_decompress_struct& cinfo,
+ const SkBitmap& bm, const char caller[]) {
+ print_jpeg_decoder_errors(cinfo, bm.width(), bm.height(), caller);
+ return false;
+}
+#endif
+
+static SkImageDecoder::Result return_failure(const jpeg_decompress_struct& cinfo,
+ const SkBitmap& bm, const char caller[]) {
+ print_jpeg_decoder_errors(cinfo, bm.width(), bm.height(), caller);
+ return SkImageDecoder::kFailure;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
// 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) {
#endif
}
-
/**
Sets all pixels in given bitmap to SK_ColorWHITE for all rows >= y.
Used when decoding fails partway through reading scanlines to fill
return true;
}
-bool SkJPEGImageDecoder::onDecode(SkStream* stream, SkBitmap* bm, Mode mode) {
+SkImageDecoder::Result SkJPEGImageDecoder::onDecode(SkStream* stream, SkBitmap* bm, Mode mode) {
#ifdef TIME_DECODE
SkAutoTime atm("JPEG Decode");
#endif
// 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, *bm, "setjmp");
+ return return_failure(cinfo, *bm, "setjmp");
}
initialize_info(&cinfo, &srcManager);
int status = jpeg_read_header(&cinfo, true);
if (status != JPEG_HEADER_OK) {
- return return_false(cinfo, *bm, "read_header");
+ return return_failure(cinfo, *bm, "read_header");
}
/* Try to fulfill the requested sampleSize. Since jpeg can do it (when it
// individual pixel. It is very unlikely to be opaque, since
// an opaque A8 bitmap would not be very interesting.
// Otherwise, a jpeg image is opaque.
- return bm->setInfo(SkImageInfo::Make(cinfo.image_width, cinfo.image_height,
- colorType, alphaType));
+ bool success = bm->setInfo(SkImageInfo::Make(cinfo.image_width, cinfo.image_height,
+ colorType, alphaType));
+ return success ? kSuccess : kFailure;
}
/* image_width and image_height are the original dimensions, available
// individual pixel. It is very unlikely to be opaque, since
// an opaque A8 bitmap would not be very interesting.
// Otherwise, a jpeg image is opaque.
- return bm->setInfo(SkImageInfo::Make(smpl.scaledWidth(), smpl.scaledHeight(),
- colorType, alphaType));
+ bool success = bm->setInfo(SkImageInfo::Make(smpl.scaledWidth(), smpl.scaledHeight(),
+ colorType, alphaType));
+ return success ? kSuccess : kFailure;
} else {
- return return_false(cinfo, *bm, "start_decompress");
+ return return_failure(cinfo, *bm, "start_decompress");
}
}
sampleSize = recompute_sampleSize(sampleSize, cinfo);
#ifdef SK_SUPPORT_LEGACY_IMAGEDECODER_CHOOSER
// should we allow the Chooser (if present) to pick a colortype for us???
if (!this->chooseFromOneChoice(colorType, cinfo.output_width, cinfo.output_height)) {
- return return_false(cinfo, *bm, "chooseFromOneChoice");
+ return return_failure(cinfo, *bm, "chooseFromOneChoice");
}
#endif
bm->setInfo(SkImageInfo::Make(sampler.scaledWidth(), sampler.scaledHeight(),
colorType, alphaType));
if (SkImageDecoder::kDecodeBounds_Mode == mode) {
- return true;
+ return kSuccess;
}
if (!this->allocPixelRef(bm, NULL)) {
- return return_false(cinfo, *bm, "allocPixelRef");
+ return return_failure(cinfo, *bm, "allocPixelRef");
}
SkAutoLockPixels alp(*bm);
// so return early. We will return a partial image.
fill_below_level(cinfo.output_scanline, bm);
cinfo.output_scanline = cinfo.output_height;
- break; // Skip to jpeg_finish_decompress()
+ jpeg_finish_decompress(&cinfo);
+ return kPartialSuccess;
}
if (this->shouldCancelDecode()) {
- return return_false(cinfo, *bm, "shouldCancelDecode");
+ return return_failure(cinfo, *bm, "shouldCancelDecode");
}
rowptr += bpr;
}
jpeg_finish_decompress(&cinfo);
- return true;
+ return kSuccess;
}
#endif
int srcBytesPerPixel;
if (!get_src_config(cinfo, &sc, &srcBytesPerPixel)) {
- return return_false(cinfo, *bm, "jpeg colorspace");
+ return return_failure(cinfo, *bm, "jpeg colorspace");
}
if (!sampler.begin(bm, sc, *this)) {
- return return_false(cinfo, *bm, "sampler.begin");
+ return return_failure(cinfo, *bm, "sampler.begin");
}
SkAutoMalloc srcStorage(cinfo.output_width * srcBytesPerPixel);
// Possibly skip initial rows [sampler.srcY0]
if (!skip_src_rows(&cinfo, srcRow, sampler.srcY0())) {
- return return_false(cinfo, *bm, "skip rows");
+ return return_failure(cinfo, *bm, "skip rows");
}
// now loop through scanlines until y == bm->height() - 1
// so return early. We will return a partial image.
fill_below_level(y, bm);
cinfo.output_scanline = cinfo.output_height;
- break; // Skip to jpeg_finish_decompress()
+ jpeg_finish_decompress(&cinfo);
+ return kSuccess;
}
if (this->shouldCancelDecode()) {
- return return_false(cinfo, *bm, "shouldCancelDecode");
+ return return_failure(cinfo, *bm, "shouldCancelDecode");
}
if (JCS_CMYK == cinfo.out_color_space) {
}
if (!skip_src_rows(&cinfo, srcRow, sampler.srcDY() - 1)) {
- return return_false(cinfo, *bm, "skip rows");
+ return return_failure(cinfo, *bm, "skip rows");
}
}
// we formally skip the rest, so we don't get a complaint from libjpeg
if (!skip_src_rows(&cinfo, srcRow,
cinfo.output_height - cinfo.output_scanline)) {
- return return_false(cinfo, *bm, "skip rows");
+ return return_failure(cinfo, *bm, "skip rows");
+ }
+ jpeg_finish_decompress(&cinfo);
+
+ return kSuccess;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+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);
+}
+
+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, "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, "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, "start_decompress YUV8");
}
+
+ if (!output_raw_data(cinfo, planes, rowBytes)) {
+ return return_false(cinfo, "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) {