Revert of Delete SkImageDecoder (patchset #9 id:150001 of https://codereview.chromium.org/1820503002/ )

Reason for revert:
Roll still failing

Original issue's description:
> Delete SkImageDecoder
>
> This image decoding implementation has been replaced
> by SkCodec in Android.
>
> Additionally, we have replaced uses of SkImageDecoder
> in Skia and Google3 with uses of SkCodec.
>
> Now we can delete SkImageDecoder :).
>
> BUG=skia:
> GOLD_TRYBOT_URL= https://gold.skia.org/search2?unt=true&query=source_type%3Dgm&master=false&issue=1820503002
> CQ_EXTRA_TRYBOTS=client.skia.compile:Build-Ubuntu-GCC-x86_64-Release-CMake-Trybot,Build-Mac-Clang-x86_64-Release-CMake-Trybot
>
> Committed: https://skia.googlesource.com/skia/+/f799706656f2581c5bf5510d94df3fa17cce1607
>
> Committed: https://skia.googlesource.com/skia/+/5b6e73e0c8282c4d85accbfbcecc6dee84f8a1eb
>
> Committed: https://skia.googlesource.com/skia/+/f037fdebda2a2626e6512d7532063f2cd41a264d

TBR=scroggo@google.com,djsollen@google.com
# Skipping CQ checks because original CL landed less than 1 days ago.
NOPRESUBMIT=true
NOTREECHECKS=true
NOTRY=true
BUG=skia:

Review URL: https://codereview.chromium.org/1830943002
This commit is contained in:
msarett 2016-03-24 04:45:39 -07:00 committed by Commit bot
parent f037fdebda
commit 910f7ec7e7
38 changed files with 6758 additions and 49 deletions

View File

@ -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()

View File

@ -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); )

223
gm/etc1bitmap.cpp Normal file
View File

@ -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

View File

@ -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',

View File

@ -31,17 +31,36 @@
],
'sources': [
'../include/images/SkForceLinking.h',
'../src/images/SkJpegUtility.h',
'../include/images/SkMovie.h',
'../include/images/SkPageFlipper.h',
'../src/images/SkForceLinking.cpp',
'../src/images/SkImageDecoder_FactoryDefault.cpp',
'../src/images/bmpdecoderhelper.cpp',
'../src/images/bmpdecoderhelper.h',
# If encoders are added/removed to/from (all/individual)
'../src/images/SkForceLinking.cpp',
'../src/images/SkImageDecoder.cpp',
'../src/images/SkImageDecoder_FactoryDefault.cpp',
'../src/images/SkImageDecoder_FactoryRegistrar.cpp',
# 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',
],
}],
],

View File

@ -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;

View File

@ -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

View File

@ -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

View File

@ -8,23 +8,20 @@
#ifndef SkPicture_DEFINED
#define SkPicture_DEFINED
#include "SkImageDecoder.h"
#include "SkRefCnt.h"
#include "SkRect.h"
#include "SkTypes.h"
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;
#define SK_SUPPORT_LEGACY_PICTURE_PTR

View File

@ -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:

View File

@ -10,7 +10,6 @@
#define SkWriteBuffer_DEFINED
#include "SkData.h"
#include "SkImage.h"
#include "SkPath.h"
#include "SkPicture.h"
#include "SkPixelSerializer.h"

View File

@ -11,6 +11,7 @@
#include "SkAndroidCodec.h"
#include "SkCodec.h"
#include "SkCodecPriv.h"
#include "SkImageDecoder.h"
SkBitmapRegionDecoder* SkBitmapRegionDecoder::Create(
SkData* data, Strategy strategy) {

View File

@ -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.

View File

@ -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

View File

@ -16,7 +16,6 @@
#include "SkChecksum.h"
#include "SkImageFilter.h"
#include "SkMessageBus.h"
#include "SkPaint.h"
#include "SkPicture.h"
#include "SkTDynamicHash.h"

View File

@ -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;
}

View File

@ -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);
}

View File

@ -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) {

View File

@ -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;
}

View File

@ -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);

View File

@ -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);

View File

@ -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;
}

View File

@ -0,0 +1,541 @@
/*
* Copyright 2006 The Android Open Source Project
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include "SkColor.h"
#include "SkColorPriv.h"
#include "SkColorTable.h"
#include "SkImageDecoder.h"
#include "SkRTConf.h"
#include "SkScaledBitmapSampler.h"
#include "SkStream.h"
#include "SkTemplates.h"
#include "SkUtils.h"
#include "gif_lib.h"
class SkGIFImageDecoder : public SkImageDecoder {
public:
Format getFormat() const override {
return kGIF_Format;
}
protected:
Result onDecode(SkStream* stream, SkBitmap* bm, Mode mode) override;
private:
typedef SkImageDecoder INHERITED;
};
static const uint8_t gStartingIterlaceYValue[] = {
0, 4, 2, 1
};
static const uint8_t gDeltaIterlaceYValue[] = {
8, 8, 4, 2
};
SK_CONF_DECLARE(bool, c_suppressGIFImageDecoderWarnings,
"images.gif.suppressDecoderWarnings", true,
"Suppress GIF warnings and errors when calling image decode "
"functions.");
/* Implement the GIF interlace algorithm in an iterator.
1) grab every 8th line beginning at 0
2) grab every 8th line beginning at 4
3) grab every 4th line beginning at 2
4) grab every 2nd line beginning at 1
*/
class GifInterlaceIter {
public:
GifInterlaceIter(int height) : fHeight(height) {
fStartYPtr = gStartingIterlaceYValue;
fDeltaYPtr = gDeltaIterlaceYValue;
fCurrY = *fStartYPtr++;
fDeltaY = *fDeltaYPtr++;
}
int currY() const {
SkASSERT(fStartYPtr);
SkASSERT(fDeltaYPtr);
return fCurrY;
}
void next() {
SkASSERT(fStartYPtr);
SkASSERT(fDeltaYPtr);
int y = fCurrY + fDeltaY;
// We went from an if statement to a while loop so that we iterate
// through fStartYPtr until a valid row is found. This is so that images
// that are smaller than 5x5 will not trash memory.
while (y >= fHeight) {
if (gStartingIterlaceYValue +
SK_ARRAY_COUNT(gStartingIterlaceYValue) == fStartYPtr) {
// we done
SkDEBUGCODE(fStartYPtr = nullptr;)
SkDEBUGCODE(fDeltaYPtr = nullptr;)
y = 0;
} else {
y = *fStartYPtr++;
fDeltaY = *fDeltaYPtr++;
}
}
fCurrY = y;
}
private:
const int fHeight;
int fCurrY;
int fDeltaY;
const uint8_t* fStartYPtr;
const uint8_t* fDeltaYPtr;
};
///////////////////////////////////////////////////////////////////////////////
static int DecodeCallBackProc(GifFileType* fileType, GifByteType* out,
int size) {
SkStream* stream = (SkStream*) fileType->UserData;
return (int) stream->read(out, size);
}
void CheckFreeExtension(SavedImage* Image) {
if (Image->ExtensionBlocks) {
#if GIFLIB_MAJOR < 5
FreeExtension(Image);
#else
GifFreeExtensions(&Image->ExtensionBlockCount, &Image->ExtensionBlocks);
#endif
}
}
// return nullptr on failure
static const ColorMapObject* find_colormap(const GifFileType* gif) {
const ColorMapObject* cmap = gif->Image.ColorMap;
if (nullptr == cmap) {
cmap = gif->SColorMap;
}
if (nullptr == cmap) {
// no colormap found
return nullptr;
}
// some sanity checks
if (cmap && ((unsigned)cmap->ColorCount > 256 ||
cmap->ColorCount != (1 << cmap->BitsPerPixel))) {
cmap = nullptr;
}
return cmap;
}
// return -1 if not found (i.e. we're completely opaque)
static int find_transpIndex(const SavedImage& image, int colorCount) {
int transpIndex = -1;
for (int i = 0; i < image.ExtensionBlockCount; ++i) {
const ExtensionBlock* eb = image.ExtensionBlocks + i;
if (eb->Function == 0xF9 && eb->ByteCount == 4) {
if (eb->Bytes[0] & 1) {
transpIndex = (unsigned char)eb->Bytes[3];
// check for valid transpIndex
if (transpIndex >= colorCount) {
transpIndex = -1;
}
break;
}
}
}
return transpIndex;
}
static SkImageDecoder::Result error_return(const SkBitmap& bm, const char msg[]) {
if (!c_suppressGIFImageDecoderWarnings) {
SkDebugf("libgif error [%s] bitmap [%d %d] pixels %p colortable %p\n",
msg, bm.width(), bm.height(), bm.getPixels(),
bm.getColorTable());
}
return SkImageDecoder::kFailure;
}
static void gif_warning(const SkBitmap& bm, const char msg[]) {
if (!c_suppressGIFImageDecoderWarnings) {
SkDebugf("libgif warning [%s] bitmap [%d %d] pixels %p colortable %p\n",
msg, bm.width(), bm.height(), bm.getPixels(),
bm.getColorTable());
}
}
/**
* Skip rows in the source gif image.
* @param gif Source image.
* @param dst Scratch output needed by gif library call. Must be >= width bytes.
* @param width Bytes per row in the source image.
* @param rowsToSkip Number of rows to skip.
* @return True on success, false on GIF_ERROR.
*/
static bool skip_src_rows(GifFileType* gif, uint8_t* dst, int width, int rowsToSkip) {
for (int i = 0; i < rowsToSkip; i++) {
if (DGifGetLine(gif, dst, width) == GIF_ERROR) {
return false;
}
}
return true;
}
/**
* GIFs with fewer then 256 color entries will sometimes index out of
* bounds of the color table (this is malformed, but libgif does not
* check sicne it is rare). This function checks for this error and
* fixes it. This makes the output image consistantly deterministic.
*/
static void sanitize_indexed_bitmap(SkBitmap* bm) {
if ((kIndex_8_SkColorType == bm->colorType()) && !(bm->empty())) {
SkAutoLockPixels alp(*bm);
if (bm->getPixels()) {
SkColorTable* ct = bm->getColorTable(); // Index8 must have it.
SkASSERT(ct != nullptr);
uint32_t count = ct->count();
SkASSERT(count > 0);
SkASSERT(count <= 0x100);
if (count != 0x100) { // Full colortables can't go wrong.
// Count is a power of 2; asserted elsewhere.
uint8_t byteMask = (~(count - 1));
bool warning = false;
uint8_t* addr = static_cast<uint8_t*>(bm->getPixels());
int height = bm->height();
int width = bm->width();
size_t rowBytes = bm->rowBytes();
while (--height >= 0) {
uint8_t* ptr = addr;
int x = width;
while (--x >= 0) {
if (0 != ((*ptr) & byteMask)) {
warning = true;
*ptr = 0;
}
++ptr;
}
addr += rowBytes;
}
if (warning) {
gif_warning(*bm, "Index out of bounds.");
}
}
}
}
}
namespace {
// This function is a template argument, so can't be static.
int close_gif(GifFileType* gif) {
#if GIFLIB_MAJOR < 5 || (GIFLIB_MAJOR == 5 && GIFLIB_MINOR == 0)
return DGifCloseFile(gif);
#else
return DGifCloseFile(gif, nullptr);
#endif
}
}//namespace
SkImageDecoder::Result SkGIFImageDecoder::onDecode(SkStream* sk_stream, SkBitmap* bm, Mode mode) {
#if GIFLIB_MAJOR < 5
GifFileType* gif = DGifOpen(sk_stream, DecodeCallBackProc);
#else
GifFileType* gif = DGifOpen(sk_stream, DecodeCallBackProc, nullptr);
#endif
if (nullptr == gif) {
return error_return(*bm, "DGifOpen");
}
SkAutoTCallIProc<GifFileType, close_gif> acp(gif);
SavedImage temp_save;
temp_save.ExtensionBlocks=nullptr;
temp_save.ExtensionBlockCount=0;
SkAutoTCallVProc<SavedImage, CheckFreeExtension> acp2(&temp_save);
int width, height;
GifRecordType recType;
GifByteType *extData;
#if GIFLIB_MAJOR >= 5
int extFunction;
#endif
int transpIndex = -1; // -1 means we don't have it (yet)
int fillIndex = gif->SBackGroundColor;
do {
if (DGifGetRecordType(gif, &recType) == GIF_ERROR) {
return error_return(*bm, "DGifGetRecordType");
}
switch (recType) {
case IMAGE_DESC_RECORD_TYPE: {
if (DGifGetImageDesc(gif) == GIF_ERROR) {
return error_return(*bm, "IMAGE_DESC_RECORD_TYPE");
}
if (gif->ImageCount < 1) { // sanity check
return error_return(*bm, "ImageCount < 1");
}
width = gif->SWidth;
height = gif->SHeight;
SavedImage* image = &gif->SavedImages[gif->ImageCount-1];
const GifImageDesc& desc = image->ImageDesc;
int imageLeft = desc.Left;
int imageTop = desc.Top;
const int innerWidth = desc.Width;
const int innerHeight = desc.Height;
if (innerWidth <= 0 || innerHeight <= 0) {
return error_return(*bm, "invalid dimensions");
}
// check for valid descriptor
if (innerWidth > width) {
gif_warning(*bm, "image too wide, expanding output to size");
width = innerWidth;
imageLeft = 0;
} else if (imageLeft + innerWidth > width) {
gif_warning(*bm, "shifting image left to fit");
imageLeft = width - innerWidth;
} else if (imageLeft < 0) {
gif_warning(*bm, "shifting image right to fit");
imageLeft = 0;
}
if (innerHeight > height) {
gif_warning(*bm, "image too tall, expanding output to size");
height = innerHeight;
imageTop = 0;
} else if (imageTop + innerHeight > height) {
gif_warning(*bm, "shifting image up to fit");
imageTop = height - innerHeight;
} else if (imageTop < 0) {
gif_warning(*bm, "shifting image down to fit");
imageTop = 0;
}
SkScaledBitmapSampler sampler(width, height, this->getSampleSize());
bm->setInfo(SkImageInfo::Make(sampler.scaledWidth(), sampler.scaledHeight(),
kIndex_8_SkColorType, kPremul_SkAlphaType));
if (SkImageDecoder::kDecodeBounds_Mode == mode) {
return kSuccess;
}
// now we decode the colortable
int colorCount = 0;
{
// Declare colorPtr here for scope.
SkPMColor colorPtr[256]; // storage for worst-case
const ColorMapObject* cmap = find_colormap(gif);
if (cmap != nullptr) {
SkASSERT(cmap->ColorCount == (1 << (cmap->BitsPerPixel)));
colorCount = cmap->ColorCount;
if (colorCount > 256) {
colorCount = 256; // our kIndex8 can't support more
}
for (int index = 0; index < colorCount; index++) {
colorPtr[index] = SkPackARGB32(0xFF,
cmap->Colors[index].Red,
cmap->Colors[index].Green,
cmap->Colors[index].Blue);
}
} else {
// find_colormap() returned nullptr. Some (rare, broken)
// GIFs don't have a color table, so we force one.
gif_warning(*bm, "missing colormap");
colorCount = 256;
sk_memset32(colorPtr, SK_ColorWHITE, colorCount);
}
transpIndex = find_transpIndex(temp_save, colorCount);
if (transpIndex >= 0) {
colorPtr[transpIndex] = SK_ColorTRANSPARENT; // ram in a transparent SkPMColor
fillIndex = transpIndex;
} else if (fillIndex >= colorCount) {
// gif->SBackGroundColor should be less than colorCount.
fillIndex = 0; // If not, fix it.
}
SkAutoTUnref<SkColorTable> ctable(new SkColorTable(colorPtr, colorCount));
if (!this->allocPixelRef(bm, ctable)) {
return error_return(*bm, "allocPixelRef");
}
}
// abort if either inner dimension is <= 0
if (innerWidth <= 0 || innerHeight <= 0) {
return error_return(*bm, "non-pos inner width/height");
}
SkAutoLockPixels alp(*bm);
SkAutoTMalloc<uint8_t> storage(innerWidth);
uint8_t* scanline = storage.get();
// GIF has an option to store the scanlines of an image, plus a larger background,
// filled by a fill color. In this case, we will use a subset of the larger bitmap
// for sampling.
SkBitmap subset;
SkBitmap* workingBitmap;
// are we only a subset of the total bounds?
if ((imageTop | imageLeft) > 0 ||
innerWidth < width || innerHeight < height) {
// Fill the background.
memset(bm->getPixels(), fillIndex, bm->getSize());
// Create a subset of the bitmap.
SkIRect subsetRect(SkIRect::MakeXYWH(imageLeft / sampler.srcDX(),
imageTop / sampler.srcDY(),
innerWidth / sampler.srcDX(),
innerHeight / sampler.srcDY()));
if (!bm->extractSubset(&subset, subsetRect)) {
return error_return(*bm, "Extract failed.");
}
// Update the sampler. We'll now be only sampling into the subset.
sampler = SkScaledBitmapSampler(innerWidth, innerHeight, this->getSampleSize());
workingBitmap = &subset;
} else {
workingBitmap = bm;
}
// bm is already locked, but if we had to take a subset, it must be locked also,
// so that getPixels() will point to its pixels.
SkAutoLockPixels alpWorking(*workingBitmap);
if (!sampler.begin(workingBitmap, SkScaledBitmapSampler::kIndex, *this)) {
return error_return(*bm, "Sampler failed to begin.");
}
// now decode each scanline
if (gif->Image.Interlace) {
// Iterate over the height of the source data. The sampler will
// take care of skipping unneeded rows.
GifInterlaceIter iter(innerHeight);
for (int y = 0; y < innerHeight; y++) {
if (DGifGetLine(gif, scanline, innerWidth) == GIF_ERROR) {
gif_warning(*bm, "interlace DGifGetLine");
memset(scanline, fillIndex, innerWidth);
for (; y < innerHeight; y++) {
sampler.sampleInterlaced(scanline, iter.currY());
iter.next();
}
return kPartialSuccess;
}
sampler.sampleInterlaced(scanline, iter.currY());
iter.next();
}
} else {
// easy, non-interlace case
const int outHeight = workingBitmap->height();
skip_src_rows(gif, scanline, innerWidth, sampler.srcY0());
for (int y = 0; y < outHeight; y++) {
if (DGifGetLine(gif, scanline, innerWidth) == GIF_ERROR) {
gif_warning(*bm, "DGifGetLine");
memset(scanline, fillIndex, innerWidth);
for (; y < outHeight; y++) {
sampler.next(scanline);
}
return kPartialSuccess;
}
// scanline now contains the raw data. Sample it.
sampler.next(scanline);
if (y < outHeight - 1) {
skip_src_rows(gif, scanline, innerWidth, sampler.srcDY() - 1);
}
}
// skip the rest of the rows (if any)
int read = (outHeight - 1) * sampler.srcDY() + sampler.srcY0() + 1;
SkASSERT(read <= innerHeight);
skip_src_rows(gif, scanline, innerWidth, innerHeight - read);
}
sanitize_indexed_bitmap(bm);
return kSuccess;
} break;
case EXTENSION_RECORD_TYPE:
#if GIFLIB_MAJOR < 5
if (DGifGetExtension(gif, &temp_save.Function,
&extData) == GIF_ERROR) {
#else
if (DGifGetExtension(gif, &extFunction, &extData) == GIF_ERROR) {
#endif
return error_return(*bm, "DGifGetExtension");
}
while (extData != nullptr) {
/* Create an extension block with our data */
#if GIFLIB_MAJOR < 5
if (AddExtensionBlock(&temp_save, extData[0],
&extData[1]) == GIF_ERROR) {
#else
if (GifAddExtensionBlock(&temp_save.ExtensionBlockCount,
&temp_save.ExtensionBlocks,
extFunction,
extData[0],
&extData[1]) == GIF_ERROR) {
#endif
return error_return(*bm, "AddExtensionBlock");
}
if (DGifGetExtensionNext(gif, &extData) == GIF_ERROR) {
return error_return(*bm, "DGifGetExtensionNext");
}
#if GIFLIB_MAJOR < 5
temp_save.Function = 0;
#endif
}
break;
case TERMINATE_RECORD_TYPE:
break;
default: /* Should be trapped by DGifGetRecordType */
break;
}
} while (recType != TERMINATE_RECORD_TYPE);
sanitize_indexed_bitmap(bm);
return kSuccess;
}
///////////////////////////////////////////////////////////////////////////////
DEFINE_DECODER_CREATOR(GIFImageDecoder);
///////////////////////////////////////////////////////////////////////////////
static bool is_gif(SkStreamRewindable* stream) {
char buf[GIF_STAMP_LEN];
if (stream->read(buf, GIF_STAMP_LEN) == GIF_STAMP_LEN) {
if (memcmp(GIF_STAMP, buf, GIF_STAMP_LEN) == 0 ||
memcmp(GIF87_STAMP, buf, GIF_STAMP_LEN) == 0 ||
memcmp(GIF89_STAMP, buf, GIF_STAMP_LEN) == 0) {
return true;
}
}
return false;
}
static SkImageDecoder* sk_libgif_dfactory(SkStreamRewindable* stream) {
if (is_gif(stream)) {
return new SkGIFImageDecoder;
}
return nullptr;
}
static SkImageDecoder_DecodeReg gReg(sk_libgif_dfactory);
static SkImageDecoder::Format get_format_gif(SkStreamRewindable* stream) {
if (is_gif(stream)) {
return SkImageDecoder::kGIF_Format;
}
return SkImageDecoder::kUnknown_Format;
}
static SkImageDecoder_FormatReg gFormatReg(get_format_gif);

View File

@ -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);

View File

@ -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);

View File

@ -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);

View File

@ -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);

View File

@ -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);

View File

@ -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);

View File

@ -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) {

View File

@ -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.

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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)

View File

@ -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.