blob: 903751426ff58086a2d4b6727031fbd8942c5751 [file] [log] [blame]
/*
* 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 <climits>
#include <memory>
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<SkBitmap> 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<SkBitmap> 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<SkBitmap>(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<SkBitmap> 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<CGDataProviderRef> dataRef(CGDataProviderCreateWithData(
bitmap.release(), pixels, s,
[](void* p, const void*, size_t) { delete reinterpret_cast<SkBitmap*>(p); }));
SkUniqueCFRef<CGColorSpaceRef> 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<CGImageRef> 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<CGColorSpaceRef> 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<CGColorSpaceRef> cs(SkCreateCGColorSpace(info.colorSpace()));
SkUniqueCFRef<CGContextRef> 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<SkColorSpace> 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<SkImage> SkMakeImageFromCGImage(CGImageRef src) {
SkBitmap bm;
if (!SkCreateBitmapFromCGImage(&bm, src)) {
return nullptr;
}
bm.setImmutable();
return bm.asImage();
}
CGDataProviderRef SkCreateCGDataProvider(sk_sp<SkData> data) {
if (!data) {
return nullptr;
}
CGDataProviderRef result = CGDataProviderCreateWithData(
data.get(), data->data(), data->size(), [](void* info, const void*, size_t) {
reinterpret_cast<SkData*>(info)->unref();
});
if (!result) {
return nullptr;
}
// Retain `data` for the release that will come when `result` is freed.
data->ref();
return result;
}
sk_sp<SkColorSpace> SkMakeColorSpaceFromCGColorSpace(CGColorSpaceRef cgColorSpace) {
if (!cgColorSpace) {
return nullptr;
}
// Attempt to convert by name.
SkUniqueCFRef<CFStringRef> name(CGColorSpaceCopyName(cgColorSpace));
if (name && CFStringCompare(name.get(), kCGColorSpaceSRGB, 0) == kCFCompareEqualTo) {
return SkColorSpace::MakeSRGB();
}
// Attempt to convert by parsing the ICC profile.
SkUniqueCFRef<CFDataRef> 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<SkData> 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<CGDataProviderRef> 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)