diff options
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 = ⊂ + } 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. |