/*
* 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 "SkCGUtils.h"
#include "SkBitmap.h"
#include "SkColorPriv.h"
static void SkBitmap_ReleaseInfo(void* info, const void* pixelData, size_t size) {
SkBitmap* bitmap = reinterpret_cast<SkBitmap*>(info);
delete bitmap;
}
#define HAS_ARGB_SHIFTS(a, r, g, b) \
(SK_A32_SHIFT == (a) && SK_R32_SHIFT == (r) \
&& SK_G32_SHIFT == (g) && SK_B32_SHIFT == (b))
static bool getBitmapInfo(const SkBitmap& bm,
size_t* bitsPerComponent,
CGBitmapInfo* info,
bool* upscaleTo32) {
if (upscaleTo32) {
*upscaleTo32 = false;
}
switch (bm.config()) {
case SkBitmap::kRGB_565_Config:
if (upscaleTo32) {
*upscaleTo32 = true;
}
// fall through
case SkBitmap::kARGB_8888_Config:
*bitsPerComponent = 8;
#if defined(SK_CPU_LENDIAN) && HAS_ARGB_SHIFTS(24, 0, 8, 16) \
|| defined(SK_CPU_BENDIAN) && HAS_ARGB_SHIFTS(0, 24, 16, 8)
*info = kCGBitmapByteOrder32Big;
if (bm.isOpaque()) {
*info |= kCGImageAlphaNoneSkipLast;
} else {
*info |= kCGImageAlphaPremultipliedLast;
}
#elif defined(SK_CPU_LENDIAN) && HAS_ARGB_SHIFTS(24, 16, 8, 0) \
|| defined(SK_CPU_BENDIAN) && HAS_ARGB_SHIFTS(24, 16, 8, 0)
// Matches the CGBitmapInfo that Apple recommends for best
// performance, used by google chrome.
*info = kCGBitmapByteOrder32Little;
if (bm.isOpaque()) {
*info |= kCGImageAlphaNoneSkipFirst;
} else {
*info |= kCGImageAlphaPremultipliedFirst;
}
#else
// ...add more formats as required...
#warning Cannot convert SkBitmap to CGImageRef with these shiftmasks. \
This will probably not work.
// Legacy behavior. Perhaps turn this into an error at some
// point.
*info = kCGBitmapByteOrder32Big;
if (bm.isOpaque()) {
*info |= kCGImageAlphaNoneSkipLast;
} else {
*info |= kCGImageAlphaPremultipliedLast;
}
#endif
break;
#if 0
case SkBitmap::kRGB_565_Config:
// doesn't see quite right. Are they thinking 1555?
*bitsPerComponent = 5;
*info = kCGBitmapByteOrder16Little | kCGImageAlphaNone;
break;
#endif
case SkBitmap::kARGB_4444_Config:
*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 NULL;
}
SkBitmap* copy;
if (upscaleTo32) {
copy = new SkBitmap;
// here we make a ceep copy of the pixels, since CG won't take our
// 565 directly
bm.copyTo(copy, SkBitmap::kARGB_8888_Config);
} else {
copy = new SkBitmap(bm);
}
return copy;
}
#undef HAS_ARGB_SHIFTS
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 (NULL == bitmap) {
return NULL;
}
const int w = bitmap->width();
const int h = bitmap->height();
const size_t s = bitmap->getSize();
// our provider "owns" the bitmap*, and will take care of deleting it
// we initially lock it, so we can access the pixels. The bitmap will be deleted in the release
// proc, which will in turn unlock the pixels
bitmap->lockPixels();
CGDataProviderRef dataRef = CGDataProviderCreateWithData(bitmap, bitmap->getPixels(), s,
SkBitmap_ReleaseInfo);
bool releaseColorSpace = false;
if (NULL == colorSpace) {
colorSpace = CGColorSpaceCreateDeviceRGB();
releaseColorSpace = true;
}
CGImageRef ref = CGImageCreate(w, h, bitsPerComponent,
bitmap->bytesPerPixel() * 8,
bitmap->rowBytes(), colorSpace, info, dataRef,
NULL, 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);
}
}
///////////////////////////////////////////////////////////////////////////////
#include "SkStream.h"
class SkAutoPDFRelease {
public:
SkAutoPDFRelease(CGPDFDocumentRef doc) : fDoc(doc) {}
~SkAutoPDFRelease() {
if (fDoc) {
CGPDFDocumentRelease(fDoc);
}
}
private:
CGPDFDocumentRef fDoc;
};
static void CGDataProviderReleaseData_FromMalloc(void*, const void* data,
size_t size) {
sk_free((void*)data);
}
bool SkPDFDocumentToBitmap(SkStream* stream, SkBitmap* output) {
size_t size = stream->getLength();
void* ptr = sk_malloc_throw(size);
stream->read(ptr, size);
CGDataProviderRef data = CGDataProviderCreateWithData(NULL, ptr, size,
CGDataProviderReleaseData_FromMalloc);
if (NULL == data) {
return false;
}
CGPDFDocumentRef pdf = CGPDFDocumentCreateWithProvider(data);
CGDataProviderRelease(data);
if (NULL == pdf) {
return false;
}
SkAutoPDFRelease releaseMe(pdf);
CGPDFPageRef page = CGPDFDocumentGetPage(pdf, 1);
if (NULL == page) {
return false;
}
CGRect bounds = CGPDFPageGetBoxRect(page, kCGPDFMediaBox);
int w = (int)CGRectGetWidth(bounds);
int h = (int)CGRectGetHeight(bounds);
SkBitmap bitmap;
bitmap.setConfig(SkBitmap::kARGB_8888_Config, w, h);
bitmap.allocPixels();
bitmap.eraseColor(SK_ColorWHITE);
size_t bitsPerComponent;
CGBitmapInfo info;
getBitmapInfo(bitmap, &bitsPerComponent, &info, NULL);
CGColorSpaceRef cs = CGColorSpaceCreateDeviceRGB();
CGContextRef ctx = CGBitmapContextCreate(bitmap.getPixels(), w, h,
bitsPerComponent, bitmap.rowBytes(),
cs, info);
CGColorSpaceRelease(cs);
if (ctx) {
CGContextDrawPDFPage(ctx, page);
CGContextRelease(ctx);
}
output->swap(bitmap);
return true;
}