| Index: src/ports/SkImageDecoder_CG.cpp
|
| diff --git a/src/ports/SkImageDecoder_CG.cpp b/src/ports/SkImageDecoder_CG.cpp
|
| index ead0ed6506ff19d5c602d541a87543620bad291e..c4446ae16d67ef5671c817af569f188a73fe177d 100644
|
| --- a/src/ports/SkImageDecoder_CG.cpp
|
| +++ b/src/ports/SkImageDecoder_CG.cpp
|
| @@ -11,6 +11,7 @@
|
| #include "SkCGUtils.h"
|
| #include "SkColorPriv.h"
|
| #include "SkData.h"
|
| +#include "SkImageDecoder.h"
|
| #include "SkImageEncoder.h"
|
| #include "SkMovie.h"
|
| #include "SkStream.h"
|
| @@ -27,6 +28,210 @@
|
| #include <ImageIO/ImageIO.h>
|
| #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;
|
| + }
|
| +}
|
|
|
| /////////////////////////////////////////////////////////////////////////
|
|
|
| @@ -150,13 +355,57 @@
|
|
|
| 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)
|
|
|