/*
* 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 "SkTypes.h"
#if defined(SK_BUILD_FOR_MAC) || defined(SK_BUILD_FOR_IOS)
#include "SkCGUtils.h"
#include "SkBitmap.h"
#include "SkColorData.h"
static CGBitmapInfo ComputeCGAlphaInfo_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 ComputeCGAlphaInfo_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 void SkBitmap_ReleaseInfo(void* info, const void* pixelData, size_t size) {
SkBitmap* bitmap = reinterpret_cast<SkBitmap*>(info);
delete bitmap;
}
static bool getBitmapInfo(const SkBitmap& bm,
size_t* bitsPerComponent,
CGBitmapInfo* info,
bool* upscaleTo32) {
if (upscaleTo32) {
*upscaleTo32 = false;
}
switch (bm.colorType()) {
case kRGB_565_SkColorType:
#if 0
// doesn't see quite right. Are they thinking 1555?
*bitsPerComponent = 5;
*info = kCGBitmapByteOrder16Little | kCGImageAlphaNone;
#else
if (upscaleTo32) {
*upscaleTo32 = true;
}
// now treat like RGBA
*bitsPerComponent = 8;
*info = ComputeCGAlphaInfo_RGBA(kOpaque_SkAlphaType);
#endif
break;
case kRGBA_8888_SkColorType:
*bitsPerComponent = 8;
*info = ComputeCGAlphaInfo_RGBA(bm.alphaType());
break;
case kBGRA_8888_SkColorType:
*bitsPerComponent = 8;
*info = ComputeCGAlphaInfo_BGRA(bm.alphaType());
break;
case kARGB_4444_SkColorType:
*bitsPerComponent = 4;
*info = kCGBitmapByteOrder16Little;
if (bm.isOpaque()) {
*info |= kCGImageAlphaNoneSkipLast;
} else {
*info |= kCGImageAlphaPremultipliedLast;
}
break;
default:
return false;
}
return true;
}
static SkBitmap* prepareForImageRef(const SkBitmap& bm,
size_t* bitsPerComponent,
CGBitmapInfo* info) {
bool upscaleTo32;
if (!getBitmapInfo(bm, bitsPerComponent, info, &upscaleTo32)) {
return nullptr;
}
SkBitmap* copy;
if (upscaleTo32) {
copy = new SkBitmap;
// here we make a deep copy of the pixels, since CG won't take our
// 565 directly
copy->allocPixels(bm.info().makeColorType(kN32_SkColorType));
bm.readPixels(copy->info(), copy->getPixels(), copy->rowBytes(), 0, 0);
} else {
copy = new SkBitmap(bm);
}
return copy;
}
CGImageRef SkCreateCGImageRefWithColorspace(const SkBitmap& bm,
CGColorSpaceRef colorSpace) {
size_t bitsPerComponent SK_INIT_TO_AVOID_WARNING;
CGBitmapInfo info SK_INIT_TO_AVOID_WARNING;
SkBitmap* bitmap = prepareForImageRef(bm, &bitsPerComponent, &info);
if (nullptr == bitmap) {
return nullptr;
}
const int w = bitmap->width();
const int h = bitmap->height();
const size_t s = bitmap->computeByteSize();
// our provider "owns" the bitmap*, and will take care of deleting it
CGDataProviderRef dataRef = CGDataProviderCreateWithData(bitmap, bitmap->getPixels(), s,
SkBitmap_ReleaseInfo);
bool releaseColorSpace = false;
if (nullptr == colorSpace) {
colorSpace = CGColorSpaceCreateDeviceRGB();
releaseColorSpace = true;
}
CGImageRef ref = CGImageCreate(w, h, bitsPerComponent,
bitmap->bytesPerPixel() * 8,
bitmap->rowBytes(), colorSpace, info, dataRef,
nullptr, false, kCGRenderingIntentDefault);
if (releaseColorSpace) {
CGColorSpaceRelease(colorSpace);
}
CGDataProviderRelease(dataRef);
return ref;
}
void SkCGDrawBitmap(CGContextRef cg, const SkBitmap& bm, float x, float y) {
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);
CGContextRestoreGState(cg);
CGImageRelease(img);
}
}
///////////////////////////////////////////////////////////////////////////////////////////////////
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 = ComputeCGAlphaInfo_RGBA(pmap.alphaType());
break;
case kBGRA_8888_SkColorType:
bitsPerComponent = 8;
cg_bitmap_info = ComputeCGAlphaInfo_BGRA(pmap.alphaType());
break;
default:
return nullptr; // no other colortypes are supported (for now)
}
size_t rb = pmap.addr() ? pmap.rowBytes() : 0;
CGColorSpaceRef cs = CGColorSpaceCreateDeviceRGB();
CGContextRef cg = CGBitmapContextCreate(pmap.writable_addr(), pmap.width(), pmap.height(),
bitsPerComponent, rb, cs, cg_bitmap_info);
CFRelease(cs);
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 = ComputeCGAlphaInfo_RGBA(info.alphaType());
break;
case kBGRA_8888_SkColorType:
bitsPerComponent = 8;
cg_bitmap_info = ComputeCGAlphaInfo_BGRA(info.alphaType());
break;
default:
return false; // no other colortypes are supported (for now)
}
CGColorSpaceRef cs = CGColorSpaceCreateDeviceRGB();
CGContextRef cg = CGBitmapContextCreate(pixels, info.width(), info.height(), bitsPerComponent,
rowBytes, cs, cg_bitmap_info);
CFRelease(cs);
if (nullptr == 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, kCGBlendModeCopy);
CGContextDrawImage(cg, CGRectMake(0, 0, info.width(), info.height()), image);
CGContextRelease(cg);
return true;
}
bool SkCreateBitmapFromCGImage(SkBitmap* dst, CGImageRef image) {
const int width = SkToInt(CGImageGetWidth(image));
const int height = SkToInt(CGImageGetHeight(image));
SkImageInfo info = SkImageInfo::MakeN32Premul(width, height);
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 SkImage::MakeFromBitmap(bm);
}
#endif//defined(SK_BUILD_FOR_MAC) || defined(SK_BUILD_FOR_IOS)