aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--cmake/CMakeLists.txt1
-rw-r--r--gm/downsamplebitmap.cpp8
-rw-r--r--gm/etc1bitmap.cpp223
-rw-r--r--gyp/core.gypi1
-rw-r--r--gyp/images.gyp29
-rw-r--r--include/core/SkCanvas.h2
-rw-r--r--include/core/SkImageDecoder.h413
-rw-r--r--include/core/SkImageEncoder.h8
-rw-r--r--include/core/SkPicture.h5
-rw-r--r--include/core/SkPngChunkReader.h2
-rw-r--r--include/core/SkWriteBuffer.h1
-rw-r--r--src/android/SkBitmapRegionDecoder.cpp1
-rw-r--r--src/core/SkBigPicture.h2
-rw-r--r--src/core/SkLayerInfo.h2
-rw-r--r--src/gpu/GrLayerCache.h1
-rw-r--r--src/images/SkForceLinking.cpp26
-rw-r--r--src/images/SkImageDecoder.cpp204
-rw-r--r--src/images/SkImageDecoder_FactoryDefault.cpp9
-rw-r--r--src/images/SkImageDecoder_FactoryRegistrar.cpp63
-rw-r--r--src/images/SkImageDecoder_astc.cpp203
-rw-r--r--src/images/SkImageDecoder_ktx.cpp242
-rw-r--r--src/images/SkImageDecoder_libbmp.cpp166
-rw-r--r--src/images/SkImageDecoder_libgif.cpp541
-rw-r--r--src/images/SkImageDecoder_libico.cpp456
-rw-r--r--src/images/SkImageDecoder_libjpeg.cpp757
-rw-r--r--src/images/SkImageDecoder_libpng.cpp683
-rw-r--r--src/images/SkImageDecoder_libwebp.cpp314
-rw-r--r--src/images/SkImageDecoder_pkm.cpp128
-rw-r--r--src/images/SkImageDecoder_wbmp.cpp173
-rw-r--r--src/images/SkJpegUtility.cpp102
-rw-r--r--src/images/SkJpegUtility.h18
-rw-r--r--src/images/SkScaledBitmapSampler.cpp877
-rw-r--r--src/images/SkScaledBitmapSampler.h107
-rw-r--r--src/images/bmpdecoderhelper.cpp369
-rw-r--r--src/images/bmpdecoderhelper.h116
-rw-r--r--src/ports/SkImageDecoder_CG.cpp255
-rw-r--r--src/ports/SkImageDecoder_WIC.cpp232
-rw-r--r--src/ports/SkImageDecoder_empty.cpp63
38 files changed, 6756 insertions, 47 deletions
diff --git a/cmake/CMakeLists.txt b/cmake/CMakeLists.txt
index b375e59f45..9fc4ea5a42 100644
--- a/cmake/CMakeLists.txt
+++ b/cmake/CMakeLists.txt
@@ -200,6 +200,7 @@ if (PNG_FOUND)
add_definitions(-DSK_CODEC_DECODES_PNG)
else()
remove_srcs(../src/images/*png*)
+ remove_srcs(../src/images/*ico*)
remove_srcs(../src/codec/*Png*)
remove_srcs(../src/codec/*Ico*)
endif()
diff --git a/gm/downsamplebitmap.cpp b/gm/downsamplebitmap.cpp
index 598382484f..a99bae0cd9 100644
--- a/gm/downsamplebitmap.cpp
+++ b/gm/downsamplebitmap.cpp
@@ -183,15 +183,23 @@ class DownsampleBitmapImageGM: public DownsampleBitmapGM {
DEF_GM( return new DownsampleBitmapTextGM(72, kHigh_SkFilterQuality); )
DEF_GM( return new DownsampleBitmapCheckerboardGM(512,256, kHigh_SkFilterQuality); )
DEF_GM( return new DownsampleBitmapImageGM("mandrill_512.png", kHigh_SkFilterQuality); )
+DEF_GM( return new DownsampleBitmapImageGM("mandrill_132x132_12x12.astc",
+ kHigh_SkFilterQuality); )
DEF_GM( return new DownsampleBitmapTextGM(72, kMedium_SkFilterQuality); )
DEF_GM( return new DownsampleBitmapCheckerboardGM(512,256, kMedium_SkFilterQuality); )
DEF_GM( return new DownsampleBitmapImageGM("mandrill_512.png", kMedium_SkFilterQuality); )
+DEF_GM( return new DownsampleBitmapImageGM("mandrill_132x132_12x12.astc",
+ kMedium_SkFilterQuality); )
DEF_GM( return new DownsampleBitmapTextGM(72, kLow_SkFilterQuality); )
DEF_GM( return new DownsampleBitmapCheckerboardGM(512,256, kLow_SkFilterQuality); )
DEF_GM( return new DownsampleBitmapImageGM("mandrill_512.png", kLow_SkFilterQuality); )
+DEF_GM( return new DownsampleBitmapImageGM("mandrill_132x132_12x12.astc",
+ kLow_SkFilterQuality); )
DEF_GM( return new DownsampleBitmapTextGM(72, kNone_SkFilterQuality); )
DEF_GM( return new DownsampleBitmapCheckerboardGM(512,256, kNone_SkFilterQuality); )
DEF_GM( return new DownsampleBitmapImageGM("mandrill_512.png", kNone_SkFilterQuality); )
+DEF_GM( return new DownsampleBitmapImageGM("mandrill_132x132_12x12.astc",
+ kNone_SkFilterQuality); )
diff --git a/gm/etc1bitmap.cpp b/gm/etc1bitmap.cpp
new file mode 100644
index 0000000000..9d47999151
--- /dev/null
+++ b/gm/etc1bitmap.cpp
@@ -0,0 +1,223 @@
+/*
+ * Copyright 2014 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "gm.h"
+
+#include "Resources.h"
+#include "SkCanvas.h"
+#include "SkData.h"
+#include "SkImage.h"
+#include "SkImageGenerator.h"
+#include "SkOSFile.h"
+#include "SkTemplates.h"
+
+#ifndef SK_IGNORE_ETC1_SUPPORT
+
+#include "etc1.h"
+
+/**
+ * Remove the last row and column of ETC1 blocks, effectively
+ * making a texture that started as power of two into a texture
+ * that is no longer power of two...
+ */
+bool slice_etc1_data(void *data, int* width, int* height) {
+
+ // First, parse the data and get to it...
+ etc1_byte *origData = reinterpret_cast<etc1_byte *>(data);
+ if (!etc1_pkm_is_valid(origData)) {
+ return false;
+ }
+
+ int origW = etc1_pkm_get_width(origData);
+ int origH = etc1_pkm_get_height(origData);
+
+ int blockWidth = (origW + 3) >> 2;
+ int blockHeight = (origH + 3) >> 2;
+
+ // Make sure that we have blocks to trim off..
+ if (blockWidth < 2 || blockHeight < 2) {
+ return false;
+ }
+
+ int newWidth = (blockWidth - 1) << 2;
+ int newHeight = (blockHeight - 1) << 2;
+
+ size_t newDataSz = etc1_get_encoded_data_size(newWidth, newHeight) + ETC_PKM_HEADER_SIZE;
+ SkAutoTMalloc<etc1_byte> am(newDataSz);
+
+ etc1_byte* newData = am.get();
+
+ etc1_pkm_format_header(newData, newWidth, newHeight);
+ newData += ETC_PKM_HEADER_SIZE;
+ origData += ETC_PKM_HEADER_SIZE;
+
+ for (int j = 0; j < blockHeight - 1; ++j) {
+ memcpy(newData, origData, (blockWidth - 1)*ETC1_ENCODED_BLOCK_SIZE);
+ origData += blockWidth*ETC1_ENCODED_BLOCK_SIZE;
+ newData += (blockWidth - 1)*ETC1_ENCODED_BLOCK_SIZE;
+ }
+
+ // Stick the data back whence it came
+ memcpy(data, am.get(), newDataSz);
+ *width = newWidth;
+ *height = newHeight;
+
+ return true;
+}
+#endif // SK_IGNORE_ETC1_SUPPORT
+
+namespace skiagm {
+
+/**
+ * Test decoding an image from a PKM or KTX file and then
+ * from compressed ETC1 data.
+ */
+class ETC1BitmapGM : public GM {
+public:
+ ETC1BitmapGM() { }
+ virtual ~ETC1BitmapGM() { }
+
+protected:
+ SkString onShortName() override {
+ SkString str = SkString("etc1bitmap_");
+ str.append(this->fileExtension());
+ return str;
+ }
+
+ SkISize onISize() override {
+ return SkISize::Make(128, 128);
+ }
+
+ virtual SkString fileExtension() const = 0;
+
+ void onDraw(SkCanvas* canvas) override {
+ SkBitmap bm;
+ SkString filename = GetResourcePath("mandrill_128.");
+ filename.append(this->fileExtension());
+ sk_sp<SkData> fileData(SkData::MakeFromFileName(filename.c_str()));
+ if (nullptr == fileData) {
+ SkDebugf("Could not open the file. Did you forget to set the resourcePath?\n");
+ return;
+ }
+
+ sk_sp<SkImage> image(SkImage::MakeFromEncoded(std::move(fileData)));
+ if (nullptr == image) {
+ SkDebugf("Could not decode the ETC file. ETC may not be included in this platform.\n");
+ return;
+ }
+ canvas->drawImage(image, 0, 0);
+ }
+
+private:
+ typedef GM INHERITED;
+};
+
+// This class specializes ETC1BitmapGM to load the mandrill_128.pkm file.
+class ETC1Bitmap_PKM_GM : public ETC1BitmapGM {
+public:
+ ETC1Bitmap_PKM_GM() : ETC1BitmapGM() { }
+ virtual ~ETC1Bitmap_PKM_GM() { }
+
+protected:
+
+ SkString fileExtension() const override { return SkString("pkm"); }
+
+private:
+ typedef ETC1BitmapGM INHERITED;
+};
+
+// This class specializes ETC1BitmapGM to load the mandrill_128.ktx file.
+class ETC1Bitmap_KTX_GM : public ETC1BitmapGM {
+public:
+ ETC1Bitmap_KTX_GM() : ETC1BitmapGM() { }
+ virtual ~ETC1Bitmap_KTX_GM() { }
+
+protected:
+
+ SkString fileExtension() const override { return SkString("ktx"); }
+
+private:
+ typedef ETC1BitmapGM INHERITED;
+};
+
+// This class specializes ETC1BitmapGM to load the mandrill_128.r11.ktx file.
+class ETC1Bitmap_R11_KTX_GM : public ETC1BitmapGM {
+public:
+ ETC1Bitmap_R11_KTX_GM() : ETC1BitmapGM() { }
+ virtual ~ETC1Bitmap_R11_KTX_GM() { }
+
+protected:
+
+ SkString fileExtension() const override { return SkString("r11.ktx"); }
+
+private:
+ typedef ETC1BitmapGM INHERITED;
+};
+
+#ifndef SK_IGNORE_ETC1_SUPPORT
+/**
+ * Test decoding an image from a PKM file and then
+ * from non-power-of-two compressed ETC1 data. First slice
+ * off a row and column of blocks in order to make it non-power
+ * of two.
+ */
+class ETC1Bitmap_NPOT_GM : public GM {
+public:
+ ETC1Bitmap_NPOT_GM() { }
+ virtual ~ETC1Bitmap_NPOT_GM() { }
+
+protected:
+ SkString onShortName() override {
+ return SkString("etc1bitmap_npot");
+ }
+
+ SkISize onISize() override {
+ return SkISize::Make(124, 124);
+ }
+
+ void onDraw(SkCanvas* canvas) override {
+ SkBitmap bm;
+ SkString pkmFilename = GetResourcePath("mandrill_128.pkm");
+ SkAutoDataUnref fileData(SkData::NewFromFileName(pkmFilename.c_str()));
+ if (nullptr == fileData) {
+ SkDebugf("Could not open the file. Did you forget to set the resourcePath?\n");
+ return;
+ }
+
+ SkAutoMalloc am(fileData->size());
+ memcpy(am.get(), fileData->data(), fileData->size());
+
+ int width, height;
+ if (!slice_etc1_data(am.get(), &width, &height)) {
+ SkDebugf("ETC1 Data is poorly formatted.\n");
+ return;
+ }
+
+ SkASSERT(124 == width);
+ SkASSERT(124 == height);
+
+ size_t dataSz = etc1_get_encoded_data_size(width, height) + ETC_PKM_HEADER_SIZE;
+ sk_sp<SkData> nonPOTData(SkData::MakeWithCopy(am.get(), dataSz));
+ canvas->drawImage(SkImage::MakeFromEncoded(std::move(nonPOTData)).get(), 0, 0);
+ }
+
+private:
+ typedef GM INHERITED;
+};
+#endif // SK_IGNORE_ETC1_SUPPORT
+
+} // namespace skiagm
+
+//////////////////////////////////////////////////////////////////////////////
+
+DEF_GM(return new skiagm::ETC1Bitmap_PKM_GM;)
+DEF_GM(return new skiagm::ETC1Bitmap_KTX_GM;)
+DEF_GM(return new skiagm::ETC1Bitmap_R11_KTX_GM;)
+
+#ifndef SK_IGNORE_ETC1_SUPPORT
+DEF_GM(return new skiagm::ETC1Bitmap_NPOT_GM;)
+#endif // SK_IGNORE_ETC1_SUPPORT
diff --git a/gyp/core.gypi b/gyp/core.gypi
index 8d558e1d64..443029d49e 100644
--- a/gyp/core.gypi
+++ b/gyp/core.gypi
@@ -360,6 +360,7 @@
'<(skia_include_path)/core/SkFontStyle.h',
'<(skia_include_path)/core/SkGraphics.h',
'<(skia_include_path)/core/SkImage.h',
+ '<(skia_include_path)/core/SkImageDecoder.h',
'<(skia_include_path)/core/SkImageEncoder.h',
'<(skia_include_path)/core/SkImageFilter.h',
'<(skia_include_path)/core/SkImageInfo.h',
diff --git a/gyp/images.gyp b/gyp/images.gyp
index 53f505d925..8c5b0a7d1b 100644
--- a/gyp/images.gyp
+++ b/gyp/images.gyp
@@ -31,17 +31,36 @@
],
'sources': [
'../include/images/SkForceLinking.h',
+ '../src/images/SkJpegUtility.h',
'../include/images/SkMovie.h',
'../include/images/SkPageFlipper.h',
+ '../src/images/bmpdecoderhelper.cpp',
+ '../src/images/bmpdecoderhelper.h',
+
'../src/images/SkForceLinking.cpp',
+ '../src/images/SkImageDecoder.cpp',
'../src/images/SkImageDecoder_FactoryDefault.cpp',
+ '../src/images/SkImageDecoder_FactoryRegistrar.cpp',
- # If encoders are added/removed to/from (all/individual)
+ # If decoders are added/removed to/from (all/individual)
# platform(s), be sure to update SkForceLinking.cpp
# so the right decoders will be forced to link.
+ # IMPORTANT: The build order of the SkImageDecoder_*.cpp files
+ # defines the order image decoders are tested when decoding a
+ # stream. The last decoder is the first one tested, so the .cpp
+ # files should be in listed in order from the least likely to be
+ # used, to the most likely (jpeg and png should be the last two
+ # for instance.) As a result, they are deliberately not in
+ # alphabetical order.
+ '../src/images/SkImageDecoder_wbmp.cpp',
+ '../src/images/SkImageDecoder_pkm.cpp',
'../src/images/SkImageDecoder_ktx.cpp',
+ '../src/images/SkImageDecoder_astc.cpp',
+ '../src/images/SkImageDecoder_libbmp.cpp',
+ '../src/images/SkImageDecoder_libgif.cpp',
+ '../src/images/SkImageDecoder_libico.cpp',
'../src/images/SkImageDecoder_libwebp.cpp',
'../src/images/SkImageDecoder_libjpeg.cpp',
'../src/images/SkImageDecoder_libpng.cpp',
@@ -53,6 +72,8 @@
'../src/images/SkMovie.cpp',
'../src/images/SkMovie_gif.cpp',
'../src/images/SkPageFlipper.cpp',
+ '../src/images/SkScaledBitmapSampler.cpp',
+ '../src/images/SkScaledBitmapSampler.h',
'../src/ports/SkImageDecoder_CG.cpp',
'../src/ports/SkImageDecoder_WIC.cpp',
@@ -60,6 +81,8 @@
'conditions': [
[ 'skia_os == "win"', {
'sources!': [
+ '../src/images/SkImageDecoder_FactoryDefault.cpp',
+ '../src/images/SkImageDecoder_libgif.cpp',
'../src/images/SkImageDecoder_libpng.cpp',
'../src/images/SkMovie_gif.cpp',
],
@@ -78,7 +101,9 @@
}],
[ 'skia_os in ["mac", "ios"]', {
'sources!': [
+ '../src/images/SkImageDecoder_FactoryDefault.cpp',
'../src/images/SkImageDecoder_libpng.cpp',
+ '../src/images/SkImageDecoder_libgif.cpp',
'../src/images/SkMovie_gif.cpp',
],
},{ #else if skia_os != mac
@@ -104,7 +129,9 @@
# The android framework disables these decoders as they are of little use to
# Java applications that can't take advantage of the compressed formats.
'sources!': [
+ '../src/images/SkImageDecoder_pkm.cpp',
'../src/images/SkImageDecoder_ktx.cpp',
+ '../src/images/SkImageDecoder_astc.cpp',
],
}],
],
diff --git a/include/core/SkCanvas.h b/include/core/SkCanvas.h
index 95a5244da6..d551031739 100644
--- a/include/core/SkCanvas.h
+++ b/include/core/SkCanvas.h
@@ -11,7 +11,6 @@
#include "SkTypes.h"
#include "SkBitmap.h"
#include "SkDeque.h"
-#include "SkImage.h"
#include "SkPaint.h"
#include "SkRefCnt.h"
#include "SkRegion.h"
@@ -27,6 +26,7 @@ class SkData;
class SkDraw;
class SkDrawable;
class SkDrawFilter;
+class SkImage;
class SkImageFilter;
class SkMetaData;
class SkPath;
diff --git a/include/core/SkImageDecoder.h b/include/core/SkImageDecoder.h
new file mode 100644
index 0000000000..7a90964b13
--- /dev/null
+++ b/include/core/SkImageDecoder.h
@@ -0,0 +1,413 @@
+/*
+ * Copyright 2006 The Android Open Source Project
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SkImageDecoder_DEFINED
+#define SkImageDecoder_DEFINED
+
+#include "SkBitmap.h"
+#include "SkImage.h"
+#include "SkPngChunkReader.h"
+#include "SkRect.h"
+#include "SkRefCnt.h"
+#include "SkTRegistry.h"
+#include "SkTypes.h"
+
+class SkStream;
+class SkStreamRewindable;
+
+/** \class SkImageDecoder
+
+ DEPRECATED Please use SkImage::NewFromEncoded() or SkImageGenerator::NewFromEncoded().
+
+ Base class for decoding compressed images into a SkBitmap
+*/
+class SkImageDecoder : SkNoncopyable {
+public:
+ virtual ~SkImageDecoder();
+
+ // TODO (scroggo): Merge with SkEncodedFormat
+ enum Format {
+ kUnknown_Format,
+ kBMP_Format,
+ kGIF_Format,
+ kICO_Format,
+ kJPEG_Format,
+ kPNG_Format,
+ kWBMP_Format,
+ kWEBP_Format,
+ kPKM_Format,
+ kKTX_Format,
+ kASTC_Format,
+
+ kLastKnownFormat = kKTX_Format,
+ };
+
+ /** Return the format of image this decoder can decode. If this decoder can decode multiple
+ formats, kUnknown_Format will be returned.
+ */
+ 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.
+ */
+ static Format GetStreamFormat(SkStreamRewindable*);
+
+ /** Return a readable string of the Format provided.
+ */
+ static const char* GetFormatName(Format);
+
+ /** Return a readable string of the value returned by getFormat().
+ */
+ const char* getFormatName() const;
+
+ /** Whether the decoder should skip writing zeroes to output if possible.
+ */
+ bool getSkipWritingZeroes() const { return fSkipWritingZeroes; }
+
+ /** Set to true if the decoder should skip writing any zeroes when
+ creating the output image.
+ This is a hint that may not be respected by the decoder.
+ It should only be used if it is known that the memory to write
+ to has already been set to 0; otherwise the resulting image will
+ have garbage.
+ This is ideal for images that contain a lot of completely transparent
+ pixels, but may be a performance hit for an image that has only a
+ few transparent pixels.
+ The default is false.
+ */
+ void setSkipWritingZeroes(bool skip) { fSkipWritingZeroes = skip; }
+
+ /** Returns true if the decoder should try to dither the resulting image.
+ The default setting is true.
+ */
+ bool getDitherImage() const { return fDitherImage; }
+
+ /** Set to true if the the decoder should try to dither the resulting image.
+ The default setting is true.
+ */
+ void setDitherImage(bool dither) { fDitherImage = dither; }
+
+ /** Returns true if the decoder should try to decode the
+ resulting image to a higher quality even at the expense of
+ the decoding speed.
+ */
+ bool getPreferQualityOverSpeed() const { return fPreferQualityOverSpeed; }
+
+ /** Set to true if the the decoder should try to decode the
+ resulting image to a higher quality even at the expense of
+ the decoding speed.
+ */
+ void setPreferQualityOverSpeed(bool qualityOverSpeed) {
+ fPreferQualityOverSpeed = qualityOverSpeed;
+ }
+
+ /** Set to true to require the decoder to return a bitmap with unpremultiplied
+ colors. The default is false, meaning the resulting bitmap will have its
+ colors premultiplied.
+ NOTE: Passing true to this function may result in a bitmap which cannot
+ be properly used by Skia.
+ */
+ void setRequireUnpremultipliedColors(bool request) {
+ fRequireUnpremultipliedColors = request;
+ }
+
+ /** Returns true if the decoder will only return bitmaps with unpremultiplied
+ colors.
+ */
+ bool getRequireUnpremultipliedColors() const { return fRequireUnpremultipliedColors; }
+
+ SkPngChunkReader* getPeeker() const { return fPeeker; }
+ SkPngChunkReader* setPeeker(SkPngChunkReader*);
+
+ /**
+ * By default, the codec will try to comply with the "pref" colortype
+ * that is passed to decode() or decodeSubset(). However, this can be called
+ * to override that, causing the codec to try to match the src depth instead
+ * (as shown below).
+ *
+ * src_8Index -> kIndex_8_SkColorType
+ * src_8Gray -> kN32_SkColorType
+ * src_8bpc -> kN32_SkColorType
+ */
+ void setPreserveSrcDepth(bool preserve) {
+ fPreserveSrcDepth = preserve;
+ }
+
+ SkBitmap::Allocator* getAllocator() const { return fAllocator; }
+ SkBitmap::Allocator* setAllocator(SkBitmap::Allocator*);
+
+ // sample-size, if set to > 1, tells the decoder to return a smaller than
+ // original bitmap, sampling 1 pixel for every size pixels. e.g. if sample
+ // size is set to 3, then the returned bitmap will be 1/3 as wide and high,
+ // and will contain 1/9 as many pixels as the original.
+ // Note: this is a hint, and the codec may choose to ignore this, or only
+ // approximate the sample size.
+ int getSampleSize() const { return fSampleSize; }
+ void setSampleSize(int size);
+
+ /** Reset the sampleSize to its default of 1
+ */
+ void resetSampleSize() { this->setSampleSize(1); }
+
+ /** Decoding is synchronous, but for long decodes, a different thread can
+ call this method safely. This sets a state that the decoders will
+ periodically check, and if they see it changed to cancel, they will
+ cancel. This will result in decode() returning false. However, there is
+ no guarantee that the decoder will see the state change in time, so
+ it is possible that cancelDecode() will be called, but will be ignored
+ and decode() will return true (assuming no other problems were
+ encountered).
+
+ This state is automatically reset at the beginning of decode().
+ */
+ void cancelDecode() {
+ // now the subclass must query shouldCancelDecode() to be informed
+ // of the request
+ fShouldCancelDecode = true;
+ }
+
+ /** Passed to the decode method. If kDecodeBounds_Mode is passed, then
+ only the bitmap's info need be set. If kDecodePixels_Mode
+ is passed, then the bitmap must have pixels or a pixelRef.
+ */
+ enum Mode {
+ kDecodeBounds_Mode, //!< only return info in bitmap
+ kDecodePixels_Mode //!< return entire bitmap (including pixels)
+ };
+
+ /** Result of a decode. If read as a boolean, a partial success is
+ considered a success (true).
+ */
+ enum Result {
+ kFailure = 0, //!< Image failed to decode. bitmap will be
+ // unchanged.
+ kPartialSuccess = 1, //!< Part of the image decoded. The rest is
+ // filled in automatically
+ kSuccess = 2 //!< The entire image was decoded, if Mode is
+ // kDecodePixels_Mode, or the bounds were
+ // decoded, in kDecodeBounds_Mode.
+ };
+
+ /** Given a stream, decode it into the specified bitmap.
+ If the decoder can decompress the image, it calls bitmap.setInfo(),
+ and then if the Mode is kDecodePixels_Mode, call allocPixelRef(),
+ which will allocated a pixelRef. To access the pixel memory, the codec
+ needs to call lockPixels/unlockPixels on the
+ bitmap. It can then set the pixels with the decompressed image.
+ * If the image cannot be decompressed, return kFailure. After the
+ * decoding, the function converts the decoded colortype in bitmap
+ * to pref if possible. Whether a conversion is feasible is
+ * tested by Bitmap::canCopyTo(pref).
+
+ If an SkBitmap::Allocator is installed via setAllocator, it will be
+ used to allocate the pixel memory. A clever allocator can be used
+ to allocate the memory from a cache, volatile memory, or even from
+ an existing bitmap's memory.
+
+ If an SkPngChunkReader is installed via setPeeker, it may be used to
+ peek into meta data during the decode.
+ */
+ Result decode(SkStream*, SkBitmap* bitmap, SkColorType pref, Mode);
+ Result decode(SkStream* stream, SkBitmap* bitmap, Mode mode) {
+ return this->decode(stream, bitmap, kUnknown_SkColorType, mode);
+ }
+
+ /** Given a stream, this will try to find an appropriate decoder object.
+ If none is found, the method returns NULL.
+
+ DEPRECATED Please use SkImage::NewFromEncoded() or SkImageGenerator::NewFromEncoded().
+ */
+ static SkImageDecoder* Factory(SkStreamRewindable*);
+
+ /** Decode the image stored in the specified file, and store the result
+ in bitmap. Return true for success or false on failure.
+
+ @param pref Prefer this colortype.
+
+ @param format On success, if format is non-null, it is set to the format
+ of the decoded file. On failure it is ignored.
+
+ DEPRECATED Do not use.
+ */
+ static bool DecodeFile(const char file[], SkBitmap* bitmap, SkColorType pref, Mode,
+ Format* format = NULL);
+ static bool DecodeFile(const char file[], SkBitmap* bitmap) {
+ return DecodeFile(file, bitmap, kUnknown_SkColorType, kDecodePixels_Mode, NULL);
+ }
+
+ /** Decode the image stored in the specified memory buffer, and store the
+ result in bitmap. Return true for success or false on failure.
+
+ @param pref Prefer this colortype.
+
+ @param format On success, if format is non-null, it is set to the format
+ of the decoded buffer. On failure it is ignored.
+
+ DEPRECATED Please use SkImage::NewFromEncoded() or SkImageGenerator::NewFromEncoded().
+ */
+ static bool DecodeMemory(const void* buffer, size_t size, SkBitmap* bitmap, SkColorType pref,
+ Mode, Format* format = NULL);
+ static bool DecodeMemory(const void* buffer, size_t size, SkBitmap* bitmap){
+ return DecodeMemory(buffer, size, bitmap, kUnknown_SkColorType, kDecodePixels_Mode, NULL);
+ }
+
+ /** Decode the image stored in the specified SkStreamRewindable, and store the result
+ in bitmap. Return true for success or false on failure.
+
+ @param pref Prefer this colortype.
+
+ @param format On success, if format is non-null, it is set to the format
+ of the decoded stream. On failure it is ignored.
+
+ DEPRECATED Please use SkImage::NewFromEncoded() or SkImageGenerator::NewFromEncoded().
+ */
+ static bool DecodeStream(SkStreamRewindable* stream, SkBitmap* bitmap, SkColorType pref, Mode,
+ Format* format = NULL);
+ static bool DecodeStream(SkStreamRewindable* stream, SkBitmap* bitmap) {
+ return DecodeStream(stream, bitmap, kUnknown_SkColorType, kDecodePixels_Mode, NULL);
+ }
+
+protected:
+ // must be overridden in subclasses. This guy is called by decode(...)
+ virtual Result onDecode(SkStream*, SkBitmap* bitmap, Mode) = 0;
+
+ /** 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*, SkISize[3] /*componentSizes*/,
+ void*[3] /*planes*/, size_t[3] /*rowBytes*/,
+ SkYUVColorSpace*) {
+ return false;
+ }
+
+ /**
+ * Copy all fields on this decoder to the other decoder. Used by subclasses
+ * to decode a subimage using a different decoder, but with the same settings.
+ */
+ void copyFieldsToOther(SkImageDecoder* other);
+
+ /** Can be queried from within onDecode, to see if the user (possibly in
+ a different thread) has requested the decode to cancel. If this returns
+ true, your onDecode() should stop and return false.
+ Each subclass needs to decide how often it can query this, to balance
+ responsiveness with performance.
+
+ Calling this outside of onDecode() may return undefined values.
+ */
+
+public:
+ bool shouldCancelDecode() const { return fShouldCancelDecode; }
+
+protected:
+ SkImageDecoder();
+
+ /**
+ * Return the default preference being used by the current or latest call to decode.
+ */
+ SkColorType getDefaultPref() { return fDefaultPref; }
+
+ /* Helper for subclasses. Call this to allocate the pixel memory given the bitmap's info.
+ Returns true on success. This method handles checking for an optional Allocator.
+ */
+ bool allocPixelRef(SkBitmap*, SkColorTable*) const;
+
+ /**
+ * The raw data of the src image.
+ */
+ enum SrcDepth {
+ // Color-indexed.
+ kIndex_SrcDepth,
+ // Grayscale in 8 bits.
+ k8BitGray_SrcDepth,
+ // 8 bits per component. Used for 24 bit if there is no alpha.
+ k32Bit_SrcDepth,
+ };
+ /** The subclass, inside onDecode(), calls this to determine the colorType of
+ the returned bitmap. SrcDepth and hasAlpha reflect the raw data of the
+ src image. This routine returns the caller's preference given
+ srcDepth and hasAlpha, or kUnknown_SkColorType if there is no preference.
+ */
+ SkColorType getPrefColorType(SrcDepth, bool hasAlpha) const;
+
+private:
+ SkPngChunkReader* fPeeker;
+ SkBitmap::Allocator* fAllocator;
+ int fSampleSize;
+ SkColorType fDefaultPref; // use if fUsePrefTable is false
+ bool fPreserveSrcDepth;
+ bool fDitherImage;
+ bool fSkipWritingZeroes;
+ mutable bool fShouldCancelDecode;
+ bool fPreferQualityOverSpeed;
+ bool fRequireUnpremultipliedColors;
+};
+
+/** Calling newDecoder with a stream returns a new matching imagedecoder
+ instance, or NULL if none can be found. The caller must manage its ownership
+ of the stream as usual, calling unref() when it is done, as the returned
+ decoder may have called ref() (and if so, the decoder is responsible for
+ balancing its ownership when it is destroyed).
+ */
+class SkImageDecoderFactory : public SkRefCnt {
+public:
+
+
+ virtual SkImageDecoder* newDecoder(SkStreamRewindable*) = 0;
+
+private:
+ typedef SkRefCnt INHERITED;
+};
+
+class SkDefaultImageDecoderFactory : SkImageDecoderFactory {
+public:
+ // calls SkImageDecoder::Factory(stream)
+ virtual SkImageDecoder* newDecoder(SkStreamRewindable* stream) {
+ return SkImageDecoder::Factory(stream);
+ }
+};
+
+// This macro declares a global (i.e., non-class owned) creation entry point
+// for each decoder (e.g., CreateJPEGImageDecoder)
+#define DECLARE_DECODER_CREATOR(codec) \
+ SkImageDecoder *Create ## codec ();
+
+// This macro defines the global creation entry point for each decoder. Each
+// decoder implementation that registers with the decoder factory must call it.
+#define DEFINE_DECODER_CREATOR(codec) \
+ SkImageDecoder* Create##codec() { return new Sk##codec; }
+
+// All the decoders known by Skia. Note that, depending on the compiler settings,
+// not all of these will be available
+DECLARE_DECODER_CREATOR(BMPImageDecoder);
+DECLARE_DECODER_CREATOR(GIFImageDecoder);
+DECLARE_DECODER_CREATOR(ICOImageDecoder);
+DECLARE_DECODER_CREATOR(JPEGImageDecoder);
+DECLARE_DECODER_CREATOR(PNGImageDecoder);
+DECLARE_DECODER_CREATOR(WBMPImageDecoder);
+DECLARE_DECODER_CREATOR(WEBPImageDecoder);
+DECLARE_DECODER_CREATOR(PKMImageDecoder);
+DECLARE_DECODER_CREATOR(KTXImageDecoder);
+DECLARE_DECODER_CREATOR(ASTCImageDecoder);
+
+// Typedefs to make registering decoder and formatter callbacks easier.
+// These have to be defined outside SkImageDecoder. :(
+typedef SkTRegistry<SkImageDecoder*(*)(SkStreamRewindable*)> SkImageDecoder_DecodeReg;
+typedef SkTRegistry<SkImageDecoder::Format(*)(SkStreamRewindable*)> SkImageDecoder_FormatReg;
+
+#endif
diff --git a/include/core/SkImageEncoder.h b/include/core/SkImageEncoder.h
index 1ccfae0bf9..bb3341f836 100644
--- a/include/core/SkImageEncoder.h
+++ b/include/core/SkImageEncoder.h
@@ -110,12 +110,8 @@ DECLARE_ENCODER_CREATOR(PNGImageEncoder);
DECLARE_ENCODER_CREATOR(KTXImageEncoder);
DECLARE_ENCODER_CREATOR(WEBPImageEncoder);
-#if defined(SK_BUILD_FOR_MAC) || defined(SK_BUILD_FOR_IOS)
-DECLARE_ENCODER_CREATOR(PNGImageEncoder_CG);
-#endif
-
-#if defined(SK_BUILD_FOR_WIN)
-DECLARE_ENCODER_CREATOR(ImageEncoder_WIC);
+#ifdef SK_BUILD_FOR_IOS
+DECLARE_ENCODER_CREATOR(PNGImageEncoder_IOS);
#endif
// Typedef to make registering encoder callback easier
diff --git a/include/core/SkPicture.h b/include/core/SkPicture.h
index 195ce242b9..6c0dda0d3e 100644
--- a/include/core/SkPicture.h
+++ b/include/core/SkPicture.h
@@ -8,6 +8,7 @@
#ifndef SkPicture_DEFINED
#define SkPicture_DEFINED
+#include "SkImageDecoder.h"
#include "SkRefCnt.h"
#include "SkTypes.h"
@@ -15,17 +16,13 @@ class GrContext;
class SkBigPicture;
class SkBitmap;
class SkCanvas;
-class SkPath;
class SkPictureData;
class SkPixelSerializer;
-class SkReadBuffer;
class SkRefCntSet;
class SkStream;
class SkTypefacePlayback;
class SkWStream;
-class SkWriteBuffer;
struct SkPictInfo;
-struct SkRect;
#define SK_SUPPORT_LEGACY_PICTURE_PTR
diff --git a/include/core/SkPngChunkReader.h b/include/core/SkPngChunkReader.h
index 0cd6634bce..f424dd8cfc 100644
--- a/include/core/SkPngChunkReader.h
+++ b/include/core/SkPngChunkReader.h
@@ -16,7 +16,7 @@
*
* Base class for optional callbacks to retrieve meta/chunk data out of a PNG
* encoded image as it is being decoded.
- * Used by SkCodec.
+ * Used by SkImageDecoder and SkCodec.
*/
class SkPngChunkReader : public SkRefCnt {
public:
diff --git a/include/core/SkWriteBuffer.h b/include/core/SkWriteBuffer.h
index 6e9d043aeb..8e4607887d 100644
--- a/include/core/SkWriteBuffer.h
+++ b/include/core/SkWriteBuffer.h
@@ -10,7 +10,6 @@
#define SkWriteBuffer_DEFINED
#include "SkData.h"
-#include "SkImage.h"
#include "SkPath.h"
#include "SkPicture.h"
#include "SkPixelSerializer.h"
diff --git a/src/android/SkBitmapRegionDecoder.cpp b/src/android/SkBitmapRegionDecoder.cpp
index 101efbda45..712034ba44 100644
--- a/src/android/SkBitmapRegionDecoder.cpp
+++ b/src/android/SkBitmapRegionDecoder.cpp
@@ -11,6 +11,7 @@
#include "SkAndroidCodec.h"
#include "SkCodec.h"
#include "SkCodecPriv.h"
+#include "SkImageDecoder.h"
SkBitmapRegionDecoder* SkBitmapRegionDecoder::Create(
SkData* data, Strategy strategy) {
diff --git a/src/core/SkBigPicture.h b/src/core/SkBigPicture.h
index 0834709f8a..2e42213539 100644
--- a/src/core/SkBigPicture.h
+++ b/src/core/SkBigPicture.h
@@ -10,11 +10,9 @@
#include "SkOncePtr.h"
#include "SkPicture.h"
-#include "SkRect.h"
#include "SkTemplates.h"
class SkBBoxHierarchy;
-class SkMatrix;
class SkRecord;
// An implementation of SkPicture supporting an arbitrary number of drawing commands.
diff --git a/src/core/SkLayerInfo.h b/src/core/SkLayerInfo.h
index aa19ecbd0c..04ae1794a2 100644
--- a/src/core/SkLayerInfo.h
+++ b/src/core/SkLayerInfo.h
@@ -9,8 +9,6 @@
#define SkLayerInfo_DEFINED
#include "SkBigPicture.h"
-#include "SkMatrix.h"
-#include "SkPaint.h"
#include "SkTArray.h"
// This class stores information about the saveLayer/restore pairs found
diff --git a/src/gpu/GrLayerCache.h b/src/gpu/GrLayerCache.h
index 4f4317c298..a606681896 100644
--- a/src/gpu/GrLayerCache.h
+++ b/src/gpu/GrLayerCache.h
@@ -16,7 +16,6 @@
#include "SkChecksum.h"
#include "SkImageFilter.h"
#include "SkMessageBus.h"
-#include "SkPaint.h"
#include "SkPicture.h"
#include "SkTDynamicHash.h"
diff --git a/src/images/SkForceLinking.cpp b/src/images/SkForceLinking.cpp
index 05fc7e08a6..55b7021432 100644
--- a/src/images/SkForceLinking.cpp
+++ b/src/images/SkForceLinking.cpp
@@ -5,8 +5,8 @@
* found in the LICENSE file.
*/
-#include "SkImageEncoder.h"
#include "SkForceLinking.h"
+#include "SkImageDecoder.h"
// This method is required to fool the linker into not discarding the pre-main
// initialization and registration of the decoder classes. Passing true will
@@ -14,22 +14,26 @@
int SkForceLinking(bool doNotPassTrue) {
if (doNotPassTrue) {
SkASSERT(false);
- CreateJPEGImageEncoder();
- CreateWEBPImageEncoder();
-
+ CreateJPEGImageDecoder();
+ CreateWEBPImageDecoder();
+ CreateBMPImageDecoder();
+ CreateICOImageDecoder();
+ CreateWBMPImageDecoder();
// Only link hardware texture codecs on platforms that build them. See images.gyp
#ifndef SK_BUILD_FOR_ANDROID_FRAMEWORK
- CreateKTXImageEncoder();
+ CreatePKMImageDecoder();
+ CreateKTXImageDecoder();
+ CreateASTCImageDecoder();
#endif
-
+ // Only link GIF and PNG on platforms that build them. See images.gyp
#if !defined(SK_BUILD_FOR_MAC) && !defined(SK_BUILD_FOR_WIN) && !defined(SK_BUILD_FOR_IOS)
- CreatePNGImageEncoder();
+ CreateGIFImageDecoder();
#endif
-#if defined(SK_BUILD_FOR_MAC) || defined(SK_BUILD_FOR_IOS)
- CreatePNGImageEncoder_CG();
+#if !defined(SK_BUILD_FOR_MAC) && !defined(SK_BUILD_FOR_WIN) && !defined(SK_BUILD_FOR_IOS)
+ CreatePNGImageDecoder();
#endif
-#if defined(SK_BUILD_FOR_WIN)
- CreateImageEncoder_WIC();
+#if defined(SK_BUILD_FOR_IOS)
+ CreatePNGImageEncoder_IOS();
#endif
return -1;
}
diff --git a/src/images/SkImageDecoder.cpp b/src/images/SkImageDecoder.cpp
new file mode 100644
index 0000000000..221faf74d5
--- /dev/null
+++ b/src/images/SkImageDecoder.cpp
@@ -0,0 +1,204 @@
+/*
+ * Copyright 2006 The Android Open Source Project
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+
+#include "SkImageDecoder.h"
+#include "SkBitmap.h"
+#include "SkImagePriv.h"
+#include "SkPixelRef.h"
+#include "SkStream.h"
+#include "SkTemplates.h"
+#include "SkCanvas.h"
+
+SkImageDecoder::SkImageDecoder()
+ : fPeeker(nullptr)
+ , fAllocator(nullptr)
+ , fSampleSize(1)
+ , fDefaultPref(kUnknown_SkColorType)
+ , fPreserveSrcDepth(false)
+ , fDitherImage(true)
+ , fSkipWritingZeroes(false)
+ , fPreferQualityOverSpeed(false)
+ , fRequireUnpremultipliedColors(false) {
+}
+
+SkImageDecoder::~SkImageDecoder() {
+ SkSafeUnref(fPeeker);
+ SkSafeUnref(fAllocator);
+}
+
+void SkImageDecoder::copyFieldsToOther(SkImageDecoder* other) {
+ if (nullptr == other) {
+ return;
+ }
+ other->setPeeker(fPeeker);
+ other->setAllocator(fAllocator);
+ other->setSampleSize(fSampleSize);
+ other->setPreserveSrcDepth(fPreserveSrcDepth);
+ other->setDitherImage(fDitherImage);
+ other->setSkipWritingZeroes(fSkipWritingZeroes);
+ other->setPreferQualityOverSpeed(fPreferQualityOverSpeed);
+ other->setRequireUnpremultipliedColors(fRequireUnpremultipliedColors);
+}
+
+SkImageDecoder::Format SkImageDecoder::getFormat() const {
+ return kUnknown_Format;
+}
+
+const char* SkImageDecoder::getFormatName() const {
+ return GetFormatName(this->getFormat());
+}
+
+const char* SkImageDecoder::GetFormatName(Format format) {
+ switch (format) {
+ case kUnknown_Format:
+ return "Unknown Format";
+ case kBMP_Format:
+ return "BMP";
+ case kGIF_Format:
+ return "GIF";
+ case kICO_Format:
+ return "ICO";
+ case kPKM_Format:
+ return "PKM";
+ case kKTX_Format:
+ return "KTX";
+ case kASTC_Format:
+ return "ASTC";
+ case kJPEG_Format:
+ return "JPEG";
+ case kPNG_Format:
+ return "PNG";
+ case kWBMP_Format:
+ return "WBMP";
+ case kWEBP_Format:
+ return "WEBP";
+ default:
+ SkDEBUGFAIL("Invalid format type!");
+ }
+ return "Unknown Format";
+}
+
+SkPngChunkReader* SkImageDecoder::setPeeker(SkPngChunkReader* peeker) {
+ SkRefCnt_SafeAssign(fPeeker, peeker);
+ return peeker;
+}
+
+SkBitmap::Allocator* SkImageDecoder::setAllocator(SkBitmap::Allocator* alloc) {
+ SkRefCnt_SafeAssign(fAllocator, alloc);
+ return alloc;
+}
+
+void SkImageDecoder::setSampleSize(int size) {
+ if (size < 1) {
+ size = 1;
+ }
+ fSampleSize = size;
+}
+
+bool SkImageDecoder::allocPixelRef(SkBitmap* bitmap,
+ SkColorTable* ctable) const {
+ return bitmap->tryAllocPixels(fAllocator, ctable);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+SkColorType SkImageDecoder::getPrefColorType(SrcDepth srcDepth, bool srcHasAlpha) const {
+ SkColorType ct = fDefaultPref;
+ if (fPreserveSrcDepth) {
+ switch (srcDepth) {
+ case kIndex_SrcDepth:
+ ct = kIndex_8_SkColorType;
+ break;
+ case k8BitGray_SrcDepth:
+ ct = kN32_SkColorType;
+ break;
+ case k32Bit_SrcDepth:
+ ct = kN32_SkColorType;
+ break;
+ }
+ }
+ return ct;
+}
+
+SkImageDecoder::Result SkImageDecoder::decode(SkStream* stream, SkBitmap* bm, SkColorType pref,
+ Mode mode) {
+ // we reset this to false before calling onDecode
+ fShouldCancelDecode = false;
+ // assign this, for use by getPrefColorType(), in case fUsePrefTable is false
+ fDefaultPref = pref;
+
+ // pass a temporary bitmap, so that if we return false, we are assured of
+ // leaving the caller's bitmap untouched.
+ SkBitmap tmp;
+ const Result result = this->onDecode(stream, &tmp, mode);
+ if (kFailure != result) {
+ bm->swap(tmp);
+ }
+ return result;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+bool SkImageDecoder::DecodeFile(const char file[], SkBitmap* bm, SkColorType pref, Mode mode,
+ Format* format) {
+ SkASSERT(file);
+ SkASSERT(bm);
+
+ SkAutoTDelete<SkStreamRewindable> stream(SkStream::NewFromFile(file));
+ if (stream.get()) {
+ if (SkImageDecoder::DecodeStream(stream, bm, pref, mode, format)) {
+ if (SkPixelRef* pr = bm->pixelRef()) {
+ pr->setURI(file);
+ }
+ return true;
+ }
+ }
+ return false;
+}
+
+bool SkImageDecoder::DecodeMemory(const void* buffer, size_t size, SkBitmap* bm, SkColorType pref,
+ Mode mode, Format* format) {
+ if (0 == size) {
+ return false;
+ }
+ SkASSERT(buffer);
+
+ SkMemoryStream stream(buffer, size);
+ return SkImageDecoder::DecodeStream(&stream, bm, pref, mode, format);
+}
+
+bool SkImageDecoder::DecodeStream(SkStreamRewindable* stream, SkBitmap* bm, SkColorType pref,
+ Mode mode, Format* format) {
+ SkASSERT(stream);
+ SkASSERT(bm);
+
+ bool success = false;
+ SkImageDecoder* codec = SkImageDecoder::Factory(stream);
+
+ if (codec) {
+ success = codec->decode(stream, bm, pref, mode) != kFailure;
+ if (success && format) {
+ *format = codec->getFormat();
+ if (kUnknown_Format == *format) {
+ if (stream->rewind()) {
+ *format = GetStreamFormat(stream);
+ }
+ }
+ }
+ delete codec;
+ }
+ 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);
+}
diff --git a/src/images/SkImageDecoder_FactoryDefault.cpp b/src/images/SkImageDecoder_FactoryDefault.cpp
index ef8ddda830..77c0a0ac57 100644
--- a/src/images/SkImageDecoder_FactoryDefault.cpp
+++ b/src/images/SkImageDecoder_FactoryDefault.cpp
@@ -6,9 +6,18 @@
* found in the LICENSE file.
*/
+#include "SkImageDecoder.h"
#include "SkMovie.h"
#include "SkStream.h"
+extern SkImageDecoder* image_decoder_from_stream(SkStreamRewindable*);
+
+SkImageDecoder* SkImageDecoder::Factory(SkStreamRewindable* stream) {
+ return image_decoder_from_stream(stream);
+}
+
+/////////////////////////////////////////////////////////////////////////
+
typedef SkTRegistry<SkMovie*(*)(SkStreamRewindable*)> MovieReg;
SkMovie* SkMovie::DecodeStream(SkStreamRewindable* stream) {
diff --git a/src/images/SkImageDecoder_FactoryRegistrar.cpp b/src/images/SkImageDecoder_FactoryRegistrar.cpp
new file mode 100644
index 0000000000..36034d20ad
--- /dev/null
+++ b/src/images/SkImageDecoder_FactoryRegistrar.cpp
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2013 The Android Open Source Project
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "SkErrorInternals.h"
+#include "SkImageDecoder.h"
+#include "SkStream.h"
+#include "SkTRegistry.h"
+
+// This file is used for registration of SkImageDecoders. It also holds a function
+// for checking all the the registered SkImageDecoders for one that matches an
+// input SkStreamRewindable.
+
+template SkImageDecoder_DecodeReg* SkImageDecoder_DecodeReg::gHead;
+
+SkImageDecoder* image_decoder_from_stream(SkStreamRewindable*);
+
+SkImageDecoder* image_decoder_from_stream(SkStreamRewindable* stream) {
+ SkImageDecoder* codec = nullptr;
+ const SkImageDecoder_DecodeReg* curr = SkImageDecoder_DecodeReg::Head();
+ while (curr) {
+ codec = curr->factory()(stream);
+ // we rewind here, because we promise later when we call "decode", that
+ // the stream will be at its beginning.
+ bool rewindSuceeded = stream->rewind();
+
+ // our image decoder's require that rewind is supported so we fail early
+ // if we are given a stream that does not support rewinding.
+ if (!rewindSuceeded) {
+ SkDEBUGF(("Unable to rewind the image stream."));
+ delete codec;
+ return nullptr;
+ }
+
+ if (codec) {
+ return codec;
+ }
+ curr = curr->next();
+ }
+ return nullptr;
+}
+
+template SkImageDecoder_FormatReg* SkImageDecoder_FormatReg::gHead;
+
+SkImageDecoder::Format SkImageDecoder::GetStreamFormat(SkStreamRewindable* stream) {
+ const SkImageDecoder_FormatReg* curr = SkImageDecoder_FormatReg::Head();
+ while (curr != nullptr) {
+ Format format = curr->factory()(stream);
+ if (!stream->rewind()) {
+ SkErrorInternals::SetError(kInvalidOperation_SkError,
+ "Unable to rewind the image stream\n");
+ return kUnknown_Format;
+ }
+ if (format != kUnknown_Format) {
+ return format;
+ }
+ curr = curr->next();
+ }
+ return kUnknown_Format;
+}
diff --git a/src/images/SkImageDecoder_astc.cpp b/src/images/SkImageDecoder_astc.cpp
new file mode 100644
index 0000000000..30d65f1f0f
--- /dev/null
+++ b/src/images/SkImageDecoder_astc.cpp
@@ -0,0 +1,203 @@
+/*
+ * Copyright 2014 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "SkData.h"
+#include "SkEndian.h"
+#include "SkColorPriv.h"
+#include "SkImageDecoder.h"
+#include "SkScaledBitmapSampler.h"
+#include "SkStream.h"
+#include "SkStreamPriv.h"
+#include "SkTypes.h"
+
+#include "SkTextureCompressor.h"
+
+class SkASTCImageDecoder : public SkImageDecoder {
+public:
+ SkASTCImageDecoder() { }
+
+ Format getFormat() const override {
+ return kASTC_Format;
+ }
+
+protected:
+ Result onDecode(SkStream* stream, SkBitmap* bm, Mode) override;
+
+private:
+ typedef SkImageDecoder INHERITED;
+};
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+static const uint32_t kASTCMagicNumber = 0x5CA1AB13;
+
+static inline int read_24bit(const uint8_t* buf) {
+ // Assume everything is little endian...
+ return
+ static_cast<int>(buf[0]) |
+ (static_cast<int>(buf[1]) << 8) |
+ (static_cast<int>(buf[2]) << 16);
+}
+
+SkImageDecoder::Result SkASTCImageDecoder::onDecode(SkStream* stream, SkBitmap* bm, Mode mode) {
+ auto data = SkCopyStreamToData(stream);
+ if (!data || !data->size()) {
+ return kFailure;
+ }
+
+ unsigned char* buf = (unsigned char*) data->data();
+
+ // Make sure that the magic header is there...
+ SkASSERT(SkEndian_SwapLE32(*(reinterpret_cast<uint32_t*>(buf))) == kASTCMagicNumber);
+
+ // Advance past the magic header
+ buf += 4;
+
+ const int blockDimX = buf[0];
+ const int blockDimY = buf[1];
+ const int blockDimZ = buf[2];
+
+ if (1 != blockDimZ) {
+ // We don't support decoding 3D
+ return kFailure;
+ }
+
+ // Choose the proper ASTC format
+ SkTextureCompressor::Format astcFormat;
+ if (4 == blockDimX && 4 == blockDimY) {
+ astcFormat = SkTextureCompressor::kASTC_4x4_Format;
+ } else if (5 == blockDimX && 4 == blockDimY) {
+ astcFormat = SkTextureCompressor::kASTC_5x4_Format;
+ } else if (5 == blockDimX && 5 == blockDimY) {
+ astcFormat = SkTextureCompressor::kASTC_5x5_Format;
+ } else if (6 == blockDimX && 5 == blockDimY) {
+ astcFormat = SkTextureCompressor::kASTC_6x5_Format;
+ } else if (6 == blockDimX && 6 == blockDimY) {
+ astcFormat = SkTextureCompressor::kASTC_6x6_Format;
+ } else if (8 == blockDimX && 5 == blockDimY) {
+ astcFormat = SkTextureCompressor::kASTC_8x5_Format;
+ } else if (8 == blockDimX && 6 == blockDimY) {
+ astcFormat = SkTextureCompressor::kASTC_8x6_Format;
+ } else if (8 == blockDimX && 8 == blockDimY) {
+ astcFormat = SkTextureCompressor::kASTC_8x8_Format;
+ } else if (10 == blockDimX && 5 == blockDimY) {
+ astcFormat = SkTextureCompressor::kASTC_10x5_Format;
+ } else if (10 == blockDimX && 6 == blockDimY) {
+ astcFormat = SkTextureCompressor::kASTC_10x6_Format;
+ } else if (10 == blockDimX && 8 == blockDimY) {
+ astcFormat = SkTextureCompressor::kASTC_10x8_Format;
+ } else if (10 == blockDimX && 10 == blockDimY) {
+ astcFormat = SkTextureCompressor::kASTC_10x10_Format;
+ } else if (12 == blockDimX && 10 == blockDimY) {
+ astcFormat = SkTextureCompressor::kASTC_12x10_Format;
+ } else if (12 == blockDimX && 12 == blockDimY) {
+ astcFormat = SkTextureCompressor::kASTC_12x12_Format;
+ } else {
+ // We don't support any other block dimensions..
+ return kFailure;
+ }
+
+ // Advance buf past the block dimensions
+ buf += 3;
+
+ // Read the width/height/depth from the buffer...
+ const int width = read_24bit(buf);
+ const int height = read_24bit(buf + 3);
+ const int depth = read_24bit(buf + 6);
+
+ if (1 != depth) {
+ // We don't support decoding 3D.
+ return kFailure;
+ }
+
+ // Advance the buffer past the image dimensions
+ buf += 9;
+
+ // Setup the sampler...
+ SkScaledBitmapSampler sampler(width, height, this->getSampleSize());
+
+ // Determine the alpha of the bitmap...
+ SkAlphaType alphaType = kOpaque_SkAlphaType;
+ if (this->getRequireUnpremultipliedColors()) {
+ alphaType = kUnpremul_SkAlphaType;
+ } else {
+ alphaType = kPremul_SkAlphaType;
+ }
+
+ // Set the config...
+ bm->setInfo(SkImageInfo::MakeN32(sampler.scaledWidth(), sampler.scaledHeight(), alphaType));
+
+ if (SkImageDecoder::kDecodeBounds_Mode == mode) {
+ return kSuccess;
+ }
+
+ if (!this->allocPixelRef(bm, nullptr)) {
+ return kFailure;
+ }
+
+ // Lock the pixels, since we're about to write to them...
+ SkAutoLockPixels alp(*bm);
+
+ if (!sampler.begin(bm, SkScaledBitmapSampler::kRGBA, *this)) {
+ return kFailure;
+ }
+
+ // ASTC Data is encoded as RGBA pixels, so we should extract it as such
+ int nPixels = width * height;
+ SkAutoMalloc outRGBAData(nPixels * 4);
+ uint8_t *outRGBADataPtr = reinterpret_cast<uint8_t *>(outRGBAData.get());
+
+ // Decode ASTC
+ if (!SkTextureCompressor::DecompressBufferFromFormat(
+ outRGBADataPtr, width*4, buf, width, height, astcFormat)) {
+ return kFailure;
+ }
+
+ // Set each of the pixels...
+ const int srcRowBytes = width * 4;
+ const int dstHeight = sampler.scaledHeight();
+ const uint8_t *srcRow = reinterpret_cast<uint8_t *>(outRGBADataPtr);
+ srcRow += sampler.srcY0() * srcRowBytes;
+ for (int y = 0; y < dstHeight; ++y) {
+ sampler.next(srcRow);
+ srcRow += sampler.srcDY() * srcRowBytes;
+ }
+
+ return kSuccess;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+DEFINE_DECODER_CREATOR(ASTCImageDecoder);
+/////////////////////////////////////////////////////////////////////////////////////////
+
+static bool is_astc(SkStreamRewindable* stream) {
+ // Read the ASTC header and make sure it's valid.
+ uint32_t magic;
+ if (stream->read((void*)&magic, 4) != 4) {
+ return false;
+ }
+
+ return kASTCMagicNumber == SkEndian_SwapLE32(magic);
+}
+
+static SkImageDecoder* sk_libastc_dfactory(SkStreamRewindable* stream) {
+ if (is_astc(stream)) {
+ return new SkASTCImageDecoder;
+ }
+ return nullptr;
+}
+
+static SkImageDecoder_DecodeReg gReg(sk_libastc_dfactory);
+
+static SkImageDecoder::Format get_format_astc(SkStreamRewindable* stream) {
+ if (is_astc(stream)) {
+ return SkImageDecoder::kASTC_Format;
+ }
+ return SkImageDecoder::kUnknown_Format;
+}
+
+static SkImageDecoder_FormatReg gFormatReg(get_format_astc);
diff --git a/src/images/SkImageDecoder_ktx.cpp b/src/images/SkImageDecoder_ktx.cpp
index 79f0293c2a..156674565c 100644
--- a/src/images/SkImageDecoder_ktx.cpp
+++ b/src/images/SkImageDecoder_ktx.cpp
@@ -6,9 +6,10 @@
*/
#include "SkColorPriv.h"
-#include "SkImageEncoder.h"
+#include "SkImageDecoder.h"
#include "SkImageGenerator.h"
#include "SkPixelRef.h"
+#include "SkScaledBitmapSampler.h"
#include "SkStream.h"
#include "SkStreamPriv.h"
#include "SkTypes.h"
@@ -16,14 +17,230 @@
#include "ktx.h"
#include "etc1.h"
-///////////////////////////////////////////////////////////////////////////////
+/////////////////////////////////////////////////////////////////////////////////////////
-// KTX Image Encoder
-//
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+// KTX Image decoder
+// ---
// KTX is a general texture data storage file format ratified by the Khronos Group. As an
// overview, a KTX file contains all of the appropriate values needed to fully specify a
// texture in an OpenGL application, including the use of compressed data.
//
+// This decoder is meant to be used with an SkDiscardablePixelRef so that GPU backends
+// can sniff the data before creating a texture. If they encounter a compressed format
+// that they understand, they can then upload the data directly to the GPU. Otherwise,
+// they will decode the data into a format that Skia supports.
+
+class SkKTXImageDecoder : public SkImageDecoder {
+public:
+ SkKTXImageDecoder() { }
+
+ Format getFormat() const override {
+ return kKTX_Format;
+ }
+
+protected:
+ Result onDecode(SkStream* stream, SkBitmap* bm, Mode) override;
+
+private:
+ typedef SkImageDecoder INHERITED;
+};
+
+SkImageDecoder::Result SkKTXImageDecoder::onDecode(SkStream* stream, SkBitmap* bm, Mode mode) {
+ // TODO: Implement SkStream::copyToData() that's cheap for memory and file streams
+ auto data = SkCopyStreamToData(stream);
+ if (nullptr == data) {
+ return kFailure;
+ }
+
+ SkKTXFile ktxFile(data.get());
+ if (!ktxFile.valid()) {
+ return kFailure;
+ }
+
+ const unsigned short width = ktxFile.width();
+ const unsigned short height = ktxFile.height();
+
+ // Set a flag if our source is premultiplied alpha
+ const SkString premulKey("KTXPremultipliedAlpha");
+ const bool bSrcIsPremul = ktxFile.getValueForKey(premulKey) == SkString("True");
+
+ // Setup the sampler...
+ SkScaledBitmapSampler sampler(width, height, this->getSampleSize());
+
+ // Determine the alpha of the bitmap...
+ SkAlphaType alphaType = kOpaque_SkAlphaType;
+ if (ktxFile.isRGBA8()) {
+ if (this->getRequireUnpremultipliedColors()) {
+ alphaType = kUnpremul_SkAlphaType;
+ // If the client wants unpremul colors and we only have
+ // premul, then we cannot honor their wish.
+ if (bSrcIsPremul) {
+ return kFailure;
+ }
+ } else {
+ alphaType = kPremul_SkAlphaType;
+ }
+ }
+
+ // Search through the compressed formats to see if the KTX file is holding
+ // compressed data
+ bool ktxIsCompressed = false;
+ SkTextureCompressor::Format ktxCompressedFormat;
+ for (int i = 0; i < SkTextureCompressor::kFormatCnt; ++i) {
+ SkTextureCompressor::Format fmt = static_cast<SkTextureCompressor::Format>(i);
+ if (ktxFile.isCompressedFormat(fmt)) {
+ ktxIsCompressed = true;
+ ktxCompressedFormat = fmt;
+ break;
+ }
+ }
+
+ // If the compressed format is a grayscale image, then setup the bitmap properly...
+ bool isCompressedAlpha = ktxIsCompressed &&
+ ((SkTextureCompressor::kLATC_Format == ktxCompressedFormat) ||
+ (SkTextureCompressor::kR11_EAC_Format == ktxCompressedFormat));
+
+ // Set the image dimensions and underlying pixel type.
+ if (isCompressedAlpha) {
+ const int w = sampler.scaledWidth();
+ const int h = sampler.scaledHeight();
+ bm->setInfo(SkImageInfo::MakeA8(w, h));
+ } else {
+ const int w = sampler.scaledWidth();
+ const int h = sampler.scaledHeight();
+ bm->setInfo(SkImageInfo::MakeN32(w, h, alphaType));
+ }
+
+ if (SkImageDecoder::kDecodeBounds_Mode == mode) {
+ return kSuccess;
+ }
+
+ // If we've made it this far, then we know how to grok the data.
+ if (!this->allocPixelRef(bm, nullptr)) {
+ return kFailure;
+ }
+
+ // Lock the pixels, since we're about to write to them...
+ SkAutoLockPixels alp(*bm);
+
+ if (isCompressedAlpha) {
+ if (!sampler.begin(bm, SkScaledBitmapSampler::kGray, *this)) {
+ return kFailure;
+ }
+
+ // Alpha data is only a single byte per pixel.
+ int nPixels = width * height;
+ SkAutoMalloc outRGBData(nPixels);
+ uint8_t *outRGBDataPtr = reinterpret_cast<uint8_t *>(outRGBData.get());
+
+ // Decode the compressed format
+ const uint8_t *buf = reinterpret_cast<const uint8_t *>(ktxFile.pixelData());
+ if (!SkTextureCompressor::DecompressBufferFromFormat(
+ outRGBDataPtr, width, buf, width, height, ktxCompressedFormat)) {
+ return kFailure;
+ }
+
+ // Set each of the pixels...
+ const int srcRowBytes = width;
+ const int dstHeight = sampler.scaledHeight();
+ const uint8_t *srcRow = reinterpret_cast<uint8_t *>(outRGBDataPtr);
+ srcRow += sampler.srcY0() * srcRowBytes;
+ for (int y = 0; y < dstHeight; ++y) {
+ sampler.next(srcRow);
+ srcRow += sampler.srcDY() * srcRowBytes;
+ }
+
+ return kSuccess;
+
+ } else if (ktxFile.isCompressedFormat(SkTextureCompressor::kETC1_Format)) {
+ if (!sampler.begin(bm, SkScaledBitmapSampler::kRGB, *this)) {
+ return kFailure;
+ }
+
+ // ETC1 Data is encoded as RGB pixels, so we should extract it as such
+ int nPixels = width * height;
+ SkAutoMalloc outRGBData(nPixels * 3);
+ uint8_t *outRGBDataPtr = reinterpret_cast<uint8_t *>(outRGBData.get());
+
+ // Decode ETC1
+ const uint8_t *buf = reinterpret_cast<const uint8_t *>(ktxFile.pixelData());
+ if (!SkTextureCompressor::DecompressBufferFromFormat(
+ outRGBDataPtr, width*3, buf, width, height, SkTextureCompressor::kETC1_Format)) {
+ return kFailure;
+ }
+
+ // Set each of the pixels...
+ const int srcRowBytes = width * 3;
+ const int dstHeight = sampler.scaledHeight();
+ const uint8_t *srcRow = reinterpret_cast<uint8_t *>(outRGBDataPtr);
+ srcRow += sampler.srcY0() * srcRowBytes;
+ for (int y = 0; y < dstHeight; ++y) {
+ sampler.next(srcRow);
+ srcRow += sampler.srcDY() * srcRowBytes;
+ }
+
+ return kSuccess;
+
+ } else if (ktxFile.isRGB8()) {
+
+ // Uncompressed RGB data (without alpha)
+ if (!sampler.begin(bm, SkScaledBitmapSampler::kRGB, *this)) {
+ return kFailure;
+ }
+
+ // Just need to read RGB pixels
+ const int srcRowBytes = width * 3;
+ const int dstHeight = sampler.scaledHeight();
+ const uint8_t *srcRow = reinterpret_cast<const uint8_t *>(ktxFile.pixelData());
+ srcRow += sampler.srcY0() * srcRowBytes;
+ for (int y = 0; y < dstHeight; ++y) {
+ sampler.next(srcRow);
+ srcRow += sampler.srcDY() * srcRowBytes;
+ }
+
+ return kSuccess;
+
+ } else if (ktxFile.isRGBA8()) {
+
+ // Uncompressed RGBA data
+
+ // If we know that the image contains premultiplied alpha, then
+ // we need to turn off the premultiplier
+ SkScaledBitmapSampler::Options opts (*this);
+ if (bSrcIsPremul) {
+ SkASSERT(bm->alphaType() == kPremul_SkAlphaType);
+ SkASSERT(!this->getRequireUnpremultipliedColors());
+
+ opts.fPremultiplyAlpha = false;
+ }
+
+ if (!sampler.begin(bm, SkScaledBitmapSampler::kRGBA, opts)) {
+ return kFailure;
+ }
+
+ // Just need to read RGBA pixels
+ const int srcRowBytes = width * 4;
+ const int dstHeight = sampler.scaledHeight();
+ const uint8_t *srcRow = reinterpret_cast<const uint8_t *>(ktxFile.pixelData());
+ srcRow += sampler.srcY0() * srcRowBytes;
+ for (int y = 0; y < dstHeight; ++y) {
+ sampler.next(srcRow);
+ srcRow += sampler.srcDY() * srcRowBytes;
+ }
+
+ return kSuccess;
+ }
+
+ return kFailure;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+// KTX Image Encoder
+//
// This encoder takes a best guess at how to encode the bitmap passed to it. If
// there is an installed discardable pixel ref with existing PKM data, then we
// will repurpose the existing ETC1 data into a KTX file. If the data contains
@@ -87,11 +304,28 @@ bool SkKTXImageEncoder::encodePKM(SkWStream* stream, const SkData *data) {
}
/////////////////////////////////////////////////////////////////////////////////////////
+DEFINE_DECODER_CREATOR(KTXImageDecoder);
DEFINE_ENCODER_CREATOR(KTXImageEncoder);
/////////////////////////////////////////////////////////////////////////////////////////
+static SkImageDecoder* sk_libktx_dfactory(SkStreamRewindable* stream) {
+ if (SkKTXFile::is_ktx(stream)) {
+ return new SkKTXImageDecoder;
+ }
+ return nullptr;
+}
+
+static SkImageDecoder::Format get_format_ktx(SkStreamRewindable* stream) {
+ if (SkKTXFile::is_ktx(stream)) {
+ return SkImageDecoder::kKTX_Format;
+ }
+ return SkImageDecoder::kUnknown_Format;
+}
+
SkImageEncoder* sk_libktx_efactory(SkImageEncoder::Type t) {
return (SkImageEncoder::kKTX_Type == t) ? new SkKTXImageEncoder : nullptr;
}
+static SkImageDecoder_DecodeReg gReg(sk_libktx_dfactory);
+static SkImageDecoder_FormatReg gFormatReg(get_format_ktx);
static SkImageEncoder_EncodeReg gEReg(sk_libktx_efactory);
diff --git a/src/images/SkImageDecoder_libbmp.cpp b/src/images/SkImageDecoder_libbmp.cpp
new file mode 100644
index 0000000000..b9359bea7a
--- /dev/null
+++ b/src/images/SkImageDecoder_libbmp.cpp
@@ -0,0 +1,166 @@
+
+/*
+ * Copyright 2007 The Android Open Source Project
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+
+#include "bmpdecoderhelper.h"
+#include "SkColorPriv.h"
+#include "SkData.h"
+#include "SkImageDecoder.h"
+#include "SkScaledBitmapSampler.h"
+#include "SkStream.h"
+#include "SkStreamPriv.h"
+#include "SkTDArray.h"
+
+class SkBMPImageDecoder : public SkImageDecoder {
+public:
+ SkBMPImageDecoder() {}
+
+ Format getFormat() const override {
+ return kBMP_Format;
+ }
+
+protected:
+ Result onDecode(SkStream* stream, SkBitmap* bm, Mode mode) override;
+
+private:
+ typedef SkImageDecoder INHERITED;
+};
+
+///////////////////////////////////////////////////////////////////////////////
+DEFINE_DECODER_CREATOR(BMPImageDecoder);
+///////////////////////////////////////////////////////////////////////////////
+
+static bool is_bmp(SkStreamRewindable* stream) {
+ static const char kBmpMagic[] = { 'B', 'M' };
+
+
+ char buffer[sizeof(kBmpMagic)];
+
+ return stream->read(buffer, sizeof(kBmpMagic)) == sizeof(kBmpMagic) &&
+ !memcmp(buffer, kBmpMagic, sizeof(kBmpMagic));
+}
+
+static SkImageDecoder* sk_libbmp_dfactory(SkStreamRewindable* stream) {
+ if (is_bmp(stream)) {
+ return new SkBMPImageDecoder;
+ }
+ return nullptr;
+}
+
+static SkImageDecoder_DecodeReg gReg(sk_libbmp_dfactory);
+
+static SkImageDecoder::Format get_format_bmp(SkStreamRewindable* stream) {
+ if (is_bmp(stream)) {
+ return SkImageDecoder::kBMP_Format;
+ }
+ return SkImageDecoder::kUnknown_Format;
+}
+
+static SkImageDecoder_FormatReg gFormatReg(get_format_bmp);
+
+///////////////////////////////////////////////////////////////////////////////
+
+class SkBmpDecoderCallback : public image_codec::BmpDecoderCallback {
+public:
+ // we don't copy the bitmap, just remember the pointer
+ SkBmpDecoderCallback(bool justBounds) : fJustBounds(justBounds) {}
+
+ // override from BmpDecoderCallback
+ virtual uint8* SetSize(int width, int height) {
+ fWidth = width;
+ fHeight = height;
+ if (fJustBounds) {
+ return nullptr;
+ }
+
+ fRGB.setCount(width * height * 3); // 3 == r, g, b
+ return fRGB.begin();
+ }
+
+ int width() const { return fWidth; }
+ int height() const { return fHeight; }
+ const uint8_t* rgb() const { return fRGB.begin(); }
+
+private:
+ SkTDArray<uint8_t> fRGB;
+ int fWidth;
+ int fHeight;
+ bool fJustBounds;
+};
+
+SkImageDecoder::Result SkBMPImageDecoder::onDecode(SkStream* stream, SkBitmap* bm, Mode mode) {
+ // First read the entire stream, so that all of the data can be passed to
+ // the BmpDecoderHelper.
+
+ auto data = SkCopyStreamToData(stream);
+ if (!data) {
+ return kFailure;
+ }
+
+ // Byte length of all of the data.
+ const size_t length = data->size();
+ if (0 == length) {
+ return kFailure;
+ }
+
+ const bool justBounds = SkImageDecoder::kDecodeBounds_Mode == mode;
+ SkBmpDecoderCallback callback(justBounds);
+
+ // Now decode the BMP into callback's rgb() array [r,g,b, r,g,b, ...]
+ {
+ image_codec::BmpDecoderHelper helper;
+ const int max_pixels = 16383*16383; // max width*height
+ if (!helper.DecodeImage((const char*) data->data(), length,
+ max_pixels, &callback)) {
+ return kFailure;
+ }
+ }
+
+ // we don't need this anymore, so free it now (before we try to allocate
+ // the bitmap's pixels) rather than waiting for its destructor
+ data.reset(nullptr);
+
+ int width = callback.width();
+ int height = callback.height();
+ SkColorType colorType = this->getPrefColorType(k32Bit_SrcDepth, false);
+
+ // only accept prefConfig if it makes sense for us
+ if (kARGB_4444_SkColorType != colorType && kRGB_565_SkColorType != colorType) {
+ colorType = kN32_SkColorType;
+ }
+
+ SkScaledBitmapSampler sampler(width, height, getSampleSize());
+
+ bm->setInfo(SkImageInfo::Make(sampler.scaledWidth(), sampler.scaledHeight(),
+ colorType, kOpaque_SkAlphaType));
+
+ if (justBounds) {
+ return kSuccess;
+ }
+
+ if (!this->allocPixelRef(bm, nullptr)) {
+ return kFailure;
+ }
+
+ SkAutoLockPixels alp(*bm);
+
+ if (!sampler.begin(bm, SkScaledBitmapSampler::kRGB, *this)) {
+ return kFailure;
+ }
+
+ const int srcRowBytes = width * 3;
+ const int dstHeight = sampler.scaledHeight();
+ const uint8_t* srcRow = callback.rgb();
+
+ srcRow += sampler.srcY0() * srcRowBytes;
+ for (int y = 0; y < dstHeight; y++) {
+ sampler.next(srcRow);
+ srcRow += sampler.srcDY() * srcRowBytes;
+ }
+ return kSuccess;
+}
diff --git a/src/images/SkImageDecoder_libgif.cpp b/src/images/SkImageDecoder_libgif.cpp
new file mode 100644
index 0000000000..2677b13073
--- /dev/null
+++ b/src/images/SkImageDecoder_libgif.cpp
@@ -0,0 +1,541 @@
+/*
+ * Copyright 2006 The Android Open Source Project
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "SkColor.h"
+#include "SkColorPriv.h"
+#include "SkColorTable.h"
+#include "SkImageDecoder.h"
+#include "SkRTConf.h"
+#include "SkScaledBitmapSampler.h"
+#include "SkStream.h"
+#include "SkTemplates.h"
+#include "SkUtils.h"
+
+#include "gif_lib.h"
+
+class SkGIFImageDecoder : public SkImageDecoder {
+public:
+ Format getFormat() const override {
+ return kGIF_Format;
+ }
+
+protected:
+ Result onDecode(SkStream* stream, SkBitmap* bm, Mode mode) override;
+
+private:
+ typedef SkImageDecoder INHERITED;
+};
+
+static const uint8_t gStartingIterlaceYValue[] = {
+ 0, 4, 2, 1
+};
+static const uint8_t gDeltaIterlaceYValue[] = {
+ 8, 8, 4, 2
+};
+
+SK_CONF_DECLARE(bool, c_suppressGIFImageDecoderWarnings,
+ "images.gif.suppressDecoderWarnings", true,
+ "Suppress GIF warnings and errors when calling image decode "
+ "functions.");
+
+
+/* Implement the GIF interlace algorithm in an iterator.
+ 1) grab every 8th line beginning at 0
+ 2) grab every 8th line beginning at 4
+ 3) grab every 4th line beginning at 2
+ 4) grab every 2nd line beginning at 1
+*/
+class GifInterlaceIter {
+public:
+ GifInterlaceIter(int height) : fHeight(height) {
+ fStartYPtr = gStartingIterlaceYValue;
+ fDeltaYPtr = gDeltaIterlaceYValue;
+
+ fCurrY = *fStartYPtr++;
+ fDeltaY = *fDeltaYPtr++;
+ }
+
+ int currY() const {
+ SkASSERT(fStartYPtr);
+ SkASSERT(fDeltaYPtr);
+ return fCurrY;
+ }
+
+ void next() {
+ SkASSERT(fStartYPtr);
+ SkASSERT(fDeltaYPtr);
+
+ int y = fCurrY + fDeltaY;
+ // We went from an if statement to a while loop so that we iterate
+ // through fStartYPtr until a valid row is found. This is so that images
+ // that are smaller than 5x5 will not trash memory.
+ while (y >= fHeight) {
+ if (gStartingIterlaceYValue +
+ SK_ARRAY_COUNT(gStartingIterlaceYValue) == fStartYPtr) {
+ // we done
+ SkDEBUGCODE(fStartYPtr = nullptr;)
+ SkDEBUGCODE(fDeltaYPtr = nullptr;)
+ y = 0;
+ } else {
+ y = *fStartYPtr++;
+ fDeltaY = *fDeltaYPtr++;
+ }
+ }
+ fCurrY = y;
+ }
+
+private:
+ const int fHeight;
+ int fCurrY;
+ int fDeltaY;
+ const uint8_t* fStartYPtr;
+ const uint8_t* fDeltaYPtr;
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+static int DecodeCallBackProc(GifFileType* fileType, GifByteType* out,
+ int size) {
+ SkStream* stream = (SkStream*) fileType->UserData;
+ return (int) stream->read(out, size);
+}
+
+void CheckFreeExtension(SavedImage* Image) {
+ if (Image->ExtensionBlocks) {
+#if GIFLIB_MAJOR < 5
+ FreeExtension(Image);
+#else
+ GifFreeExtensions(&Image->ExtensionBlockCount, &Image->ExtensionBlocks);
+#endif
+ }
+}
+
+// return nullptr on failure
+static const ColorMapObject* find_colormap(const GifFileType* gif) {
+ const ColorMapObject* cmap = gif->Image.ColorMap;
+ if (nullptr == cmap) {
+ cmap = gif->SColorMap;
+ }
+
+ if (nullptr == cmap) {
+ // no colormap found
+ return nullptr;
+ }
+ // some sanity checks
+ if (cmap && ((unsigned)cmap->ColorCount > 256 ||
+ cmap->ColorCount != (1 << cmap->BitsPerPixel))) {
+ cmap = nullptr;
+ }
+ return cmap;
+}
+
+// return -1 if not found (i.e. we're completely opaque)
+static int find_transpIndex(const SavedImage& image, int colorCount) {
+ int transpIndex = -1;
+ for (int i = 0; i < image.ExtensionBlockCount; ++i) {
+ const ExtensionBlock* eb = image.ExtensionBlocks + i;
+ if (eb->Function == 0xF9 && eb->ByteCount == 4) {
+ if (eb->Bytes[0] & 1) {
+ transpIndex = (unsigned char)eb->Bytes[3];
+ // check for valid transpIndex
+ if (transpIndex >= colorCount) {
+ transpIndex = -1;
+ }
+ break;
+ }
+ }
+ }
+ return transpIndex;
+}
+
+static SkImageDecoder::Result error_return(const SkBitmap& bm, const char msg[]) {
+ if (!c_suppressGIFImageDecoderWarnings) {
+ SkDebugf("libgif error [%s] bitmap [%d %d] pixels %p colortable %p\n",
+ msg, bm.width(), bm.height(), bm.getPixels(),
+ bm.getColorTable());
+ }
+ return SkImageDecoder::kFailure;
+}
+
+static void gif_warning(const SkBitmap& bm, const char msg[]) {
+ if (!c_suppressGIFImageDecoderWarnings) {
+ SkDebugf("libgif warning [%s] bitmap [%d %d] pixels %p colortable %p\n",
+ msg, bm.width(), bm.height(), bm.getPixels(),
+ bm.getColorTable());
+ }
+}
+
+/**
+ * Skip rows in the source gif image.
+ * @param gif Source image.
+ * @param dst Scratch output needed by gif library call. Must be >= width bytes.
+ * @param width Bytes per row in the source image.
+ * @param rowsToSkip Number of rows to skip.
+ * @return True on success, false on GIF_ERROR.
+ */
+static bool skip_src_rows(GifFileType* gif, uint8_t* dst, int width, int rowsToSkip) {
+ for (int i = 0; i < rowsToSkip; i++) {
+ if (DGifGetLine(gif, dst, width) == GIF_ERROR) {
+ return false;
+ }
+ }
+ return true;
+}
+
+/**
+ * GIFs with fewer then 256 color entries will sometimes index out of
+ * bounds of the color table (this is malformed, but libgif does not
+ * check sicne it is rare). This function checks for this error and
+ * fixes it. This makes the output image consistantly deterministic.
+ */
+static void sanitize_indexed_bitmap(SkBitmap* bm) {
+ if ((kIndex_8_SkColorType == bm->colorType()) && !(bm->empty())) {
+ SkAutoLockPixels alp(*bm);
+ if (bm->getPixels()) {
+ SkColorTable* ct = bm->getColorTable(); // Index8 must have it.
+ SkASSERT(ct != nullptr);
+ uint32_t count = ct->count();
+ SkASSERT(count > 0);
+ SkASSERT(count <= 0x100);
+ if (count != 0x100) { // Full colortables can't go wrong.
+ // Count is a power of 2; asserted elsewhere.
+ uint8_t byteMask = (~(count - 1));
+ bool warning = false;
+ uint8_t* addr = static_cast<uint8_t*>(bm->getPixels());
+ int height = bm->height();
+ int width = bm->width();
+ size_t rowBytes = bm->rowBytes();
+ while (--height >= 0) {
+ uint8_t* ptr = addr;
+ int x = width;
+ while (--x >= 0) {
+ if (0 != ((*ptr) & byteMask)) {
+ warning = true;
+ *ptr = 0;
+ }
+ ++ptr;
+ }
+ addr += rowBytes;
+ }
+ if (warning) {
+ gif_warning(*bm, "Index out of bounds.");
+ }
+ }
+ }
+ }
+}
+
+namespace {
+// This function is a template argument, so can't be static.
+int close_gif(GifFileType* gif) {
+#if GIFLIB_MAJOR < 5 || (GIFLIB_MAJOR == 5 && GIFLIB_MINOR == 0)
+ return DGifCloseFile(gif);
+#else
+ return DGifCloseFile(gif, nullptr);
+#endif
+}
+}//namespace
+
+SkImageDecoder::Result SkGIFImageDecoder::onDecode(SkStream* sk_stream, SkBitmap* bm, Mode mode) {
+#if GIFLIB_MAJOR < 5
+ GifFileType* gif = DGifOpen(sk_stream, DecodeCallBackProc);
+#else
+ GifFileType* gif = DGifOpen(sk_stream, DecodeCallBackProc, nullptr);
+#endif
+ if (nullptr == gif) {
+ return error_return(*bm, "DGifOpen");
+ }
+
+ SkAutoTCallIProc<GifFileType, close_gif> acp(gif);
+
+ SavedImage temp_save;
+ temp_save.ExtensionBlocks=nullptr;
+ temp_save.ExtensionBlockCount=0;
+ SkAutoTCallVProc<SavedImage, CheckFreeExtension> acp2(&temp_save);
+
+ int width, height;
+ GifRecordType recType;
+ GifByteType *extData;
+#if GIFLIB_MAJOR >= 5
+ int extFunction;
+#endif
+ int transpIndex = -1; // -1 means we don't have it (yet)
+ int fillIndex = gif->SBackGroundColor;
+
+ do {
+ if (DGifGetRecordType(gif, &recType) == GIF_ERROR) {
+ return error_return(*bm, "DGifGetRecordType");
+ }
+
+ switch (recType) {
+ case IMAGE_DESC_RECORD_TYPE: {
+ if (DGifGetImageDesc(gif) == GIF_ERROR) {
+ return error_return(*bm, "IMAGE_DESC_RECORD_TYPE");
+ }
+
+ if (gif->ImageCount < 1) { // sanity check
+ return error_return(*bm, "ImageCount < 1");
+ }
+
+ width = gif->SWidth;
+ height = gif->SHeight;
+
+ SavedImage* image = &gif->SavedImages[gif->ImageCount-1];
+ const GifImageDesc& desc = image->ImageDesc;
+
+ int imageLeft = desc.Left;
+ int imageTop = desc.Top;
+ const int innerWidth = desc.Width;
+ const int innerHeight = desc.Height;
+ if (innerWidth <= 0 || innerHeight <= 0) {
+ return error_return(*bm, "invalid dimensions");
+ }
+
+ // check for valid descriptor
+ if (innerWidth > width) {
+ gif_warning(*bm, "image too wide, expanding output to size");
+ width = innerWidth;
+ imageLeft = 0;
+ } else if (imageLeft + innerWidth > width) {
+ gif_warning(*bm, "shifting image left to fit");
+ imageLeft = width - innerWidth;
+ } else if (imageLeft < 0) {
+ gif_warning(*bm, "shifting image right to fit");
+ imageLeft = 0;
+ }
+
+
+ if (innerHeight > height) {
+ gif_warning(*bm, "image too tall, expanding output to size");
+ height = innerHeight;
+ imageTop = 0;
+ } else if (imageTop + innerHeight > height) {
+ gif_warning(*bm, "shifting image up to fit");
+ imageTop = height - innerHeight;
+ } else if (imageTop < 0) {
+ gif_warning(*bm, "shifting image down to fit");
+ imageTop = 0;
+ }
+
+ SkScaledBitmapSampler sampler(width, height, this->getSampleSize());
+
+ bm->setInfo(SkImageInfo::Make(sampler.scaledWidth(), sampler.scaledHeight(),
+ kIndex_8_SkColorType, kPremul_SkAlphaType));
+
+ if (SkImageDecoder::kDecodeBounds_Mode == mode) {
+ return kSuccess;
+ }
+
+
+ // now we decode the colortable
+ int colorCount = 0;
+ {
+ // Declare colorPtr here for scope.
+ SkPMColor colorPtr[256]; // storage for worst-case
+ const ColorMapObject* cmap = find_colormap(gif);
+ if (cmap != nullptr) {
+ SkASSERT(cmap->ColorCount == (1 << (cmap->BitsPerPixel)));
+ colorCount = cmap->ColorCount;
+ if (colorCount > 256) {
+ colorCount = 256; // our kIndex8 can't support more
+ }
+ for (int index = 0; index < colorCount; index++) {
+ colorPtr[index] = SkPackARGB32(0xFF,
+ cmap->Colors[index].Red,
+ cmap->Colors[index].Green,
+ cmap->Colors[index].Blue);
+ }
+ } else {
+ // find_colormap() returned nullptr. Some (rare, broken)
+ // GIFs don't have a color table, so we force one.
+ gif_warning(*bm, "missing colormap");
+ colorCount = 256;
+ sk_memset32(colorPtr, SK_ColorWHITE, colorCount);
+ }
+ transpIndex = find_transpIndex(temp_save, colorCount);
+ if (transpIndex >= 0) {
+ colorPtr[transpIndex] = SK_ColorTRANSPARENT; // ram in a transparent SkPMColor
+ fillIndex = transpIndex;
+ } else if (fillIndex >= colorCount) {
+ // gif->SBackGroundColor should be less than colorCount.
+ fillIndex = 0; // If not, fix it.
+ }
+
+ SkAutoTUnref<SkColorTable> ctable(new SkColorTable(colorPtr, colorCount));
+ if (!this->allocPixelRef(bm, ctable)) {
+ return error_return(*bm, "allocPixelRef");
+ }
+ }
+
+ // abort if either inner dimension is <= 0
+ if (innerWidth <= 0 || innerHeight <= 0) {
+ return error_return(*bm, "non-pos inner width/height");
+ }
+
+ SkAutoLockPixels alp(*bm);
+
+ SkAutoTMalloc<uint8_t> storage(innerWidth);
+ uint8_t* scanline = storage.get();
+
+ // GIF has an option to store the scanlines of an image, plus a larger background,
+ // filled by a fill color. In this case, we will use a subset of the larger bitmap
+ // for sampling.
+ SkBitmap subset;
+ SkBitmap* workingBitmap;
+ // are we only a subset of the total bounds?
+ if ((imageTop | imageLeft) > 0 ||
+ innerWidth < width || innerHeight < height) {
+ // Fill the background.
+ memset(bm->getPixels(), fillIndex, bm->getSize());
+
+ // Create a subset of the bitmap.
+ SkIRect subsetRect(SkIRect::MakeXYWH(imageLeft / sampler.srcDX(),
+ imageTop / sampler.srcDY(),
+ innerWidth / sampler.srcDX(),
+ innerHeight / sampler.srcDY()));
+ if (!bm->extractSubset(&subset, subsetRect)) {
+ return error_return(*bm, "Extract failed.");
+ }
+ // Update the sampler. We'll now be only sampling into the subset.
+ sampler = SkScaledBitmapSampler(innerWidth, innerHeight, this->getSampleSize());
+ workingBitmap = &subset;
+ } else {
+ workingBitmap = bm;
+ }
+
+ // bm is already locked, but if we had to take a subset, it must be locked also,
+ // so that getPixels() will point to its pixels.
+ SkAutoLockPixels alpWorking(*workingBitmap);
+
+ if (!sampler.begin(workingBitmap, SkScaledBitmapSampler::kIndex, *this)) {
+ return error_return(*bm, "Sampler failed to begin.");
+ }
+
+ // now decode each scanline
+ if (gif->Image.Interlace) {
+ // Iterate over the height of the source data. The sampler will
+ // take care of skipping unneeded rows.
+ GifInterlaceIter iter(innerHeight);
+ for (int y = 0; y < innerHeight; y++) {
+ if (DGifGetLine(gif, scanline, innerWidth) == GIF_ERROR) {
+ gif_warning(*bm, "interlace DGifGetLine");
+ memset(scanline, fillIndex, innerWidth);
+ for (; y < innerHeight; y++) {
+ sampler.sampleInterlaced(scanline, iter.currY());
+ iter.next();
+ }
+ return kPartialSuccess;
+ }
+ sampler.sampleInterlaced(scanline, iter.currY());
+ iter.next();
+ }
+ } else {
+ // easy, non-interlace case
+ const int outHeight = workingBitmap->height();
+ skip_src_rows(gif, scanline, innerWidth, sampler.srcY0());
+ for (int y = 0; y < outHeight; y++) {
+ if (DGifGetLine(gif, scanline, innerWidth) == GIF_ERROR) {
+ gif_warning(*bm, "DGifGetLine");
+ memset(scanline, fillIndex, innerWidth);
+ for (; y < outHeight; y++) {
+ sampler.next(scanline);
+ }
+ return kPartialSuccess;
+ }
+ // scanline now contains the raw data. Sample it.
+ sampler.next(scanline);
+ if (y < outHeight - 1) {
+ skip_src_rows(gif, scanline, innerWidth, sampler.srcDY() - 1);
+ }
+ }
+ // skip the rest of the rows (if any)
+ int read = (outHeight - 1) * sampler.srcDY() + sampler.srcY0() + 1;
+ SkASSERT(read <= innerHeight);
+ skip_src_rows(gif, scanline, innerWidth, innerHeight - read);
+ }
+ sanitize_indexed_bitmap(bm);
+ return kSuccess;
+ } break;
+
+ case EXTENSION_RECORD_TYPE:
+#if GIFLIB_MAJOR < 5
+ if (DGifGetExtension(gif, &temp_save.Function,
+ &extData) == GIF_ERROR) {
+#else
+ if (DGifGetExtension(gif, &extFunction, &extData) == GIF_ERROR) {
+#endif
+ return error_return(*bm, "DGifGetExtension");
+ }
+
+ while (extData != nullptr) {
+ /* Create an extension block with our data */
+#if GIFLIB_MAJOR < 5
+ if (AddExtensionBlock(&temp_save, extData[0],
+ &extData[1]) == GIF_ERROR) {
+#else
+ if (GifAddExtensionBlock(&temp_save.ExtensionBlockCount,
+ &temp_save.ExtensionBlocks,
+ extFunction,
+ extData[0],
+ &extData[1]) == GIF_ERROR) {
+#endif
+ return error_return(*bm, "AddExtensionBlock");
+ }
+ if (DGifGetExtensionNext(gif, &extData) == GIF_ERROR) {
+ return error_return(*bm, "DGifGetExtensionNext");
+ }
+#if GIFLIB_MAJOR < 5
+ temp_save.Function = 0;
+#endif
+ }
+ break;
+
+ case TERMINATE_RECORD_TYPE:
+ break;
+
+ default: /* Should be trapped by DGifGetRecordType */
+ break;
+ }
+ } while (recType != TERMINATE_RECORD_TYPE);
+
+ sanitize_indexed_bitmap(bm);
+ return kSuccess;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+DEFINE_DECODER_CREATOR(GIFImageDecoder);
+///////////////////////////////////////////////////////////////////////////////
+
+static bool is_gif(SkStreamRewindable* stream) {
+ char buf[GIF_STAMP_LEN];
+ if (stream->read(buf, GIF_STAMP_LEN) == GIF_STAMP_LEN) {
+ if (memcmp(GIF_STAMP, buf, GIF_STAMP_LEN) == 0 ||
+ memcmp(GIF87_STAMP, buf, GIF_STAMP_LEN) == 0 ||
+ memcmp(GIF89_STAMP, buf, GIF_STAMP_LEN) == 0) {
+ return true;
+ }
+ }
+ return false;
+}
+
+static SkImageDecoder* sk_libgif_dfactory(SkStreamRewindable* stream) {
+ if (is_gif(stream)) {
+ return new SkGIFImageDecoder;
+ }
+ return nullptr;
+}
+
+static SkImageDecoder_DecodeReg gReg(sk_libgif_dfactory);
+
+static SkImageDecoder::Format get_format_gif(SkStreamRewindable* stream) {
+ if (is_gif(stream)) {
+ return SkImageDecoder::kGIF_Format;
+ }
+ return SkImageDecoder::kUnknown_Format;
+}
+
+static SkImageDecoder_FormatReg gFormatReg(get_format_gif);
diff --git a/src/images/SkImageDecoder_libico.cpp b/src/images/SkImageDecoder_libico.cpp
new file mode 100644
index 0000000000..ff04d74d06
--- /dev/null
+++ b/src/images/SkImageDecoder_libico.cpp
@@ -0,0 +1,456 @@
+/*
+ * Copyright 2006 The Android Open Source Project
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "SkColorPriv.h"
+#include "SkData.h"
+#include "SkImageDecoder.h"
+#include "SkStream.h"
+#include "SkStreamPriv.h"
+#include "SkTypes.h"
+
+class SkICOImageDecoder : public SkImageDecoder {
+public:
+ SkICOImageDecoder();
+
+ Format getFormat() const override {
+ return kICO_Format;
+ }
+
+protected:
+ Result onDecode(SkStream* stream, SkBitmap* bm, Mode) override;
+
+private:
+ typedef SkImageDecoder INHERITED;
+};
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+//read bytes starting from the begin-th index in the buffer
+//read in Intel order, and return an integer
+
+#define readByte(buffer,begin) buffer[begin]
+#define read2Bytes(buffer,begin) buffer[begin]+SkLeftShift(buffer[begin+1],8)
+#define read4Bytes(buffer,begin) buffer[begin]+SkLeftShift(buffer[begin+1],8)+SkLeftShift(buffer[begin+2],16)+SkLeftShift(buffer[begin+3],24)
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+SkICOImageDecoder::SkICOImageDecoder()
+{
+}
+
+//helpers - my function pointer will call one of these, depending on the bitCount, each time through the inner loop
+static void editPixelBit1(const int pixelNo, const unsigned char* buf,
+ const int xorOffset, int& x, int y, const int w,
+ SkBitmap* bm, int alphaByte, int m, int shift, SkPMColor* colors);
+static void editPixelBit4(const int pixelNo, const unsigned char* buf,
+ const int xorOffset, int& x, int y, const int w,
+ SkBitmap* bm, int alphaByte, int m, int shift, SkPMColor* colors);
+static void editPixelBit8(const int pixelNo, const unsigned char* buf,
+ const int xorOffset, int& x, int y, const int w,
+ SkBitmap* bm, int alphaByte, int m, int shift, SkPMColor* colors);
+static void editPixelBit24(const int pixelNo, const unsigned char* buf,
+ const int xorOffset, int& x, int y, const int w,
+ SkBitmap* bm, int alphaByte, int m, int shift, SkPMColor* colors);
+static void editPixelBit32(const int pixelNo, const unsigned char* buf,
+ const int xorOffset, int& x, int y, const int w,
+ SkBitmap* bm, int alphaByte, int m, int shift, SkPMColor* colors);
+
+
+static int calculateRowBytesFor8888(int w, int bitCount)
+{
+ // Default rowBytes is w << 2 for kARGB_8888
+ // In the case of a 4 bit image with an odd width, we need to add some
+ // so we can go off the end of the drawn bitmap.
+ // Add 4 to ensure that it is still a multiple of 4.
+ if (4 == bitCount && (w & 0x1)) {
+ return (w + 1) << 2;
+ }
+ // Otherwise return 0, which will allow it to be calculated automatically.
+ return 0;
+}
+
+SkImageDecoder::Result SkICOImageDecoder::onDecode(SkStream* stream, SkBitmap* bm, Mode mode) {
+ auto data = SkCopyStreamToData(stream);
+ if (!data) {
+ return kFailure;
+ }
+
+ const size_t length = data->size();
+ // Check that the buffer is large enough to read the directory header
+ if (length < 6) {
+ return kFailure;
+ }
+
+ unsigned char* buf = (unsigned char*) data->data();
+
+ //these should always be the same - should i use for error checking? - what about files that have some
+ //incorrect values, but still decode properly?
+ int reserved = read2Bytes(buf, 0); // 0
+ int type = read2Bytes(buf, 2); // 1
+ if (reserved != 0 || type != 1) {
+ return kFailure;
+ }
+
+ int count = read2Bytes(buf, 4);
+ // Check that there are directory entries
+ if (count < 1) {
+ return kFailure;
+ }
+
+ // Check that buffer is large enough to read directory entries.
+ // We are guaranteed that count is at least 1. We might as well assume
+ // count is 1 because this deprecated decoder only looks at the first
+ // directory entry.
+ if (length < (size_t)(6 + count*16)) {
+ return kFailure;
+ }
+
+ //skip ahead to the correct header
+ //commented out lines are not used, but if i switch to other read method, need to know how many to skip
+ //otherwise, they could be used for error checking
+ int w = readByte(buf, 6);
+ int h = readByte(buf, 7);
+ SkASSERT(w >= 0 && h >= 0);
+ int colorCount = readByte(buf, 8);
+ //int reservedToo = readByte(buf, 9 + choice*16); //0
+ //int planes = read2Bytes(buf, 10 + choice*16); //1 - but often 0
+ //int fakeBitCount = read2Bytes(buf, 12 + choice*16); //should be real - usually 0
+ const size_t size = read4Bytes(buf, 14); //matters?
+ const size_t offset = read4Bytes(buf, 18);
+ // promote the sum to 64-bits to avoid overflow
+ // Check that buffer is large enough to read image data
+ if (offset > length || size > length || ((uint64_t)offset + size) > length) {
+ return kFailure;
+ }
+
+ // Check to see if this is a PNG image inside the ICO
+ {
+ SkMemoryStream subStream(buf + offset, size, false);
+ SkAutoTDelete<SkImageDecoder> otherDecoder(SkImageDecoder::Factory(&subStream));
+ if (otherDecoder.get() != nullptr) {
+ // Disallow nesting ICO files within one another
+ // FIXME: Can ICO files contain other formats besides PNG?
+ if (otherDecoder->getFormat() == SkImageDecoder::kICO_Format) {
+ return kFailure;
+ }
+ // Set fields on the other decoder to be the same as this one.
+ this->copyFieldsToOther(otherDecoder.get());
+ const Result result = otherDecoder->decode(&subStream, bm, this->getDefaultPref(),
+ mode);
+ // FIXME: Should we just return result here? Is it possible that data that looked like
+ // a subimage was not, but was actually a valid ICO?
+ if (result != kFailure) {
+ return result;
+ }
+ }
+ }
+
+ //int infoSize = read4Bytes(buf, offset); //40
+ //int width = read4Bytes(buf, offset+4); //should == w
+ //int height = read4Bytes(buf, offset+8); //should == 2*h
+ //int planesToo = read2Bytes(buf, offset+12); //should == 1 (does it?)
+
+ // For ico images, only a byte is used to store each dimension
+ // 0 is used to represent 256
+ if (w == 0) {
+ w = 256;
+ }
+ if (h == 0) {
+ h = 256;
+ }
+
+ // Check that buffer is large enough to read the bit depth
+ if (length < offset + 16) {
+ return kFailure;
+ }
+ int bitCount = read2Bytes(buf, offset+14);
+
+ void (*placePixel)(const int pixelNo, const unsigned char* buf,
+ const int xorOffset, int& x, int y, const int w,
+ SkBitmap* bm, int alphaByte, int m, int shift, SkPMColor* colors) = nullptr;
+ switch (bitCount)
+ {
+ case 1:
+ placePixel = &editPixelBit1;
+ colorCount = 2;
+ break;
+ case 4:
+ placePixel = &editPixelBit4;
+ colorCount = 16;
+ break;
+ case 8:
+ placePixel = &editPixelBit8;
+ colorCount = 256;
+ break;
+ case 24:
+ placePixel = &editPixelBit24;
+ colorCount = 0;
+ break;
+ case 32:
+ placePixel = &editPixelBit32;
+ colorCount = 0;
+ break;
+ default:
+ SkDEBUGF(("Decoding %ibpp is unimplemented\n", bitCount));
+ return kFailure;
+ }
+
+ //these should all be zero, but perhaps are not - need to check
+ //int compression = read4Bytes(buf, offset+16); //0
+ //int imageSize = read4Bytes(buf, offset+20); //0 - sometimes has a value
+ //int xPixels = read4Bytes(buf, offset+24); //0
+ //int yPixels = read4Bytes(buf, offset+28); //0
+ //int colorsUsed = read4Bytes(buf, offset+32) //0 - might have an actual value though
+ //int colorsImportant = read4Bytes(buf, offset+36); //0
+
+ int begin = SkToInt(offset + 40);
+ // Check that the buffer is large enough to read the color table
+ // For bmp-in-icos, there should be 4 bytes per color
+ if (length < (size_t) (begin + 4*colorCount)) {
+ return kFailure;
+ }
+
+ //this array represents the colortable
+ //if i allow other types of bitmaps, it may actually be used as a part of the bitmap
+ SkPMColor* colors = nullptr;
+ int blue, green, red;
+ if (colorCount)
+ {
+ colors = new SkPMColor[colorCount];
+ for (int j = 0; j < colorCount; j++)
+ {
+ //should this be a function - maybe a #define?
+ blue = readByte(buf, begin + 4*j);
+ green = readByte(buf, begin + 4*j + 1);
+ red = readByte(buf, begin + 4*j + 2);
+ colors[j] = SkPackARGB32(0xFF, red & 0xFF, green & 0xFF, blue & 0xFF);
+ }
+ }
+ int bitWidth = w*bitCount;
+ int test = bitWidth & 0x1F;
+ int mask = -(((test >> 4) | (test >> 3) | (test >> 2) | (test >> 1) | test) & 0x1); //either 0xFFFFFFFF or 0
+ int lineBitWidth = (bitWidth & 0xFFFFFFE0) + (0x20 & mask);
+ int lineWidth = lineBitWidth/bitCount;
+
+ int xorOffset = begin + colorCount*4; //beginning of the color bitmap
+ //other read method means we will just be here already
+ int andOffset = xorOffset + ((lineWidth*h*bitCount) >> 3);
+
+ /*int */test = w & 0x1F; //the low 5 bits - we are rounding up to the next 32 (2^5)
+ /*int */mask = -(((test >> 4) | (test >> 3) | (test >> 2) | (test >> 1) | test) & 0x1); //either 0xFFFFFFFF or 0
+ int andLineWidth = (w & 0xFFFFFFE0) + (0x20 & mask);
+ //if we allow different Configs, everything is the same til here
+ //change the config, and use different address getter, and place index vs color, and add the color table
+ //FIXME: what is the tradeoff in size?
+ //if the andbitmap (mask) is all zeroes, then we can easily do an index bitmap
+ //however, with small images with large colortables, maybe it's better to still do argb_8888
+
+ bm->setInfo(SkImageInfo::MakeN32Premul(w, h), calculateRowBytesFor8888(w, bitCount));
+
+ if (SkImageDecoder::kDecodeBounds_Mode == mode) {
+ delete[] colors;
+ return kSuccess;
+ }
+
+ if (!this->allocPixelRef(bm, nullptr))
+ {
+ delete[] colors;
+ return kFailure;
+ }
+
+ // The AND mask is a 1-bit alpha mask for each pixel that comes after the
+ // XOR mask in the bmp. If we check that the largest AND offset is safe,
+ // it should mean all other buffer accesses will be at smaller indices and
+ // will therefore be safe.
+ size_t maxAndOffset = andOffset + ((andLineWidth*(h-1)+(w-1)) >> 3);
+ if (length <= maxAndOffset) {
+ return kFailure;
+ }
+
+ // Here we assert that all reads from the buffer using the XOR offset are
+ // less than the AND offset. This should be guaranteed based on the above
+ // calculations.
+#ifdef SK_DEBUG
+ int maxPixelNum = lineWidth*(h-1)+w-1;
+ int maxByte;
+ switch (bitCount) {
+ case 1:
+ maxByte = maxPixelNum >> 3;
+ break;
+ case 4:
+ maxByte = maxPixelNum >> 1;
+ break;
+ case 8:
+ maxByte = maxPixelNum;
+ break;
+ case 24:
+ maxByte = maxPixelNum * 3 + 2;
+ break;
+ case 32:
+ maxByte = maxPixelNum * 4 + 3;
+ break;
+ default:
+ SkASSERT(false);
+ return kFailure;
+ }
+ int maxXOROffset = xorOffset + maxByte;
+ SkASSERT(maxXOROffset < andOffset);
+#endif
+
+ SkAutoLockPixels alp(*bm);
+
+ for (int y = 0; y < h; y++)
+ {
+ for (int x = 0; x < w; x++)
+ {
+ //U32* address = bm->getAddr32(x, y);
+
+ //check the alpha bit first, but pass it along to the function to figure out how to deal with it
+ int andPixelNo = andLineWidth*(h-y-1)+x;
+ //only need to get a new alphaByte when x %8 == 0
+ //but that introduces an if and a mod - probably much slower
+ //that's ok, it's just a read of an array, not a stream
+ int alphaByte = readByte(buf, andOffset + (andPixelNo >> 3));
+ int shift = 7 - (andPixelNo & 0x7);
+ int m = 1 << shift;
+
+ int pixelNo = lineWidth*(h-y-1)+x;
+ placePixel(pixelNo, buf, xorOffset, x, y, w, bm, alphaByte, m, shift, colors);
+
+ }
+ }
+
+ delete [] colors;
+ //ensure we haven't read off the end?
+ //of course this doesn't help us if the andOffset was a lie...
+ //return andOffset + (andLineWidth >> 3) <= length;
+ return kSuccess;
+} //onDecode
+
+//function to place the pixel, determined by the bitCount
+static void editPixelBit1(const int pixelNo, const unsigned char* buf,
+ const int xorOffset, int& x, int y, const int w,
+ SkBitmap* bm, int alphaByte, int m, int shift, SkPMColor* colors)
+{
+ // note that this should be the same as/similar to the AND bitmap
+ SkPMColor* address = bm->getAddr32(x,y);
+ int byte = readByte(buf, xorOffset + (pixelNo >> 3));
+ int colorBit;
+ int alphaBit;
+ // Read all of the bits in this byte.
+ int i = x + 8;
+ // Pin to the width so we do not write outside the bounds of
+ // our color table.
+ i = i > w ? w : i;
+ // While loop to check all 8 bits individually.
+ while (x < i)
+ {
+
+ colorBit = (byte & m) >> shift;
+ alphaBit = (alphaByte & m) >> shift;
+ *address = (alphaBit-1)&(colors[colorBit]);
+ x++;
+ // setup for the next pixel
+ address = address + 1;
+ m = m >> 1;
+ shift -= 1;
+ }
+ x--;
+}
+static void editPixelBit4(const int pixelNo, const unsigned char* buf,
+ const int xorOffset, int& x, int y, const int w,
+ SkBitmap* bm, int alphaByte, int m, int shift, SkPMColor* colors)
+{
+ SkPMColor* address = bm->getAddr32(x, y);
+ int byte = readByte(buf, xorOffset + (pixelNo >> 1));
+ int pixel = (byte >> 4) & 0xF;
+ int alphaBit = (alphaByte & m) >> shift;
+ *address = (alphaBit-1)&(colors[pixel]);
+ x++;
+ //if w is odd, x may be the same as w, which means we are writing to an unused portion of the bitmap
+ //but that's okay, since i've added an extra rowByte for just this purpose
+ address = address + 1;
+ pixel = byte & 0xF;
+ m = m >> 1;
+ alphaBit = (alphaByte & m) >> (shift-1);
+ //speed up trick here
+ *address = (alphaBit-1)&(colors[pixel]);
+}
+
+static void editPixelBit8(const int pixelNo, const unsigned char* buf,
+ const int xorOffset, int& x, int y, const int w,
+ SkBitmap* bm, int alphaByte, int m, int shift, SkPMColor* colors)
+{
+ SkPMColor* address = bm->getAddr32(x, y);
+ int pixel = readByte(buf, xorOffset + pixelNo);
+ int alphaBit = (alphaByte & m) >> shift;
+ *address = (alphaBit-1)&(colors[pixel]);
+}
+
+static void editPixelBit24(const int pixelNo, const unsigned char* buf,
+ const int xorOffset, int& x, int y, const int w,
+ SkBitmap* bm, int alphaByte, int m, int shift, SkPMColor* colors)
+{
+ SkPMColor* address = bm->getAddr32(x, y);
+ int blue = readByte(buf, xorOffset + 3*pixelNo);
+ int green = readByte(buf, xorOffset + 3*pixelNo + 1);
+ int red = readByte(buf, xorOffset + 3*pixelNo + 2);
+ int alphaBit = (alphaByte & m) >> shift;
+ //alphaBit == 1 => alpha = 0
+ int alpha = (alphaBit-1) & 0xFF;
+ *address = SkPreMultiplyARGB(alpha, red, green, blue);
+}
+
+static void editPixelBit32(const int pixelNo, const unsigned char* buf,
+ const int xorOffset, int& x, int y, const int w,
+ SkBitmap* bm, int alphaByte, int m, int shift, SkPMColor* colors)
+{
+ SkPMColor* address = bm->getAddr32(x, y);
+ int blue = readByte(buf, xorOffset + 4*pixelNo);
+ int green = readByte(buf, xorOffset + 4*pixelNo + 1);
+ int red = readByte(buf, xorOffset + 4*pixelNo + 2);
+ int alphaBit = (alphaByte & m) >> shift;
+#if 1 // don't trust the alphaBit for 32bit images <mrr>
+ alphaBit = 0;
+#endif
+ int alpha = readByte(buf, xorOffset + 4*pixelNo + 3) & ((alphaBit-1)&0xFF);
+ *address = SkPreMultiplyARGB(alpha, red, green, blue);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+DEFINE_DECODER_CREATOR(ICOImageDecoder);
+/////////////////////////////////////////////////////////////////////////////////////////
+
+static bool is_ico(SkStreamRewindable* stream) {
+ // Check to see if the first four bytes are 0,0,1,0
+ // FIXME: Is that required and sufficient?
+ char buf[4];
+ if (stream->read((void*)buf, 4) != 4) {
+ return false;
+ }
+ int reserved = read2Bytes(buf, 0);
+ int type = read2Bytes(buf, 2);
+ return 0 == reserved && 1 == type;
+}
+
+static SkImageDecoder* sk_libico_dfactory(SkStreamRewindable* stream) {
+ if (is_ico(stream)) {
+ return new SkICOImageDecoder;
+ }
+ return nullptr;
+}
+
+static SkImageDecoder_DecodeReg gReg(sk_libico_dfactory);
+
+static SkImageDecoder::Format get_format_ico(SkStreamRewindable* stream) {
+ if (is_ico(stream)) {
+ return SkImageDecoder::kICO_Format;
+ }
+ return SkImageDecoder::kUnknown_Format;
+}
+
+static SkImageDecoder_FormatReg gFormatReg(get_format_ico);
diff --git a/src/images/SkImageDecoder_libjpeg.cpp b/src/images/SkImageDecoder_libjpeg.cpp
index fd10bdbdf6..89bfefcd45 100644
--- a/src/images/SkImageDecoder_libjpeg.cpp
+++ b/src/images/SkImageDecoder_libjpeg.cpp
@@ -6,10 +6,13 @@
*/
+#include "SkImageDecoder.h"
#include "SkImageEncoder.h"
#include "SkJpegUtility.h"
#include "SkColorPriv.h"
#include "SkDither.h"
+#include "SkMSAN.h"
+#include "SkScaledBitmapSampler.h"
#include "SkStream.h"
#include "SkTemplates.h"
#include "SkTime.h"
@@ -25,12 +28,730 @@ extern "C" {
#include "jerror.h"
}
-// These enable timing code that report milliseconds for an encoding
+// These enable timing code that report milliseconds for an encoding/decoding
//#define TIME_ENCODE
+//#define TIME_DECODE
// this enables our rgb->yuv code, which is faster than libjpeg on ARM
#define WE_CONVERT_TO_YUV
+// If ANDROID_RGB is defined by in the jpeg headers it indicates that jpeg offers
+// support for two additional formats (1) JCS_RGBA_8888 and (2) JCS_RGB_565.
+
+#define DEFAULT_FOR_SUPPRESS_JPEG_IMAGE_DECODER_WARNINGS true
+#define DEFAULT_FOR_SUPPRESS_JPEG_IMAGE_DECODER_ERRORS true
+SK_CONF_DECLARE(bool, c_suppressJPEGImageDecoderWarnings,
+ "images.jpeg.suppressDecoderWarnings",
+ DEFAULT_FOR_SUPPRESS_JPEG_IMAGE_DECODER_WARNINGS,
+ "Suppress most JPG warnings when calling decode functions.");
+SK_CONF_DECLARE(bool, c_suppressJPEGImageDecoderErrors,
+ "images.jpeg.suppressDecoderErrors",
+ DEFAULT_FOR_SUPPRESS_JPEG_IMAGE_DECODER_ERRORS,
+ "Suppress most JPG error messages when decode "
+ "function fails.");
+
+//////////////////////////////////////////////////////////////////////////
+//////////////////////////////////////////////////////////////////////////
+
+static void do_nothing_emit_message(jpeg_common_struct*, int) {
+ /* do nothing */
+}
+static void do_nothing_output_message(j_common_ptr) {
+ /* do nothing */
+}
+
+static void initialize_info(jpeg_decompress_struct* cinfo, skjpeg_source_mgr* src_mgr) {
+ SkASSERT(cinfo != nullptr);
+ SkASSERT(src_mgr != nullptr);
+ jpeg_create_decompress(cinfo);
+ cinfo->src = src_mgr;
+ /* To suppress warnings with a SK_DEBUG binary, set the
+ * environment variable "skia_images_jpeg_suppressDecoderWarnings"
+ * to "true". Inside a program that links to skia:
+ * SK_CONF_SET("images.jpeg.suppressDecoderWarnings", true); */
+ if (c_suppressJPEGImageDecoderWarnings) {
+ cinfo->err->emit_message = &do_nothing_emit_message;
+ }
+ /* To suppress error messages with a SK_DEBUG binary, set the
+ * environment variable "skia_images_jpeg_suppressDecoderErrors"
+ * to "true". Inside a program that links to skia:
+ * SK_CONF_SET("images.jpeg.suppressDecoderErrors", true); */
+ if (c_suppressJPEGImageDecoderErrors) {
+ cinfo->err->output_message = &do_nothing_output_message;
+ }
+}
+
+class SkJPEGImageDecoder : public SkImageDecoder {
+public:
+
+ Format getFormat() const override {
+ return kJPEG_Format;
+ }
+
+protected:
+ Result onDecode(SkStream* stream, SkBitmap* bm, Mode) override;
+ bool onDecodeYUV8Planes(SkStream* stream, SkISize componentSizes[3],
+ void* planes[3], size_t rowBytes[3],
+ SkYUVColorSpace* colorSpace) override;
+
+private:
+
+ /**
+ * Determine the appropriate bitmap colortype and out_color_space based on
+ * both the preference of the caller and the jpeg_color_space on the
+ * jpeg_decompress_struct passed in.
+ * Must be called after jpeg_read_header.
+ */
+ SkColorType getBitmapColorType(jpeg_decompress_struct*);
+
+ typedef SkImageDecoder INHERITED;
+};
+
+//////////////////////////////////////////////////////////////////////////
+
+/* Automatically clean up after throwing an exception */
+class JPEGAutoClean {
+public:
+ JPEGAutoClean(): cinfo_ptr(nullptr) {}
+ ~JPEGAutoClean() {
+ if (cinfo_ptr) {
+ jpeg_destroy_decompress(cinfo_ptr);
+ }
+ }
+ void set(jpeg_decompress_struct* info) {
+ cinfo_ptr = info;
+ }
+private:
+ jpeg_decompress_struct* cinfo_ptr;
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+/* If we need to better match the request, we might examine the image and
+ output dimensions, and determine if the downsampling jpeg provided is
+ not sufficient. If so, we can recompute a modified sampleSize value to
+ make up the difference.
+
+ To skip this additional scaling, just set sampleSize = 1; below.
+ */
+static int recompute_sampleSize(int sampleSize,
+ const jpeg_decompress_struct& cinfo) {
+ return sampleSize * cinfo.output_width / cinfo.image_width;
+}
+
+static bool valid_output_dimensions(const jpeg_decompress_struct& cinfo) {
+ /* These are initialized to 0, so if they have non-zero values, we assume
+ they are "valid" (i.e. have been computed by libjpeg)
+ */
+ return 0 != cinfo.output_width && 0 != cinfo.output_height;
+}
+
+static bool skip_src_rows(jpeg_decompress_struct* cinfo, void* buffer, int count) {
+ for (int i = 0; i < count; i++) {
+ JSAMPLE* rowptr = (JSAMPLE*)buffer;
+ int row_count = jpeg_read_scanlines(cinfo, &rowptr, 1);
+ if (1 != row_count) {
+ return false;
+ }
+ }
+ return true;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+// 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 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, width, height);
+ }
+}
+
+static bool return_false(const jpeg_decompress_struct& cinfo,
+ const char caller[]) {
+ print_jpeg_decoder_errors(cinfo, 0, 0, caller);
+ return false;
+}
+
+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) {
+ // At this point we've received CMYK pixels from libjpeg. We
+ // perform a crude conversion to RGB (based on the formulae
+ // from easyrgb.com):
+ // CMYK -> CMY
+ // C = ( C * (1 - K) + K ) // for each CMY component
+ // CMY -> RGB
+ // R = ( 1 - C ) * 255 // for each RGB component
+ // Unfortunately we are seeing inverted CMYK so all the original terms
+ // are 1-. This yields:
+ // CMYK -> CMY
+ // C = ( (1-C) * (1 - (1-K) + (1-K) ) -> C = 1 - C*K
+ // The conversion from CMY->RGB remains the same
+ for (unsigned int x = 0; x < width; ++x, scanline += 4) {
+ scanline[0] = SkMulDiv255Round(scanline[0], scanline[3]);
+ scanline[1] = SkMulDiv255Round(scanline[1], scanline[3]);
+ scanline[2] = SkMulDiv255Round(scanline[2], scanline[3]);
+ scanline[3] = 255;
+ }
+}
+
+/**
+ * Common code for setting the error manager.
+ */
+static void set_error_mgr(jpeg_decompress_struct* cinfo, skjpeg_error_mgr* errorManager) {
+ SkASSERT(cinfo != nullptr);
+ SkASSERT(errorManager != nullptr);
+ cinfo->err = jpeg_std_error(errorManager);
+ errorManager->error_exit = skjpeg_error_exit;
+}
+
+/**
+ * Common code for setting the dct method.
+ */
+static void set_dct_method(const SkImageDecoder& decoder, jpeg_decompress_struct* cinfo) {
+ SkASSERT(cinfo != nullptr);
+ cinfo->dct_method = JDCT_ISLOW;
+}
+
+SkColorType SkJPEGImageDecoder::getBitmapColorType(jpeg_decompress_struct* cinfo) {
+ SkASSERT(cinfo != nullptr);
+
+ SrcDepth srcDepth = k32Bit_SrcDepth;
+ if (JCS_GRAYSCALE == cinfo->jpeg_color_space) {
+ srcDepth = k8BitGray_SrcDepth;
+ }
+
+ SkColorType colorType = this->getPrefColorType(srcDepth, /*hasAlpha*/ false);
+ switch (colorType) {
+ case kAlpha_8_SkColorType:
+ // Only respect A8 colortype if the original is grayscale,
+ // in which case we will treat the grayscale as alpha
+ // values.
+ if (cinfo->jpeg_color_space != JCS_GRAYSCALE) {
+ colorType = kN32_SkColorType;
+ }
+ break;
+ case kN32_SkColorType:
+ // Fall through.
+ case kARGB_4444_SkColorType:
+ // Fall through.
+ case kRGB_565_SkColorType:
+ // These are acceptable destination colortypes.
+ break;
+ default:
+ // Force all other colortypes to 8888.
+ colorType = kN32_SkColorType;
+ break;
+ }
+
+ switch (cinfo->jpeg_color_space) {
+ case JCS_CMYK:
+ // Fall through.
+ case JCS_YCCK:
+ // libjpeg cannot convert from CMYK or YCCK to RGB - here we set up
+ // so libjpeg will give us CMYK samples back and we will later
+ // manually convert them to RGB
+ cinfo->out_color_space = JCS_CMYK;
+ break;
+ case JCS_GRAYSCALE:
+ if (kAlpha_8_SkColorType == colorType) {
+ cinfo->out_color_space = JCS_GRAYSCALE;
+ break;
+ }
+ // The data is JCS_GRAYSCALE, but the caller wants some sort of RGB
+ // colortype. Fall through to set to the default.
+ default:
+ cinfo->out_color_space = JCS_RGB;
+ break;
+ }
+ return colorType;
+}
+
+/**
+ * Based on the colortype and dither mode, adjust out_color_space and
+ * dither_mode of cinfo. Only does work in ANDROID_RGB
+ */
+static void adjust_out_color_space_and_dither(jpeg_decompress_struct* cinfo,
+ SkColorType colorType,
+ const SkImageDecoder& decoder) {
+ SkASSERT(cinfo != nullptr);
+#ifdef ANDROID_RGB
+ cinfo->dither_mode = JDITHER_NONE;
+ if (JCS_CMYK == cinfo->out_color_space) {
+ return;
+ }
+ switch (colorType) {
+ case kN32_SkColorType:
+ cinfo->out_color_space = JCS_RGBA_8888;
+ break;
+ case kRGB_565_SkColorType:
+ cinfo->out_color_space = JCS_RGB_565;
+ if (decoder.getDitherImage()) {
+ cinfo->dither_mode = JDITHER_ORDERED;
+ }
+ break;
+ default:
+ break;
+ }
+#endif
+}
+
+/**
+ Sets all pixels in given bitmap to SK_ColorWHITE for all rows >= y.
+ Used when decoding fails partway through reading scanlines to fill
+ remaining lines. */
+static void fill_below_level(int y, SkBitmap* bitmap) {
+ SkIRect rect = SkIRect::MakeLTRB(0, y, bitmap->width(), bitmap->height());
+ SkCanvas canvas(*bitmap);
+ canvas.clipRect(SkRect::Make(rect));
+ canvas.drawColor(SK_ColorWHITE);
+}
+
+/**
+ * Get the config and bytes per pixel of the source data. Return
+ * whether the data is supported.
+ */
+static bool get_src_config(const jpeg_decompress_struct& cinfo,
+ SkScaledBitmapSampler::SrcConfig* sc,
+ int* srcBytesPerPixel) {
+ SkASSERT(sc != nullptr && srcBytesPerPixel != nullptr);
+ if (JCS_CMYK == cinfo.out_color_space) {
+ // In this case we will manually convert the CMYK values to RGB
+ *sc = SkScaledBitmapSampler::kRGBX;
+ // The CMYK work-around relies on 4 components per pixel here
+ *srcBytesPerPixel = 4;
+ } else if (3 == cinfo.out_color_components && JCS_RGB == cinfo.out_color_space) {
+ *sc = SkScaledBitmapSampler::kRGB;
+ *srcBytesPerPixel = 3;
+#ifdef ANDROID_RGB
+ } else if (JCS_RGBA_8888 == cinfo.out_color_space) {
+ *sc = SkScaledBitmapSampler::kRGBX;
+ *srcBytesPerPixel = 4;
+ } else if (JCS_RGB_565 == cinfo.out_color_space) {
+ *sc = SkScaledBitmapSampler::kRGB_565;
+ *srcBytesPerPixel = 2;
+#endif
+ } else if (1 == cinfo.out_color_components &&
+ JCS_GRAYSCALE == cinfo.out_color_space) {
+ *sc = SkScaledBitmapSampler::kGray;
+ *srcBytesPerPixel = 1;
+ } else {
+ return false;
+ }
+ return true;
+}
+
+SkImageDecoder::Result SkJPEGImageDecoder::onDecode(SkStream* stream, SkBitmap* bm, Mode mode) {
+#ifdef TIME_DECODE
+ SkAutoTime atm("JPEG Decode");
+#endif
+
+ 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_failure(cinfo, *bm, "setjmp");
+ }
+
+ initialize_info(&cinfo, &srcManager);
+ autoClean.set(&cinfo);
+
+ int status = jpeg_read_header(&cinfo, true);
+ if (status != JPEG_HEADER_OK) {
+ return return_failure(cinfo, *bm, "read_header");
+ }
+
+ /* Try to fulfill the requested sampleSize. Since jpeg can do it (when it
+ can) much faster that we, just use their num/denom api to approximate
+ the size.
+ */
+ int sampleSize = this->getSampleSize();
+
+ set_dct_method(*this, &cinfo);
+
+ SkASSERT(1 == cinfo.scale_num);
+ cinfo.scale_denom = sampleSize;
+
+ const SkColorType colorType = this->getBitmapColorType(&cinfo);
+ const SkAlphaType alphaType = kAlpha_8_SkColorType == colorType ?
+ kPremul_SkAlphaType : kOpaque_SkAlphaType;
+
+ adjust_out_color_space_and_dither(&cinfo, colorType, *this);
+
+ if (1 == sampleSize && SkImageDecoder::kDecodeBounds_Mode == mode) {
+ // Assume an A8 bitmap is not opaque to avoid the check of each
+ // 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.
+ 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
+ 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)) {
+ /* If we failed here, we may still have enough information to return
+ to the caller if they just wanted (subsampled bounds). If sampleSize
+ was 1, then we would have already returned. Thus we just check if
+ we're in kDecodeBounds_Mode, and that we have valid output sizes.
+
+ One reason to fail here is that we have insufficient stream data
+ to complete the setup. However, output dimensions seem to get
+ computed very early, which is why this special check can pay off.
+ */
+ if (SkImageDecoder::kDecodeBounds_Mode == mode && valid_output_dimensions(cinfo)) {
+ SkScaledBitmapSampler smpl(cinfo.output_width, cinfo.output_height,
+ recompute_sampleSize(sampleSize, cinfo));
+ // Assume an A8 bitmap is not opaque to avoid the check of each
+ // 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.
+ bool success = bm->setInfo(SkImageInfo::Make(smpl.scaledWidth(), smpl.scaledHeight(),
+ colorType, alphaType));
+ return success ? kSuccess : kFailure;
+ } else {
+ return return_failure(cinfo, *bm, "start_decompress");
+ }
+ }
+ sampleSize = recompute_sampleSize(sampleSize, cinfo);
+
+ SkScaledBitmapSampler sampler(cinfo.output_width, cinfo.output_height, sampleSize);
+ // Assume an A8 bitmap is not opaque to avoid the check of each
+ // 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.
+ bm->setInfo(SkImageInfo::Make(sampler.scaledWidth(), sampler.scaledHeight(),
+ colorType, alphaType));
+ if (SkImageDecoder::kDecodeBounds_Mode == mode) {
+ return kSuccess;
+ }
+ if (!this->allocPixelRef(bm, nullptr)) {
+ return return_failure(cinfo, *bm, "allocPixelRef");
+ }
+
+ SkAutoLockPixels alp(*bm);
+
+#ifdef ANDROID_RGB
+ /* short-circuit the SkScaledBitmapSampler when possible, as this gives
+ a significant performance boost.
+ */
+ if (sampleSize == 1 &&
+ ((kN32_SkColorType == colorType && cinfo.out_color_space == JCS_RGBA_8888) ||
+ (kRGB_565_SkColorType == colorType && cinfo.out_color_space == JCS_RGB_565)))
+ {
+ JSAMPLE* rowptr = (JSAMPLE*)bm->getPixels();
+ INT32 const bpr = bm->rowBytes();
+
+ while (cinfo.output_scanline < cinfo.output_height) {
+ int row_count = jpeg_read_scanlines(&cinfo, &rowptr, 1);
+ if (0 == row_count) {
+ // if row_count == 0, then we didn't get a scanline,
+ // so return early. We will return a partial image.
+ fill_below_level(cinfo.output_scanline, bm);
+ cinfo.output_scanline = cinfo.output_height;
+ jpeg_finish_decompress(&cinfo);
+ return kPartialSuccess;
+ }
+ if (this->shouldCancelDecode()) {
+ return return_failure(cinfo, *bm, "shouldCancelDecode");
+ }
+ rowptr += bpr;
+ }
+ jpeg_finish_decompress(&cinfo);
+ return kSuccess;
+ }
+#endif
+
+ // check for supported formats
+ SkScaledBitmapSampler::SrcConfig sc;
+ int srcBytesPerPixel;
+
+ if (!get_src_config(cinfo, &sc, &srcBytesPerPixel)) {
+ return return_failure(cinfo, *bm, "jpeg colorspace");
+ }
+
+ if (!sampler.begin(bm, sc, *this)) {
+ return return_failure(cinfo, *bm, "sampler.begin");
+ }
+
+ SkAutoTMalloc<uint8_t> srcStorage(cinfo.output_width * srcBytesPerPixel);
+ uint8_t* srcRow = srcStorage.get();
+
+ // Possibly skip initial rows [sampler.srcY0]
+ if (!skip_src_rows(&cinfo, srcRow, sampler.srcY0())) {
+ return return_failure(cinfo, *bm, "skip rows");
+ }
+
+ // now loop through scanlines until y == bm->height() - 1
+ for (int y = 0;; y++) {
+ JSAMPLE* rowptr = (JSAMPLE*)srcRow;
+ int row_count = jpeg_read_scanlines(&cinfo, &rowptr, 1);
+ sk_msan_mark_initialized(srcRow, srcRow + cinfo.output_width * srcBytesPerPixel,
+ "skbug.com/4550");
+ if (0 == row_count) {
+ // if row_count == 0, then we didn't get a scanline,
+ // so return early. We will return a partial image.
+ fill_below_level(y, bm);
+ cinfo.output_scanline = cinfo.output_height;
+ jpeg_finish_decompress(&cinfo);
+ return kPartialSuccess;
+ }
+ if (this->shouldCancelDecode()) {
+ return return_failure(cinfo, *bm, "shouldCancelDecode");
+ }
+
+ if (JCS_CMYK == cinfo.out_color_space) {
+ convert_CMYK_to_RGB(srcRow, cinfo.output_width);
+ }
+
+
+ sampler.next(srcRow);
+ if (bm->height() - 1 == y) {
+ // we're done
+ break;
+ }
+
+ if (!skip_src_rows(&cinfo, srcRow, sampler.srcDY() - 1)) {
+ 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_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 bool appears_to_be_yuv(const jpeg_decompress_struct& info) {
+ return (info.jpeg_color_space == JCS_YCbCr)
+ && (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);
+}
+
+static void update_components_sizes(const jpeg_decompress_struct& cinfo, SkISize componentSizes[3],
+ SizeType sizeType) {
+ SkASSERT(appears_to_be_yuv(cinfo));
+ 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]) {
+ SkASSERT(appears_to_be_yuv(cinfo));
+ // 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(rowBytesY * 4);
+ JSAMPROW yLastRow = (JSAMPROW)lastRowStorage.get();
+ JSAMPROW uLastRow = yLastRow + rowBytesY;
+ JSAMPROW vLastRow = uLastRow + rowBytesY;
+ JSAMPROW dummyRow = vLastRow + rowBytesY;
+
+ 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 (!appears_to_be_yuv(cinfo)) {
+ // 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;
+
+#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");
+ }
+
+ // Seems like jpeg_start_decompress is updating our opinion of whether cinfo represents YUV.
+ // Again, not really an error.
+ if (!appears_to_be_yuv(cinfo)) {
+ return false;
+ }
+
+ 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 (nullptr != colorSpace) {
+ *colorSpace = kJPEG_SkYUVColorSpace;
+ }
+
+ return true;
+}
+
///////////////////////////////////////////////////////////////////////////////
#include "SkColorPriv.h"
@@ -272,11 +993,45 @@ protected:
};
///////////////////////////////////////////////////////////////////////////////
+DEFINE_DECODER_CREATOR(JPEGImageDecoder);
DEFINE_ENCODER_CREATOR(JPEGImageEncoder);
///////////////////////////////////////////////////////////////////////////////
+static bool is_jpeg(SkStreamRewindable* stream) {
+ static const unsigned char gHeader[] = { 0xFF, 0xD8, 0xFF };
+ static const size_t HEADER_SIZE = sizeof(gHeader);
+
+ char buffer[HEADER_SIZE];
+ size_t len = stream->read(buffer, HEADER_SIZE);
+
+ if (len != HEADER_SIZE) {
+ return false; // can't read enough
+ }
+ if (memcmp(buffer, gHeader, HEADER_SIZE)) {
+ return false;
+ }
+ return true;
+}
+
+
+static SkImageDecoder* sk_libjpeg_dfactory(SkStreamRewindable* stream) {
+ if (is_jpeg(stream)) {
+ return new SkJPEGImageDecoder;
+ }
+ return nullptr;
+}
+
+static SkImageDecoder::Format get_format_jpeg(SkStreamRewindable* stream) {
+ if (is_jpeg(stream)) {
+ return SkImageDecoder::kJPEG_Format;
+ }
+ return SkImageDecoder::kUnknown_Format;
+}
+
static SkImageEncoder* sk_libjpeg_efactory(SkImageEncoder::Type t) {
return (SkImageEncoder::kJPEG_Type == t) ? new SkJPEGImageEncoder : nullptr;
}
+static SkImageDecoder_DecodeReg gDReg(sk_libjpeg_dfactory);
+static SkImageDecoder_FormatReg gFormatReg(get_format_jpeg);
static SkImageEncoder_EncodeReg gEReg(sk_libjpeg_efactory);
diff --git a/src/images/SkImageDecoder_libpng.cpp b/src/images/SkImageDecoder_libpng.cpp
index c3df5d10a8..cd8152a36b 100644
--- a/src/images/SkImageDecoder_libpng.cpp
+++ b/src/images/SkImageDecoder_libpng.cpp
@@ -5,12 +5,14 @@
* found in the LICENSE file.
*/
+#include "SkImageDecoder.h"
#include "SkImageEncoder.h"
#include "SkColor.h"
#include "SkColorPriv.h"
#include "SkDither.h"
#include "SkMath.h"
#include "SkRTConf.h"
+#include "SkScaledBitmapSampler.h"
#include "SkStream.h"
#include "SkTemplates.h"
#include "SkUtils.h"
@@ -42,10 +44,88 @@ SK_CONF_DECLARE(bool, c_suppressPNGImageDecoderWarnings,
"Suppress most PNG warnings when calling image decode "
"functions.");
-///////////////////////////////////////////////////////////////////////////////
+class SkPNGImageIndex {
+public:
+ // Takes ownership of stream.
+ SkPNGImageIndex(SkStreamRewindable* stream, png_structp png_ptr, png_infop info_ptr)
+ : fStream(stream)
+ , fPng_ptr(png_ptr)
+ , fInfo_ptr(info_ptr)
+ , fColorType(kUnknown_SkColorType) {
+ SkASSERT(stream != nullptr);
+ }
+ ~SkPNGImageIndex() {
+ if (fPng_ptr) {
+ png_destroy_read_struct(&fPng_ptr, &fInfo_ptr, png_infopp_NULL);
+ }
+ }
-#include "SkColorPriv.h"
-#include "SkUnPreMultiply.h"
+ SkAutoTDelete<SkStreamRewindable> fStream;
+ png_structp fPng_ptr;
+ png_infop fInfo_ptr;
+ SkColorType fColorType;
+};
+
+class SkPNGImageDecoder : public SkImageDecoder {
+public:
+ SkPNGImageDecoder() {
+ fImageIndex = nullptr;
+ }
+ Format getFormat() const override {
+ return kPNG_Format;
+ }
+
+ virtual ~SkPNGImageDecoder() { delete fImageIndex; }
+
+protected:
+ Result onDecode(SkStream* stream, SkBitmap* bm, Mode) override;
+
+private:
+ SkPNGImageIndex* fImageIndex;
+
+ bool onDecodeInit(SkStream* stream, png_structp *png_ptrp, png_infop *info_ptrp);
+ bool decodePalette(png_structp png_ptr, png_infop info_ptr, int bitDepth,
+ bool * SK_RESTRICT hasAlphap, bool *reallyHasAlphap,
+ SkColorTable **colorTablep);
+ bool getBitmapColorType(png_structp, png_infop, SkColorType*, bool* hasAlpha,
+ SkPMColor* theTranspColor);
+
+ typedef SkImageDecoder INHERITED;
+};
+
+#ifndef png_jmpbuf
+# define png_jmpbuf(png_ptr) ((png_ptr)->jmpbuf)
+#endif
+
+#define PNG_BYTES_TO_CHECK 4
+
+/* Automatically clean up after throwing an exception */
+struct PNGAutoClean {
+ PNGAutoClean(png_structp p, png_infop i): png_ptr(p), info_ptr(i) {}
+ ~PNGAutoClean() {
+ png_destroy_read_struct(&png_ptr, &info_ptr, png_infopp_NULL);
+ }
+private:
+ png_structp png_ptr;
+ png_infop info_ptr;
+};
+
+static void sk_read_fn(png_structp png_ptr, png_bytep data, png_size_t length) {
+ SkStream* sk_stream = (SkStream*) png_get_io_ptr(png_ptr);
+ size_t bytes = sk_stream->read(data, length);
+ if (bytes != length) {
+ png_error(png_ptr, "Read Error!");
+ }
+}
+
+#ifdef PNG_READ_UNKNOWN_CHUNKS_SUPPORTED
+static int sk_read_user_chunk(png_structp png_ptr, png_unknown_chunkp chunk) {
+ SkPngChunkReader* peeker = (SkPngChunkReader*)png_get_user_chunk_ptr(png_ptr);
+ // readChunk() returning true means continue decoding
+ return peeker->readChunk((const char*)chunk->name, chunk->data, chunk->size) ?
+ 1 : -1;
+}
+#endif
static void sk_error_fn(png_structp png_ptr, png_const_charp msg) {
if (!c_suppressPNGImageDecoderWarnings) {
@@ -54,6 +134,577 @@ static void sk_error_fn(png_structp png_ptr, png_const_charp msg) {
longjmp(png_jmpbuf(png_ptr), 1);
}
+static void skip_src_rows(png_structp png_ptr, uint8_t storage[], int count) {
+ for (int i = 0; i < count; i++) {
+ uint8_t* tmp = storage;
+ png_read_rows(png_ptr, &tmp, png_bytepp_NULL, 1);
+ }
+}
+
+static bool pos_le(int value, int max) {
+ return value > 0 && value <= max;
+}
+
+static bool substituteTranspColor(SkBitmap* bm, SkPMColor match) {
+ SkASSERT(bm->colorType() == kN32_SkColorType);
+
+ bool reallyHasAlpha = false;
+
+ for (int y = bm->height() - 1; y >= 0; --y) {
+ SkPMColor* p = bm->getAddr32(0, y);
+ for (int x = bm->width() - 1; x >= 0; --x) {
+ if (match == *p) {
+ *p = 0;
+ reallyHasAlpha = true;
+ }
+ p += 1;
+ }
+ }
+ return reallyHasAlpha;
+}
+
+static bool canUpscalePaletteToConfig(SkColorType dstColorType, bool srcHasAlpha) {
+ switch (dstColorType) {
+ case kN32_SkColorType:
+ case kARGB_4444_SkColorType:
+ return true;
+ case kRGB_565_SkColorType:
+ // only return true if the src is opaque (since 565 is opaque)
+ return !srcHasAlpha;
+ default:
+ return false;
+ }
+}
+
+// call only if color_type is PALETTE. Returns true if the ctable has alpha
+static bool hasTransparencyInPalette(png_structp png_ptr, png_infop info_ptr) {
+ png_bytep trans;
+ int num_trans;
+
+ if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) {
+ png_get_tRNS(png_ptr, info_ptr, &trans, &num_trans, nullptr);
+ return num_trans > 0;
+ }
+ return false;
+}
+
+void do_nothing_warning_fn(png_structp, png_const_charp) {
+ /* do nothing */
+}
+
+bool SkPNGImageDecoder::onDecodeInit(SkStream* sk_stream, png_structp *png_ptrp,
+ png_infop *info_ptrp) {
+ /* Create and initialize the png_struct with the desired error handler
+ * functions. If you want to use the default stderr and longjump method,
+ * you can supply nullptr for the last three parameters. We also supply the
+ * the compiler header file version, so that we know if the application
+ * was compiled with a compatible version of the library. */
+
+ png_error_ptr user_warning_fn =
+ (c_suppressPNGImageDecoderWarnings) ? (&do_nothing_warning_fn) : nullptr;
+ /* nullptr means to leave as default library behavior. */
+ /* c_suppressPNGImageDecoderWarnings default depends on SK_DEBUG. */
+ /* To suppress warnings with a SK_DEBUG binary, set the
+ * environment variable "skia_images_png_suppressDecoderWarnings"
+ * to "true". Inside a program that links to skia:
+ * SK_CONF_SET("images.png.suppressDecoderWarnings", true); */
+
+ png_structp png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING,
+ nullptr, sk_error_fn, user_warning_fn);
+ // png_voidp user_error_ptr, user_error_fn, user_warning_fn);
+ if (png_ptr == nullptr) {
+ return false;
+ }
+
+ *png_ptrp = png_ptr;
+
+ /* Allocate/initialize the memory for image information. */
+ png_infop info_ptr = png_create_info_struct(png_ptr);
+ if (info_ptr == nullptr) {
+ png_destroy_read_struct(&png_ptr, png_infopp_NULL, png_infopp_NULL);
+ return false;
+ }
+ *info_ptrp = info_ptr;
+
+ /* Set error handling if you are using the setjmp/longjmp method (this is
+ * the normal method of doing things with libpng). REQUIRED unless you
+ * set up your own error handlers in the png_create_read_struct() earlier.
+ */
+ if (setjmp(png_jmpbuf(png_ptr))) {
+ png_destroy_read_struct(&png_ptr, &info_ptr, png_infopp_NULL);
+ return false;
+ }
+
+ /* If you are using replacement read functions, instead of calling
+ * png_init_io() here you would call:
+ */
+ png_set_read_fn(png_ptr, (void *)sk_stream, sk_read_fn);
+ /* where user_io_ptr is a structure you want available to the callbacks */
+ /* If we have already read some of the signature */
+// png_set_sig_bytes(png_ptr, 0 /* sig_read */ );
+
+#ifdef PNG_READ_UNKNOWN_CHUNKS_SUPPORTED
+ // hookup our peeker so we can see any user-chunks the caller may be interested in
+ png_set_keep_unknown_chunks(png_ptr, PNG_HANDLE_CHUNK_ALWAYS, (png_byte*)"", 0);
+ if (this->getPeeker()) {
+ png_set_read_user_chunk_fn(png_ptr, (png_voidp)this->getPeeker(), sk_read_user_chunk);
+ }
+#endif
+ /* The call to png_read_info() gives us all of the information from the
+ * PNG file before the first IDAT (image data chunk). */
+ png_read_info(png_ptr, info_ptr);
+ png_uint_32 origWidth, origHeight;
+ int bitDepth, colorType;
+ png_get_IHDR(png_ptr, info_ptr, &origWidth, &origHeight, &bitDepth,
+ &colorType, int_p_NULL, int_p_NULL, int_p_NULL);
+
+ /* tell libpng to strip 16 bit/color files down to 8 bits/color */
+ if (bitDepth == 16) {
+ png_set_strip_16(png_ptr);
+ }
+#ifdef PNG_READ_PACK_SUPPORTED
+ /* Extract multiple pixels with bit depths of 1, 2, and 4 from a single
+ * byte into separate bytes (useful for paletted and grayscale images). */
+ if (bitDepth < 8) {
+ png_set_packing(png_ptr);
+ }
+#endif
+ /* Expand grayscale images to the full 8 bits from 1, 2, or 4 bits/pixel */
+ if (colorType == PNG_COLOR_TYPE_GRAY && bitDepth < 8) {
+ png_set_expand_gray_1_2_4_to_8(png_ptr);
+ }
+
+ return true;
+}
+
+SkImageDecoder::Result SkPNGImageDecoder::onDecode(SkStream* sk_stream, SkBitmap* decodedBitmap,
+ Mode mode) {
+ png_structp png_ptr;
+ png_infop info_ptr;
+
+ if (!onDecodeInit(sk_stream, &png_ptr, &info_ptr)) {
+ return kFailure;
+ }
+
+ PNGAutoClean autoClean(png_ptr, info_ptr);
+
+ if (setjmp(png_jmpbuf(png_ptr))) {
+ return kFailure;
+ }
+
+ png_uint_32 origWidth, origHeight;
+ int bitDepth, pngColorType, interlaceType;
+ png_get_IHDR(png_ptr, info_ptr, &origWidth, &origHeight, &bitDepth,
+ &pngColorType, &interlaceType, int_p_NULL, int_p_NULL);
+
+ SkColorType colorType;
+ bool hasAlpha = false;
+ SkPMColor theTranspColor = 0; // 0 tells us not to try to match
+
+ if (!this->getBitmapColorType(png_ptr, info_ptr, &colorType, &hasAlpha, &theTranspColor)) {
+ return kFailure;
+ }
+
+ SkAlphaType alphaType = this->getRequireUnpremultipliedColors() ?
+ kUnpremul_SkAlphaType : kPremul_SkAlphaType;
+ const int sampleSize = this->getSampleSize();
+ SkScaledBitmapSampler sampler(origWidth, origHeight, sampleSize);
+ decodedBitmap->setInfo(SkImageInfo::Make(sampler.scaledWidth(), sampler.scaledHeight(),
+ colorType, alphaType));
+
+ if (SkImageDecoder::kDecodeBounds_Mode == mode) {
+ return kSuccess;
+ }
+
+ // from here down we are concerned with colortables and pixels
+
+ // we track if we actually see a non-opaque pixels, since sometimes a PNG sets its colortype
+ // to |= PNG_COLOR_MASK_ALPHA, but all of its pixels are in fact opaque. We care, since we
+ // draw lots faster if we can flag the bitmap has being opaque
+ bool reallyHasAlpha = false;
+ SkColorTable* colorTable = nullptr;
+
+ if (pngColorType == PNG_COLOR_TYPE_PALETTE) {
+ decodePalette(png_ptr, info_ptr, bitDepth, &hasAlpha, &reallyHasAlpha, &colorTable);
+ }
+
+ SkAutoUnref aur(colorTable);
+
+ if (!this->allocPixelRef(decodedBitmap,
+ kIndex_8_SkColorType == colorType ? colorTable : nullptr)) {
+ return kFailure;
+ }
+
+ SkAutoLockPixels alp(*decodedBitmap);
+
+ // Repeat setjmp, otherwise variables declared since the last call (e.g. alp
+ // and aur) won't get their destructors called in case of a failure.
+ if (setjmp(png_jmpbuf(png_ptr))) {
+ return kFailure;
+ }
+
+ /* Turn on interlace handling. REQUIRED if you are not using
+ * png_read_image(). To see how to handle interlacing passes,
+ * see the png_read_row() method below:
+ */
+ const int number_passes = (interlaceType != PNG_INTERLACE_NONE) ?
+ png_set_interlace_handling(png_ptr) : 1;
+
+ /* Optional call to gamma correct and add the background to the palette
+ * and update info structure. REQUIRED if you are expecting libpng to
+ * update the palette for you (ie you selected such a transform above).
+ */
+ png_read_update_info(png_ptr, info_ptr);
+
+ if ((kAlpha_8_SkColorType == colorType || kIndex_8_SkColorType == colorType) &&
+ 1 == sampleSize) {
+ if (kAlpha_8_SkColorType == colorType) {
+ // For an A8 bitmap, we assume there is an alpha for speed. It is
+ // possible the bitmap is opaque, but that is an unlikely use case
+ // since it would not be very interesting.
+ reallyHasAlpha = true;
+ // A8 is only allowed if the original was GRAY.
+ SkASSERT(PNG_COLOR_TYPE_GRAY == pngColorType);
+ }
+ for (int i = 0; i < number_passes; i++) {
+ for (png_uint_32 y = 0; y < origHeight; y++) {
+ uint8_t* bmRow = decodedBitmap->getAddr8(0, y);
+ png_read_rows(png_ptr, &bmRow, png_bytepp_NULL, 1);
+ }
+ }
+ } else {
+ SkScaledBitmapSampler::SrcConfig sc;
+ int srcBytesPerPixel = 4;
+
+ if (colorTable != nullptr) {
+ sc = SkScaledBitmapSampler::kIndex;
+ srcBytesPerPixel = 1;
+ } else if (kAlpha_8_SkColorType == colorType) {
+ // A8 is only allowed if the original was GRAY.
+ SkASSERT(PNG_COLOR_TYPE_GRAY == pngColorType);
+ sc = SkScaledBitmapSampler::kGray;
+ srcBytesPerPixel = 1;
+ } else if (hasAlpha) {
+ sc = SkScaledBitmapSampler::kRGBA;
+ } else {
+ sc = SkScaledBitmapSampler::kRGBX;
+ }
+
+ /* We have to pass the colortable explicitly, since we may have one
+ even if our decodedBitmap doesn't, due to the request that we
+ upscale png's palette to a direct model
+ */
+ const SkPMColor* colors = colorTable ? colorTable->readColors() : nullptr;
+ if (!sampler.begin(decodedBitmap, sc, *this, colors)) {
+ return kFailure;
+ }
+ const int height = decodedBitmap->height();
+
+ if (number_passes > 1) {
+ SkAutoTMalloc<uint8_t> storage(origWidth * origHeight * srcBytesPerPixel);
+ uint8_t* base = storage.get();
+ size_t rowBytes = origWidth * srcBytesPerPixel;
+
+ for (int i = 0; i < number_passes; i++) {
+ uint8_t* row = base;
+ for (png_uint_32 y = 0; y < origHeight; y++) {
+ uint8_t* bmRow = row;
+ png_read_rows(png_ptr, &bmRow, png_bytepp_NULL, 1);
+ row += rowBytes;
+ }
+ }
+ // now sample it
+ base += sampler.srcY0() * rowBytes;
+ for (int y = 0; y < height; y++) {
+ reallyHasAlpha |= sampler.next(base);
+ base += sampler.srcDY() * rowBytes;
+ }
+ } else {
+ SkAutoTMalloc<uint8_t> storage(origWidth * srcBytesPerPixel);
+ uint8_t* srcRow = storage.get();
+ skip_src_rows(png_ptr, srcRow, sampler.srcY0());
+
+ for (int y = 0; y < height; y++) {
+ uint8_t* tmp = srcRow;
+ png_read_rows(png_ptr, &tmp, png_bytepp_NULL, 1);
+ reallyHasAlpha |= sampler.next(srcRow);
+ if (y < height - 1) {
+ skip_src_rows(png_ptr, srcRow, sampler.srcDY() - 1);
+ }
+ }
+
+ // skip the rest of the rows (if any)
+ png_uint_32 read = (height - 1) * sampler.srcDY() +
+ sampler.srcY0() + 1;
+ SkASSERT(read <= origHeight);
+ skip_src_rows(png_ptr, srcRow, origHeight - read);
+ }
+ }
+
+ /* read rest of file, and get additional chunks in info_ptr - REQUIRED */
+ png_read_end(png_ptr, info_ptr);
+
+ if (0 != theTranspColor) {
+ reallyHasAlpha |= substituteTranspColor(decodedBitmap, theTranspColor);
+ }
+ if (reallyHasAlpha && this->getRequireUnpremultipliedColors()) {
+ switch (decodedBitmap->colorType()) {
+ case kIndex_8_SkColorType:
+ // Fall through.
+ case kARGB_4444_SkColorType:
+ // We have chosen not to support unpremul for these colortypes.
+ return kFailure;
+ default: {
+ // Fall through to finish the decode. This colortype either
+ // supports unpremul or it is irrelevant because it has no
+ // alpha (or only alpha).
+ // These brackets prevent a warning.
+ }
+ }
+ }
+
+ if (!reallyHasAlpha) {
+ decodedBitmap->setAlphaType(kOpaque_SkAlphaType);
+ }
+ return kSuccess;
+}
+
+
+
+bool SkPNGImageDecoder::getBitmapColorType(png_structp png_ptr, png_infop info_ptr,
+ SkColorType* colorTypep,
+ bool* hasAlphap,
+ SkPMColor* SK_RESTRICT theTranspColorp) {
+ png_uint_32 origWidth, origHeight;
+ int bitDepth, colorType;
+ png_get_IHDR(png_ptr, info_ptr, &origWidth, &origHeight, &bitDepth,
+ &colorType, int_p_NULL, int_p_NULL, int_p_NULL);
+
+#ifdef PNG_sBIT_SUPPORTED
+ // check for sBIT chunk data, in case we should disable dithering because
+ // our data is not truely 8bits per component
+ png_color_8p sig_bit;
+ if (this->getDitherImage() && png_get_sBIT(png_ptr, info_ptr, &sig_bit)) {
+#if 0
+ SkDebugf("----- sBIT %d %d %d %d\n", sig_bit->red, sig_bit->green,
+ sig_bit->blue, sig_bit->alpha);
+#endif
+ // 0 seems to indicate no information available
+ if (pos_le(sig_bit->red, SK_R16_BITS) &&
+ pos_le(sig_bit->green, SK_G16_BITS) &&
+ pos_le(sig_bit->blue, SK_B16_BITS)) {
+ this->setDitherImage(false);
+ }
+ }
+#endif
+
+ if (colorType == PNG_COLOR_TYPE_PALETTE) {
+ bool paletteHasAlpha = hasTransparencyInPalette(png_ptr, info_ptr);
+ *colorTypep = this->getPrefColorType(kIndex_SrcDepth, paletteHasAlpha);
+ // now see if we can upscale to their requested colortype
+ if (!canUpscalePaletteToConfig(*colorTypep, paletteHasAlpha)) {
+ *colorTypep = kIndex_8_SkColorType;
+ }
+ } else {
+ png_color_16p transpColor = nullptr;
+ int numTransp = 0;
+
+ png_get_tRNS(png_ptr, info_ptr, nullptr, &numTransp, &transpColor);
+
+ bool valid = png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS);
+
+ if (valid && numTransp == 1 && transpColor != nullptr) {
+ /* Compute our transparent color, which we'll match against later.
+ We don't really handle 16bit components properly here, since we
+ do our compare *after* the values have been knocked down to 8bit
+ which means we will find more matches than we should. The real
+ fix seems to be to see the actual 16bit components, do the
+ compare, and then knock it down to 8bits ourselves.
+ */
+ if (colorType & PNG_COLOR_MASK_COLOR) {
+ if (16 == bitDepth) {
+ *theTranspColorp = SkPackARGB32(0xFF, transpColor->red >> 8,
+ transpColor->green >> 8,
+ transpColor->blue >> 8);
+ } else {
+ /* We apply the mask because in a very small
+ number of corrupt PNGs, (transpColor->red > 255)
+ and (bitDepth == 8), for certain versions of libpng. */
+ *theTranspColorp = SkPackARGB32(0xFF,
+ 0xFF & (transpColor->red),
+ 0xFF & (transpColor->green),
+ 0xFF & (transpColor->blue));
+ }
+ } else { // gray
+ if (16 == bitDepth) {
+ *theTranspColorp = SkPackARGB32(0xFF, transpColor->gray >> 8,
+ transpColor->gray >> 8,
+ transpColor->gray >> 8);
+ } else {
+ /* We apply the mask because in a very small
+ number of corrupt PNGs, (transpColor->red >
+ 255) and (bitDepth == 8), for certain versions
+ of libpng. For safety we assume the same could
+ happen with a grayscale PNG. */
+ *theTranspColorp = SkPackARGB32(0xFF,
+ 0xFF & (transpColor->gray),
+ 0xFF & (transpColor->gray),
+ 0xFF & (transpColor->gray));
+ }
+ }
+ }
+
+ if (valid ||
+ PNG_COLOR_TYPE_RGB_ALPHA == colorType ||
+ PNG_COLOR_TYPE_GRAY_ALPHA == colorType) {
+ *hasAlphap = true;
+ }
+
+ SrcDepth srcDepth = k32Bit_SrcDepth;
+ if (PNG_COLOR_TYPE_GRAY == colorType) {
+ srcDepth = k8BitGray_SrcDepth;
+ // Remove this assert, which fails on desk_pokemonwiki.skp
+ //SkASSERT(!*hasAlphap);
+ }
+
+ *colorTypep = this->getPrefColorType(srcDepth, *hasAlphap);
+ // now match the request against our capabilities
+ if (*hasAlphap) {
+ if (*colorTypep != kARGB_4444_SkColorType) {
+ *colorTypep = kN32_SkColorType;
+ }
+ } else {
+ if (kAlpha_8_SkColorType == *colorTypep) {
+ if (k8BitGray_SrcDepth != srcDepth) {
+ // Converting a non grayscale image to A8 is not currently supported.
+ *colorTypep = kN32_SkColorType;
+ }
+ } else if (*colorTypep != kRGB_565_SkColorType &&
+ *colorTypep != kARGB_4444_SkColorType) {
+ *colorTypep = kN32_SkColorType;
+ }
+ }
+ }
+
+ // sanity check for size
+ {
+ int64_t size = sk_64_mul(origWidth, origHeight);
+ // now check that if we are 4-bytes per pixel, we also don't overflow
+ if (size < 0 || size > (0x7FFFFFFF >> 2)) {
+ return false;
+ }
+ }
+
+ // If the image has alpha and the decoder wants unpremultiplied
+ // colors, the only supported colortype is 8888.
+ if (this->getRequireUnpremultipliedColors() && *hasAlphap) {
+ *colorTypep = kN32_SkColorType;
+ }
+
+ if (fImageIndex != nullptr) {
+ if (kUnknown_SkColorType == fImageIndex->fColorType) {
+ // This is the first time for this subset decode. From now on,
+ // all decodes must be in the same colortype.
+ fImageIndex->fColorType = *colorTypep;
+ } else if (fImageIndex->fColorType != *colorTypep) {
+ // Requesting a different colortype for a subsequent decode is not
+ // supported. Report failure before we make changes to png_ptr.
+ return false;
+ }
+ }
+
+ bool convertGrayToRGB = PNG_COLOR_TYPE_GRAY == colorType && *colorTypep != kAlpha_8_SkColorType;
+
+ // Unless the user is requesting A8, convert a grayscale image into RGB.
+ // GRAY_ALPHA will always be converted to RGB
+ if (convertGrayToRGB || colorType == PNG_COLOR_TYPE_GRAY_ALPHA) {
+ png_set_gray_to_rgb(png_ptr);
+ }
+
+ // Add filler (or alpha) byte (after each RGB triplet) if necessary.
+ if (colorType == PNG_COLOR_TYPE_RGB || convertGrayToRGB) {
+ png_set_filler(png_ptr, 0xff, PNG_FILLER_AFTER);
+ }
+
+ return true;
+}
+
+typedef uint32_t (*PackColorProc)(U8CPU a, U8CPU r, U8CPU g, U8CPU b);
+
+bool SkPNGImageDecoder::decodePalette(png_structp png_ptr, png_infop info_ptr,
+ int bitDepth, bool *hasAlphap,
+ bool *reallyHasAlphap,
+ SkColorTable **colorTablep) {
+ int numPalette;
+ png_colorp palette;
+ png_bytep trans;
+ int numTrans;
+
+ png_get_PLTE(png_ptr, info_ptr, &palette, &numPalette);
+
+ SkPMColor colorStorage[256]; // worst-case storage
+ SkPMColor* colorPtr = colorStorage;
+
+ if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) {
+ png_get_tRNS(png_ptr, info_ptr, &trans, &numTrans, nullptr);
+ *hasAlphap = (numTrans > 0);
+ } else {
+ numTrans = 0;
+ }
+
+ // check for bad images that might make us crash
+ if (numTrans > numPalette) {
+ numTrans = numPalette;
+ }
+
+ int index = 0;
+ int transLessThanFF = 0;
+
+ // Choose which function to use to create the color table. If the final destination's
+ // colortype is unpremultiplied, the color table will store unpremultiplied colors.
+ PackColorProc proc;
+ if (this->getRequireUnpremultipliedColors()) {
+ proc = &SkPackARGB32NoCheck;
+ } else {
+ proc = &SkPreMultiplyARGB;
+ }
+ for (; index < numTrans; index++) {
+ transLessThanFF |= (int)*trans - 0xFF;
+ *colorPtr++ = proc(*trans++, palette->red, palette->green, palette->blue);
+ palette++;
+ }
+ bool reallyHasAlpha = (transLessThanFF < 0);
+
+ for (; index < numPalette; index++) {
+ *colorPtr++ = SkPackARGB32(0xFF, palette->red, palette->green, palette->blue);
+ palette++;
+ }
+
+ /* BUGGY IMAGE WORKAROUND
+
+ Invalid images could contain pixel values that are greater than the number of palette
+ entries. Since we use pixel values as indices into the palette this could result in reading
+ beyond the end of the palette which could leak the contents of uninitialized memory. To
+ ensure this doesn't happen, we grow the colortable to the maximum size that can be
+ addressed by the bitdepth of the image and fill it with the last palette color or black if
+ the palette is empty (really broken image).
+ */
+ int colorCount = SkTMax(numPalette, 1 << SkTMin(bitDepth, 8));
+ SkPMColor lastColor = index > 0 ? colorPtr[-1] : SkPackARGB32(0xFF, 0, 0, 0);
+ for (; index < colorCount; index++) {
+ *colorPtr++ = lastColor;
+ }
+
+ *colorTablep = new SkColorTable(colorStorage, colorCount);
+ *reallyHasAlphap = reallyHasAlpha;
+ return true;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+#include "SkColorPriv.h"
+#include "SkUnPreMultiply.h"
+
static void sk_write_fn(png_structp png_ptr, png_bytep data, png_size_t len) {
SkWStream* sk_stream = (SkWStream*)png_get_io_ptr(png_ptr);
if (!sk_stream->write(data, len)) {
@@ -334,11 +985,37 @@ bool SkPNGImageEncoder::doEncode(SkWStream* stream, const SkBitmap& bitmap,
}
///////////////////////////////////////////////////////////////////////////////
+DEFINE_DECODER_CREATOR(PNGImageDecoder);
DEFINE_ENCODER_CREATOR(PNGImageEncoder);
///////////////////////////////////////////////////////////////////////////////
+static bool is_png(SkStreamRewindable* stream) {
+ char buf[PNG_BYTES_TO_CHECK];
+ if (stream->read(buf, PNG_BYTES_TO_CHECK) == PNG_BYTES_TO_CHECK &&
+ !png_sig_cmp((png_bytep) buf, (png_size_t)0, PNG_BYTES_TO_CHECK)) {
+ return true;
+ }
+ return false;
+}
+
+SkImageDecoder* sk_libpng_dfactory(SkStreamRewindable* stream) {
+ if (is_png(stream)) {
+ return new SkPNGImageDecoder;
+ }
+ return nullptr;
+}
+
+static SkImageDecoder::Format get_format_png(SkStreamRewindable* stream) {
+ if (is_png(stream)) {
+ return SkImageDecoder::kPNG_Format;
+ }
+ return SkImageDecoder::kUnknown_Format;
+}
+
SkImageEncoder* sk_libpng_efactory(SkImageEncoder::Type t) {
return (SkImageEncoder::kPNG_Type == t) ? new SkPNGImageEncoder : nullptr;
}
+static SkImageDecoder_DecodeReg gDReg(sk_libpng_dfactory);
+static SkImageDecoder_FormatReg gFormatReg(get_format_png);
static SkImageEncoder_EncodeReg gEReg(sk_libpng_efactory);
diff --git a/src/images/SkImageDecoder_libwebp.cpp b/src/images/SkImageDecoder_libwebp.cpp
index 116608a253..2db08cee83 100644
--- a/src/images/SkImageDecoder_libwebp.cpp
+++ b/src/images/SkImageDecoder_libwebp.cpp
@@ -14,9 +14,10 @@
* limitations under the License.
*/
-#include "SkBitmap.h"
+#include "SkImageDecoder.h"
#include "SkImageEncoder.h"
#include "SkColorPriv.h"
+#include "SkScaledBitmapSampler.h"
#include "SkStream.h"
#include "SkTemplates.h"
#include "SkUtils.h"
@@ -31,9 +32,299 @@
extern "C" {
// If moving libwebp out of skia source tree, path for webp headers must be
// updated accordingly. Here, we enforce using local copy in webp sub-directory.
+#include "webp/decode.h"
#include "webp/encode.h"
}
+// this enables timing code to report milliseconds for a decode
+//#define TIME_DECODE
+
+//////////////////////////////////////////////////////////////////////////
+//////////////////////////////////////////////////////////////////////////
+
+// Define VP8 I/O on top of Skia stream
+
+//////////////////////////////////////////////////////////////////////////
+//////////////////////////////////////////////////////////////////////////
+
+static const size_t WEBP_VP8_HEADER_SIZE = 64;
+static const size_t WEBP_IDECODE_BUFFER_SZ = (1 << 16);
+
+// Parse headers of RIFF container, and check for valid Webp (VP8) content.
+static bool webp_parse_header(SkStream* stream, int* width, int* height, int* alpha) {
+ unsigned char buffer[WEBP_VP8_HEADER_SIZE];
+ size_t bytesToRead = WEBP_VP8_HEADER_SIZE;
+ size_t totalBytesRead = 0;
+ do {
+ unsigned char* dst = buffer + totalBytesRead;
+ const size_t bytesRead = stream->read(dst, bytesToRead);
+ if (0 == bytesRead) {
+ SkASSERT(stream->isAtEnd());
+ break;
+ }
+ bytesToRead -= bytesRead;
+ totalBytesRead += bytesRead;
+ SkASSERT(bytesToRead + totalBytesRead == WEBP_VP8_HEADER_SIZE);
+ } while (!stream->isAtEnd() && bytesToRead > 0);
+
+ WebPBitstreamFeatures features;
+ VP8StatusCode status = WebPGetFeatures(buffer, totalBytesRead, &features);
+ if (VP8_STATUS_OK != status) {
+ return false; // Invalid WebP file.
+ }
+ *width = features.width;
+ *height = features.height;
+ *alpha = features.has_alpha;
+
+ // sanity check for image size that's about to be decoded.
+ {
+ int64_t size = sk_64_mul(*width, *height);
+ if (!sk_64_isS32(size)) {
+ return false;
+ }
+ // now check that if we are 4-bytes per pixel, we also don't overflow
+ if (sk_64_asS32(size) > (0x7FFFFFFF >> 2)) {
+ return false;
+ }
+ }
+ return true;
+}
+
+class SkWEBPImageDecoder: public SkImageDecoder {
+public:
+ SkWEBPImageDecoder() {
+ fOrigWidth = 0;
+ fOrigHeight = 0;
+ fHasAlpha = 0;
+ }
+
+ Format getFormat() const override {
+ return kWEBP_Format;
+ }
+
+protected:
+ Result onDecode(SkStream* stream, SkBitmap* bm, Mode) override;
+
+private:
+ /**
+ * Called when determining the output config to request to webp.
+ * If the image does not have alpha, there is no need to premultiply.
+ * If the caller wants unpremultiplied colors, that is respected.
+ */
+ bool shouldPremultiply() const {
+ return SkToBool(fHasAlpha) && !this->getRequireUnpremultipliedColors();
+ }
+
+ bool setDecodeConfig(SkBitmap* decodedBitmap, int width, int height);
+
+ SkAutoTDelete<SkStream> fInputStream;
+ int fOrigWidth;
+ int fOrigHeight;
+ int fHasAlpha;
+
+ typedef SkImageDecoder INHERITED;
+};
+
+//////////////////////////////////////////////////////////////////////////
+
+#ifdef TIME_DECODE
+
+#include "SkTime.h"
+
+class AutoTimeMillis {
+public:
+ AutoTimeMillis(const char label[]) :
+ fLabel(label) {
+ if (nullptr == fLabel) {
+ fLabel = "";
+ }
+ fNow = SkTime::GetMSecs();
+ }
+ ~AutoTimeMillis() {
+ SkDebugf("---- Time (ms): %s %d\n", fLabel, SkTime::GetMSecs() - fNow);
+ }
+private:
+ const char* fLabel;
+ SkMSec fNow;
+};
+
+#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 void print_webp_error(const SkBitmap& bm, const char msg[]) {
+ SkDEBUGF(("libwebp error %s [%d %d]", msg, bm.width(), bm.height()));
+}
+
+static SkImageDecoder::Result return_failure(const SkBitmap& bm, const char msg[]) {
+ print_webp_error(bm, msg);
+ return SkImageDecoder::kFailure; // must always return kFailure
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+static WEBP_CSP_MODE webp_decode_mode(const SkBitmap* decodedBitmap, bool premultiply) {
+ WEBP_CSP_MODE mode = MODE_LAST;
+ const SkColorType ct = decodedBitmap->colorType();
+
+ if (ct == kN32_SkColorType) {
+ #if SK_PMCOLOR_BYTE_ORDER(B,G,R,A)
+ mode = premultiply ? MODE_bgrA : MODE_BGRA;
+ #elif SK_PMCOLOR_BYTE_ORDER(R,G,B,A)
+ mode = premultiply ? MODE_rgbA : MODE_RGBA;
+ #else
+ #error "Skia uses BGRA or RGBA byte order"
+ #endif
+ } else if (ct == kARGB_4444_SkColorType) {
+ mode = premultiply ? MODE_rgbA_4444 : MODE_RGBA_4444;
+ } else if (ct == kRGB_565_SkColorType) {
+ mode = MODE_RGB_565;
+ }
+ SkASSERT(MODE_LAST != mode);
+ return mode;
+}
+
+// Incremental WebP image decoding. Reads input buffer of 64K size iteratively
+// and decodes this block to appropriate color-space as per config object.
+static bool webp_idecode(SkStream* stream, WebPDecoderConfig* config) {
+ WebPIDecoder* idec = WebPIDecode(nullptr, 0, config);
+ if (nullptr == idec) {
+ WebPFreeDecBuffer(&config->output);
+ return false;
+ }
+
+ if (!stream->rewind()) {
+ SkDebugf("Failed to rewind webp stream!");
+ return false;
+ }
+ const size_t readBufferSize = stream->hasLength() ?
+ SkTMin(stream->getLength(), WEBP_IDECODE_BUFFER_SZ) : WEBP_IDECODE_BUFFER_SZ;
+ SkAutoTMalloc<unsigned char> srcStorage(readBufferSize);
+ unsigned char* input = srcStorage.get();
+ if (nullptr == input) {
+ WebPIDelete(idec);
+ WebPFreeDecBuffer(&config->output);
+ return false;
+ }
+
+ bool success = true;
+ VP8StatusCode status = VP8_STATUS_SUSPENDED;
+ do {
+ const size_t bytesRead = stream->read(input, readBufferSize);
+ if (0 == bytesRead) {
+ success = false;
+ break;
+ }
+
+ status = WebPIAppend(idec, input, bytesRead);
+ if (VP8_STATUS_OK != status && VP8_STATUS_SUSPENDED != status) {
+ success = false;
+ break;
+ }
+ } while (VP8_STATUS_OK != status);
+ srcStorage.reset();
+ WebPIDelete(idec);
+ WebPFreeDecBuffer(&config->output);
+
+ return success;
+}
+
+static bool webp_get_config_resize(WebPDecoderConfig* config,
+ SkBitmap* decodedBitmap,
+ int width, int height, bool premultiply) {
+ WEBP_CSP_MODE mode = webp_decode_mode(decodedBitmap, premultiply);
+ if (MODE_LAST == mode) {
+ return false;
+ }
+
+ if (0 == WebPInitDecoderConfig(config)) {
+ return false;
+ }
+
+ config->output.colorspace = mode;
+ config->output.u.RGBA.rgba = (uint8_t*)decodedBitmap->getPixels();
+ config->output.u.RGBA.stride = (int) decodedBitmap->rowBytes();
+ config->output.u.RGBA.size = decodedBitmap->getSize();
+ config->output.is_external_memory = 1;
+
+ if (width != decodedBitmap->width() || height != decodedBitmap->height()) {
+ config->options.use_scaling = 1;
+ config->options.scaled_width = decodedBitmap->width();
+ config->options.scaled_height = decodedBitmap->height();
+ }
+
+ return true;
+}
+
+bool SkWEBPImageDecoder::setDecodeConfig(SkBitmap* decodedBitmap, int width, int height) {
+ SkColorType colorType = this->getPrefColorType(k32Bit_SrcDepth, SkToBool(fHasAlpha));
+
+ // YUV converter supports output in RGB565, RGBA4444 and RGBA8888 formats.
+ if (fHasAlpha) {
+ if (colorType != kARGB_4444_SkColorType) {
+ colorType = kN32_SkColorType;
+ }
+ } else {
+ if (colorType != kRGB_565_SkColorType && colorType != kARGB_4444_SkColorType) {
+ colorType = kN32_SkColorType;
+ }
+ }
+
+ SkAlphaType alphaType = kOpaque_SkAlphaType;
+ if (SkToBool(fHasAlpha)) {
+ if (this->getRequireUnpremultipliedColors()) {
+ alphaType = kUnpremul_SkAlphaType;
+ } else {
+ alphaType = kPremul_SkAlphaType;
+ }
+ }
+ return decodedBitmap->setInfo(SkImageInfo::Make(width, height, colorType, alphaType));
+}
+
+SkImageDecoder::Result SkWEBPImageDecoder::onDecode(SkStream* stream, SkBitmap* decodedBitmap,
+ Mode mode) {
+#ifdef TIME_DECODE
+ AutoTimeMillis atm("WEBP Decode");
+#endif
+
+ int origWidth, origHeight, hasAlpha;
+ if (!webp_parse_header(stream, &origWidth, &origHeight, &hasAlpha)) {
+ return kFailure;
+ }
+ this->fHasAlpha = hasAlpha;
+
+ const int sampleSize = this->getSampleSize();
+ SkScaledBitmapSampler sampler(origWidth, origHeight, sampleSize);
+ if (!setDecodeConfig(decodedBitmap, sampler.scaledWidth(),
+ sampler.scaledHeight())) {
+ return kFailure;
+ }
+
+ // If only bounds are requested, done
+ if (SkImageDecoder::kDecodeBounds_Mode == mode) {
+ return kSuccess;
+ }
+
+ if (!this->allocPixelRef(decodedBitmap, nullptr)) {
+ return return_failure(*decodedBitmap, "allocPixelRef");
+ }
+
+ SkAutoLockPixels alp(*decodedBitmap);
+
+ WebPDecoderConfig config;
+ if (!webp_get_config_resize(&config, decodedBitmap, origWidth, origHeight,
+ this->shouldPremultiply())) {
+ return kFailure;
+ }
+
+ // Decode the WebP image data stream using WebP incremental decoding.
+ return webp_idecode(stream, &config) ? kSuccess : kFailure;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
#include "SkUnPreMultiply.h"
typedef void (*ScanlineImporter)(const uint8_t* in, uint8_t* out, int width,
@@ -237,11 +528,32 @@ bool SkWEBPImageEncoder::onEncode(SkWStream* stream, const SkBitmap& bm,
///////////////////////////////////////////////////////////////////////////////
+DEFINE_DECODER_CREATOR(WEBPImageDecoder);
DEFINE_ENCODER_CREATOR(WEBPImageEncoder);
///////////////////////////////////////////////////////////////////////////////
+static SkImageDecoder* sk_libwebp_dfactory(SkStreamRewindable* stream) {
+ int width, height, hasAlpha;
+ if (!webp_parse_header(stream, &width, &height, &hasAlpha)) {
+ return nullptr;
+ }
+
+ // Magic matches, call decoder
+ return new SkWEBPImageDecoder;
+}
+
+static SkImageDecoder::Format get_format_webp(SkStreamRewindable* stream) {
+ int width, height, hasAlpha;
+ if (webp_parse_header(stream, &width, &height, &hasAlpha)) {
+ return SkImageDecoder::kWEBP_Format;
+ }
+ return SkImageDecoder::kUnknown_Format;
+}
+
static SkImageEncoder* sk_libwebp_efactory(SkImageEncoder::Type t) {
return (SkImageEncoder::kWEBP_Type == t) ? new SkWEBPImageEncoder : nullptr;
}
+static SkImageDecoder_DecodeReg gDReg(sk_libwebp_dfactory);
+static SkImageDecoder_FormatReg gFormatReg(get_format_webp);
static SkImageEncoder_EncodeReg gEReg(sk_libwebp_efactory);
diff --git a/src/images/SkImageDecoder_pkm.cpp b/src/images/SkImageDecoder_pkm.cpp
new file mode 100644
index 0000000000..af68f20d97
--- /dev/null
+++ b/src/images/SkImageDecoder_pkm.cpp
@@ -0,0 +1,128 @@
+/*
+ * Copyright 2014 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "SkColorPriv.h"
+#include "SkData.h"
+#include "SkImageDecoder.h"
+#include "SkScaledBitmapSampler.h"
+#include "SkStream.h"
+#include "SkStreamPriv.h"
+#include "SkTextureCompressor.h"
+#include "SkTypes.h"
+
+#include "etc1.h"
+
+class SkPKMImageDecoder : public SkImageDecoder {
+public:
+ SkPKMImageDecoder() { }
+
+ Format getFormat() const override {
+ return kPKM_Format;
+ }
+
+protected:
+ Result onDecode(SkStream* stream, SkBitmap* bm, Mode) override;
+
+private:
+ typedef SkImageDecoder INHERITED;
+};
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+SkImageDecoder::Result SkPKMImageDecoder::onDecode(SkStream* stream, SkBitmap* bm, Mode mode) {
+ sk_sp<SkData> data(SkCopyStreamToData(stream));
+ if (!data || !data->size()) {
+ return kFailure;
+ }
+
+ unsigned char* buf = (unsigned char*) data->data();
+
+ // Make sure original PKM header is there...
+ SkASSERT(etc1_pkm_is_valid(buf));
+
+ const unsigned short width = etc1_pkm_get_width(buf);
+ const unsigned short height = etc1_pkm_get_height(buf);
+
+ // Setup the sampler...
+ SkScaledBitmapSampler sampler(width, height, this->getSampleSize());
+
+ // Set the config...
+ bm->setInfo(SkImageInfo::MakeN32(sampler.scaledWidth(), sampler.scaledHeight(),
+ kOpaque_SkAlphaType));
+ if (SkImageDecoder::kDecodeBounds_Mode == mode) {
+ return kSuccess;
+ }
+
+ if (!this->allocPixelRef(bm, nullptr)) {
+ return kFailure;
+ }
+
+ // Lock the pixels, since we're about to write to them...
+ SkAutoLockPixels alp(*bm);
+
+ if (!sampler.begin(bm, SkScaledBitmapSampler::kRGB, *this)) {
+ return kFailure;
+ }
+
+ // Advance buffer past the header
+ buf += ETC_PKM_HEADER_SIZE;
+
+ // ETC1 Data is encoded as RGB pixels, so we should extract it as such
+ int nPixels = width * height;
+ SkAutoMalloc outRGBData(nPixels * 3);
+ uint8_t *outRGBDataPtr = reinterpret_cast<uint8_t *>(outRGBData.get());
+
+ // Decode ETC1
+ if (!SkTextureCompressor::DecompressBufferFromFormat(
+ outRGBDataPtr, width*3, buf, width, height, SkTextureCompressor::kETC1_Format)) {
+ return kFailure;
+ }
+
+ // Set each of the pixels...
+ const int srcRowBytes = width * 3;
+ const int dstHeight = sampler.scaledHeight();
+ const uint8_t *srcRow = reinterpret_cast<uint8_t *>(outRGBDataPtr);
+ srcRow += sampler.srcY0() * srcRowBytes;
+ for (int y = 0; y < dstHeight; ++y) {
+ sampler.next(srcRow);
+ srcRow += sampler.srcDY() * srcRowBytes;
+ }
+
+ return kSuccess;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+DEFINE_DECODER_CREATOR(PKMImageDecoder);
+/////////////////////////////////////////////////////////////////////////////////////////
+
+static bool is_pkm(SkStreamRewindable* stream) {
+ // Read the PKM header and make sure it's valid.
+ unsigned char buf[ETC_PKM_HEADER_SIZE];
+ if (stream->read((void*)buf, ETC_PKM_HEADER_SIZE) != ETC_PKM_HEADER_SIZE) {
+ return false;
+ }
+
+ return SkToBool(etc1_pkm_is_valid(buf));
+}
+
+static SkImageDecoder* sk_libpkm_dfactory(SkStreamRewindable* stream) {
+ if (is_pkm(stream)) {
+ return new SkPKMImageDecoder;
+ }
+ return nullptr;
+}
+
+static SkImageDecoder_DecodeReg gReg(sk_libpkm_dfactory);
+
+static SkImageDecoder::Format get_format_pkm(SkStreamRewindable* stream) {
+ if (is_pkm(stream)) {
+ return SkImageDecoder::kPKM_Format;
+ }
+ return SkImageDecoder::kUnknown_Format;
+}
+
+static SkImageDecoder_FormatReg gFormatReg(get_format_pkm);
diff --git a/src/images/SkImageDecoder_wbmp.cpp b/src/images/SkImageDecoder_wbmp.cpp
new file mode 100644
index 0000000000..335966b29a
--- /dev/null
+++ b/src/images/SkImageDecoder_wbmp.cpp
@@ -0,0 +1,173 @@
+
+/*
+ * Copyright 2006 The Android Open Source Project
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+
+#include "SkImageDecoder.h"
+#include "SkColor.h"
+#include "SkColorPriv.h"
+#include "SkMath.h"
+#include "SkStream.h"
+#include "SkTemplates.h"
+#include "SkUtils.h"
+
+class SkWBMPImageDecoder : public SkImageDecoder {
+public:
+ Format getFormat() const override {
+ return kWBMP_Format;
+ }
+
+protected:
+ Result onDecode(SkStream* stream, SkBitmap* bm, Mode) override;
+
+private:
+ typedef SkImageDecoder INHERITED;
+};
+
+static bool read_byte(SkStream* stream, uint8_t* data)
+{
+ return stream->read(data, 1) == 1;
+}
+
+static bool read_mbf(SkStream* stream, int* value)
+{
+ int n = 0;
+ uint8_t data;
+ do {
+ if (!read_byte(stream, &data)) {
+ return false;
+ }
+ n = (n << 7) | (data & 0x7F);
+ } while (data & 0x80);
+
+ *value = n;
+ return true;
+}
+
+struct wbmp_head {
+ int fWidth;
+ int fHeight;
+
+ bool init(SkStream* stream)
+ {
+ uint8_t data;
+
+ if (!read_byte(stream, &data) || data != 0) { // unknown type
+ return false;
+ }
+ if (!read_byte(stream, &data) || (data & 0x9F)) { // skip fixed header
+ return false;
+ }
+ if (!read_mbf(stream, &fWidth) || (unsigned)fWidth > 0xFFFF) {
+ return false;
+ }
+ if (!read_mbf(stream, &fHeight) || (unsigned)fHeight > 0xFFFF) {
+ return false;
+ }
+ return fWidth != 0 && fHeight != 0;
+ }
+};
+
+static void expand_bits_to_bytes(uint8_t dst[], const uint8_t src[], int bits)
+{
+ int bytes = bits >> 3;
+
+ for (int i = 0; i < bytes; i++) {
+ unsigned mask = *src++;
+ dst[0] = (mask >> 7) & 1;
+ dst[1] = (mask >> 6) & 1;
+ dst[2] = (mask >> 5) & 1;
+ dst[3] = (mask >> 4) & 1;
+ dst[4] = (mask >> 3) & 1;
+ dst[5] = (mask >> 2) & 1;
+ dst[6] = (mask >> 1) & 1;
+ dst[7] = (mask >> 0) & 1;
+ dst += 8;
+ }
+
+ bits &= 7;
+ if (bits > 0) {
+ unsigned mask = *src;
+ do {
+ *dst++ = (mask >> 7) & 1;
+ mask <<= 1;
+ } while (--bits != 0);
+ }
+}
+
+SkImageDecoder::Result SkWBMPImageDecoder::onDecode(SkStream* stream, SkBitmap* decodedBitmap,
+ Mode mode)
+{
+ wbmp_head head;
+
+ if (!head.init(stream)) {
+ return kFailure;
+ }
+
+ int width = head.fWidth;
+ int height = head.fHeight;
+
+ decodedBitmap->setInfo(SkImageInfo::Make(width, height,
+ kIndex_8_SkColorType, kOpaque_SkAlphaType));
+
+ if (SkImageDecoder::kDecodeBounds_Mode == mode) {
+ return kSuccess;
+ }
+
+ const SkPMColor colors[] = { SK_ColorBLACK, SK_ColorWHITE };
+ SkColorTable* ct = new SkColorTable(colors, 2);
+ SkAutoUnref aur(ct);
+
+ if (!this->allocPixelRef(decodedBitmap, ct)) {
+ return kFailure;
+ }
+
+ SkAutoLockPixels alp(*decodedBitmap);
+
+ uint8_t* dst = decodedBitmap->getAddr8(0, 0);
+ // store the 1-bit valuess at the end of our pixels, so we won't stomp
+ // on them before we're read them. Just trying to avoid a temp allocation
+ size_t srcRB = SkAlign8(width) >> 3;
+ size_t srcSize = height * srcRB;
+ uint8_t* src = dst + decodedBitmap->getSize() - srcSize;
+ if (stream->read(src, srcSize) != srcSize) {
+ return kFailure;
+ }
+
+ for (int y = 0; y < height; y++)
+ {
+ expand_bits_to_bytes(dst, src, width);
+ dst += decodedBitmap->rowBytes();
+ src += srcRB;
+ }
+
+ return kSuccess;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+DEFINE_DECODER_CREATOR(WBMPImageDecoder);
+///////////////////////////////////////////////////////////////////////////////
+
+static SkImageDecoder* sk_wbmp_dfactory(SkStreamRewindable* stream) {
+ wbmp_head head;
+
+ if (head.init(stream)) {
+ return new SkWBMPImageDecoder;
+ }
+ return nullptr;
+}
+
+static SkImageDecoder::Format get_format_wbmp(SkStreamRewindable* stream) {
+ wbmp_head head;
+ if (head.init(stream)) {
+ return SkImageDecoder::kWBMP_Format;
+ }
+ return SkImageDecoder::kUnknown_Format;
+}
+
+static SkImageDecoder_DecodeReg gDReg(sk_wbmp_dfactory);
+static SkImageDecoder_FormatReg gFormatReg(get_format_wbmp);
diff --git a/src/images/SkJpegUtility.cpp b/src/images/SkJpegUtility.cpp
index ab8486bcf6..f1a32cae10 100644
--- a/src/images/SkJpegUtility.cpp
+++ b/src/images/SkJpegUtility.cpp
@@ -8,6 +8,108 @@
#include "SkJpegUtility.h"
+/////////////////////////////////////////////////////////////////////
+static void sk_init_source(j_decompress_ptr cinfo) {
+ skjpeg_source_mgr* src = (skjpeg_source_mgr*)cinfo->src;
+ src->next_input_byte = (const JOCTET*)src->fBuffer;
+ src->bytes_in_buffer = 0;
+#ifdef SK_JPEG_INDEX_SUPPORTED
+ src->current_offset = 0;
+#endif
+ if (!src->fStream->rewind()) {
+ SkDebugf("xxxxxxxxxxxxxx failure to rewind\n");
+ cinfo->err->error_exit((j_common_ptr)cinfo);
+ }
+}
+
+#ifdef SK_JPEG_INDEX_SUPPORTED
+static boolean sk_seek_input_data(j_decompress_ptr cinfo, long byte_offset) {
+ skjpeg_source_mgr* src = (skjpeg_source_mgr*)cinfo->src;
+ size_t bo = (size_t) byte_offset;
+
+ if (bo > src->current_offset) {
+ (void)src->fStream->skip(bo - src->current_offset);
+ } else {
+ if (!src->fStream->rewind()) {
+ SkDebugf("xxxxxxxxxxxxxx failure to rewind\n");
+ cinfo->err->error_exit((j_common_ptr)cinfo);
+ return false;
+ }
+ (void)src->fStream->skip(bo);
+ }
+
+ src->current_offset = bo;
+ src->next_input_byte = (const JOCTET*)src->fBuffer;
+ src->bytes_in_buffer = 0;
+ return true;
+}
+#endif
+
+static boolean sk_fill_input_buffer(j_decompress_ptr cinfo) {
+ skjpeg_source_mgr* src = (skjpeg_source_mgr*)cinfo->src;
+ if (src->fDecoder != nullptr && src->fDecoder->shouldCancelDecode()) {
+ return FALSE;
+ }
+ size_t bytes = src->fStream->read(src->fBuffer, skjpeg_source_mgr::kBufferSize);
+ // note that JPEG is happy with less than the full read,
+ // as long as the result is non-zero
+ if (bytes == 0) {
+ return FALSE;
+ }
+
+#ifdef SK_JPEG_INDEX_SUPPORTED
+ src->current_offset += bytes;
+#endif
+ src->next_input_byte = (const JOCTET*)src->fBuffer;
+ src->bytes_in_buffer = bytes;
+ return TRUE;
+}
+
+static void sk_skip_input_data(j_decompress_ptr cinfo, long num_bytes) {
+ skjpeg_source_mgr* src = (skjpeg_source_mgr*)cinfo->src;
+
+ if (num_bytes > (long)src->bytes_in_buffer) {
+ size_t bytesToSkip = num_bytes - src->bytes_in_buffer;
+ while (bytesToSkip > 0) {
+ size_t bytes = src->fStream->skip(bytesToSkip);
+ if (bytes <= 0 || bytes > bytesToSkip) {
+// SkDebugf("xxxxxxxxxxxxxx failure to skip request %d returned %d\n", bytesToSkip, bytes);
+ cinfo->err->error_exit((j_common_ptr)cinfo);
+ return;
+ }
+#ifdef SK_JPEG_INDEX_SUPPORTED
+ src->current_offset += bytes;
+#endif
+ bytesToSkip -= bytes;
+ }
+ src->next_input_byte = (const JOCTET*)src->fBuffer;
+ src->bytes_in_buffer = 0;
+ } else {
+ src->next_input_byte += num_bytes;
+ src->bytes_in_buffer -= num_bytes;
+ }
+}
+
+static void sk_term_source(j_decompress_ptr /*cinfo*/) {}
+
+
+///////////////////////////////////////////////////////////////////////////////
+
+skjpeg_source_mgr::skjpeg_source_mgr(SkStream* stream, SkImageDecoder* decoder)
+ : fStream(stream)
+ , fDecoder(decoder) {
+
+ 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;
+#ifdef SK_JPEG_INDEX_SUPPORTED
+ seek_input_data = sk_seek_input_data;
+#endif
+// SkDebugf("**************** use memorybase %p %d\n", fMemoryBase, fMemoryBaseSize);
+}
+
///////////////////////////////////////////////////////////////////////////////
static void sk_init_destination(j_compress_ptr cinfo) {
diff --git a/src/images/SkJpegUtility.h b/src/images/SkJpegUtility.h
index c84465289c..1a763f843c 100644
--- a/src/images/SkJpegUtility.h
+++ b/src/images/SkJpegUtility.h
@@ -10,6 +10,7 @@
#ifndef SkJpegUtility_DEFINED
#define SkJpegUtility_DEFINED
+#include "SkImageDecoder.h"
#include "SkStream.h"
extern "C" {
@@ -29,6 +30,23 @@ struct skjpeg_error_mgr : jpeg_error_mgr {
void skjpeg_error_exit(j_common_ptr cinfo);
+///////////////////////////////////////////////////////////////////////////
+/* Our source struct for directing jpeg to our stream object.
+*/
+struct skjpeg_source_mgr : jpeg_source_mgr {
+ skjpeg_source_mgr(SkStream* stream, SkImageDecoder* decoder);
+
+ // Unowned.
+ SkStream* fStream;
+ // Unowned pointer to the decoder, used to check if the decoding process
+ // has been cancelled.
+ SkImageDecoder* fDecoder;
+ enum {
+ kBufferSize = 1024
+ };
+ char fBuffer[kBufferSize];
+};
+
/////////////////////////////////////////////////////////////////////////////
/* Our destination struct for directing decompressed pixels to our stream
* object.
diff --git a/src/images/SkScaledBitmapSampler.cpp b/src/images/SkScaledBitmapSampler.cpp
new file mode 100644
index 0000000000..5ffd648893
--- /dev/null
+++ b/src/images/SkScaledBitmapSampler.cpp
@@ -0,0 +1,877 @@
+/*
+ * Copyright 2007 The Android Open Source Project
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+
+#include "SkScaledBitmapSampler.h"
+#include "SkBitmap.h"
+#include "SkColorPriv.h"
+#include "SkDither.h"
+#include "SkTypes.h"
+
+// 8888
+
+static bool Sample_Gray_D8888(void* SK_RESTRICT dstRow,
+ const uint8_t* SK_RESTRICT src,
+ int width, int deltaSrc, int, const SkPMColor[]) {
+ SkPMColor* SK_RESTRICT dst = (SkPMColor*)dstRow;
+ for (int x = 0; x < width; x++) {
+ dst[x] = SkPackARGB32(0xFF, src[0], src[0], src[0]);
+ src += deltaSrc;
+ }
+ return false;
+}
+
+static SkScaledBitmapSampler::RowProc
+get_gray_to_8888_proc(const SkScaledBitmapSampler::Options& opts) {
+ // Dither, unpremul, and skipZeroes have no effect
+ return Sample_Gray_D8888;
+}
+
+static bool Sample_RGBx_D8888(void* SK_RESTRICT dstRow,
+ const uint8_t* SK_RESTRICT src,
+ int width, int deltaSrc, int, const SkPMColor[]) {
+ SkPMColor* SK_RESTRICT dst = (SkPMColor*)dstRow;
+ for (int x = 0; x < width; x++) {
+ dst[x] = SkPackARGB32(0xFF, src[0], src[1], src[2]);
+ src += deltaSrc;
+ }
+ return false;
+}
+
+static SkScaledBitmapSampler::RowProc
+get_RGBx_to_8888_proc(const SkScaledBitmapSampler::Options& opts) {
+ // Dither, unpremul, and skipZeroes have no effect
+ return Sample_RGBx_D8888;
+}
+
+static bool Sample_RGBA_D8888(void* SK_RESTRICT dstRow,
+ const uint8_t* SK_RESTRICT src,
+ int width, int deltaSrc, int, const SkPMColor[]) {
+ SkPMColor* SK_RESTRICT dst = (SkPMColor*)dstRow;
+ unsigned alphaMask = 0xFF;
+ for (int x = 0; x < width; x++) {
+ unsigned alpha = src[3];
+ dst[x] = SkPreMultiplyARGB(alpha, src[0], src[1], src[2]);
+ src += deltaSrc;
+ alphaMask &= alpha;
+ }
+ return alphaMask != 0xFF;
+}
+
+static bool Sample_RGBA_D8888_Unpremul(void* SK_RESTRICT dstRow,
+ const uint8_t* SK_RESTRICT src,
+ int width, int deltaSrc, int,
+ const SkPMColor[]) {
+ uint32_t* SK_RESTRICT dst = reinterpret_cast<uint32_t*>(dstRow);
+ unsigned alphaMask = 0xFF;
+ for (int x = 0; x < width; x++) {
+ unsigned alpha = src[3];
+ dst[x] = SkPackARGB32NoCheck(alpha, src[0], src[1], src[2]);
+ src += deltaSrc;
+ alphaMask &= alpha;
+ }
+ return alphaMask != 0xFF;
+}
+
+static bool Sample_RGBA_D8888_SkipZ(void* SK_RESTRICT dstRow,
+ const uint8_t* SK_RESTRICT src,
+ int width, int deltaSrc, int,
+ const SkPMColor[]) {
+ SkPMColor* SK_RESTRICT dst = (SkPMColor*)dstRow;
+ unsigned alphaMask = 0xFF;
+ for (int x = 0; x < width; x++) {
+ unsigned alpha = src[3];
+ if (0 != alpha) {
+ dst[x] = SkPreMultiplyARGB(alpha, src[0], src[1], src[2]);
+ }
+ src += deltaSrc;
+ alphaMask &= alpha;
+ }
+ return alphaMask != 0xFF;
+}
+
+static SkScaledBitmapSampler::RowProc
+get_RGBA_to_8888_proc(const SkScaledBitmapSampler::Options& opts) {
+ // Dither has no effect.
+ if (!opts.fPremultiplyAlpha) {
+ // We could check each component for a zero, at the expense of extra checks.
+ // For now, just return unpremul.
+ return Sample_RGBA_D8888_Unpremul;
+ }
+ // Supply the versions that premultiply the colors
+ if (opts.fSkipZeros) {
+ return Sample_RGBA_D8888_SkipZ;
+ }
+ return Sample_RGBA_D8888;
+}
+
+// 565
+
+static bool Sample_Gray_D565(void* SK_RESTRICT dstRow,
+ const uint8_t* SK_RESTRICT src,
+ int width, int deltaSrc, int, const SkPMColor[]) {
+ uint16_t* SK_RESTRICT dst = (uint16_t*)dstRow;
+ for (int x = 0; x < width; x++) {
+ dst[x] = SkPack888ToRGB16(src[0], src[0], src[0]);
+ src += deltaSrc;
+ }
+ return false;
+}
+
+static bool Sample_Gray_D565_D(void* SK_RESTRICT dstRow,
+ const uint8_t* SK_RESTRICT src,
+ int width, int deltaSrc, int y, const SkPMColor[]) {
+ uint16_t* SK_RESTRICT dst = (uint16_t*)dstRow;
+ DITHER_565_SCAN(y);
+ for (int x = 0; x < width; x++) {
+ dst[x] = SkDitherRGBTo565(src[0], src[0], src[0], DITHER_VALUE(x));
+ src += deltaSrc;
+ }
+ return false;
+}
+
+static SkScaledBitmapSampler::RowProc
+get_gray_to_565_proc(const SkScaledBitmapSampler::Options& opts) {
+ // Unpremul and skip zeroes make no difference
+ if (opts.fDither) {
+ return Sample_Gray_D565_D;
+ }
+ return Sample_Gray_D565;
+}
+
+static bool Sample_RGBx_D565(void* SK_RESTRICT dstRow,
+ const uint8_t* SK_RESTRICT src,
+ int width, int deltaSrc, int, const SkPMColor[]) {
+ uint16_t* SK_RESTRICT dst = (uint16_t*)dstRow;
+ for (int x = 0; x < width; x++) {
+ dst[x] = SkPack888ToRGB16(src[0], src[1], src[2]);
+ src += deltaSrc;
+ }
+ return false;
+}
+
+static bool Sample_RGBx_D565_D(void* SK_RESTRICT dstRow,
+ const uint8_t* SK_RESTRICT src,
+ int width, int deltaSrc, int y,
+ const SkPMColor[]) {
+ uint16_t* SK_RESTRICT dst = (uint16_t*)dstRow;
+ DITHER_565_SCAN(y);
+ for (int x = 0; x < width; x++) {
+ dst[x] = SkDitherRGBTo565(src[0], src[1], src[2], DITHER_VALUE(x));
+ src += deltaSrc;
+ }
+ return false;
+}
+
+static SkScaledBitmapSampler::RowProc
+get_RGBx_to_565_proc(const SkScaledBitmapSampler::Options& opts) {
+ // Unpremul and skip zeroes make no difference
+ if (opts.fDither) {
+ return Sample_RGBx_D565_D;
+ }
+ return Sample_RGBx_D565;
+}
+
+
+static bool Sample_D565_D565(void* SK_RESTRICT dstRow,
+ const uint8_t* SK_RESTRICT src,
+ int width, int deltaSrc, int, const SkPMColor[]) {
+ uint16_t* SK_RESTRICT dst = (uint16_t*)dstRow;
+ uint16_t* SK_RESTRICT castedSrc = (uint16_t*) src;
+ for (int x = 0; x < width; x++) {
+ dst[x] = castedSrc[0];
+ castedSrc += deltaSrc >> 1;
+ }
+ return false;
+}
+
+static SkScaledBitmapSampler::RowProc
+get_565_to_565_proc(const SkScaledBitmapSampler::Options& opts) {
+ // Unpremul, dither, and skip zeroes have no effect
+ return Sample_D565_D565;
+}
+
+// 4444
+
+static bool Sample_Gray_D4444(void* SK_RESTRICT dstRow,
+ const uint8_t* SK_RESTRICT src,
+ int width, int deltaSrc, int, const SkPMColor[]) {
+ SkPMColor16* SK_RESTRICT dst = (SkPMColor16*)dstRow;
+ for (int x = 0; x < width; x++) {
+ unsigned gray = src[0] >> 4;
+ dst[x] = SkPackARGB4444(0xF, gray, gray, gray);
+ src += deltaSrc;
+ }
+ return false;
+}
+
+static bool Sample_Gray_D4444_D(void* SK_RESTRICT dstRow,
+ const uint8_t* SK_RESTRICT src,
+ int width, int deltaSrc, int y, const SkPMColor[]) {
+ SkPMColor16* SK_RESTRICT dst = (SkPMColor16*)dstRow;
+ DITHER_4444_SCAN(y);
+ for (int x = 0; x < width; x++) {
+ dst[x] = SkDitherARGB32To4444(0xFF, src[0], src[0], src[0],
+ DITHER_VALUE(x));
+ src += deltaSrc;
+ }
+ return false;
+}
+
+static SkScaledBitmapSampler::RowProc
+get_gray_to_4444_proc(const SkScaledBitmapSampler::Options& opts) {
+ // Skip zeroes and unpremul make no difference
+ if (opts.fDither) {
+ return Sample_Gray_D4444_D;
+ }
+ return Sample_Gray_D4444;
+}
+
+static bool Sample_RGBx_D4444(void* SK_RESTRICT dstRow,
+ const uint8_t* SK_RESTRICT src,
+ int width, int deltaSrc, int, const SkPMColor[]) {
+ SkPMColor16* SK_RESTRICT dst = (SkPMColor16*)dstRow;
+ for (int x = 0; x < width; x++) {
+ dst[x] = SkPackARGB4444(0xF, src[0] >> 4, src[1] >> 4, src[2] >> 4);
+ src += deltaSrc;
+ }
+ return false;
+}
+
+static bool Sample_RGBx_D4444_D(void* SK_RESTRICT dstRow,
+ const uint8_t* SK_RESTRICT src,
+ int width, int deltaSrc, int y, const SkPMColor[]) {
+ SkPMColor16* dst = (SkPMColor16*)dstRow;
+ DITHER_4444_SCAN(y);
+
+ for (int x = 0; x < width; x++) {
+ dst[x] = SkDitherARGB32To4444(0xFF, src[0], src[1], src[2],
+ DITHER_VALUE(x));
+ src += deltaSrc;
+ }
+ return false;
+}
+
+static SkScaledBitmapSampler::RowProc
+get_RGBx_to_4444_proc(const SkScaledBitmapSampler::Options& opts) {
+ // Skip zeroes and unpremul make no difference
+ if (opts.fDither) {
+ return Sample_RGBx_D4444_D;
+ }
+ return Sample_RGBx_D4444;
+}
+
+static bool Sample_RGBA_D4444(void* SK_RESTRICT dstRow,
+ const uint8_t* SK_RESTRICT src,
+ int width, int deltaSrc, int, const SkPMColor[]) {
+ SkPMColor16* SK_RESTRICT dst = (SkPMColor16*)dstRow;
+ unsigned alphaMask = 0xFF;
+
+ for (int x = 0; x < width; x++) {
+ unsigned alpha = src[3];
+ SkPMColor c = SkPreMultiplyARGB(alpha, src[0], src[1], src[2]);
+ dst[x] = SkPixel32ToPixel4444(c);
+ src += deltaSrc;
+ alphaMask &= alpha;
+ }
+ return alphaMask != 0xFF;
+}
+
+static bool Sample_RGBA_D4444_SkipZ(void* SK_RESTRICT dstRow,
+ const uint8_t* SK_RESTRICT src,
+ int width, int deltaSrc, int,
+ const SkPMColor[]) {
+ SkPMColor16* SK_RESTRICT dst = (SkPMColor16*)dstRow;
+ unsigned alphaMask = 0xFF;
+
+ for (int x = 0; x < width; x++) {
+ unsigned alpha = src[3];
+ if (alpha != 0) {
+ SkPMColor c = SkPreMultiplyARGB(alpha, src[0], src[1], src[2]);
+ dst[x] = SkPixel32ToPixel4444(c);
+ }
+ src += deltaSrc;
+ alphaMask &= alpha;
+ }
+ return alphaMask != 0xFF;
+}
+
+
+static bool Sample_RGBA_D4444_D(void* SK_RESTRICT dstRow,
+ const uint8_t* SK_RESTRICT src,
+ int width, int deltaSrc, int y,
+ const SkPMColor[]) {
+ SkPMColor16* SK_RESTRICT dst = (SkPMColor16*)dstRow;
+ unsigned alphaMask = 0xFF;
+ DITHER_4444_SCAN(y);
+
+ for (int x = 0; x < width; x++) {
+ unsigned alpha = src[3];
+ SkPMColor c = SkPreMultiplyARGB(alpha, src[0], src[1], src[2]);
+ dst[x] = SkDitherARGB32To4444(c, DITHER_VALUE(x));
+ src += deltaSrc;
+ alphaMask &= alpha;
+ }
+ return alphaMask != 0xFF;
+}
+
+static bool Sample_RGBA_D4444_D_SkipZ(void* SK_RESTRICT dstRow,
+ const uint8_t* SK_RESTRICT src,
+ int width, int deltaSrc, int y,
+ const SkPMColor[]) {
+ SkPMColor16* SK_RESTRICT dst = (SkPMColor16*)dstRow;
+ unsigned alphaMask = 0xFF;
+ DITHER_4444_SCAN(y);
+
+ for (int x = 0; x < width; x++) {
+ unsigned alpha = src[3];
+ if (alpha != 0) {
+ SkPMColor c = SkPreMultiplyARGB(alpha, src[0], src[1], src[2]);
+ dst[x] = SkDitherARGB32To4444(c, DITHER_VALUE(x));
+ }
+ src += deltaSrc;
+ alphaMask &= alpha;
+ }
+ return alphaMask != 0xFF;
+}
+
+static SkScaledBitmapSampler::RowProc
+get_RGBA_to_4444_proc(const SkScaledBitmapSampler::Options& opts) {
+ if (!opts.fPremultiplyAlpha) {
+ // Unpremultiplied is not supported for 4444
+ return nullptr;
+ }
+ if (opts.fSkipZeros) {
+ if (opts.fDither) {
+ return Sample_RGBA_D4444_D_SkipZ;
+ }
+ return Sample_RGBA_D4444_SkipZ;
+ }
+ if (opts.fDither) {
+ return Sample_RGBA_D4444_D;
+ }
+ return Sample_RGBA_D4444;
+}
+
+// Index
+
+#define A32_MASK_IN_PLACE (SkPMColor)(SK_A32_MASK << SK_A32_SHIFT)
+
+static bool Sample_Index_D8888(void* SK_RESTRICT dstRow,
+ const uint8_t* SK_RESTRICT src,
+ int width, int deltaSrc, int, const SkPMColor ctable[]) {
+
+ SkPMColor* SK_RESTRICT dst = (SkPMColor*)dstRow;
+ SkPMColor cc = A32_MASK_IN_PLACE;
+ for (int x = 0; x < width; x++) {
+ SkPMColor c = ctable[*src];
+ cc &= c;
+ dst[x] = c;
+ src += deltaSrc;
+ }
+ return cc != A32_MASK_IN_PLACE;
+}
+
+static bool Sample_Index_D8888_SkipZ(void* SK_RESTRICT dstRow,
+ const uint8_t* SK_RESTRICT src,
+ int width, int deltaSrc, int,
+ const SkPMColor ctable[]) {
+
+ SkPMColor* SK_RESTRICT dst = (SkPMColor*)dstRow;
+ SkPMColor cc = A32_MASK_IN_PLACE;
+ for (int x = 0; x < width; x++) {
+ SkPMColor c = ctable[*src];
+ cc &= c;
+ if (c != 0) {
+ dst[x] = c;
+ }
+ src += deltaSrc;
+ }
+ return cc != A32_MASK_IN_PLACE;
+}
+
+static SkScaledBitmapSampler::RowProc
+get_index_to_8888_proc(const SkScaledBitmapSampler::Options& opts) {
+ // The caller is expected to have created the source colortable
+ // properly with respect to opts.fPremultiplyAlpha, so premul makes
+ // no difference here.
+ // Dither makes no difference
+ if (opts.fSkipZeros) {
+ return Sample_Index_D8888_SkipZ;
+ }
+ return Sample_Index_D8888;
+}
+
+static bool Sample_Index_D565(void* SK_RESTRICT dstRow,
+ const uint8_t* SK_RESTRICT src,
+ int width, int deltaSrc, int, const SkPMColor ctable[]) {
+
+ uint16_t* SK_RESTRICT dst = (uint16_t*)dstRow;
+ for (int x = 0; x < width; x++) {
+ dst[x] = SkPixel32ToPixel16(ctable[*src]);
+ src += deltaSrc;
+ }
+ return false;
+}
+
+static bool Sample_Index_D565_D(void* SK_RESTRICT dstRow,
+ const uint8_t* SK_RESTRICT src, int width,
+ int deltaSrc, int y, const SkPMColor ctable[]) {
+
+ uint16_t* SK_RESTRICT dst = (uint16_t*)dstRow;
+ DITHER_565_SCAN(y);
+
+ for (int x = 0; x < width; x++) {
+ SkPMColor c = ctable[*src];
+ dst[x] = SkDitherRGBTo565(SkGetPackedR32(c), SkGetPackedG32(c),
+ SkGetPackedB32(c), DITHER_VALUE(x));
+ src += deltaSrc;
+ }
+ return false;
+}
+
+static SkScaledBitmapSampler::RowProc
+get_index_to_565_proc(const SkScaledBitmapSampler::Options& opts) {
+ // Unpremultiplied and skip zeroes make no difference
+ if (opts.fDither) {
+ return Sample_Index_D565_D;
+ }
+ return Sample_Index_D565;
+}
+
+static bool Sample_Index_D4444(void* SK_RESTRICT dstRow,
+ const uint8_t* SK_RESTRICT src, int width,
+ int deltaSrc, int y, const SkPMColor ctable[]) {
+
+ SkPMColor16* SK_RESTRICT dst = (SkPMColor16*)dstRow;
+ SkPMColor cc = A32_MASK_IN_PLACE;
+ for (int x = 0; x < width; x++) {
+ SkPMColor c = ctable[*src];
+ cc &= c;
+ dst[x] = SkPixel32ToPixel4444(c);
+ src += deltaSrc;
+ }
+ return cc != A32_MASK_IN_PLACE;
+}
+
+static bool Sample_Index_D4444_D(void* SK_RESTRICT dstRow,
+ const uint8_t* SK_RESTRICT src, int width,
+ int deltaSrc, int y, const SkPMColor ctable[]) {
+
+ SkPMColor16* SK_RESTRICT dst = (SkPMColor16*)dstRow;
+ SkPMColor cc = A32_MASK_IN_PLACE;
+ DITHER_4444_SCAN(y);
+
+ for (int x = 0; x < width; x++) {
+ SkPMColor c = ctable[*src];
+ cc &= c;
+ dst[x] = SkDitherARGB32To4444(c, DITHER_VALUE(x));
+ src += deltaSrc;
+ }
+ return cc != A32_MASK_IN_PLACE;
+}
+
+static bool Sample_Index_D4444_SkipZ(void* SK_RESTRICT dstRow,
+ const uint8_t* SK_RESTRICT src, int width,
+ int deltaSrc, int y, const SkPMColor ctable[]) {
+
+ SkPMColor16* SK_RESTRICT dst = (SkPMColor16*)dstRow;
+ SkPMColor cc = A32_MASK_IN_PLACE;
+ for (int x = 0; x < width; x++) {
+ SkPMColor c = ctable[*src];
+ cc &= c;
+ if (c != 0) {
+ dst[x] = SkPixel32ToPixel4444(c);
+ }
+ src += deltaSrc;
+ }
+ return cc != A32_MASK_IN_PLACE;
+}
+
+static bool Sample_Index_D4444_D_SkipZ(void* SK_RESTRICT dstRow,
+ const uint8_t* SK_RESTRICT src, int width,
+ int deltaSrc, int y, const SkPMColor ctable[]) {
+
+ SkPMColor16* SK_RESTRICT dst = (SkPMColor16*)dstRow;
+ SkPMColor cc = A32_MASK_IN_PLACE;
+ DITHER_4444_SCAN(y);
+
+ for (int x = 0; x < width; x++) {
+ SkPMColor c = ctable[*src];
+ cc &= c;
+ if (c != 0) {
+ dst[x] = SkDitherARGB32To4444(c, DITHER_VALUE(x));
+ }
+ src += deltaSrc;
+ }
+ return cc != A32_MASK_IN_PLACE;
+}
+
+static SkScaledBitmapSampler::RowProc
+get_index_to_4444_proc(const SkScaledBitmapSampler::Options& opts) {
+ // Unpremul not allowed
+ if (!opts.fPremultiplyAlpha) {
+ return nullptr;
+ }
+ if (opts.fSkipZeros) {
+ if (opts.fDither) {
+ return Sample_Index_D4444_D_SkipZ;
+ }
+ return Sample_Index_D4444_SkipZ;
+ }
+ if (opts.fDither) {
+ return Sample_Index_D4444_D;
+ }
+ return Sample_Index_D4444;
+}
+
+static bool Sample_Index_DI(void* SK_RESTRICT dstRow,
+ const uint8_t* SK_RESTRICT src,
+ int width, int deltaSrc, int, const SkPMColor[]) {
+ if (1 == deltaSrc) {
+ memcpy(dstRow, src, width);
+ } else {
+ uint8_t* SK_RESTRICT dst = (uint8_t*)dstRow;
+ for (int x = 0; x < width; x++) {
+ dst[x] = src[0];
+ src += deltaSrc;
+ }
+ }
+ return false;
+}
+
+static SkScaledBitmapSampler::RowProc
+get_index_to_index_proc(const SkScaledBitmapSampler::Options& opts) {
+ // Unpremul not allowed
+ if (!opts.fPremultiplyAlpha) {
+ return nullptr;
+ }
+ // Ignore dither and skip zeroes
+ return Sample_Index_DI;
+}
+
+// A8
+static bool Sample_Gray_DA8(void* SK_RESTRICT dstRow,
+ const uint8_t* SK_RESTRICT src,
+ int width, int deltaSrc, int,
+ const SkPMColor[]) {
+ // Sampling Gray to A8 uses the same function as Index to Index8,
+ // except we assume that there is alpha for speed, since an A8
+ // bitmap with no alpha is not interesting.
+ (void) Sample_Index_DI(dstRow, src, width, deltaSrc, /* y unused */ 0,
+ /* ctable unused */ nullptr);
+ return true;
+}
+
+static SkScaledBitmapSampler::RowProc
+get_gray_to_A8_proc(const SkScaledBitmapSampler::Options& opts) {
+ if (!opts.fPremultiplyAlpha) {
+ return nullptr;
+ }
+ // Ignore skip and dither.
+ return Sample_Gray_DA8;
+}
+
+typedef SkScaledBitmapSampler::RowProc (*RowProcChooser)(const SkScaledBitmapSampler::Options&);
+///////////////////////////////////////////////////////////////////////////////
+
+#include "SkScaledBitmapSampler.h"
+
+SkScaledBitmapSampler::SkScaledBitmapSampler(int width, int height,
+ int sampleSize) {
+ fCTable = nullptr;
+ fDstRow = nullptr;
+ fRowProc = nullptr;
+
+ if (width <= 0 || height <= 0) {
+ sk_throw();
+ }
+
+ SkDEBUGCODE(fSampleMode = kUninitialized_SampleMode);
+
+ if (sampleSize <= 1) {
+ fScaledWidth = width;
+ fScaledHeight = height;
+ fX0 = fY0 = 0;
+ fDX = fDY = 1;
+ return;
+ }
+
+ int dx = SkMin32(sampleSize, width);
+ int dy = SkMin32(sampleSize, height);
+
+ fScaledWidth = width / dx;
+ fScaledHeight = height / dy;
+
+ SkASSERT(fScaledWidth > 0);
+ SkASSERT(fScaledHeight > 0);
+
+ fX0 = dx >> 1;
+ fY0 = dy >> 1;
+
+ SkASSERT(fX0 >= 0 && fX0 < width);
+ SkASSERT(fY0 >= 0 && fY0 < height);
+
+ fDX = dx;
+ fDY = dy;
+
+ SkASSERT(fDX > 0 && (fX0 + fDX * (fScaledWidth - 1)) < width);
+ SkASSERT(fDY > 0 && (fY0 + fDY * (fScaledHeight - 1)) < height);
+}
+
+bool SkScaledBitmapSampler::begin(SkBitmap* dst, SrcConfig sc,
+ const Options& opts,
+ const SkPMColor ctable[]) {
+ static const RowProcChooser gProcChoosers[] = {
+ get_gray_to_8888_proc,
+ get_RGBx_to_8888_proc,
+ get_RGBA_to_8888_proc,
+ get_index_to_8888_proc,
+ nullptr, // 565 to 8888
+
+ get_gray_to_565_proc,
+ get_RGBx_to_565_proc,
+ get_RGBx_to_565_proc, // The source alpha will be ignored.
+ get_index_to_565_proc,
+ get_565_to_565_proc,
+
+ get_gray_to_4444_proc,
+ get_RGBx_to_4444_proc,
+ get_RGBA_to_4444_proc,
+ get_index_to_4444_proc,
+ nullptr, // 565 to 4444
+
+ nullptr, // gray to index
+ nullptr, // rgbx to index
+ nullptr, // rgba to index
+ get_index_to_index_proc,
+ nullptr, // 565 to index
+
+ get_gray_to_A8_proc,
+ nullptr, // rgbx to a8
+ nullptr, // rgba to a8
+ nullptr, // index to a8
+ nullptr, // 565 to a8
+ };
+
+ // The jump between dst configs in the table
+ static const int gProcDstConfigSpan = 5;
+ static_assert(SK_ARRAY_COUNT(gProcChoosers) == 5 * gProcDstConfigSpan,
+ "gProcs_has_the_wrong_number_of_entries");
+
+ fCTable = ctable;
+
+ int index = 0;
+ switch (sc) {
+ case SkScaledBitmapSampler::kGray:
+ fSrcPixelSize = 1;
+ index += 0;
+ break;
+ case SkScaledBitmapSampler::kRGB:
+ fSrcPixelSize = 3;
+ index += 1;
+ break;
+ case SkScaledBitmapSampler::kRGBX:
+ fSrcPixelSize = 4;
+ index += 1;
+ break;
+ case SkScaledBitmapSampler::kRGBA:
+ fSrcPixelSize = 4;
+ index += 2;
+ break;
+ case SkScaledBitmapSampler::kIndex:
+ fSrcPixelSize = 1;
+ index += 3;
+ break;
+ case SkScaledBitmapSampler::kRGB_565:
+ fSrcPixelSize = 2;
+ index += 4;
+ break;
+ default:
+ return false;
+ }
+
+ switch (dst->colorType()) {
+ case kN32_SkColorType:
+ index += 0 * gProcDstConfigSpan;
+ break;
+ case kRGB_565_SkColorType:
+ index += 1 * gProcDstConfigSpan;
+ break;
+ case kARGB_4444_SkColorType:
+ index += 2 * gProcDstConfigSpan;
+ break;
+ case kIndex_8_SkColorType:
+ index += 3 * gProcDstConfigSpan;
+ break;
+ case kAlpha_8_SkColorType:
+ index += 4 * gProcDstConfigSpan;
+ break;
+ default:
+ return false;
+ }
+
+ RowProcChooser chooser = gProcChoosers[index];
+ if (nullptr == chooser) {
+ fRowProc = nullptr;
+ } else {
+ fRowProc = chooser(opts);
+ }
+ fDstRow = (char*)dst->getPixels();
+ fDstRowBytes = dst->rowBytes();
+ fCurrY = 0;
+ return fRowProc != nullptr;
+}
+
+bool SkScaledBitmapSampler::begin(SkBitmap* dst, SrcConfig sc,
+ const SkImageDecoder& decoder,
+ const SkPMColor ctable[]) {
+ return this->begin(dst, sc, Options(decoder), ctable);
+}
+
+bool SkScaledBitmapSampler::next(const uint8_t* SK_RESTRICT src) {
+ SkASSERT(kInterlaced_SampleMode != fSampleMode);
+ SkDEBUGCODE(fSampleMode = kConsecutive_SampleMode);
+ SkASSERT((unsigned)fCurrY < (unsigned)fScaledHeight);
+
+ bool hadAlpha = fRowProc(fDstRow, src + fX0 * fSrcPixelSize, fScaledWidth,
+ fDX * fSrcPixelSize, fCurrY, fCTable);
+ fDstRow += fDstRowBytes;
+ fCurrY += 1;
+ return hadAlpha;
+}
+
+bool SkScaledBitmapSampler::sampleInterlaced(const uint8_t* SK_RESTRICT src, int srcY) {
+ SkASSERT(kConsecutive_SampleMode != fSampleMode);
+ SkDEBUGCODE(fSampleMode = kInterlaced_SampleMode);
+ // Any line that should be a part of the destination can be created by the formula:
+ // fY0 + (some multiplier) * fDY
+ // so if srcY - fY0 is not an integer multiple of fDY that srcY will be skipped.
+ const int srcYMinusY0 = srcY - fY0;
+ if (srcYMinusY0 % fDY != 0) {
+ // This line is not part of the output, so return false for alpha, since we have
+ // not added an alpha to the output.
+ return false;
+ }
+ // Unlike in next(), where the data is used sequentially, this function skips around,
+ // so fDstRow and fCurrY are never updated. fDstRow must always be the starting point
+ // of the destination bitmap's pixels, which is used to calculate the destination row
+ // each time this function is called.
+ const int dstY = srcYMinusY0 / fDY;
+ if (dstY >= fScaledHeight) {
+ return false;
+ }
+ char* dstRow = fDstRow + dstY * fDstRowBytes;
+ return fRowProc(dstRow, src + fX0 * fSrcPixelSize, fScaledWidth,
+ fDX * fSrcPixelSize, dstY, fCTable);
+}
+
+#ifdef SK_DEBUG
+// The following code is for a test to ensure that changing the method to get the right row proc
+// did not change the row proc unintentionally. Tested by ImageDecodingTest.cpp
+
+// friend of SkScaledBitmapSampler solely for the purpose of accessing fRowProc.
+class RowProcTester {
+public:
+ static SkScaledBitmapSampler::RowProc getRowProc(const SkScaledBitmapSampler& sampler) {
+ return sampler.fRowProc;
+ }
+};
+
+
+// Table showing the expected RowProc for each combination of inputs.
+// Table formated as follows:
+// Each group of 5 consecutive rows represents sampling from a single
+// SkScaledBitmapSampler::SrcConfig.
+// Within each set, each row represents a different destination SkBitmap::Config
+// Each column represents a different combination of dither and unpremul.
+// D = dither ~D = no dither
+// U = unpremul ~U = no unpremul
+// ~D~U D~U ~DU DU
+SkScaledBitmapSampler::RowProc gTestProcs[] = {
+ // Gray
+ Sample_Gray_DA8, Sample_Gray_DA8, nullptr, nullptr, // to A8
+ nullptr, nullptr, nullptr, nullptr, // to Index8
+ Sample_Gray_D565, Sample_Gray_D565_D, Sample_Gray_D565, Sample_Gray_D565_D, // to 565
+ Sample_Gray_D4444, Sample_Gray_D4444_D, Sample_Gray_D4444, Sample_Gray_D4444_D, // to 4444
+ Sample_Gray_D8888, Sample_Gray_D8888, Sample_Gray_D8888, Sample_Gray_D8888, // to 8888
+ // Index
+ nullptr, nullptr, nullptr, nullptr, // to A8
+ Sample_Index_DI, Sample_Index_DI, nullptr, nullptr, // to Index8
+ Sample_Index_D565, Sample_Index_D565_D, Sample_Index_D565, Sample_Index_D565_D, // to 565
+ Sample_Index_D4444, Sample_Index_D4444_D, nullptr, nullptr, // to 4444
+ Sample_Index_D8888, Sample_Index_D8888, Sample_Index_D8888, Sample_Index_D8888, // to 8888
+ // RGB
+ nullptr, nullptr, nullptr, nullptr, // to A8
+ nullptr, nullptr, nullptr, nullptr, // to Index8
+ Sample_RGBx_D565, Sample_RGBx_D565_D, Sample_RGBx_D565, Sample_RGBx_D565_D, // to 565
+ Sample_RGBx_D4444, Sample_RGBx_D4444_D, Sample_RGBx_D4444, Sample_RGBx_D4444_D, // to 4444
+ Sample_RGBx_D8888, Sample_RGBx_D8888, Sample_RGBx_D8888, Sample_RGBx_D8888, // to 8888
+ // RGBx is the same as RGB
+ nullptr, nullptr, nullptr, nullptr, // to A8
+ nullptr, nullptr, nullptr, nullptr, // to Index8
+ Sample_RGBx_D565, Sample_RGBx_D565_D, Sample_RGBx_D565, Sample_RGBx_D565_D, // to 565
+ Sample_RGBx_D4444, Sample_RGBx_D4444_D, Sample_RGBx_D4444, Sample_RGBx_D4444_D, // to 4444
+ Sample_RGBx_D8888, Sample_RGBx_D8888, Sample_RGBx_D8888, Sample_RGBx_D8888, // to 8888
+ // RGBA
+ nullptr, nullptr, nullptr, nullptr, // to A8
+ nullptr, nullptr, nullptr, nullptr, // to Index8
+ Sample_RGBx_D565, Sample_RGBx_D565_D, Sample_RGBx_D565, Sample_RGBx_D565_D, // to 565
+ Sample_RGBA_D4444, Sample_RGBA_D4444_D, nullptr, nullptr, // to 4444
+ Sample_RGBA_D8888, Sample_RGBA_D8888, Sample_RGBA_D8888_Unpremul, Sample_RGBA_D8888_Unpremul, // to 8888
+ // RGB_565
+ nullptr, nullptr, nullptr, nullptr, // to A8
+ nullptr, nullptr, nullptr, nullptr, // to Index8
+ Sample_D565_D565, Sample_D565_D565, Sample_D565_D565, Sample_D565_D565, // to 565
+ nullptr, nullptr, nullptr, nullptr, // to 4444
+ nullptr, nullptr, nullptr, nullptr, // to 8888
+};
+
+// Dummy class that allows instantiation of an ImageDecoder, so begin can query its fields.
+class DummyDecoder : public SkImageDecoder {
+public:
+ DummyDecoder() {}
+protected:
+ Result onDecode(SkStream*, SkBitmap*, SkImageDecoder::Mode) override {
+ return kFailure;
+ }
+};
+
+void test_row_proc_choice();
+void test_row_proc_choice() {
+ const SkColorType colorTypes[] = {
+ kAlpha_8_SkColorType, kIndex_8_SkColorType, kRGB_565_SkColorType, kARGB_4444_SkColorType,
+ kN32_SkColorType
+ };
+
+ SkBitmap dummyBitmap;
+ DummyDecoder dummyDecoder;
+ size_t procCounter = 0;
+ for (int sc = SkScaledBitmapSampler::kGray; sc <= SkScaledBitmapSampler::kRGB_565; ++sc) {
+ for (size_t c = 0; c < SK_ARRAY_COUNT(colorTypes); ++c) {
+ for (int unpremul = 0; unpremul <= 1; ++unpremul) {
+ for (int dither = 0; dither <= 1; ++dither) {
+ // Arbitrary width/height/sampleSize to allow SkScaledBitmapSampler to
+ // be considered valid.
+ SkScaledBitmapSampler sampler(10, 10, 1);
+ dummyBitmap.setInfo(SkImageInfo::Make(10, 10,
+ colorTypes[c], kPremul_SkAlphaType));
+ dummyDecoder.setDitherImage(SkToBool(dither));
+ dummyDecoder.setRequireUnpremultipliedColors(SkToBool(unpremul));
+ sampler.begin(&dummyBitmap, (SkScaledBitmapSampler::SrcConfig) sc,
+ dummyDecoder);
+ SkScaledBitmapSampler::RowProc expected = gTestProcs[procCounter];
+ SkScaledBitmapSampler::RowProc actual = RowProcTester::getRowProc(sampler);
+ SkASSERT(expected == actual);
+ procCounter++;
+ }
+ }
+ }
+ }
+ SkASSERT(SK_ARRAY_COUNT(gTestProcs) == procCounter);
+}
+#endif // SK_DEBUG
diff --git a/src/images/SkScaledBitmapSampler.h b/src/images/SkScaledBitmapSampler.h
new file mode 100644
index 0000000000..198dc07572
--- /dev/null
+++ b/src/images/SkScaledBitmapSampler.h
@@ -0,0 +1,107 @@
+
+/*
+ * Copyright 2011 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+#ifndef SkScaledBitmapSampler_DEFINED
+#define SkScaledBitmapSampler_DEFINED
+
+#include "SkTypes.h"
+#include "SkColor.h"
+#include "SkImageDecoder.h"
+
+class SkBitmap;
+
+class SkScaledBitmapSampler {
+public:
+ SkScaledBitmapSampler(int origWidth, int origHeight, int cellSize);
+
+ int scaledWidth() const { return fScaledWidth; }
+ int scaledHeight() const { return fScaledHeight; }
+
+ int srcY0() const { return fY0; }
+ int srcDX() const { return fDX; }
+ int srcDY() const { return fDY; }
+
+ enum SrcConfig {
+ kGray, // 1 byte per pixel
+ kIndex, // 1 byte per pixel
+ kRGB, // 3 bytes per pixel
+ kRGBX, // 4 byes per pixel (ignore 4th)
+ kRGBA, // 4 bytes per pixel
+ kRGB_565 // 2 bytes per pixel
+ };
+
+ struct Options {
+ bool fDither;
+ bool fPremultiplyAlpha;
+ bool fSkipZeros;
+ explicit Options(const SkImageDecoder &dec)
+ : fDither(dec.getDitherImage())
+ , fPremultiplyAlpha(!dec.getRequireUnpremultipliedColors())
+ , fSkipZeros(dec.getSkipWritingZeroes())
+ { }
+ };
+
+ // Given a dst bitmap (with pixels already allocated) and a src-config,
+ // prepares iterator to process the src colors and write them into dst.
+ // Returns false if the request cannot be fulfulled.
+ bool begin(SkBitmap* dst, SrcConfig sc, const SkImageDecoder& decoder,
+ const SkPMColor* = nullptr);
+ bool begin(SkBitmap* dst, SrcConfig sc, const Options& opts,
+ const SkPMColor* = nullptr);
+ // call with row of src pixels, for y = 0...scaledHeight-1.
+ // returns true if the row had non-opaque alpha in it
+ bool next(const uint8_t* SK_RESTRICT src);
+
+ // Like next(), but specifies the y value of the source row, so the
+ // rows can come in any order. If the row is not part of the output
+ // sample, it will be skipped. Only sampleInterlaced OR next should
+ // be called for one SkScaledBitmapSampler.
+ bool sampleInterlaced(const uint8_t* SK_RESTRICT src, int srcY);
+
+ typedef bool (*RowProc)(void* SK_RESTRICT dstRow,
+ const uint8_t* SK_RESTRICT src,
+ int width, int deltaSrc, int y,
+ const SkPMColor[]);
+
+private:
+ int fScaledWidth;
+ int fScaledHeight;
+
+ int fX0; // first X coord to sample
+ int fY0; // first Y coord (scanline) to sample
+ int fDX; // step between X samples
+ int fDY; // step between Y samples
+
+#ifdef SK_DEBUG
+ // Keep track of whether the caller is using next or sampleInterlaced.
+ // Only one can be used per sampler.
+ enum SampleMode {
+ kUninitialized_SampleMode,
+ kConsecutive_SampleMode,
+ kInterlaced_SampleMode,
+ };
+
+ SampleMode fSampleMode;
+#endif
+
+ // setup state
+ char* fDstRow; // points into bitmap's pixels
+ size_t fDstRowBytes;
+ int fCurrY; // used for dithering
+ int fSrcPixelSize; // 1, 3, 4
+ RowProc fRowProc;
+
+ // optional reference to the src colors if the src is a palette model
+ const SkPMColor* fCTable;
+
+#ifdef SK_DEBUG
+ // Helper class allowing a test to have access to fRowProc.
+ friend class RowProcTester;
+#endif
+};
+
+#endif
diff --git a/src/images/bmpdecoderhelper.cpp b/src/images/bmpdecoderhelper.cpp
new file mode 100644
index 0000000000..9171b5d527
--- /dev/null
+++ b/src/images/bmpdecoderhelper.cpp
@@ -0,0 +1,369 @@
+
+/*
+ * Copyright 2007 The Android Open Source Project
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+// Author: cevans@google.com (Chris Evans)
+
+#include "bmpdecoderhelper.h"
+
+namespace image_codec {
+
+static const int kBmpHeaderSize = 14;
+static const int kBmpInfoSize = 40;
+static const int kBmpOS2InfoSize = 12;
+static const int kMaxDim = SHRT_MAX / 2;
+
+bool BmpDecoderHelper::DecodeImage(const char* p,
+ size_t len,
+ int max_pixels,
+ BmpDecoderCallback* callback) {
+ data_ = reinterpret_cast<const uint8*>(p);
+ pos_ = 0;
+ len_ = len;
+ inverted_ = true;
+ // Parse the header structure.
+ if (len < kBmpHeaderSize + 4) {
+ return false;
+ }
+ GetShort(); // Signature.
+ GetInt(); // Size.
+ GetInt(); // Reserved.
+ int offset = GetInt();
+ // Parse the info structure.
+ int infoSize = GetInt();
+ if (infoSize != kBmpOS2InfoSize && infoSize < kBmpInfoSize) {
+ return false;
+ }
+ int cols = 0;
+ int comp = 0;
+ int colLen = 4;
+ if (infoSize >= kBmpInfoSize) {
+ if (len < kBmpHeaderSize + kBmpInfoSize) {
+ return false;
+ }
+ width_ = GetInt();
+ height_ = GetInt();
+ GetShort(); // Planes.
+ bpp_ = GetShort();
+ comp = GetInt();
+ GetInt(); // Size.
+ GetInt(); // XPPM.
+ GetInt(); // YPPM.
+ cols = GetInt();
+ GetInt(); // Important colours.
+ } else {
+ if (len < kBmpHeaderSize + kBmpOS2InfoSize) {
+ return false;
+ }
+ colLen = 3;
+ width_ = GetShort();
+ height_ = GetShort();
+ GetShort(); // Planes.
+ bpp_ = GetShort();
+ }
+ if (height_ < 0) {
+ height_ = -height_;
+ inverted_ = false;
+ }
+ if (width_ <= 0 || width_ > kMaxDim || height_ <= 0 || height_ > kMaxDim) {
+ return false;
+ }
+ if (width_ * height_ > max_pixels) {
+ return false;
+ }
+ if (cols < 0 || cols > 256) {
+ return false;
+ }
+ // Allocate then read in the colour map.
+ if (cols == 0 && bpp_ <= 8) {
+ cols = 1 << bpp_;
+ }
+ if (bpp_ <= 8 || cols > 0) {
+ uint8* colBuf = new uint8[256 * 3];
+ memset(colBuf, '\0', 256 * 3);
+ colTab_.reset(colBuf);
+ }
+ if (cols > 0) {
+ if (pos_ + (cols * colLen) > len_) {
+ return false;
+ }
+ for (int i = 0; i < cols; ++i) {
+ int base = i * 3;
+ colTab_[base + 2] = GetByte();
+ colTab_[base + 1] = GetByte();
+ colTab_[base] = GetByte();
+ if (colLen == 4) {
+ GetByte();
+ }
+ }
+ }
+ // Read in the compression data if necessary.
+ redBits_ = 0x7c00;
+ greenBits_ = 0x03e0;
+ blueBits_ = 0x001f;
+ bool rle = false;
+ if (comp == 1 || comp == 2) {
+ rle = true;
+ } else if (comp == 3) {
+ if (pos_ + 12 > len_) {
+ return false;
+ }
+ redBits_ = GetInt() & 0xffff;
+ greenBits_ = GetInt() & 0xffff;
+ blueBits_ = GetInt() & 0xffff;
+ }
+ redShiftRight_ = CalcShiftRight(redBits_);
+ greenShiftRight_ = CalcShiftRight(greenBits_);
+ blueShiftRight_ = CalcShiftRight(blueBits_);
+ redShiftLeft_ = CalcShiftLeft(redBits_);
+ greenShiftLeft_ = CalcShiftLeft(greenBits_);
+ blueShiftLeft_ = CalcShiftLeft(blueBits_);
+ rowPad_ = 0;
+ pixelPad_ = 0;
+ int rowLen;
+ if (bpp_ == 32) {
+ rowLen = width_ * 4;
+ pixelPad_ = 1;
+ } else if (bpp_ == 24) {
+ rowLen = width_ * 3;
+ } else if (bpp_ == 16) {
+ rowLen = width_ * 2;
+ } else if (bpp_ == 8) {
+ rowLen = width_;
+ } else if (bpp_ == 4) {
+ rowLen = width_ / 2;
+ if (width_ & 1) {
+ rowLen++;
+ }
+ } else if (bpp_ == 1) {
+ rowLen = width_ / 8;
+ if (width_ & 7) {
+ rowLen++;
+ }
+ } else {
+ return false;
+ }
+ // Round the rowLen up to a multiple of 4.
+ if (rowLen % 4 != 0) {
+ rowPad_ = 4 - (rowLen % 4);
+ rowLen += rowPad_;
+ }
+
+ if (offset > 0 && (size_t)offset > pos_ && (size_t)offset < len_) {
+ pos_ = offset;
+ }
+ // Deliberately off-by-one; a load of BMPs seem to have their last byte
+ // missing.
+ if (!rle && (pos_ + (rowLen * height_) > len_ + 1)) {
+ return false;
+ }
+
+ output_ = callback->SetSize(width_, height_);
+ if (nullptr == output_) {
+ return true; // meaning we succeeded, but they want us to stop now
+ }
+
+ if (rle && (bpp_ == 4 || bpp_ == 8)) {
+ DoRLEDecode();
+ } else {
+ DoStandardDecode();
+ }
+ return true;
+}
+
+void BmpDecoderHelper::DoRLEDecode() {
+ static const uint8 RLE_ESCAPE = 0;
+ static const uint8 RLE_EOL = 0;
+ static const uint8 RLE_EOF = 1;
+ static const uint8 RLE_DELTA = 2;
+ int x = 0;
+ int y = height_ - 1;
+ while (pos_ + 1 < len_) {
+ uint8 cmd = GetByte();
+ if (cmd != RLE_ESCAPE) {
+ uint8 pixels = GetByte();
+ int num = 0;
+ uint8 col = pixels;
+ while (cmd-- && x < width_) {
+ if (bpp_ == 4) {
+ if (num & 1) {
+ col = pixels & 0xf;
+ } else {
+ col = pixels >> 4;
+ }
+ }
+ PutPixel(x++, y, col);
+ num++;
+ }
+ } else {
+ cmd = GetByte();
+ if (cmd == RLE_EOF) {
+ return;
+ } else if (cmd == RLE_EOL) {
+ x = 0;
+ y--;
+ if (y < 0) {
+ return;
+ }
+ } else if (cmd == RLE_DELTA) {
+ if (pos_ + 1 < len_) {
+ uint8 dx = GetByte();
+ uint8 dy = GetByte();
+ x += dx;
+ if (x > width_) {
+ x = width_;
+ }
+ y -= dy;
+ if (y < 0) {
+ return;
+ }
+ }
+ } else {
+ int num = 0;
+ int bytesRead = 0;
+ uint8 val = 0;
+ while (cmd-- && pos_ < len_) {
+ if (bpp_ == 8 || !(num & 1)) {
+ val = GetByte();
+ bytesRead++;
+ }
+ uint8 col = val;
+ if (bpp_ == 4) {
+ if (num & 1) {
+ col = col & 0xf;
+ } else {
+ col >>= 4;
+ }
+ }
+ if (x < width_) {
+ PutPixel(x++, y, col);
+ }
+ num++;
+ }
+ // All pixel runs must be an even number of bytes - skip a byte if we
+ // read an odd number.
+ if ((bytesRead & 1) && pos_ < len_) {
+ GetByte();
+ }
+ }
+ }
+ }
+}
+
+void BmpDecoderHelper::PutPixel(int x, int y, uint8 col) {
+ CHECK(x >= 0 && x < width_);
+ CHECK(y >= 0 && y < height_);
+ if (!inverted_) {
+ y = height_ - (y + 1);
+ }
+
+ int base = ((y * width_) + x) * 3;
+ int colBase = col * 3;
+ output_[base] = colTab_[colBase];
+ output_[base + 1] = colTab_[colBase + 1];
+ output_[base + 2] = colTab_[colBase + 2];
+}
+
+void BmpDecoderHelper::DoStandardDecode() {
+ int row = 0;
+ uint8 currVal = 0;
+ for (int h = height_ - 1; h >= 0; h--, row++) {
+ int realH = h;
+ if (!inverted_) {
+ realH = height_ - (h + 1);
+ }
+ uint8* line = output_ + (3 * width_ * realH);
+ for (int w = 0; w < width_; w++) {
+ if (bpp_ >= 24) {
+ line[2] = GetByte();
+ line[1] = GetByte();
+ line[0] = GetByte();
+ } else if (bpp_ == 16) {
+ uint32 val = GetShort();
+ line[0] = ((val & redBits_) >> redShiftRight_) << redShiftLeft_;
+ line[1] = ((val & greenBits_) >> greenShiftRight_) << greenShiftLeft_;
+ line[2] = ((val & blueBits_) >> blueShiftRight_) << blueShiftLeft_;
+ } else if (bpp_ <= 8) {
+ uint8 col;
+ if (bpp_ == 8) {
+ col = GetByte();
+ } else if (bpp_ == 4) {
+ if ((w % 2) == 0) {
+ currVal = GetByte();
+ col = currVal >> 4;
+ } else {
+ col = currVal & 0xf;
+ }
+ } else {
+ if ((w % 8) == 0) {
+ currVal = GetByte();
+ }
+ int bit = w & 7;
+ col = ((currVal >> (7 - bit)) & 1);
+ }
+ int base = col * 3;
+ line[0] = colTab_[base];
+ line[1] = colTab_[base + 1];
+ line[2] = colTab_[base + 2];
+ }
+ line += 3;
+ for (int i = 0; i < pixelPad_; ++i) {
+ GetByte();
+ }
+ }
+ for (int i = 0; i < rowPad_; ++i) {
+ GetByte();
+ }
+ }
+}
+
+int BmpDecoderHelper::GetInt() {
+ uint8 b1 = GetByte();
+ uint8 b2 = GetByte();
+ uint8 b3 = GetByte();
+ uint8 b4 = GetByte();
+ return b1 | (b2 << 8) | (b3 << 16) | (b4 << 24);
+}
+
+int BmpDecoderHelper::GetShort() {
+ uint8 b1 = GetByte();
+ uint8 b2 = GetByte();
+ return b1 | (b2 << 8);
+}
+
+uint8 BmpDecoderHelper::GetByte() {
+ CHECK(pos_ <= len_);
+ // We deliberately allow this off-by-one access to cater for BMPs with their
+ // last byte missing.
+ if (pos_ == len_) {
+ return 0;
+ }
+ return data_[pos_++];
+}
+
+int BmpDecoderHelper::CalcShiftRight(uint32 mask) {
+ int ret = 0;
+ while (mask != 0 && !(mask & 1)) {
+ mask >>= 1;
+ ret++;
+ }
+ return ret;
+}
+
+int BmpDecoderHelper::CalcShiftLeft(uint32 mask) {
+ int ret = 0;
+ while (mask != 0 && !(mask & 1)) {
+ mask >>= 1;
+ }
+ while (mask != 0 && !(mask & 0x80)) {
+ mask <<= 1;
+ ret++;
+ }
+ return ret;
+}
+
+} // namespace image_codec
diff --git a/src/images/bmpdecoderhelper.h b/src/images/bmpdecoderhelper.h
new file mode 100644
index 0000000000..b448734bbc
--- /dev/null
+++ b/src/images/bmpdecoderhelper.h
@@ -0,0 +1,116 @@
+
+/*
+ * Copyright 2007 The Android Open Source Project
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+
+#ifndef IMAGE_CODEC_BMPDECODERHELPER_H__
+#define IMAGE_CODEC_BMPDECODERHELPER_H__
+
+///////////////////////////////////////////////////////////////////////////////
+// this section is my current "glue" between google3 code and android.
+// will be fixed soon
+
+#include "SkTypes.h"
+#include <limits.h>
+#define DISALLOW_EVIL_CONSTRUCTORS(name)
+#define CHECK(predicate) SkASSERT(predicate)
+typedef uint8_t uint8;
+typedef uint32_t uint32;
+
+template <typename T> class scoped_array {
+private:
+ T* ptr_;
+ scoped_array(scoped_array const&);
+ scoped_array& operator=(const scoped_array&);
+
+public:
+ explicit scoped_array(T* p = 0) : ptr_(p) {}
+ ~scoped_array() {
+ delete[] ptr_;
+ }
+
+ void reset(T* p = 0) {
+ if (p != ptr_) {
+ delete[] ptr_;
+ ptr_ = p;
+ }
+ }
+
+ T& operator[](int i) const {
+ return ptr_[i];
+ }
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+namespace image_codec {
+
+class BmpDecoderCallback {
+ public:
+ BmpDecoderCallback() { }
+ virtual ~BmpDecoderCallback() {}
+
+ /**
+ * This is called once for an image. It is passed the width and height and
+ * should return the address of a buffer that is large enough to store
+ * all of the resulting pixels (widht * height * 3 bytes). If it returns nullptr,
+ * then the decoder will abort, but return true, as the caller has received
+ * valid dimensions.
+ */
+ virtual uint8* SetSize(int width, int height) = 0;
+
+ private:
+ DISALLOW_EVIL_CONSTRUCTORS(BmpDecoderCallback);
+};
+
+class BmpDecoderHelper {
+ public:
+ BmpDecoderHelper() { }
+ ~BmpDecoderHelper() { }
+ bool DecodeImage(const char* data,
+ size_t len,
+ int max_pixels,
+ BmpDecoderCallback* callback);
+
+ private:
+ DISALLOW_EVIL_CONSTRUCTORS(BmpDecoderHelper);
+
+ void DoRLEDecode();
+ void DoStandardDecode();
+ void PutPixel(int x, int y, uint8 col);
+
+ int GetInt();
+ int GetShort();
+ uint8 GetByte();
+ int CalcShiftRight(uint32 mask);
+ int CalcShiftLeft(uint32 mask);
+
+ const uint8* data_;
+ size_t pos_;
+ size_t len_;
+ int width_;
+ int height_;
+ int bpp_;
+ int pixelPad_;
+ int rowPad_;
+ scoped_array<uint8> colTab_;
+ uint32 redBits_;
+ uint32 greenBits_;
+ uint32 blueBits_;
+ int redShiftRight_;
+ int greenShiftRight_;
+ int blueShiftRight_;
+ int redShiftLeft_;
+ int greenShiftLeft_;
+ int blueShiftLeft_;
+ uint8* output_;
+ bool inverted_;
+};
+
+} // namespace
+
+#endif
diff --git a/src/ports/SkImageDecoder_CG.cpp b/src/ports/SkImageDecoder_CG.cpp
index ead0ed6506..c4446ae16d 100644
--- a/src/ports/SkImageDecoder_CG.cpp
+++ b/src/ports/SkImageDecoder_CG.cpp
@@ -11,6 +11,7 @@
#include "SkCGUtils.h"
#include "SkColorPriv.h"
#include "SkData.h"
+#include "SkImageDecoder.h"
#include "SkImageEncoder.h"
#include "SkMovie.h"
#include "SkStream.h"
@@ -28,6 +29,210 @@
#include <MobileCoreServices/MobileCoreServices.h>
#endif
+static void data_unref_proc(void* skdata, const void*, size_t) {
+ SkASSERT(skdata);
+ static_cast<SkData*>(skdata)->unref();
+}
+
+static CGDataProviderRef SkStreamToDataProvider(SkStream* stream) {
+ // TODO: use callbacks, so we don't have to load all the data into RAM
+ SkData* skdata = SkCopyStreamToData(stream).release();
+ if (!skdata) {
+ return nullptr;
+ }
+
+ return CGDataProviderCreateWithData(skdata, skdata->data(), skdata->size(), data_unref_proc);
+}
+
+static CGImageSourceRef SkStreamToCGImageSource(SkStream* stream) {
+ CGDataProviderRef data = SkStreamToDataProvider(stream);
+ if (!data) {
+ return nullptr;
+ }
+ CGImageSourceRef imageSrc = CGImageSourceCreateWithDataProvider(data, 0);
+ CGDataProviderRelease(data);
+ return imageSrc;
+}
+
+class SkImageDecoder_CG : public SkImageDecoder {
+protected:
+ virtual Result onDecode(SkStream* stream, SkBitmap* bm, Mode);
+};
+
+static void argb_4444_force_opaque(void* row, int count) {
+ uint16_t* row16 = (uint16_t*)row;
+ for (int i = 0; i < count; ++i) {
+ row16[i] |= 0xF000;
+ }
+}
+
+static void argb_8888_force_opaque(void* row, int count) {
+ // can use RGBA or BGRA, they have the same shift for alpha
+ const uint32_t alphaMask = 0xFF << SK_RGBA_A32_SHIFT;
+ uint32_t* row32 = (uint32_t*)row;
+ for (int i = 0; i < count; ++i) {
+ row32[i] |= alphaMask;
+ }
+}
+
+static void alpha_8_force_opaque(void* row, int count) {
+ memset(row, 0xFF, count);
+}
+
+static void force_opaque(SkBitmap* bm) {
+ SkAutoLockPixels alp(*bm);
+ if (!bm->getPixels()) {
+ return;
+ }
+
+ void (*proc)(void*, int);
+ switch (bm->colorType()) {
+ case kARGB_4444_SkColorType:
+ proc = argb_4444_force_opaque;
+ break;
+ case kRGBA_8888_SkColorType:
+ case kBGRA_8888_SkColorType:
+ proc = argb_8888_force_opaque;
+ break;
+ case kAlpha_8_SkColorType:
+ proc = alpha_8_force_opaque;
+ break;
+ default:
+ return;
+ }
+
+ char* row = (char*)bm->getPixels();
+ for (int y = 0; y < bm->height(); ++y) {
+ proc(row, bm->width());
+ row += bm->rowBytes();
+ }
+ bm->setAlphaType(kOpaque_SkAlphaType);
+}
+
+#define BITMAP_INFO (kCGBitmapByteOrder32Big | kCGImageAlphaPremultipliedLast)
+
+class AutoCFDataRelease {
+ CFDataRef fDR;
+public:
+ AutoCFDataRelease(CFDataRef dr) : fDR(dr) {}
+ ~AutoCFDataRelease() { if (fDR) { CFRelease(fDR); } }
+
+ operator CFDataRef () { return fDR; }
+};
+
+static bool colorspace_is_sRGB(CGColorSpaceRef cs) {
+#ifdef SK_BUILD_FOR_IOS
+ return true; // iOS seems to define itself to always return sRGB <reed>
+#else
+ AutoCFDataRelease data(CGColorSpaceCopyICCProfile(cs));
+ if (data) {
+ // found by inspection -- need a cleaner way to sniff a profile
+ const CFIndex ICC_PROFILE_OFFSET_TO_SRGB_TAG = 52;
+
+ if (CFDataGetLength(data) >= ICC_PROFILE_OFFSET_TO_SRGB_TAG + 4) {
+ return !memcmp(CFDataGetBytePtr(data) + ICC_PROFILE_OFFSET_TO_SRGB_TAG, "sRGB", 4);
+ }
+ }
+ return false;
+#endif
+}
+
+SkImageDecoder::Result SkImageDecoder_CG::onDecode(SkStream* stream, SkBitmap* bm, Mode mode) {
+ CGImageSourceRef imageSrc = SkStreamToCGImageSource(stream);
+
+ if (nullptr == imageSrc) {
+ return kFailure;
+ }
+ SkAutoTCallVProc<const void, CFRelease> arsrc(imageSrc);
+
+ CGImageRef image = CGImageSourceCreateImageAtIndex(imageSrc, 0, nullptr);
+ if (nullptr == image) {
+ return kFailure;
+ }
+ SkAutoTCallVProc<CGImage, CGImageRelease> arimage(image);
+
+ const int width = SkToInt(CGImageGetWidth(image));
+ const int height = SkToInt(CGImageGetHeight(image));
+ SkColorProfileType cpType = kLinear_SkColorProfileType;
+
+ CGColorSpaceRef cs = CGImageGetColorSpace(image);
+ if (cs) {
+ CGColorSpaceModel m = CGColorSpaceGetModel(cs);
+ if (kCGColorSpaceModelRGB == m && colorspace_is_sRGB(cs)) {
+ cpType = kSRGB_SkColorProfileType;
+ }
+ }
+
+ SkAlphaType at = kPremul_SkAlphaType;
+ switch (CGImageGetAlphaInfo(image)) {
+ case kCGImageAlphaNone:
+ case kCGImageAlphaNoneSkipLast:
+ case kCGImageAlphaNoneSkipFirst:
+ at = kOpaque_SkAlphaType;
+ break;
+ default:
+ break;
+ }
+
+ bm->setInfo(SkImageInfo::Make(width, height, kN32_SkColorType, at, cpType));
+ if (SkImageDecoder::kDecodeBounds_Mode == mode) {
+ return kSuccess;
+ }
+
+ if (!this->allocPixelRef(bm, nullptr)) {
+ return kFailure;
+ }
+
+ SkAutoLockPixels alp(*bm);
+
+ if (!SkCopyPixelsFromCGImage(bm->info(), bm->rowBytes(), bm->getPixels(), image)) {
+ return kFailure;
+ }
+
+ CGImageAlphaInfo info = CGImageGetAlphaInfo(image);
+ switch (info) {
+ case kCGImageAlphaNone:
+ case kCGImageAlphaNoneSkipLast:
+ case kCGImageAlphaNoneSkipFirst:
+ // We're opaque, but we can't rely on the data always having 0xFF
+ // in the alpha slot (which Skia wants), so we have to ram it in
+ // ourselves.
+ force_opaque(bm);
+ break;
+ default:
+ // we don't know if we're opaque or not, so compute it.
+ if (SkBitmap::ComputeIsOpaque(*bm)) {
+ bm->setAlphaType(kOpaque_SkAlphaType);
+ }
+ }
+ if (!bm->isOpaque() && this->getRequireUnpremultipliedColors()) {
+ // CGBitmapContext does not support unpremultiplied, so the image has been premultiplied.
+ // Convert to unpremultiplied.
+ for (int i = 0; i < width; ++i) {
+ for (int j = 0; j < height; ++j) {
+ uint32_t* addr = bm->getAddr32(i, j);
+ *addr = SkUnPreMultiply::UnPreMultiplyPreservingByteOrder(*addr);
+ }
+ }
+ bm->setAlphaType(kUnpremul_SkAlphaType);
+ }
+ return kSuccess;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+extern SkImageDecoder* image_decoder_from_stream(SkStreamRewindable*);
+
+SkImageDecoder* SkImageDecoder::Factory(SkStreamRewindable* stream) {
+ SkImageDecoder* decoder = image_decoder_from_stream(stream);
+ if (nullptr == decoder) {
+ // If no image decoder specific to the stream exists, use SkImageDecoder_CG.
+ return new SkImageDecoder_CG;
+ } else {
+ return decoder;
+ }
+}
+
/////////////////////////////////////////////////////////////////////////
SkMovie* SkMovie::DecodeStream(SkStreamRewindable* stream) {
@@ -150,13 +355,57 @@ static SkImageEncoder* sk_imageencoder_cg_factory(SkImageEncoder::Type t) {
static SkImageEncoder_EncodeReg gEReg(sk_imageencoder_cg_factory);
-class SkPNGImageEncoder_CG : public SkImageEncoder_CG {
+#ifdef SK_BUILD_FOR_IOS
+class SkPNGImageEncoder_IOS : public SkImageEncoder_CG {
public:
- SkPNGImageEncoder_CG()
+ SkPNGImageEncoder_IOS()
: SkImageEncoder_CG(kPNG_Type) {
}
};
-DEFINE_ENCODER_CREATOR(PNGImageEncoder_CG);
+DEFINE_ENCODER_CREATOR(PNGImageEncoder_IOS);
+#endif
+
+struct FormatConversion {
+ CFStringRef fUTType;
+ SkImageDecoder::Format fFormat;
+};
+
+// Array of the types supported by the decoder.
+static const FormatConversion gFormatConversions[] = {
+ { kUTTypeBMP, SkImageDecoder::kBMP_Format },
+ { kUTTypeGIF, SkImageDecoder::kGIF_Format },
+ { kUTTypeICO, SkImageDecoder::kICO_Format },
+ { kUTTypeJPEG, SkImageDecoder::kJPEG_Format },
+ // Also include JPEG2000
+ { kUTTypeJPEG2000, SkImageDecoder::kJPEG_Format },
+ { kUTTypePNG, SkImageDecoder::kPNG_Format },
+};
+
+static SkImageDecoder::Format UTType_to_Format(const CFStringRef uttype) {
+ for (size_t i = 0; i < SK_ARRAY_COUNT(gFormatConversions); i++) {
+ if (CFStringCompare(uttype, gFormatConversions[i].fUTType, 0) == kCFCompareEqualTo) {
+ return gFormatConversions[i].fFormat;
+ }
+ }
+ return SkImageDecoder::kUnknown_Format;
+}
+
+static SkImageDecoder::Format get_format_cg(SkStreamRewindable* stream) {
+ CGImageSourceRef imageSrc = SkStreamToCGImageSource(stream);
+
+ if (nullptr == imageSrc) {
+ return SkImageDecoder::kUnknown_Format;
+ }
+
+ SkAutoTCallVProc<const void, CFRelease> arsrc(imageSrc);
+ const CFStringRef name = CGImageSourceGetType(imageSrc);
+ if (nullptr == name) {
+ return SkImageDecoder::kUnknown_Format;
+ }
+ return UTType_to_Format(name);
+}
+
+static SkImageDecoder_FormatReg gFormatReg(get_format_cg);
#endif//defined(SK_BUILD_FOR_MAC) || defined(SK_BUILD_FOR_IOS)
diff --git a/src/ports/SkImageDecoder_WIC.cpp b/src/ports/SkImageDecoder_WIC.cpp
index 43068fc8f6..5febd856d6 100644
--- a/src/ports/SkImageDecoder_WIC.cpp
+++ b/src/ports/SkImageDecoder_WIC.cpp
@@ -31,6 +31,7 @@
#include <wincodec.h>
#include "SkAutoCoInitialize.h"
+#include "SkImageDecoder.h"
#include "SkImageEncoder.h"
#include "SkIStream.h"
#include "SkMovie.h"
@@ -47,6 +48,222 @@
#undef CLSID_WICImagingFactory
#endif
+class SkImageDecoder_WIC : public SkImageDecoder {
+public:
+ // Decoding modes corresponding to SkImageDecoder::Mode, plus an extra mode for decoding
+ // only the format.
+ enum WICModes {
+ kDecodeFormat_WICMode,
+ kDecodeBounds_WICMode,
+ kDecodePixels_WICMode,
+ };
+
+ /**
+ * Helper function to decode an SkStream.
+ * @param stream SkStream to decode. Must be at the beginning.
+ * @param bm SkBitmap to decode into. Only used if wicMode is kDecodeBounds_WICMode or
+ * kDecodePixels_WICMode, in which case it must not be nullptr.
+ * @param format Out parameter for the SkImageDecoder::Format of the SkStream. Only used if
+ * wicMode is kDecodeFormat_WICMode.
+ */
+ bool decodeStream(SkStream* stream, SkBitmap* bm, WICModes wicMode, Format* format) const;
+
+protected:
+ Result onDecode(SkStream* stream, SkBitmap* bm, Mode mode) override;
+};
+
+struct FormatConversion {
+ GUID fGuidFormat;
+ SkImageDecoder::Format fFormat;
+};
+
+static const FormatConversion gFormatConversions[] = {
+ { GUID_ContainerFormatBmp, SkImageDecoder::kBMP_Format },
+ { GUID_ContainerFormatGif, SkImageDecoder::kGIF_Format },
+ { GUID_ContainerFormatIco, SkImageDecoder::kICO_Format },
+ { GUID_ContainerFormatJpeg, SkImageDecoder::kJPEG_Format },
+ { GUID_ContainerFormatPng, SkImageDecoder::kPNG_Format },
+};
+
+static SkImageDecoder::Format GuidContainerFormat_to_Format(REFGUID guid) {
+ for (size_t i = 0; i < SK_ARRAY_COUNT(gFormatConversions); i++) {
+ if (IsEqualGUID(guid, gFormatConversions[i].fGuidFormat)) {
+ return gFormatConversions[i].fFormat;
+ }
+ }
+ return SkImageDecoder::kUnknown_Format;
+}
+
+SkImageDecoder::Result SkImageDecoder_WIC::onDecode(SkStream* stream, SkBitmap* bm, Mode mode) {
+ WICModes wicMode;
+ switch (mode) {
+ case SkImageDecoder::kDecodeBounds_Mode:
+ wicMode = kDecodeBounds_WICMode;
+ break;
+ case SkImageDecoder::kDecodePixels_Mode:
+ wicMode = kDecodePixels_WICMode;
+ break;
+ }
+ return this->decodeStream(stream, bm, wicMode, nullptr) ? kSuccess : kFailure;
+}
+
+bool SkImageDecoder_WIC::decodeStream(SkStream* stream, SkBitmap* bm, WICModes wicMode,
+ Format* format) const {
+ //Initialize COM.
+ SkAutoCoInitialize scopedCo;
+ if (!scopedCo.succeeded()) {
+ return false;
+ }
+
+ HRESULT hr = S_OK;
+
+ //Create Windows Imaging Component ImagingFactory.
+ SkTScopedComPtr<IWICImagingFactory> piImagingFactory;
+ if (SUCCEEDED(hr)) {
+ hr = CoCreateInstance(
+ CLSID_WICImagingFactory
+ , nullptr
+ , CLSCTX_INPROC_SERVER
+ , IID_PPV_ARGS(&piImagingFactory)
+ );
+ }
+
+ //Convert SkStream to IStream.
+ SkTScopedComPtr<IStream> piStream;
+ if (SUCCEEDED(hr)) {
+ hr = SkIStream::CreateFromSkStream(stream, false, &piStream);
+ }
+
+ //Make sure we're at the beginning of the stream.
+ if (SUCCEEDED(hr)) {
+ LARGE_INTEGER liBeginning = { 0 };
+ hr = piStream->Seek(liBeginning, STREAM_SEEK_SET, nullptr);
+ }
+
+ //Create the decoder from the stream content.
+ SkTScopedComPtr<IWICBitmapDecoder> piBitmapDecoder;
+ if (SUCCEEDED(hr)) {
+ hr = piImagingFactory->CreateDecoderFromStream(
+ piStream.get() //Image to be decoded
+ , nullptr //No particular vendor
+ , WICDecodeMetadataCacheOnDemand //Cache metadata when needed
+ , &piBitmapDecoder //Pointer to the decoder
+ );
+ }
+
+ if (kDecodeFormat_WICMode == wicMode) {
+ SkASSERT(format != nullptr);
+ //Get the format
+ if (SUCCEEDED(hr)) {
+ GUID guidFormat;
+ hr = piBitmapDecoder->GetContainerFormat(&guidFormat);
+ if (SUCCEEDED(hr)) {
+ *format = GuidContainerFormat_to_Format(guidFormat);
+ return true;
+ }
+ }
+ return false;
+ }
+
+ //Get the first frame from the decoder.
+ SkTScopedComPtr<IWICBitmapFrameDecode> piBitmapFrameDecode;
+ if (SUCCEEDED(hr)) {
+ hr = piBitmapDecoder->GetFrame(0, &piBitmapFrameDecode);
+ }
+
+ //Get the BitmapSource interface of the frame.
+ SkTScopedComPtr<IWICBitmapSource> piBitmapSourceOriginal;
+ if (SUCCEEDED(hr)) {
+ hr = piBitmapFrameDecode->QueryInterface(
+ IID_PPV_ARGS(&piBitmapSourceOriginal)
+ );
+ }
+
+ //Get the size of the bitmap.
+ UINT width;
+ UINT height;
+ if (SUCCEEDED(hr)) {
+ hr = piBitmapSourceOriginal->GetSize(&width, &height);
+ }
+
+ //Exit early if we're only looking for the bitmap bounds.
+ if (SUCCEEDED(hr)) {
+ bm->setInfo(SkImageInfo::MakeN32Premul(width, height));
+ if (kDecodeBounds_WICMode == wicMode) {
+ return true;
+ }
+ if (!this->allocPixelRef(bm, nullptr)) {
+ return false;
+ }
+ }
+
+ //Create a format converter.
+ SkTScopedComPtr<IWICFormatConverter> piFormatConverter;
+ if (SUCCEEDED(hr)) {
+ hr = piImagingFactory->CreateFormatConverter(&piFormatConverter);
+ }
+
+ GUID destinationPixelFormat;
+ if (this->getRequireUnpremultipliedColors()) {
+ destinationPixelFormat = GUID_WICPixelFormat32bppBGRA;
+ } else {
+ destinationPixelFormat = GUID_WICPixelFormat32bppPBGRA;
+ }
+
+ if (SUCCEEDED(hr)) {
+ hr = piFormatConverter->Initialize(
+ piBitmapSourceOriginal.get() //Input bitmap to convert
+ , destinationPixelFormat //Destination pixel format
+ , WICBitmapDitherTypeNone //Specified dither patterm
+ , nullptr //Specify a particular palette
+ , 0.f //Alpha threshold
+ , WICBitmapPaletteTypeCustom //Palette translation type
+ );
+ }
+
+ //Get the BitmapSource interface of the format converter.
+ SkTScopedComPtr<IWICBitmapSource> piBitmapSourceConverted;
+ if (SUCCEEDED(hr)) {
+ hr = piFormatConverter->QueryInterface(
+ IID_PPV_ARGS(&piBitmapSourceConverted)
+ );
+ }
+
+ //Copy the pixels into the bitmap.
+ if (SUCCEEDED(hr)) {
+ SkAutoLockPixels alp(*bm);
+ bm->eraseColor(SK_ColorTRANSPARENT);
+ const UINT stride = (UINT) bm->rowBytes();
+ hr = piBitmapSourceConverted->CopyPixels(
+ nullptr, //Get all the pixels
+ stride,
+ stride * height,
+ reinterpret_cast<BYTE *>(bm->getPixels())
+ );
+
+ // Note: we don't need to premultiply here since we specified PBGRA
+ if (SkBitmap::ComputeIsOpaque(*bm)) {
+ bm->setAlphaType(kOpaque_SkAlphaType);
+ }
+ }
+
+ return SUCCEEDED(hr);
+}
+
+/////////////////////////////////////////////////////////////////////////
+
+extern SkImageDecoder* image_decoder_from_stream(SkStreamRewindable*);
+
+SkImageDecoder* SkImageDecoder::Factory(SkStreamRewindable* stream) {
+ SkImageDecoder* decoder = image_decoder_from_stream(stream);
+ if (nullptr == decoder) {
+ // If no image decoder specific to the stream exists, use SkImageDecoder_WIC.
+ return new SkImageDecoder_WIC;
+ } else {
+ return decoder;
+ }
+}
+
/////////////////////////////////////////////////////////////////////////
SkMovie* SkMovie::DecodeStream(SkStreamRewindable* stream) {
@@ -58,10 +275,6 @@ SkMovie* SkMovie::DecodeStream(SkStreamRewindable* stream) {
class SkImageEncoder_WIC : public SkImageEncoder {
public:
SkImageEncoder_WIC(Type t) : fType(t) {}
-
- // DO NOT USE this constructor. This exists only so SkForceLinking can
- // link the WIC image encoder.
- SkImageEncoder_WIC() {}
protected:
virtual bool onEncode(SkWStream* stream, const SkBitmap& bm, int quality);
@@ -241,6 +454,15 @@ static SkImageEncoder* sk_imageencoder_wic_factory(SkImageEncoder::Type t) {
static SkImageEncoder_EncodeReg gEReg(sk_imageencoder_wic_factory);
-DEFINE_ENCODER_CREATOR(ImageEncoder_WIC);
+static SkImageDecoder::Format get_format_wic(SkStreamRewindable* stream) {
+ SkImageDecoder::Format format;
+ SkImageDecoder_WIC codec;
+ if (!codec.decodeStream(stream, nullptr, SkImageDecoder_WIC::kDecodeFormat_WICMode, &format)) {
+ format = SkImageDecoder::kUnknown_Format;
+ }
+ return format;
+}
+
+static SkImageDecoder_FormatReg gFormatReg(get_format_wic);
#endif // defined(SK_BUILD_FOR_WIN32)
diff --git a/src/ports/SkImageDecoder_empty.cpp b/src/ports/SkImageDecoder_empty.cpp
index 33e07acea6..f52dada73b 100644
--- a/src/ports/SkImageDecoder_empty.cpp
+++ b/src/ports/SkImageDecoder_empty.cpp
@@ -8,11 +8,74 @@
#include "SkBitmap.h"
#include "SkImage.h"
+#include "SkImageDecoder.h"
#include "SkImageEncoder.h"
#include "SkMovie.h"
#include "SkPixelSerializer.h"
#include "SkStream.h"
+class SkColorTable;
+class SkPngChunkReader;
+
+// Empty implementations for SkImageDecoder.
+
+SkImageDecoder::SkImageDecoder() {}
+
+SkImageDecoder::~SkImageDecoder() {}
+
+SkImageDecoder* SkImageDecoder::Factory(SkStreamRewindable*) {
+ return nullptr;
+}
+
+void SkImageDecoder::copyFieldsToOther(SkImageDecoder* ) {}
+
+bool SkImageDecoder::DecodeFile(const char[], SkBitmap*, SkColorType, Mode, Format*) {
+ return false;
+}
+
+SkImageDecoder::Result SkImageDecoder::decode(SkStream*, SkBitmap*, SkColorType, Mode) {
+ return kFailure;
+}
+
+bool SkImageDecoder::DecodeStream(SkStreamRewindable*, SkBitmap*, SkColorType, Mode, Format*) {
+ return false;
+}
+
+bool SkImageDecoder::DecodeMemory(const void*, size_t, SkBitmap*, SkColorType, Mode, Format*) {
+ return false;
+}
+
+bool SkImageDecoder::decodeYUV8Planes(SkStream*, SkISize[3], void*[3],
+ size_t[3], SkYUVColorSpace*) {
+ return false;
+}
+
+SkImageDecoder::Format SkImageDecoder::getFormat() const {
+ return kUnknown_Format;
+}
+
+SkImageDecoder::Format SkImageDecoder::GetStreamFormat(SkStreamRewindable*) {
+ return kUnknown_Format;
+}
+
+const char* SkImageDecoder::GetFormatName(Format) {
+ return nullptr;
+}
+
+SkPngChunkReader* SkImageDecoder::setPeeker(SkPngChunkReader*) {
+ return nullptr;
+}
+
+SkBitmap::Allocator* SkImageDecoder::setAllocator(SkBitmap::Allocator*) {
+ return nullptr;
+}
+
+void SkImageDecoder::setSampleSize(int) {}
+
+bool SkImageDecoder::allocPixelRef(SkBitmap*, SkColorTable*) const {
+ return false;
+}
+
/////////////////////////////////////////////////////////////////////////
// Empty implementation for SkMovie.