aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorGravatar msarett <msarett@google.com>2015-04-15 07:32:19 -0700
committerGravatar Commit bot <commit-bot@chromium.org>2015-04-15 07:32:20 -0700
commite16b04aa6041efb6507546547737e9603fa1606e (patch)
tree3bd78e41a9ff3df445c64d3c0429d83bb698570d
parentf91e676f941c7e9ec91ac298eaa32e4bf8f52762 (diff)
SkJpegCodec
Enables basic decoding for jpegs Includes rewinding 565, YUV, and Jpeg encoding are not yet implemented BUG=skia:3257 Review URL: https://codereview.chromium.org/1076923002
-rw-r--r--dm/DM.cpp4
-rw-r--r--dm/DMSrcSink.cpp6
-rw-r--r--gyp/codec.gyp4
-rw-r--r--src/codec/SkCodec.cpp2
-rw-r--r--src/codec/SkCodec_libbmp.cpp36
-rw-r--r--src/codec/SkJpegCodec.cpp347
-rw-r--r--src/codec/SkJpegCodec.h99
-rw-r--r--src/codec/SkJpegDecoderMgr.cpp109
-rw-r--r--src/codec/SkJpegDecoderMgr.h77
-rw-r--r--src/codec/SkJpegUtility.cpp89
-rw-r--r--src/codec/SkJpegUtility.h50
-rw-r--r--src/codec/SkSwizzler.cpp46
-rw-r--r--src/codec/SkSwizzler.h2
-rw-r--r--tests/CodexTest.cpp50
-rw-r--r--tests/SwizzlerTest.cpp25
15 files changed, 924 insertions, 22 deletions
diff --git a/dm/DM.cpp b/dm/DM.cpp
index c6d633c001..ff9dc57264 100644
--- a/dm/DM.cpp
+++ b/dm/DM.cpp
@@ -223,8 +223,8 @@ static bool codec_supported(const char* ext) {
// FIXME: Once other versions of SkCodec are available, we can add them to this
// list (and eventually we can remove this check once they are all supported).
static const char* const exts[] = {
- "bmp", "gif", "png", "ico", "wbmp",
- "BMP", "GIF", "PNG", "ICO", "WBMP"
+ "bmp", "gif", "jpg", "jpeg", "png", "ico", "wbmp",
+ "BMP", "GIF", "JPG", "JPEG", "PNG", "ICO", "WBMP"
};
for (uint32_t i = 0; i < SK_ARRAY_COUNT(exts); i++) {
diff --git a/dm/DMSrcSink.cpp b/dm/DMSrcSink.cpp
index e70dd3e2b0..6b3ec9749e 100644
--- a/dm/DMSrcSink.cpp
+++ b/dm/DMSrcSink.cpp
@@ -83,7 +83,13 @@ Error CodecSrc::draw(SkCanvas* canvas) const {
SkColorType canvasColorType = canvasInfo.colorType();
switch (fDstColorType) {
case kIndex8_Always_DstColorType:
+ decodeInfo = codec->getInfo().makeColorType(kIndex_8_SkColorType);
+ if (kRGB_565_SkColorType == canvasColorType) {
+ return Error::Nonfatal("Testing non-565 to 565 is uninteresting.");
+ }
+ break;
case kGrayscale_Always_DstColorType:
+ decodeInfo = codec->getInfo().makeColorType(kGray_8_SkColorType);
if (kRGB_565_SkColorType == canvasColorType) {
return Error::Nonfatal("Testing non-565 to 565 is uninteresting.");
}
diff --git a/gyp/codec.gyp b/gyp/codec.gyp
index 1373773940..f3fed41355 100644
--- a/gyp/codec.gyp
+++ b/gyp/codec.gyp
@@ -18,6 +18,7 @@
'dependencies': [
'core.gyp:*',
'giflib.gyp:giflib',
+ 'libjpeg.gyp:libjpeg',
],
'cflags':[
# FIXME: This gets around a longjmp warning. See
@@ -37,6 +38,9 @@
'../src/codec/SkCodec_libpng.cpp',
'../src/codec/SkCodec_wbmp.cpp',
'../src/codec/SkGifInterlaceIter.cpp',
+ '../src/codec/SkJpegCodec.cpp',
+ '../src/codec/SkJpegDecoderMgr.cpp',
+ '../src/codec/SkJpegUtility.cpp',
'../src/codec/SkMaskSwizzler.cpp',
'../src/codec/SkMasks.cpp',
'../src/codec/SkSwizzler.cpp',
diff --git a/src/codec/SkCodec.cpp b/src/codec/SkCodec.cpp
index 7bc9146041..c4e488565e 100644
--- a/src/codec/SkCodec.cpp
+++ b/src/codec/SkCodec.cpp
@@ -13,6 +13,7 @@
#include "SkCodec_libpng.h"
#include "SkCodec_wbmp.h"
#include "SkCodecPriv.h"
+#include "SkJpegCodec.h"
#include "SkStream.h"
struct DecoderProc {
@@ -22,6 +23,7 @@ struct DecoderProc {
static const DecoderProc gDecoderProcs[] = {
{ SkPngCodec::IsPng, SkPngCodec::NewFromStream },
+ { SkJpegCodec::IsJpeg, SkJpegCodec::NewFromStream },
{ SkGifCodec::IsGif, SkGifCodec::NewFromStream },
{ SkIcoCodec::IsIco, SkIcoCodec::NewFromStream },
{ SkBmpCodec::IsBmp, SkBmpCodec::NewFromStream },
diff --git a/src/codec/SkCodec_libbmp.cpp b/src/codec/SkCodec_libbmp.cpp
index 67be0dbe25..56663f84ee 100644
--- a/src/codec/SkCodec_libbmp.cpp
+++ b/src/codec/SkCodec_libbmp.cpp
@@ -97,7 +97,6 @@ enum BitmapCompressionMethod {
*/
bool SkBmpCodec::IsBmp(SkStream* stream) {
// TODO: Support "IC", "PT", "CI", "CP", "BA"
- // TODO: ICO files may contain a BMP and need to use this decoder
const char bmpSig[] = { 'B', 'M' };
char buffer[sizeof(bmpSig)];
return stream->read(buffer, sizeof(bmpSig)) == sizeof(bmpSig) &&
@@ -773,12 +772,12 @@ SkCodec::Result SkBmpCodec::decodeMask(const SkImageInfo& dstInfo,
if (stream()->read(srcRow, rowBytes) != rowBytes) {
SkCodecPrintf("Warning: incomplete input stream.\n");
// Fill the destination image on failure
- // By using zero as the fill value, we will fill with transparent
- // pixels for non-opaque images and white for opaque images.
- // These are arbitrary choices but allow for consistent behavior.
- if (kNo_ZeroInitialized == opts.fZeroInitialized) {
+ SkPMColor fillColor = dstInfo.alphaType() == kOpaque_SkAlphaType ?
+ SK_ColorBLACK : SK_ColorTRANSPARENT;
+ if (kNo_ZeroInitialized == opts.fZeroInitialized || 0 != fillColor) {
void* dstStart = get_dst_start_row(dst, dstRowBytes, y, fRowOrder);
- SkSwizzler::Fill(dstStart, dstInfo, dstRowBytes, dstInfo.height() - y, 0, NULL);
+ SkSwizzler::Fill(dstStart, dstInfo, dstRowBytes, dstInfo.height() - y, fillColor,
+ NULL);
}
return kIncompleteInput;
}
@@ -1090,29 +1089,42 @@ SkCodec::Result SkBmpCodec::decode(const SkImageInfo& dstInfo,
const int height = dstInfo.height();
const size_t rowBytes = SkAlign4(compute_row_bytes(width, fBitsPerPixel));
- // Get swizzler configuration
+ // Get swizzler configuration and choose the fill value for failures. We will use
+ // zero as the default palette index, black for opaque images, and transparent for
+ // non-opaque images.
SkSwizzler::SrcConfig config;
+ uint32_t fillColorOrIndex;
+ bool zeroFill = true;
switch (fBitsPerPixel) {
case 1:
config = SkSwizzler::kIndex1;
+ fillColorOrIndex = 0;
break;
case 2:
config = SkSwizzler::kIndex2;
+ fillColorOrIndex = 0;
break;
case 4:
config = SkSwizzler::kIndex4;
+ fillColorOrIndex = 0;
break;
case 8:
config = SkSwizzler::kIndex;
+ fillColorOrIndex = 0;
break;
case 24:
config = SkSwizzler::kBGR;
+ fillColorOrIndex = SK_ColorBLACK;
+ zeroFill = false;
break;
case 32:
if (kOpaque_SkAlphaType == dstInfo.alphaType()) {
config = SkSwizzler::kBGRX;
+ fillColorOrIndex = SK_ColorBLACK;
+ zeroFill = false;
} else {
config = SkSwizzler::kBGRA;
+ fillColorOrIndex = SK_ColorTRANSPARENT;
}
break;
default:
@@ -1138,14 +1150,10 @@ SkCodec::Result SkBmpCodec::decode(const SkImageInfo& dstInfo,
if (stream()->read(srcBuffer.get(), rowBytes) != rowBytes) {
SkCodecPrintf("Warning: incomplete input stream.\n");
// Fill the destination image on failure
- // By using zero as the fill value, we will fill with the first
- // color in the color table for palette images, transparent
- // pixels for non-opaque images, and white for opaque images.
- // These are arbitrary choices but allow for consistent behavior.
- if (kNo_ZeroInitialized == opts.fZeroInitialized) {
+ if (kNo_ZeroInitialized == opts.fZeroInitialized || !zeroFill) {
void* dstStart = get_dst_start_row(dst, dstRowBytes, y, fRowOrder);
- SkSwizzler::Fill(dstStart, dstInfo, dstRowBytes, dstInfo.height() - y, 0,
- colorPtr);
+ SkSwizzler::Fill(dstStart, dstInfo, dstRowBytes, dstInfo.height() - y,
+ fillColorOrIndex, colorPtr);
}
return kIncompleteInput;
}
diff --git a/src/codec/SkJpegCodec.cpp b/src/codec/SkJpegCodec.cpp
new file mode 100644
index 0000000000..6f7af49975
--- /dev/null
+++ b/src/codec/SkJpegCodec.cpp
@@ -0,0 +1,347 @@
+/*
+ * Copyright 2015 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "SkCodec.h"
+#include "SkJpegCodec.h"
+#include "SkJpegDecoderMgr.h"
+#include "SkJpegUtility.h"
+#include "SkCodecPriv.h"
+#include "SkColorPriv.h"
+#include "SkStream.h"
+#include "SkTemplates.h"
+#include "SkTypes.h"
+
+// stdio is needed for jpeglib
+#include <stdio.h>
+
+extern "C" {
+ #include "jerror.h"
+ #include "jmorecfg.h"
+ #include "jpegint.h"
+ #include "jpeglib.h"
+}
+
+// ANDROID_RGB
+// If this is defined in the jpeg headers it indicates that jpeg offers
+// support for two additional formats: JCS_RGBA_8888 and JCS_RGB_565.
+
+/*
+ * Get the source configuarion for the swizzler
+ */
+SkSwizzler::SrcConfig get_src_config(const jpeg_decompress_struct& dinfo) {
+ if (JCS_CMYK == dinfo.out_color_space) {
+ // We will need to perform a manual conversion
+ return SkSwizzler::kRGBX;
+ }
+ if (3 == dinfo.out_color_components && JCS_RGB == dinfo.out_color_space) {
+ return SkSwizzler::kRGB;
+ }
+#ifdef ANDROID_RGB
+ if (JCS_RGBA_8888 == dinfo.out_color_space) {
+ return SkSwizzler::kRGBX;
+ }
+
+ if (JCS_RGB_565 == dinfo.out_color_space) {
+ return SkSwizzler::kRGB_565;
+ }
+#endif
+ if (1 == dinfo.out_color_components && JCS_GRAYSCALE == dinfo.out_color_space) {
+ return SkSwizzler::kGray;
+ }
+ return SkSwizzler::kUnknown;
+}
+
+/*
+ * Convert a row of CMYK samples to RGBX in place.
+ * Note that this method moves the row pointer.
+ * @param width the number of pixels in the row that is being converted
+ * CMYK is stored as four bytes per pixel
+ */
+static void convert_CMYK_to_RGB(uint8_t* row, uint32_t width) {
+ // We will implement a crude conversion from CMYK -> RGB using formulas
+ // from easyrgb.com.
+ //
+ // CMYK -> CMY
+ // C = C * (1 - K) + K
+ // M = M * (1 - K) + K
+ // Y = Y * (1 - K) + K
+ //
+ // libjpeg actually gives us inverted CMYK, so we must subtract the
+ // original terms from 1.
+ // CMYK -> CMY
+ // C = (1 - C) * (1 - (1 - K)) + (1 - K)
+ // M = (1 - M) * (1 - (1 - K)) + (1 - K)
+ // Y = (1 - Y) * (1 - (1 - K)) + (1 - K)
+ //
+ // Simplifying the above expression.
+ // CMYK -> CMY
+ // C = 1 - CK
+ // M = 1 - MK
+ // Y = 1 - YK
+ //
+ // CMY -> RGB
+ // R = (1 - C) * 255
+ // G = (1 - M) * 255
+ // B = (1 - Y) * 255
+ //
+ // Therefore the full conversion is below. This can be verified at
+ // www.rapidtables.com (assuming inverted CMYK).
+ // CMYK -> RGB
+ // R = C * K * 255
+ // G = M * K * 255
+ // B = Y * K * 255
+ //
+ // As a final note, we have treated the CMYK values as if they were on
+ // a scale from 0-1, when in fact they are 8-bit ints scaling from 0-255.
+ // We must divide each CMYK component by 255 to obtain the true conversion
+ // we should perform.
+ // CMYK -> RGB
+ // R = C * K / 255
+ // G = M * K / 255
+ // B = Y * K / 255
+ for (uint32_t x = 0; x < width; x++, row += 4) {
+ row[0] = SkMulDiv255Round(row[0], row[3]);
+ row[1] = SkMulDiv255Round(row[1], row[3]);
+ row[2] = SkMulDiv255Round(row[2], row[3]);
+ row[3] = 0xFF;
+ }
+}
+
+bool SkJpegCodec::IsJpeg(SkStream* stream) {
+ static const uint8_t jpegSig[] = { 0xFF, 0xD8, 0xFF };
+ char buffer[sizeof(jpegSig)];
+ return stream->read(buffer, sizeof(jpegSig)) == sizeof(jpegSig) &&
+ !memcmp(buffer, jpegSig, sizeof(jpegSig));
+}
+
+bool SkJpegCodec::ReadHeader(SkStream* stream, SkCodec** codecOut,
+ JpegDecoderMgr** decoderMgrOut) {
+
+ // Create a JpegDecoderMgr to own all of the decompress information
+ SkAutoTDelete<JpegDecoderMgr> decoderMgr(SkNEW_ARGS(JpegDecoderMgr, (stream)));
+
+ // libjpeg errors will be caught and reported here
+ if (setjmp(decoderMgr->getJmpBuf())) {
+ return decoderMgr->returnFalse("setjmp");
+ }
+
+ // Initialize the decompress info and the source manager
+ decoderMgr->init();
+
+ // Read the jpeg header
+ if (JPEG_HEADER_OK != jpeg_read_header(decoderMgr->dinfo(), true)) {
+ return decoderMgr->returnFalse("read_header");
+ }
+
+ if (NULL != codecOut) {
+ // Recommend the color type to decode to
+ const SkColorType colorType = decoderMgr->getColorType();
+
+ // Create image info object and the codec
+ const SkImageInfo& imageInfo = SkImageInfo::Make(decoderMgr->dinfo()->image_width,
+ decoderMgr->dinfo()->image_height, colorType, kOpaque_SkAlphaType);
+ *codecOut = SkNEW_ARGS(SkJpegCodec, (imageInfo, stream, decoderMgr.detach()));
+ } else {
+ SkASSERT(NULL != decoderMgrOut);
+ *decoderMgrOut = decoderMgr.detach();
+ }
+ return true;
+}
+
+SkCodec* SkJpegCodec::NewFromStream(SkStream* stream) {
+ SkAutoTDelete<SkStream> streamDeleter(stream);
+ SkCodec* codec = NULL;
+ if (ReadHeader(stream, &codec, NULL)) {
+ // Codec has taken ownership of the stream, we do not need to delete it
+ SkASSERT(codec);
+ streamDeleter.detach();
+ return codec;
+ }
+ return NULL;
+}
+
+SkJpegCodec::SkJpegCodec(const SkImageInfo& srcInfo, SkStream* stream,
+ JpegDecoderMgr* decoderMgr)
+ : INHERITED(srcInfo, stream)
+ , fDecoderMgr(decoderMgr)
+{}
+
+/*
+ * Return a valid set of output dimensions for this decoder, given an input scale
+ */
+SkISize SkJpegCodec::onGetScaledDimensions(float desiredScale) const {
+ // libjpeg supports scaling by 1/1, 1/2, 1/4, and 1/8, so we will support these as well
+ long scale;
+ if (desiredScale > 0.75f) {
+ scale = 1;
+ } else if (desiredScale > 0.375f) {
+ scale = 2;
+ } else if (desiredScale > 0.1875f) {
+ scale = 4;
+ } else {
+ scale = 8;
+ }
+
+ // Set up a fake decompress struct in order to use libjpeg to calculate output dimensions
+ jpeg_decompress_struct dinfo;
+ dinfo.image_width = this->getInfo().width();
+ dinfo.image_height = this->getInfo().height();
+ dinfo.global_state = DSTATE_READY;
+ dinfo.num_components = 0;
+ dinfo.scale_num = 1;
+ dinfo.scale_denom = scale;
+ jpeg_calc_output_dimensions(&dinfo);
+
+ // Return the calculated output dimensions for the given scale
+ return SkISize::Make(dinfo.output_width, dinfo.output_height);
+}
+
+/*
+ * Checks if the conversion between the input image and the requested output
+ * image has been implemented
+ */
+static bool conversion_possible(const SkImageInfo& dst,
+ const SkImageInfo& src) {
+ // Ensure that the profile type is unchanged
+ if (dst.profileType() != src.profileType()) {
+ return false;
+ }
+
+ // Ensure that the alpha type is opaque
+ if (kOpaque_SkAlphaType != dst.alphaType()) {
+ return false;
+ }
+
+ // Always allow kN32 as the color type
+ if (kN32_SkColorType == dst.colorType()) {
+ return true;
+ }
+
+ // Otherwise require that the destination color type match our recommendation
+ return dst.colorType() == src.colorType();
+}
+
+/*
+ * 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);
+ }
+
+ // Get a pointer to the decompress info since we will use it quite frequently
+ jpeg_decompress_struct* dinfo = fDecoderMgr->dinfo();
+
+ // Set the jump location for libjpeg errors
+ if (setjmp(fDecoderMgr->getJmpBuf())) {
+ return fDecoderMgr->returnFailure("setjmp", kInvalidInput);
+ }
+
+ // Check if we can decode to the requested destination
+ 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);
+ }
+
+ // Now, given valid output dimensions, we can start the decompress
+ if (!jpeg_start_decompress(dinfo)) {
+ return fDecoderMgr->returnFailure("startDecompress", kInvalidInput);
+ }
+
+ // 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);
+ }
+ 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
+ // by using the recommendation from libjpeg.
+ const uint32_t rowsPerDecode = dinfo->rec_outbuf_height;
+ 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));
+ JSAMPLE* srcRows[4];
+ uint8_t* srcPtr = srcBuffer.get();
+ for (uint8_t i = 0; i < rowsPerDecode; i++) {
+ srcRows[i] = (JSAMPLE*) srcPtr;
+ srcPtr += srcRowBytes;
+ }
+
+ // Ensure that we loop enough times to decode all of the rows
+ // libjpeg will prevent us from reading past the bottom of the image
+ 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);
+ }
+
+ // Swizzle to output destination
+ for (uint32_t i = 0; i < rowsDecoded; i++) {
+ swizzler->next(srcRows[i]);
+ }
+
+ // If we cannot read enough rows, assume the input is incomplete
+ if (rowsDecoded < rowsPerDecode && y + rowsDecoded < dstHeight) {
+ // Fill the remainder of the image with black. This error handling
+ // behavior is unspecified but SkCodec consistently uses black as
+ // 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,
+ dstHeight - y - rowsDecoded, SK_ColorBLACK, NULL);
+
+ // Prevent libjpeg from failing on incomplete decode
+ dinfo->output_scanline = dstHeight;
+
+ // Finish the decode and indicate that the input was incomplete.
+ jpeg_finish_decompress(dinfo);
+ return fDecoderMgr->returnFailure("Incomplete image data", kIncompleteInput);
+ }
+ }
+ jpeg_finish_decompress(dinfo);
+
+ return kSuccess;
+}
diff --git a/src/codec/SkJpegCodec.h b/src/codec/SkJpegCodec.h
new file mode 100644
index 0000000000..51a741a525
--- /dev/null
+++ b/src/codec/SkJpegCodec.h
@@ -0,0 +1,99 @@
+/*
+ * Copyright 2015 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SkJpegCodec_DEFINED
+#define SkJpegCodec_DEFINED
+
+#include "SkCodec.h"
+#include "SkImageInfo.h"
+#include "SkJpegDecoderMgr.h"
+#include "SkJpegUtility.h"
+#include "SkStream.h"
+
+extern "C" {
+ #include "jpeglib.h"
+}
+
+/*
+ *
+ * This class implements the decoding for jpeg images
+ *
+ */
+class SkJpegCodec : public SkCodec {
+public:
+
+ /*
+ * Checks the start of the stream to see if the image is a jpeg
+ * Does not take ownership of the stream
+ */
+ static bool IsJpeg(SkStream*);
+
+ /*
+ * Assumes IsJpeg was called and returned true
+ * Creates a jpeg decoder
+ * Takes ownership of the stream
+ */
+ static SkCodec* NewFromStream(SkStream*);
+
+protected:
+
+ /*
+ * Recommend a set of destination dimensions given a requested scale
+ */
+ SkISize onGetScaledDimensions(float desiredScale) const override;
+
+ /*
+ * Initiates the jpeg decode
+ */
+ Result onGetPixels(const SkImageInfo& dstInfo, void* dst, size_t dstRowBytes, const Options&,
+ SkPMColor*, int*) override;
+
+ SkEncodedFormat onGetEncodedFormat() const override {
+ return kJPEG_SkEncodedFormat;
+ }
+
+private:
+
+ /*
+ * Read enough of the stream to initialize the SkJpegCodec.
+ * Returns a bool representing success or failure.
+ *
+ * @param codecOut
+ * If this returns true, and codecOut was not NULL,
+ * codecOut will be set to a new SkJpegCodec.
+ *
+ * @param decoderMgrOut
+ * If this returns true, and codecOut was NULL,
+ * decoderMgrOut must be non-NULL and decoderMgrOut will be set to a new
+ * JpegDecoderMgr pointer.
+ *
+ * @param stream
+ * Deleted on failure.
+ * codecOut will take ownership of it in the case where we created a codec.
+ * Ownership is unchanged when we set decoderMgrOut.
+ *
+ */
+ static bool ReadHeader(SkStream* stream, SkCodec** codecOut,
+ JpegDecoderMgr** decoderMgrOut);
+
+ /*
+ * Creates an instance of the decoder
+ * Called only by NewFromStream
+ *
+ * @param srcInfo contains the source width and height
+ * @param stream the encoded image data
+ * @param decoderMgr holds decompress struct, src manager, and error manager
+ * takes ownership
+ */
+ SkJpegCodec(const SkImageInfo& srcInfo, SkStream* stream, JpegDecoderMgr* decoderMgr);
+
+ SkAutoTDelete<JpegDecoderMgr> fDecoderMgr;
+
+ typedef SkCodec INHERITED;
+};
+
+#endif
diff --git a/src/codec/SkJpegDecoderMgr.cpp b/src/codec/SkJpegDecoderMgr.cpp
new file mode 100644
index 0000000000..c1f044c64f
--- /dev/null
+++ b/src/codec/SkJpegDecoderMgr.cpp
@@ -0,0 +1,109 @@
+/*
+ * Copyright 2015 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "SkJpegDecoderMgr.h"
+#include "SkJpegUtility.h"
+
+/*
+ * Print information, warning, and error messages
+ */
+static void print_message(const j_common_ptr info, const char caller[]) {
+ char buffer[JMSG_LENGTH_MAX];
+ info->err->format_message(info, buffer);
+ SkCodecPrintf("libjpeg error %d <%s> from %s\n", info->err->msg_code, buffer, caller);
+}
+
+/*
+ * Reporting functions for libjpeg
+ */
+static void emit_message(j_common_ptr info, int) {
+ print_message(info, "emit_message");
+}
+static void output_message(j_common_ptr info) {
+ print_message(info, "output_message");
+}
+
+/*
+ * Choose the size of the memory buffer on Android
+ */
+static void overwrite_mem_buffer_size(jpeg_decompress_struct* dinfo) {
+#ifdef SK_BUILD_FOR_ANDROID
+
+// Use 30 MB for devices with a large amount of system memory and 5MB otherwise
+// TODO: (msarett) This matches SkImageDecoder. Why were these values chosen?
+#ifdef ANDROID_LARGE_MEMORY_DEVICE
+ dinfo->mem->max_memory_to_use = 30 * 1024 * 1024;
+#else
+ dinfo->mem->max_memory_to_use = 5 * 1024 * 1024;
+#endif
+
+#endif // SK_BUILD_FOR_ANDROID
+}
+
+bool JpegDecoderMgr::returnFalse(const char caller[]) {
+ print_message((j_common_ptr) &fDInfo, caller);
+ return false;
+}
+
+SkCodec::Result JpegDecoderMgr::returnFailure(const char caller[], SkCodec::Result result) {
+ print_message((j_common_ptr) &fDInfo, caller);
+ return result;
+}
+
+SkColorType JpegDecoderMgr::getColorType() {
+ switch (fDInfo.jpeg_color_space) {
+ case JCS_CMYK:
+ case JCS_YCCK:
+ // libjpeg cannot convert from CMYK or YCCK to RGB.
+ // Here, we ask libjpeg to give us CMYK samples back and
+ // we will later manually convert them to RGB.
+ fDInfo.out_color_space = JCS_CMYK;
+ return kN32_SkColorType;
+ case JCS_GRAYSCALE:
+ fDInfo.out_color_space = JCS_GRAYSCALE;
+ return kGray_8_SkColorType;
+ default:
+#ifdef ANDROID_RGB
+ fDInfo.out_color_space = JCS_RGBA_8888;
+#else
+ fDInfo.out_color_space = JCS_RGB;
+#endif
+ return kN32_SkColorType;
+ }
+}
+
+JpegDecoderMgr::JpegDecoderMgr(SkStream* stream)
+ : fSrcMgr(stream)
+ , fInit(false)
+{
+ // Error manager must be set before any calls to libjeg in order to handle failures
+ fDInfo.err = jpeg_std_error(&fErrorMgr);
+ fErrorMgr.error_exit = skjpeg_err_exit;
+}
+
+void JpegDecoderMgr::init() {
+ jpeg_create_decompress(&fDInfo);
+ fInit = true;
+ fDInfo.src = &fSrcMgr;
+ overwrite_mem_buffer_size(&fDInfo);
+ fDInfo.err->emit_message = &emit_message;
+ fDInfo.err->output_message = &output_message;
+}
+
+JpegDecoderMgr::~JpegDecoderMgr() {
+ if (fInit) {
+ jpeg_destroy_decompress(&fDInfo);
+ }
+}
+
+jmp_buf& JpegDecoderMgr::getJmpBuf() {
+ return fErrorMgr.fJmpBuf;
+}
+
+jpeg_decompress_struct* JpegDecoderMgr::dinfo() {
+ return &fDInfo;
+}
diff --git a/src/codec/SkJpegDecoderMgr.h b/src/codec/SkJpegDecoderMgr.h
new file mode 100644
index 0000000000..444e693742
--- /dev/null
+++ b/src/codec/SkJpegDecoderMgr.h
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2015 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SkJpegDecoderMgr_DEFINED
+#define SkJpegDecoderMgr_DEFINED
+
+#include "SkCodec.h"
+#include "SkCodecPriv.h"
+#include "SkJpegUtility.h"
+#include "SkSwizzler.h"
+#include "SkTemplates.h"
+
+// stdio is needed for jpeglib
+#include <stdio.h>
+
+extern "C" {
+ #include "jpeglib.h"
+}
+
+class JpegDecoderMgr : SkNoncopyable {
+public:
+
+ /*
+ * Print a useful error message and return false
+ */
+ bool returnFalse(const char caller[]);
+
+ /*
+ * Print a useful error message and return a decode failure
+ */
+ SkCodec::Result returnFailure(const char caller[], SkCodec::Result result);
+
+ /*
+ * Create the decode manager
+ * Does not take ownership of stream
+ */
+ JpegDecoderMgr(SkStream* stream);
+
+ /*
+ * Initialize decompress struct
+ * Initialize the source manager
+ */
+ void init();
+
+ /*
+ * Recommend a color type based on the encoded format
+ */
+ SkColorType getColorType();
+
+ /*
+ * Free memory used by the decode manager
+ */
+ ~JpegDecoderMgr();
+
+ /*
+ * Get the jump buffer in order to set an error return point
+ */
+ jmp_buf& getJmpBuf();
+
+ /*
+ * Get function for the decompress info struct
+ */
+ jpeg_decompress_struct* dinfo();
+
+private:
+
+ jpeg_decompress_struct fDInfo;
+ skjpeg_source_mgr fSrcMgr;
+ skjpeg_error_mgr fErrorMgr;
+ bool fInit;
+};
+
+#endif
diff --git a/src/codec/SkJpegUtility.cpp b/src/codec/SkJpegUtility.cpp
new file mode 100644
index 0000000000..5dccf942bc
--- /dev/null
+++ b/src/codec/SkJpegUtility.cpp
@@ -0,0 +1,89 @@
+/*
+ * Copyright 2015 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "SkCodecPriv.h"
+#include "SkJpegUtility.h"
+
+/*
+ * Initialize the source manager
+ */
+static void sk_init_source(j_decompress_ptr dinfo) {
+ skjpeg_source_mgr* src = (skjpeg_source_mgr*) dinfo->src;
+ src->next_input_byte = (const JOCTET*) src->fBuffer;
+ src->bytes_in_buffer = 0;
+}
+
+/*
+ * Fill the input buffer from the stream
+ */
+static boolean sk_fill_input_buffer(j_decompress_ptr dinfo) {
+ skjpeg_source_mgr* src = (skjpeg_source_mgr*) dinfo->src;
+ size_t bytes = src->fStream->read(src->fBuffer, skjpeg_source_mgr::kBufferSize);
+
+ // libjpeg is still happy with a less than full read, as long as the result is non-zero
+ if (bytes == 0) {
+ return false;
+ }
+
+ src->next_input_byte = (const JOCTET*) src->fBuffer;
+ src->bytes_in_buffer = bytes;
+ return true;
+}
+
+/*
+ * Skip a certain number of bytes in the stream
+ */
+static void sk_skip_input_data(j_decompress_ptr dinfo, long numBytes) {
+ skjpeg_source_mgr* src = (skjpeg_source_mgr*) dinfo->src;
+ size_t bytes = (size_t) numBytes;
+
+ if (bytes > src->bytes_in_buffer) {
+ size_t bytesToSkip = bytes - src->bytes_in_buffer;
+ if (bytesToSkip != src->fStream->skip(bytesToSkip)) {
+ SkCodecPrintf("Failure to skip.\n");
+ dinfo->err->error_exit((j_common_ptr) dinfo);
+ return;
+ }
+
+ src->next_input_byte = (const JOCTET*) src->fBuffer;
+ src->bytes_in_buffer = 0;
+ } else {
+ src->next_input_byte += numBytes;
+ src->bytes_in_buffer -= numBytes;
+ }
+}
+
+/*
+ * We do not need to do anything to terminate our stream
+ */
+static void sk_term_source(j_decompress_ptr dinfo)
+{}
+
+/*
+ * Constructor for the source manager that we provide to libjpeg
+ * We provide skia implementations of all of the stream processing functions required by libjpeg
+ */
+skjpeg_source_mgr::skjpeg_source_mgr(SkStream* stream)
+ : fStream(stream)
+{
+ init_source = sk_init_source;
+ fill_input_buffer = sk_fill_input_buffer;
+ skip_input_data = sk_skip_input_data;
+ resync_to_restart = jpeg_resync_to_restart;
+ term_source = sk_term_source;
+}
+
+/*
+ * Call longjmp to continue execution on an error
+ */
+void skjpeg_err_exit(j_common_ptr dinfo) {
+ // Simply return to Skia client code
+ // JpegDecoderMgr will take care of freeing memory
+ skjpeg_error_mgr* error = (skjpeg_error_mgr*) dinfo->err;
+ (*error->output_message) (dinfo);
+ longjmp(error->fJmpBuf, 1);
+}
diff --git a/src/codec/SkJpegUtility.h b/src/codec/SkJpegUtility.h
new file mode 100644
index 0000000000..42cd7af760
--- /dev/null
+++ b/src/codec/SkJpegUtility.h
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2015 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+
+#ifndef SkJpegUtility_DEFINED
+#define SkJpegUtility_DEFINED
+
+#include "SkStream.h"
+
+#include <setjmp.h>
+// stdio is needed for jpeglib
+#include <stdio.h>
+
+extern "C" {
+ #include "jpeglib.h"
+ #include "jerror.h"
+}
+
+/*
+ * Error handling struct
+ */
+struct skjpeg_error_mgr : jpeg_error_mgr {
+ jmp_buf fJmpBuf;
+};
+
+/*
+ * Error handling function
+ */
+void skjpeg_err_exit(j_common_ptr cinfo);
+
+/*
+ * Source handling struct for that allows libjpeg to use our stream object
+ */
+struct skjpeg_source_mgr : jpeg_source_mgr {
+ skjpeg_source_mgr(SkStream* stream);
+
+ SkStream* fStream; // unowned
+ enum {
+ // TODO (msarett): Experiment with different buffer sizes.
+ // This size was chosen because it matches SkImageDecoder.
+ kBufferSize = 1024
+ };
+ uint8_t fBuffer[kBufferSize];
+};
+
+#endif
diff --git a/src/codec/SkSwizzler.cpp b/src/codec/SkSwizzler.cpp
index 791363381f..294229c37c 100644
--- a/src/codec/SkSwizzler.cpp
+++ b/src/codec/SkSwizzler.cpp
@@ -68,6 +68,8 @@ static SkSwizzler::ResultAlpha swizzle_small_index_to_n32(
return COMPUTE_RESULT_ALPHA;
}
+// kIndex
+
static SkSwizzler::ResultAlpha swizzle_index_to_index(
void* SK_RESTRICT dstRow, const uint8_t* SK_RESTRICT src, int width,
int bytesPerPixel, int y, const SkPMColor ctable[]) {
@@ -84,8 +86,6 @@ static SkSwizzler::ResultAlpha swizzle_index_to_index(
return COMPUTE_RESULT_ALPHA;
}
-// kIndex
-
static SkSwizzler::ResultAlpha swizzle_index_to_n32(
void* SK_RESTRICT dstRow, const uint8_t* SK_RESTRICT src, int width,
int bytesPerPixel, int y, const SkPMColor ctable[]) {
@@ -118,6 +118,28 @@ static SkSwizzler::ResultAlpha swizzle_index_to_n32_skipZ(
#undef A32_MASK_IN_PLACE
+// kGray
+
+static SkSwizzler::ResultAlpha swizzle_gray_to_n32(
+ void* SK_RESTRICT dstRow, const uint8_t* SK_RESTRICT src, int width,
+ int bytesPerPixel, int y, const SkPMColor ctable[]) {
+
+ SkPMColor* SK_RESTRICT dst = (SkPMColor*)dstRow;
+ for (int x = 0; x < width; x++) {
+ dst[x] = SkPackARGB32NoCheck(0xFF, src[x], src[x], src[x]);
+ }
+ return SkSwizzler::kOpaque_ResultAlpha;
+}
+
+static SkSwizzler::ResultAlpha swizzle_gray_to_gray(
+ void* SK_RESTRICT dstRow, const uint8_t* SK_RESTRICT src, int width,
+ int bytesPerPixel, int y, const SkPMColor ctable[]) {
+ memcpy(dstRow, src, width);
+ return SkSwizzler::kOpaque_ResultAlpha;
+}
+
+// kBGRX
+
static SkSwizzler::ResultAlpha swizzle_bgrx_to_n32(
void* SK_RESTRICT dstRow, const uint8_t* SK_RESTRICT src, int width,
int bytesPerPixel, int y, const SkPMColor ctable[]) {
@@ -299,6 +321,17 @@ SkSwizzler* SkSwizzler::CreateSwizzler(SkSwizzler::SrcConfig sc,
break;
}
break;
+ case kGray:
+ switch (info.colorType()) {
+ case kN32_SkColorType:
+ proc = &swizzle_gray_to_n32;
+ break;
+ case kGray_8_SkColorType:
+ proc = &swizzle_gray_to_gray;
+ default:
+ break;
+ }
+ break;
case kBGR:
case kBGRX:
switch (info.colorType()) {
@@ -464,6 +497,15 @@ void SkSwizzler::Fill(void* dstStartRow, const SkImageInfo& dstInfo, size_t dstR
SkASSERT(colorOrIndex == (uint8_t) colorOrIndex);
memset(dstStartRow, colorOrIndex, bytesToFill);
break;
+ case kGray_8_SkColorType:
+ // If the destination is kGray, the caller passes in an 8-bit color.
+ // We will not assert that the high bits of colorOrIndex must be zeroed.
+ // This allows us to take advantage of the fact that the low 8 bits of an
+ // SKPMColor may be a valid a grayscale color. For example, the low 8
+ // bits of SK_ColorBLACK are identical to the grayscale representation
+ // for black.
+ memset(dstStartRow, (uint8_t) colorOrIndex, bytesToFill);
+ break;
default:
SkCodecPrintf("Error: Unsupported dst color type for fill(). Doing nothing.\n");
SkASSERT(false);
diff --git a/src/codec/SkSwizzler.h b/src/codec/SkSwizzler.h
index 8a471e9b73..e3a38cbbf4 100644
--- a/src/codec/SkSwizzler.h
+++ b/src/codec/SkSwizzler.h
@@ -155,6 +155,8 @@ public:
* the colorTable. i.e. each 4-byte pixel will be set to
* colorTable[(uint8_t) colorOrIndex].
*
+ * If dstInfo.colorType() is kGray, colorOrIndex is always treated as an 8-bit color.
+ *
* Other SkColorTypes are not supported.
*
*/
diff --git a/tests/CodexTest.cpp b/tests/CodexTest.cpp
index a771716a24..d7142518a3 100644
--- a/tests/CodexTest.cpp
+++ b/tests/CodexTest.cpp
@@ -104,6 +104,13 @@ DEF_TEST(Codec, r) {
check(r, "color_wheel.gif", SkISize::Make(128, 128), false);
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);
+
// PNG
check(r, "arrow.png", SkISize::Make(187, 312), true);
check(r, "baby_tux.png", SkISize::Make(240, 246), true);
@@ -148,3 +155,46 @@ DEF_TEST(Codec_leaks, r) {
test_invalid_stream(r, emptyIco, sizeof(emptyIco));
test_invalid_stream(r, emptyGif, sizeof(emptyGif));
}
+
+static void test_dimensions(skiatest::Reporter* r, const char path[]) {
+ // Create the codec from the resource file
+ SkAutoTDelete<SkStream> stream(resource(path));
+ if (!stream) {
+ SkDebugf("Missing resource '%s'\n", path);
+ return;
+ }
+ SkAutoTDelete<SkCodec> codec(SkCodec::NewFromStream(stream.detach()));
+ if (!codec) {
+ ERRORF(r, "Unable to create codec '%s'", path);
+ return;
+ }
+
+ // Check that the decode is successful for a variety of scales
+ for (float scale = -0.05f; scale < 2.0f; scale += 0.05f) {
+ // Scale the output dimensions
+ SkISize scaledDims = codec->getScaledDimensions(scale);
+ SkImageInfo scaledInfo = codec->getInfo().makeWH(scaledDims.width(), scaledDims.height());
+
+ // Set up for the decode
+ size_t rowBytes = scaledDims.width() * sizeof(SkPMColor);
+ size_t totalBytes = scaledInfo.getSafeSize(rowBytes);
+ SkAutoTMalloc<SkPMColor> pixels(totalBytes);
+
+ SkImageGenerator::Result result =
+ codec->getPixels(scaledInfo, pixels.get(), rowBytes, NULL, NULL, NULL);
+ REPORTER_ASSERT(r, SkImageGenerator::kSuccess == result);
+ }
+}
+
+// Ensure that onGetScaledDimensions returns valid image dimensions to use for decodes
+DEF_TEST(Codec_Dimensions, r) {
+ // JPG
+ test_dimensions(r, "CMYK.jpg");
+ test_dimensions(r, "color_wheel.jpg");
+ test_dimensions(r, "grayscale.jpg");
+ test_dimensions(r, "mandrill_512_q075.jpg");
+ test_dimensions(r, "randPixels.jpg");
+}
+
+
+
diff --git a/tests/SwizzlerTest.cpp b/tests/SwizzlerTest.cpp
index 147dfaa83d..7ed1c390fb 100644
--- a/tests/SwizzlerTest.cpp
+++ b/tests/SwizzlerTest.cpp
@@ -42,13 +42,24 @@ static void check_fill(skiatest::Reporter* r,
// Ensure that the pixels are filled properly
// The bots should catch any memory corruption
uint8_t* indexPtr = imageData + startRow * rowBytes;
+ uint8_t* grayPtr = indexPtr;
uint32_t* colorPtr = (uint32_t*) indexPtr;
for (uint32_t y = startRow; y <= endRow; y++) {
for (int32_t x = 0; x < imageInfo.width(); x++) {
- if (kIndex_8_SkColorType == imageInfo.colorType()) {
- REPORTER_ASSERT(r, kFillIndex == indexPtr[x]);
- } else {
- REPORTER_ASSERT(r, kFillColor == colorPtr[x]);
+ switch (imageInfo.colorType()) {
+ case kIndex_8_SkColorType:
+ REPORTER_ASSERT(r, kFillIndex == indexPtr[x]);
+ break;
+ case kN32_SkColorType:
+ REPORTER_ASSERT(r, kFillColor == colorPtr[x]);
+ break;
+ case kGray_8_SkColorType:
+ // We always fill kGray with black
+ REPORTER_ASSERT(r, (uint8_t) kFillColor == grayPtr[x]);
+ break;
+ default:
+ REPORTER_ASSERT(r, false);
+ break;
}
}
indexPtr += rowBytes;
@@ -82,6 +93,7 @@ DEF_TEST(SwizzlerFill, r) {
const SkImageInfo colorInfo = SkImageInfo::MakeN32(width, height,
kUnknown_SkAlphaType);
const SkImageInfo indexInfo = colorInfo.makeColorType(kIndex_8_SkColorType);
+ const SkImageInfo grayInfo = colorInfo.makeColorType(kGray_8_SkColorType);
for (uint32_t padding : paddings) {
@@ -89,6 +101,7 @@ DEF_TEST(SwizzlerFill, r) {
size_t colorRowBytes = SkColorTypeBytesPerPixel(kN32_SkColorType) * width +
padding;
size_t indexRowBytes = width + padding;
+ size_t grayRowBytes = indexRowBytes;
// If there is padding, we can invent an offset to change the memory alignment
for (uint32_t offset = 0; offset <= padding; offset++) {
@@ -108,6 +121,10 @@ DEF_TEST(SwizzlerFill, r) {
// Fill with an index
check_fill(r, indexInfo, startRow, endRow, indexRowBytes, offset,
kFillIndex, NULL);
+
+ // Fill a grayscale image
+ check_fill(r, grayInfo, startRow, endRow, grayRowBytes, offset,
+ kFillColor, NULL);
}
}
}