/* * Copyright 2011 Google Inc. * * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. */ #include "include/core/SkTypes.h" #if defined(SK_BUILD_FOR_MAC) || defined(SK_BUILD_FOR_IOS) #include "include/core/SkBitmap.h" #include "include/core/SkColorSpace.h" #include "include/core/SkData.h" #include "include/encode/SkICC.h" #include "include/private/SkColorData.h" #include "include/private/base/SkMacros.h" #include "include/private/base/SkTo.h" #include "include/utils/mac/SkCGUtils.h" #include "src/utils/mac/SkUniqueCFRef.h" #include #include static CGBitmapInfo compute_cgalpha_info_rgba(SkAlphaType at) { CGBitmapInfo info = kCGBitmapByteOrder32Big; switch (at) { case kUnknown_SkAlphaType: break; case kOpaque_SkAlphaType: info |= kCGImageAlphaNoneSkipLast; break; case kPremul_SkAlphaType: info |= kCGImageAlphaPremultipliedLast; break; case kUnpremul_SkAlphaType: info |= kCGImageAlphaLast; break; } return info; } static CGBitmapInfo compute_cgalpha_info_bgra(SkAlphaType at) { CGBitmapInfo info = kCGBitmapByteOrder32Little; switch (at) { case kUnknown_SkAlphaType: break; case kOpaque_SkAlphaType: info |= kCGImageAlphaNoneSkipFirst; break; case kPremul_SkAlphaType: info |= kCGImageAlphaPremultipliedFirst; break; case kUnpremul_SkAlphaType: info |= kCGImageAlphaFirst; break; } return info; } static CGBitmapInfo compute_cgalpha_info_4444(SkAlphaType at) { CGBitmapInfo info = kCGBitmapByteOrder16Little; switch (at) { case kOpaque_SkAlphaType: info |= kCGImageAlphaNoneSkipLast; break; default: info |= kCGImageAlphaPremultipliedLast; break; } return info; } static bool get_bitmap_info(SkColorType skColorType, SkAlphaType skAlphaType, size_t* bitsPerComponent, CGBitmapInfo* info, bool* upscaleTo32) { if (upscaleTo32) { *upscaleTo32 = false; } switch (skColorType) { case kRGB_565_SkColorType: if (upscaleTo32) { *upscaleTo32 = true; } // now treat like RGBA *bitsPerComponent = 8; *info = compute_cgalpha_info_rgba(kOpaque_SkAlphaType); break; case kRGBA_8888_SkColorType: *bitsPerComponent = 8; *info = compute_cgalpha_info_rgba(skAlphaType); break; case kBGRA_8888_SkColorType: *bitsPerComponent = 8; *info = compute_cgalpha_info_bgra(skAlphaType); break; case kARGB_4444_SkColorType: *bitsPerComponent = 4; *info = compute_cgalpha_info_4444(skAlphaType); break; default: return false; } return true; } static std::unique_ptr prepare_for_image_ref(const SkBitmap& bm, size_t* bitsPerComponent, CGBitmapInfo* info) { bool upscaleTo32; if (!get_bitmap_info(bm.colorType(), bm.alphaType(), bitsPerComponent, info, &upscaleTo32)) { return nullptr; } if (upscaleTo32) { std::unique_ptr copy(new SkBitmap); // here we make a deep copy of the pixels, since CG won't take our // 565 directly, so we always go to RGBA copy->allocPixels(bm.info().makeColorType(kRGBA_8888_SkColorType)); bm.readPixels(copy->info(), copy->getPixels(), copy->rowBytes(), 0, 0); return copy; } return std::make_unique(bm); } CGImageRef SkCreateCGImageRefWithColorspace(const SkBitmap& bm, CGColorSpaceRef colorSpace) { return SkCreateCGImageRef(bm); } CGImageRef SkCreateCGImageRef(const SkBitmap& bm) { if (bm.drawsNothing()) { return nullptr; } size_t bitsPerComponent SK_INIT_TO_AVOID_WARNING; CGBitmapInfo info SK_INIT_TO_AVOID_WARNING; std::unique_ptr bitmap = prepare_for_image_ref(bm, &bitsPerComponent, &info); if (nullptr == bitmap) { return nullptr; } SkPixmap pm = bitmap->pixmap(); // Copy bitmap info before releasing it. const size_t s = bitmap->computeByteSize(); void* pixels = bitmap->getPixels(); // our provider "owns" the bitmap*, and will take care of deleting it SkUniqueCFRef dataRef(CGDataProviderCreateWithData( bitmap.release(), pixels, s, [](void* p, const void*, size_t) { delete reinterpret_cast(p); })); SkUniqueCFRef colorSpace(SkCreateCGColorSpace(bm.colorSpace())); return CGImageCreate(pm.width(), pm.height(), bitsPerComponent, pm.info().bytesPerPixel() * CHAR_BIT, pm.rowBytes(), colorSpace.get(), info, dataRef.get(), nullptr, false, kCGRenderingIntentDefault); } void SkCGDrawBitmap(CGContextRef cg, const SkBitmap& bm, float x, float y) { SkUniqueCFRef img(SkCreateCGImageRef(bm)); if (img) { CGRect r = CGRectMake(0, 0, bm.width(), bm.height()); CGContextSaveGState(cg); CGContextTranslateCTM(cg, x, r.size.height + y); CGContextScaleCTM(cg, 1, -1); CGContextDrawImage(cg, r, img.get()); CGContextRestoreGState(cg); } } /////////////////////////////////////////////////////////////////////////////////////////////////// CGContextRef SkCreateCGContext(const SkPixmap& pmap) { CGBitmapInfo cg_bitmap_info = 0; size_t bitsPerComponent = 0; switch (pmap.colorType()) { case kRGBA_8888_SkColorType: bitsPerComponent = 8; cg_bitmap_info = compute_cgalpha_info_rgba(pmap.alphaType()); break; case kBGRA_8888_SkColorType: bitsPerComponent = 8; cg_bitmap_info = compute_cgalpha_info_bgra(pmap.alphaType()); break; default: return nullptr; // no other colortypes are supported (for now) } size_t rb = pmap.addr() ? pmap.rowBytes() : 0; SkUniqueCFRef cs(SkCreateCGColorSpace(pmap.colorSpace())); CGContextRef cg = CGBitmapContextCreate(pmap.writable_addr(), pmap.width(), pmap.height(), bitsPerComponent, rb, cs.get(), cg_bitmap_info); return cg; } bool SkCopyPixelsFromCGImage(const SkImageInfo& info, size_t rowBytes, void* pixels, CGImageRef image) { CGBitmapInfo cg_bitmap_info = 0; size_t bitsPerComponent = 0; switch (info.colorType()) { case kRGBA_8888_SkColorType: bitsPerComponent = 8; cg_bitmap_info = compute_cgalpha_info_rgba(info.alphaType()); break; case kBGRA_8888_SkColorType: bitsPerComponent = 8; cg_bitmap_info = compute_cgalpha_info_bgra(info.alphaType()); break; default: return false; // no other colortypes are supported (for now) } SkUniqueCFRef cs(SkCreateCGColorSpace(info.colorSpace())); SkUniqueCFRef cg(CGBitmapContextCreate( pixels, info.width(), info.height(), bitsPerComponent, rowBytes, cs.get(), cg_bitmap_info)); if (!cg) { return false; } // use this blend mode, to avoid having to erase the pixels first, and to avoid CG performing // any blending (which could introduce errors and be slower). CGContextSetBlendMode(cg.get(), kCGBlendModeCopy); CGContextDrawImage(cg.get(), CGRectMake(0, 0, info.width(), info.height()), image); return true; } bool SkCreateBitmapFromCGImage(SkBitmap* dst, CGImageRef image) { const int width = SkToInt(CGImageGetWidth(image)); const int height = SkToInt(CGImageGetHeight(image)); sk_sp colorSpace(SkMakeColorSpaceFromCGColorSpace(CGImageGetColorSpace(image))); SkImageInfo info = SkImageInfo::MakeN32Premul(width, height, colorSpace); SkBitmap tmp; if (!tmp.tryAllocPixels(info)) { return false; } if (!SkCopyPixelsFromCGImage(tmp.info(), tmp.rowBytes(), tmp.getPixels(), image)) { return false; } CGImageAlphaInfo cgInfo = CGImageGetAlphaInfo(image); switch (cgInfo) { case kCGImageAlphaNone: case kCGImageAlphaNoneSkipLast: case kCGImageAlphaNoneSkipFirst: SkASSERT(SkBitmap::ComputeIsOpaque(tmp)); tmp.setAlphaType(kOpaque_SkAlphaType); break; default: // we don't know if we're opaque or not, so compute it. if (SkBitmap::ComputeIsOpaque(tmp)) { tmp.setAlphaType(kOpaque_SkAlphaType); } } *dst = tmp; return true; } sk_sp SkMakeImageFromCGImage(CGImageRef src) { SkBitmap bm; if (!SkCreateBitmapFromCGImage(&bm, src)) { return nullptr; } bm.setImmutable(); return bm.asImage(); } CGDataProviderRef SkCreateCGDataProvider(sk_sp data) { if (!data) { return nullptr; } CGDataProviderRef result = CGDataProviderCreateWithData( data.get(), data->data(), data->size(), [](void* info, const void*, size_t) { reinterpret_cast(info)->unref(); }); if (!result) { return nullptr; } // Retain `data` for the release that will come when `result` is freed. data->ref(); return result; } sk_sp SkMakeColorSpaceFromCGColorSpace(CGColorSpaceRef cgColorSpace) { if (!cgColorSpace) { return nullptr; } // Attempt to convert by name. SkUniqueCFRef name(CGColorSpaceCopyName(cgColorSpace)); if (name && CFStringCompare(name.get(), kCGColorSpaceSRGB, 0) == kCFCompareEqualTo) { return SkColorSpace::MakeSRGB(); } // Attempt to convert by parsing the ICC profile. SkUniqueCFRef iccData(CGColorSpaceCopyICCData(cgColorSpace)); if (!iccData) { return nullptr; } skcms_ICCProfile iccProfile; if (!skcms_Parse( CFDataGetBytePtr(iccData.get()), CFDataGetLength(iccData.get()), &iccProfile)) { return nullptr; } return SkColorSpace::Make(iccProfile); } CGColorSpaceRef SkCreateCGColorSpace(const SkColorSpace* space) { // Initialize result to sRGB. We will use this as the fallback on failure. CGColorSpaceRef cgSRGB = CGColorSpaceCreateWithName(kCGColorSpaceSRGB); // Early-out of this is sRGB (or nullptr defaulting to sRGB). if (!space || space->isSRGB()) { return cgSRGB; } // Create an SkData with the ICC profile. skcms_TransferFunction fn; skcms_Matrix3x3 to_xyzd50; space->transferFn(&fn); space->toXYZD50(&to_xyzd50); sk_sp iccData = SkWriteICCProfile(fn, to_xyzd50); if (!iccData) { return cgSRGB; } // Create a CGColorSpaceRef from that ICC data. const size_t kNumComponents = 3; const CGFloat kComponentRanges[6] = {0, 1, 0, 1, 0, 1}; SkUniqueCFRef iccDataProvider(SkCreateCGDataProvider(iccData)); CGColorSpaceRef result = CGColorSpaceCreateICCBased( kNumComponents, kComponentRanges, iccDataProvider.get(), cgSRGB); if (!result) { return cgSRGB; } // We will not be returning |cgSRGB|, so free it now. CFRelease(cgSRGB); cgSRGB = nullptr; return result; } #endif//defined(SK_BUILD_FOR_MAC) || defined(SK_BUILD_FOR_IOS)