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