| // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "skia/ext/bitmap_platform_device_mac.h" |
| |
| #import <ApplicationServices/ApplicationServices.h> |
| #include <time.h> |
| |
| #include "base/mac/mac_util.h" |
| #include "base/memory/ref_counted.h" |
| #include "skia/ext/bitmap_platform_device.h" |
| #include "skia/ext/platform_canvas.h" |
| #include "skia/ext/skia_utils_mac.h" |
| #include "third_party/skia/include/core/SkMatrix.h" |
| #include "third_party/skia/include/core/SkRegion.h" |
| #include "third_party/skia/include/core/SkTypes.h" |
| #include "third_party/skia/include/core/SkUtils.h" |
| |
| namespace skia { |
| |
| namespace { |
| |
| static CGContextRef CGContextForData(void* data, int width, int height) { |
| #define HAS_ARGB_SHIFTS(a, r, g, b) \ |
| (SK_A32_SHIFT == (a) && SK_R32_SHIFT == (r) \ |
| && SK_G32_SHIFT == (g) && SK_B32_SHIFT == (b)) |
| #if defined(SK_CPU_LENDIAN) && HAS_ARGB_SHIFTS(24, 16, 8, 0) |
| // Allocate a bitmap context with 4 components per pixel (BGRA). Apple |
| // recommends these flags for improved CG performance. |
| |
| // CGBitmapContextCreate returns NULL if width/height are 0. However, our |
| // callers expect to get a canvas back (which they later resize/reallocate) |
| // so we pin the dimensions here. |
| width = SkMax32(1, width); |
| height = SkMax32(1, height); |
| CGContextRef context = |
| CGBitmapContextCreate(data, width, height, 8, width * 4, |
| base::mac::GetSystemColorSpace(), |
| kCGImageAlphaPremultipliedFirst | |
| kCGBitmapByteOrder32Host); |
| #else |
| #error We require that Skia's and CoreGraphics's recommended \ |
| image memory layout match. |
| #endif |
| #undef HAS_ARGB_SHIFTS |
| |
| if (!context) |
| return NULL; |
| |
| // Change the coordinate system to match WebCore's |
| CGContextTranslateCTM(context, 0, height); |
| CGContextScaleCTM(context, 1.0, -1.0); |
| |
| return context; |
| } |
| |
| } // namespace |
| |
| void BitmapPlatformDevice::ReleaseBitmapContext() { |
| SkASSERT(bitmap_context_); |
| CGContextRelease(bitmap_context_); |
| bitmap_context_ = NULL; |
| } |
| |
| void BitmapPlatformDevice::SetMatrixClip( |
| const SkMatrix& transform, |
| const SkRegion& region) { |
| transform_ = transform; |
| clip_region_ = region; |
| config_dirty_ = true; |
| } |
| |
| void BitmapPlatformDevice::LoadConfig() { |
| if (!config_dirty_ || !bitmap_context_) |
| return; // Nothing to do. |
| config_dirty_ = false; |
| |
| // We must restore and then save the state of the graphics context since the |
| // calls to Load the clipping region to the context are strictly cummulative, |
| // i.e., you can't replace a clip rect, other than with a save/restore. |
| // But this implies that no other changes to the state are done elsewhere. |
| // If we ever get to need to change this, then we must replace the clip rect |
| // calls in LoadClippingRegionToCGContext() with an image mask instead. |
| CGContextRestoreGState(bitmap_context_); |
| CGContextSaveGState(bitmap_context_); |
| LoadTransformToCGContext(bitmap_context_, transform_); |
| LoadClippingRegionToCGContext(bitmap_context_, clip_region_, transform_); |
| } |
| |
| |
| // We use this static factory function instead of the regular constructor so |
| // that we can create the pixel data before calling the constructor. This is |
| // required so that we can call the base class' constructor with the pixel |
| // data. |
| BitmapPlatformDevice* BitmapPlatformDevice::Create(CGContextRef context, |
| int width, |
| int height, |
| bool is_opaque) { |
| if (RasterDeviceTooBigToAllocate(width, height)) |
| return NULL; |
| |
| SkBitmap bitmap; |
| // TODO: verify that the CG Context's pixels will have tight rowbytes or pass in the correct |
| // rowbytes for the case when context != NULL. |
| bitmap.setInfo(SkImageInfo::MakeN32(width, height, is_opaque ? kOpaque_SkAlphaType : kPremul_SkAlphaType)); |
| |
| void* data; |
| if (context) { |
| data = CGBitmapContextGetData(context); |
| bitmap.setPixels(data); |
| } else { |
| if (!bitmap.tryAllocPixels()) |
| return NULL; |
| data = bitmap.getPixels(); |
| } |
| |
| // If we were given data, then don't clobber it! |
| #ifndef NDEBUG |
| if (!context && is_opaque) { |
| // To aid in finding bugs, we set the background color to something |
| // obviously wrong so it will be noticable when it is not cleared |
| bitmap.eraseARGB(255, 0, 255, 128); // bright bluish green |
| } |
| #endif |
| |
| if (!context) { |
| context = CGContextForData(data, width, height); |
| if (!context) |
| return NULL; |
| } else |
| CGContextRetain(context); |
| |
| BitmapPlatformDevice* rv = new BitmapPlatformDevice(context, bitmap); |
| |
| // The device object took ownership of the graphics context with its own |
| // CGContextRetain call. |
| CGContextRelease(context); |
| |
| return rv; |
| } |
| |
| BitmapPlatformDevice* BitmapPlatformDevice::CreateAndClear(int width, |
| int height, |
| bool is_opaque) { |
| BitmapPlatformDevice* device = Create(NULL, width, height, is_opaque); |
| if (!is_opaque) |
| device->clear(0); |
| return device; |
| } |
| |
| BitmapPlatformDevice* BitmapPlatformDevice::CreateWithData(uint8_t* data, |
| int width, |
| int height, |
| bool is_opaque) { |
| CGContextRef context = NULL; |
| if (data) |
| context = CGContextForData(data, width, height); |
| |
| BitmapPlatformDevice* rv = Create(context, width, height, is_opaque); |
| |
| // The device object took ownership of the graphics context with its own |
| // CGContextRetain call. |
| if (context) |
| CGContextRelease(context); |
| |
| return rv; |
| } |
| |
| // The device will own the bitmap, which corresponds to also owning the pixel |
| // data. Therefore, we do not transfer ownership to the SkBitmapDevice's bitmap. |
| BitmapPlatformDevice::BitmapPlatformDevice( |
| CGContextRef context, const SkBitmap& bitmap) |
| : SkBitmapDevice(bitmap), |
| bitmap_context_(context), |
| config_dirty_(true), // Want to load the config next time. |
| transform_(SkMatrix::I()) { |
| SetPlatformDevice(this, this); |
| SkASSERT(bitmap_context_); |
| // Initialize the clip region to the entire bitmap. |
| |
| SkIRect rect; |
| rect.set(0, 0, |
| CGBitmapContextGetWidth(bitmap_context_), |
| CGBitmapContextGetHeight(bitmap_context_)); |
| clip_region_ = SkRegion(rect); |
| CGContextRetain(bitmap_context_); |
| // We must save the state once so that we can use the restore/save trick |
| // in LoadConfig(). |
| CGContextSaveGState(bitmap_context_); |
| } |
| |
| BitmapPlatformDevice::~BitmapPlatformDevice() { |
| if (bitmap_context_) |
| CGContextRelease(bitmap_context_); |
| } |
| |
| CGContextRef BitmapPlatformDevice::GetBitmapContext() { |
| LoadConfig(); |
| return bitmap_context_; |
| } |
| |
| void BitmapPlatformDevice::setMatrixClip(const SkMatrix& transform, |
| const SkRegion& region, |
| const SkClipStack&) { |
| SetMatrixClip(transform, region); |
| } |
| |
| void BitmapPlatformDevice::DrawToNativeContext(CGContextRef context, int x, |
| int y, const CGRect* src_rect) { |
| bool created_dc = false; |
| if (!bitmap_context_) { |
| created_dc = true; |
| GetBitmapContext(); |
| } |
| |
| // this should not make a copy of the bits, since we're not doing |
| // anything to trigger copy on write |
| CGImageRef image = CGBitmapContextCreateImage(bitmap_context_); |
| CGRect bounds; |
| bounds.origin.x = x; |
| bounds.origin.y = y; |
| if (src_rect) { |
| bounds.size.width = src_rect->size.width; |
| bounds.size.height = src_rect->size.height; |
| CGImageRef sub_image = CGImageCreateWithImageInRect(image, *src_rect); |
| CGContextDrawImage(context, bounds, sub_image); |
| CGImageRelease(sub_image); |
| } else { |
| bounds.size.width = width(); |
| bounds.size.height = height(); |
| CGContextDrawImage(context, bounds, image); |
| } |
| CGImageRelease(image); |
| |
| if (created_dc) |
| ReleaseBitmapContext(); |
| } |
| |
| SkBaseDevice* BitmapPlatformDevice::onCreateDevice(const SkImageInfo& info, |
| Usage /*usage*/) { |
| SkASSERT(info.colorType() == kN32_SkColorType); |
| return BitmapPlatformDevice::CreateAndClear(info.width(), info.height(), |
| info.isOpaque()); |
| } |
| |
| // PlatformCanvas impl |
| |
| SkCanvas* CreatePlatformCanvas(CGContextRef ctx, int width, int height, |
| bool is_opaque, OnFailureType failureType) { |
| skia::RefPtr<SkBaseDevice> dev = skia::AdoptRef( |
| BitmapPlatformDevice::Create(ctx, width, height, is_opaque)); |
| return CreateCanvas(dev, failureType); |
| } |
| |
| SkCanvas* CreatePlatformCanvas(int width, int height, bool is_opaque, |
| uint8_t* data, OnFailureType failureType) { |
| skia::RefPtr<SkBaseDevice> dev = skia::AdoptRef( |
| BitmapPlatformDevice::CreateWithData(data, width, height, is_opaque)); |
| return CreateCanvas(dev, failureType); |
| } |
| |
| // Port of PlatformBitmap to mac |
| |
| PlatformBitmap::~PlatformBitmap() { |
| if (surface_) |
| CGContextRelease(surface_); |
| } |
| |
| bool PlatformBitmap::Allocate(int width, int height, bool is_opaque) { |
| if (RasterDeviceTooBigToAllocate(width, height)) |
| return false; |
| |
| if (!bitmap_.tryAllocN32Pixels(width, height, is_opaque)) |
| return false; |
| |
| if (!is_opaque) |
| bitmap_.eraseColor(0); |
| |
| surface_ = CGContextForData(bitmap_.getPixels(), bitmap_.width(), |
| bitmap_.height()); |
| return true; |
| } |
| |
| } // namespace skia |