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:
parent
f037fdebda
commit
910f7ec7e7
@ -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()
|
||||
|
@ -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
223
gm/etc1bitmap.cpp
Normal 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
|
@ -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',
|
||||
|
@ -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',
|
||||
],
|
||||
}],
|
||||
],
|
||||
|
@ -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;
|
||||
|
413
include/core/SkImageDecoder.h
Normal file
413
include/core/SkImageDecoder.h
Normal 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
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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:
|
||||
|
@ -10,7 +10,6 @@
|
||||
#define SkWriteBuffer_DEFINED
|
||||
|
||||
#include "SkData.h"
|
||||
#include "SkImage.h"
|
||||
#include "SkPath.h"
|
||||
#include "SkPicture.h"
|
||||
#include "SkPixelSerializer.h"
|
||||
|
@ -11,6 +11,7 @@
|
||||
#include "SkAndroidCodec.h"
|
||||
#include "SkCodec.h"
|
||||
#include "SkCodecPriv.h"
|
||||
#include "SkImageDecoder.h"
|
||||
|
||||
SkBitmapRegionDecoder* SkBitmapRegionDecoder::Create(
|
||||
SkData* data, Strategy strategy) {
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
|
@ -16,7 +16,6 @@
|
||||
#include "SkChecksum.h"
|
||||
#include "SkImageFilter.h"
|
||||
#include "SkMessageBus.h"
|
||||
#include "SkPaint.h"
|
||||
#include "SkPicture.h"
|
||||
#include "SkTDynamicHash.h"
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
204
src/images/SkImageDecoder.cpp
Normal file
204
src/images/SkImageDecoder.cpp
Normal 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);
|
||||
}
|
@ -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) {
|
||||
|
63
src/images/SkImageDecoder_FactoryRegistrar.cpp
Normal file
63
src/images/SkImageDecoder_FactoryRegistrar.cpp
Normal 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;
|
||||
}
|
203
src/images/SkImageDecoder_astc.cpp
Normal file
203
src/images/SkImageDecoder_astc.cpp
Normal 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);
|
@ -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);
|
||||
|
166
src/images/SkImageDecoder_libbmp.cpp
Normal file
166
src/images/SkImageDecoder_libbmp.cpp
Normal 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;
|
||||
}
|
541
src/images/SkImageDecoder_libgif.cpp
Normal file
541
src/images/SkImageDecoder_libgif.cpp
Normal 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 = ⊂
|
||||
} 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);
|
456
src/images/SkImageDecoder_libico.cpp
Normal file
456
src/images/SkImageDecoder_libico.cpp
Normal 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);
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
128
src/images/SkImageDecoder_pkm.cpp
Normal file
128
src/images/SkImageDecoder_pkm.cpp
Normal 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);
|
173
src/images/SkImageDecoder_wbmp.cpp
Normal file
173
src/images/SkImageDecoder_wbmp.cpp
Normal 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);
|
@ -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) {
|
||||
|
@ -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.
|
||||
|
877
src/images/SkScaledBitmapSampler.cpp
Normal file
877
src/images/SkScaledBitmapSampler.cpp
Normal 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
|
107
src/images/SkScaledBitmapSampler.h
Normal file
107
src/images/SkScaledBitmapSampler.h
Normal 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
|
369
src/images/bmpdecoderhelper.cpp
Normal file
369
src/images/bmpdecoderhelper.cpp
Normal 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
|
116
src/images/bmpdecoderhelper.h
Normal file
116
src/images/bmpdecoderhelper.h
Normal 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
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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.
|
||||
|
Loading…
Reference in New Issue
Block a user