aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorGravatar msarett <msarett@google.com>2015-04-29 08:17:15 -0700
committerGravatar Commit bot <commit-bot@chromium.org>2015-04-29 08:17:15 -0700
commit97fdea6c4393cf0102d7eee5790782509fb4f57b (patch)
treedfc56d116e70785df50af08dd7972c344a923b13
parent2420e10d5a86da1964a05a749ab9bb625ae9f13e (diff)
Implementing a scanline decoder for jpeg
-rw-r--r--include/codec/SkScanlineDecoder.h2
-rw-r--r--src/codec/SkJpegCodec.cpp237
-rw-r--r--src/codec/SkJpegCodec.h24
-rw-r--r--tests/CodexTest.cpp10
4 files changed, 228 insertions, 45 deletions
diff --git a/include/codec/SkScanlineDecoder.h b/include/codec/SkScanlineDecoder.h
index c047223d7c..d7f73dda56 100644
--- a/include/codec/SkScanlineDecoder.h
+++ b/include/codec/SkScanlineDecoder.h
@@ -77,6 +77,8 @@ protected:
virtual bool onReallyHasAlpha() const { return false; }
+ const SkImageInfo& dstInfo() const { return fDstInfo; }
+
private:
const SkImageInfo fDstInfo;
int fCurrScanline;
diff --git a/src/codec/SkJpegCodec.cpp b/src/codec/SkJpegCodec.cpp
index a4ad766517..c12587424a 100644
--- a/src/codec/SkJpegCodec.cpp
+++ b/src/codec/SkJpegCodec.cpp
@@ -168,6 +168,8 @@ SkJpegCodec::SkJpegCodec(const SkImageInfo& srcInfo, SkStream* stream,
JpegDecoderMgr* decoderMgr)
: INHERITED(srcInfo, stream)
, fDecoderMgr(decoderMgr)
+ , fSwizzler(NULL)
+ , fSrcRowBytes(0)
{}
/*
@@ -227,22 +229,77 @@ static bool conversion_possible(const SkImageInfo& dst,
}
/*
+ * Handles rewinding the input stream if it is necessary
+ */
+bool SkJpegCodec::handleRewind() {
+ switch(this->rewindIfNeeded()) {
+ case kCouldNotRewind_RewindState:
+ return fDecoderMgr->returnFalse("could not rewind");
+ case kRewound_RewindState: {
+ JpegDecoderMgr* decoderMgr = NULL;
+ if (!ReadHeader(this->stream(), NULL, &decoderMgr)) {
+ return fDecoderMgr->returnFalse("could not rewind");
+ }
+ SkASSERT(NULL != decoderMgr);
+ fDecoderMgr.reset(decoderMgr);
+ return true;
+ }
+ case kNoRewindNecessary_RewindState:
+ return true;
+ default:
+ SkASSERT(false);
+ return false;
+ }
+}
+
+/*
+ * Checks if we can scale to the requested dimensions and scales the dimensions
+ * if possible
+ */
+bool SkJpegCodec::scaleToDimensions(uint32_t dstWidth, uint32_t dstHeight) {
+ // libjpeg can scale to 1/1, 1/2, 1/4, and 1/8
+ SkASSERT(1 == fDecoderMgr->dinfo()->scale_num);
+ SkASSERT(1 == fDecoderMgr->dinfo()->scale_denom);
+ jpeg_calc_output_dimensions(fDecoderMgr->dinfo());
+ while (fDecoderMgr->dinfo()->output_width != dstWidth ||
+ fDecoderMgr->dinfo()->output_height != dstHeight) {
+
+ // Return a failure if we have tried all of the possible scales
+ if (8 == fDecoderMgr->dinfo()->scale_denom ||
+ dstWidth > fDecoderMgr->dinfo()->output_width ||
+ dstHeight > fDecoderMgr->dinfo()->output_height) {
+ return fDecoderMgr->returnFalse("could not scale to requested dimensions");
+ }
+
+ // Try the next scale
+ fDecoderMgr->dinfo()->scale_denom *= 2;
+ jpeg_calc_output_dimensions(fDecoderMgr->dinfo());
+ }
+ return true;
+}
+
+/*
+ * Create the swizzler based on the encoded format
+ */
+void SkJpegCodec::initializeSwizzler(const SkImageInfo& dstInfo,
+ void* dst, size_t dstRowBytes,
+ const Options& options) {
+ SkSwizzler::SrcConfig srcConfig = get_src_config(*fDecoderMgr->dinfo());
+ fSwizzler.reset(SkSwizzler::CreateSwizzler(srcConfig, NULL, dstInfo, dst, dstRowBytes,
+ options.fZeroInitialized));
+ fSrcRowBytes = SkSwizzler::BytesPerPixel(srcConfig) * dstInfo.width();
+}
+
+/*
* Performs the jpeg decode
*/
SkCodec::Result SkJpegCodec::onGetPixels(const SkImageInfo& dstInfo,
void* dst, size_t dstRowBytes,
const Options& options, SkPMColor*, int*) {
+
// Rewind the stream if needed
- SkCodec::RewindState rewindState = this->rewindIfNeeded();
- if (rewindState == kCouldNotRewind_RewindState) {
- return kCouldNotRewind;
- } else if (rewindState == kRewound_RewindState) {
- JpegDecoderMgr* decoderMgr = NULL;
- if (!ReadHeader(this->stream(), NULL, &decoderMgr)) {
- return kCouldNotRewind;
- }
- SkASSERT(NULL != decoderMgr);
- fDecoderMgr.reset(decoderMgr);
+ if (!this->handleRewind()) {
+ fDecoderMgr->returnFailure("could not rewind stream", kCouldNotRewind);
}
// Get a pointer to the decompress info since we will use it quite frequently
@@ -257,25 +314,10 @@ SkCodec::Result SkJpegCodec::onGetPixels(const SkImageInfo& dstInfo,
if (!conversion_possible(dstInfo, this->getInfo())) {
return fDecoderMgr->returnFailure("conversion_possible", kInvalidConversion);
}
- // Check if we can scale to the requested dimensions
- // libjpeg can scale to 1/1, 1/2, 1/4, and 1/8
- SkASSERT(1 == dinfo->scale_num);
- SkASSERT(1 == dinfo->scale_denom);
- jpeg_calc_output_dimensions(dinfo);
- const uint32_t dstWidth = dstInfo.width();
- const uint32_t dstHeight = dstInfo.height();
- while (dinfo->output_width != dstWidth || dinfo->output_height != dstHeight) {
-
- // Return a failure if we have tried all of the possible scales
- if (8 == dinfo->scale_denom ||
- dstWidth > dinfo->output_width ||
- dstHeight > dinfo->output_height) {
- return fDecoderMgr->returnFailure("cannot scale to requested dims", kInvalidScale);
- }
- // Try the next scale
- dinfo->scale_denom *= 2;
- jpeg_calc_output_dimensions(dinfo);
+ // Perform the necessary scaling
+ if (!this->scaleToDimensions(dstInfo.width(), dstInfo.height())) {
+ fDecoderMgr->returnFailure("cannot scale to requested dims", kInvalidScale);
}
// Now, given valid output dimensions, we can start the decompress
@@ -284,13 +326,10 @@ SkCodec::Result SkJpegCodec::onGetPixels(const SkImageInfo& dstInfo,
}
// Create the swizzler
- SkSwizzler::SrcConfig srcConfig = get_src_config(*dinfo);
- SkAutoTDelete<SkSwizzler> swizzler(SkSwizzler::CreateSwizzler(srcConfig, NULL, dstInfo, dst,
- dstRowBytes, options.fZeroInitialized));
- if (NULL == swizzler) {
- return fDecoderMgr->returnFailure("getSwizzler", kInvalidInput);
+ this->initializeSwizzler(dstInfo, dst, dstRowBytes, options);
+ if (NULL == fSwizzler) {
+ return fDecoderMgr->returnFailure("getSwizzler", kUnimplemented);
}
- const uint32_t srcBytesPerPixel = SkSwizzler::BytesPerPixel(srcConfig);
// This is usually 1, but can also be 2 or 4.
// If we wanted to always read one row at a time, we could, but we will save space and time
@@ -299,29 +338,30 @@ SkCodec::Result SkJpegCodec::onGetPixels(const SkImageInfo& dstInfo,
SkASSERT(rowsPerDecode <= 4);
// Create a buffer to contain decoded rows (libjpeg requires a 2D array)
- const uint32_t srcRowBytes = srcBytesPerPixel * dstWidth;
- SkAutoTDeleteArray<uint8_t> srcBuffer(SkNEW_ARRAY(uint8_t, srcRowBytes * rowsPerDecode));
+ SkASSERT(0 != fSrcRowBytes);
+ SkAutoTDeleteArray<uint8_t> srcBuffer(SkNEW_ARRAY(uint8_t, fSrcRowBytes * rowsPerDecode));
JSAMPLE* srcRows[4];
uint8_t* srcPtr = srcBuffer.get();
for (uint8_t i = 0; i < rowsPerDecode; i++) {
srcRows[i] = (JSAMPLE*) srcPtr;
- srcPtr += srcRowBytes;
+ srcPtr += fSrcRowBytes;
}
// Ensure that we loop enough times to decode all of the rows
// libjpeg will prevent us from reading past the bottom of the image
+ uint32_t dstHeight = dstInfo.height();
for (uint32_t y = 0; y < dstHeight + rowsPerDecode - 1; y += rowsPerDecode) {
// Read rows of the image
uint32_t rowsDecoded = jpeg_read_scanlines(dinfo, srcRows, rowsPerDecode);
// Convert to RGB if necessary
if (JCS_CMYK == dinfo->out_color_space) {
- convert_CMYK_to_RGB(srcRows[0], dstWidth * rowsDecoded);
+ convert_CMYK_to_RGB(srcRows[0], dstInfo.width() * rowsDecoded);
}
// Swizzle to output destination
for (uint32_t i = 0; i < rowsDecoded; i++) {
- swizzler->next(srcRows[i]);
+ fSwizzler->next(srcRows[i]);
}
// If we cannot read enough rows, assume the input is incomplete
@@ -331,7 +371,7 @@ SkCodec::Result SkJpegCodec::onGetPixels(const SkImageInfo& dstInfo,
// the fill color for opaque images. If the destination is kGray,
// the low 8 bits of SK_ColorBLACK will be used. Conveniently,
// these are zeros, which is the representation for black in kGray.
- SkSwizzler::Fill(swizzler->getDstRow(), dstInfo, dstRowBytes,
+ SkSwizzler::Fill(fSwizzler->getDstRow(), dstInfo, dstRowBytes,
dstHeight - y - rowsDecoded, SK_ColorBLACK, NULL);
// Prevent libjpeg from failing on incomplete decode
@@ -346,3 +386,120 @@ SkCodec::Result SkJpegCodec::onGetPixels(const SkImageInfo& dstInfo,
return kSuccess;
}
+
+/*
+ * Enable scanline decoding for jpegs
+ */
+class SkJpegScanlineDecoder : public SkScanlineDecoder {
+public:
+ SkJpegScanlineDecoder(const SkImageInfo& dstInfo, SkJpegCodec* codec)
+ : INHERITED(dstInfo)
+ , fCodec(codec)
+ {
+ fStorage.reset(fCodec->fSrcRowBytes);
+ fSrcRow = static_cast<uint8_t*>(fStorage.get());
+ }
+
+ SkImageGenerator::Result onGetScanlines(void* dst, int count, size_t rowBytes) override {
+ // Set the jump location for libjpeg errors
+ if (setjmp(fCodec->fDecoderMgr->getJmpBuf())) {
+ return fCodec->fDecoderMgr->returnFailure("setjmp", SkImageGenerator::kInvalidInput);
+ }
+
+ // Read rows one at a time
+ for (int y = 0; y < count; y++) {
+ // Read row of the image
+ uint32_t rowsDecoded = jpeg_read_scanlines(fCodec->fDecoderMgr->dinfo(), &fSrcRow, 1);
+ if (rowsDecoded != 1) {
+ SkSwizzler::Fill(dst, this->dstInfo(), rowBytes, count - y, SK_ColorBLACK, NULL);
+ return SkImageGenerator::kIncompleteInput;
+ }
+
+ // Convert to RGB if necessary
+ if (JCS_CMYK == fCodec->fDecoderMgr->dinfo()->out_color_space) {
+ convert_CMYK_to_RGB(fSrcRow, dstInfo().width());
+ }
+
+ // Swizzle to output destination
+ fCodec->fSwizzler->setDstRow(dst);
+ fCodec->fSwizzler->next(fSrcRow);
+ dst = SkTAddOffset<void>(dst, rowBytes);
+ }
+
+ return SkImageGenerator::kSuccess;
+ }
+
+ SkImageGenerator::Result onSkipScanlines(int count) override {
+ // Set the jump location for libjpeg errors
+ if (setjmp(fCodec->fDecoderMgr->getJmpBuf())) {
+ return fCodec->fDecoderMgr->returnFailure("setjmp", SkImageGenerator::kInvalidInput);
+ }
+
+ // Read rows but ignore the output
+ for (int y = 0; y < count; y++) {
+ jpeg_read_scanlines(fCodec->fDecoderMgr->dinfo(), &fSrcRow, 1);
+ }
+
+ return SkImageGenerator::kSuccess;
+ }
+
+ void onFinish() override {
+ if (setjmp(fCodec->fDecoderMgr->getJmpBuf())) {
+ SkCodecPrintf("setjmp: Error in libjpeg finish_decompress\n");
+ return;
+ }
+
+ jpeg_finish_decompress(fCodec->fDecoderMgr->dinfo());
+ }
+
+private:
+ SkJpegCodec* fCodec; // unowned
+ SkAutoMalloc fStorage;
+ uint8_t* fSrcRow; // ptr into fStorage
+
+ typedef SkScanlineDecoder INHERITED;
+};
+
+SkScanlineDecoder* SkJpegCodec::onGetScanlineDecoder(const SkImageInfo& dstInfo,
+ const Options& options, SkPMColor ctable[], int* ctableCount) {
+
+ // Rewind the stream if needed
+ if (!this->handleRewind()) {
+ SkCodecPrintf("Could not rewind\n");
+ return NULL;
+ }
+
+ // Set the jump location for libjpeg errors
+ if (setjmp(fDecoderMgr->getJmpBuf())) {
+ SkCodecPrintf("setjmp: Error from libjpeg\n");
+ return NULL;
+ }
+
+ // Check if we can decode to the requested destination
+ if (!conversion_possible(dstInfo, this->getInfo())) {
+ SkCodecPrintf("Cannot convert to output type\n");
+ return NULL;
+ }
+
+ // Perform the necessary scaling
+ if (!this->scaleToDimensions(dstInfo.width(), dstInfo.height())) {
+ SkCodecPrintf("Cannot scale ot output dimensions\n");
+ return NULL;
+ }
+
+ // Now, given valid output dimensions, we can start the decompress
+ if (!jpeg_start_decompress(fDecoderMgr->dinfo())) {
+ SkCodecPrintf("start decompress failed\n");
+ return NULL;
+ }
+
+ // Create the swizzler
+ this->initializeSwizzler(dstInfo, NULL, dstInfo.minRowBytes(), options);
+ if (NULL == fSwizzler) {
+ SkCodecPrintf("Could not create swizzler\n");
+ return NULL;
+ }
+
+ // Return the new scanline decoder
+ return SkNEW_ARGS(SkJpegScanlineDecoder, (dstInfo, this));
+}
diff --git a/src/codec/SkJpegCodec.h b/src/codec/SkJpegCodec.h
index 51a741a525..3447a10f39 100644
--- a/src/codec/SkJpegCodec.h
+++ b/src/codec/SkJpegCodec.h
@@ -56,6 +56,9 @@ protected:
return kJPEG_SkEncodedFormat;
}
+ SkScanlineDecoder* onGetScanlineDecoder(const SkImageInfo& dstInfo, const Options& options,
+ SkPMColor ctable[], int* ctableCount) override;
+
private:
/*
@@ -91,7 +94,28 @@ private:
*/
SkJpegCodec(const SkImageInfo& srcInfo, SkStream* stream, JpegDecoderMgr* decoderMgr);
+ /*
+ * Handles rewinding the input stream if it is necessary
+ */
+ bool handleRewind();
+
+ /*
+ * Checks if we can scale to the requested dimensions and scales the dimensions
+ * if possible
+ */
+ bool scaleToDimensions(uint32_t width, uint32_t height);
+
+ /*
+ * Create the swizzler based on the encoded format
+ */
+ void initializeSwizzler(const SkImageInfo& dstInfo, void* dst, size_t dstRowBytes,
+ const Options& options);
+
SkAutoTDelete<JpegDecoderMgr> fDecoderMgr;
+ SkAutoTDelete<SkSwizzler> fSwizzler;
+ size_t fSrcRowBytes;
+
+ friend class SkJpegScanlineDecoder;
typedef SkCodec INHERITED;
};
diff --git a/tests/CodexTest.cpp b/tests/CodexTest.cpp
index b33e0bebea..080e6e5974 100644
--- a/tests/CodexTest.cpp
+++ b/tests/CodexTest.cpp
@@ -105,11 +105,11 @@ DEF_TEST(Codec, r) {
check(r, "randPixels.gif", SkISize::Make(8, 8), false);
// JPG
- check(r, "CMYK.jpg", SkISize::Make(642, 516), false);
- check(r, "color_wheel.jpg", SkISize::Make(128, 128), false);
- check(r, "grayscale.jpg", SkISize::Make(128, 128), false);
- check(r, "mandrill_512_q075.jpg", SkISize::Make(512, 512), false);
- check(r, "randPixels.jpg", SkISize::Make(8, 8), false);
+ check(r, "CMYK.jpg", SkISize::Make(642, 516), true);
+ check(r, "color_wheel.jpg", SkISize::Make(128, 128), true);
+ check(r, "grayscale.jpg", SkISize::Make(128, 128), true);
+ check(r, "mandrill_512_q075.jpg", SkISize::Make(512, 512), true);
+ check(r, "randPixels.jpg", SkISize::Make(8, 8), true);
// PNG
check(r, "arrow.png", SkISize::Make(187, 312), true);