1// Copyright (c) 2012 The Chromium Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5#include "skia/ext/bitmap_platform_device_mac.h" 6 7#import <ApplicationServices/ApplicationServices.h> 8#include <time.h> 9 10#include "base/mac/mac_util.h" 11#include "base/memory/ref_counted.h" 12#include "skia/ext/bitmap_platform_device.h" 13#include "skia/ext/platform_canvas.h" 14#include "skia/ext/skia_utils_mac.h" 15#include "third_party/skia/include/core/SkMatrix.h" 16#include "third_party/skia/include/core/SkRegion.h" 17#include "third_party/skia/include/core/SkTypes.h" 18#include "third_party/skia/include/core/SkUtils.h" 19 20namespace skia { 21 22namespace { 23 24static CGContextRef CGContextForData(void* data, int width, int height) { 25#define HAS_ARGB_SHIFTS(a, r, g, b) \ 26 (SK_A32_SHIFT == (a) && SK_R32_SHIFT == (r) \ 27 && SK_G32_SHIFT == (g) && SK_B32_SHIFT == (b)) 28#if defined(SK_CPU_LENDIAN) && HAS_ARGB_SHIFTS(24, 16, 8, 0) 29 // Allocate a bitmap context with 4 components per pixel (BGRA). Apple 30 // recommends these flags for improved CG performance. 31 32 // CGBitmapContextCreate returns NULL if width/height are 0. However, our 33 // callers expect to get a canvas back (which they later resize/reallocate) 34 // so we pin the dimensions here. 35 width = SkMax32(1, width); 36 height = SkMax32(1, height); 37 CGContextRef context = 38 CGBitmapContextCreate(data, width, height, 8, width * 4, 39 base::mac::GetSystemColorSpace(), 40 kCGImageAlphaPremultipliedFirst | 41 kCGBitmapByteOrder32Host); 42#else 43#error We require that Skia's and CoreGraphics's recommended \ 44 image memory layout match. 45#endif 46#undef HAS_ARGB_SHIFTS 47 48 if (!context) 49 return NULL; 50 51 // Change the coordinate system to match WebCore's 52 CGContextTranslateCTM(context, 0, height); 53 CGContextScaleCTM(context, 1.0, -1.0); 54 55 return context; 56} 57 58} // namespace 59 60void BitmapPlatformDevice::ReleaseBitmapContext() { 61 SkASSERT(bitmap_context_); 62 CGContextRelease(bitmap_context_); 63 bitmap_context_ = NULL; 64} 65 66void BitmapPlatformDevice::SetMatrixClip( 67 const SkMatrix& transform, 68 const SkRegion& region) { 69 transform_ = transform; 70 clip_region_ = region; 71 config_dirty_ = true; 72} 73 74void BitmapPlatformDevice::LoadConfig() { 75 if (!config_dirty_ || !bitmap_context_) 76 return; // Nothing to do. 77 config_dirty_ = false; 78 79 // We must restore and then save the state of the graphics context since the 80 // calls to Load the clipping region to the context are strictly cummulative, 81 // i.e., you can't replace a clip rect, other than with a save/restore. 82 // But this implies that no other changes to the state are done elsewhere. 83 // If we ever get to need to change this, then we must replace the clip rect 84 // calls in LoadClippingRegionToCGContext() with an image mask instead. 85 CGContextRestoreGState(bitmap_context_); 86 CGContextSaveGState(bitmap_context_); 87 LoadTransformToCGContext(bitmap_context_, transform_); 88 LoadClippingRegionToCGContext(bitmap_context_, clip_region_, transform_); 89} 90 91 92// We use this static factory function instead of the regular constructor so 93// that we can create the pixel data before calling the constructor. This is 94// required so that we can call the base class' constructor with the pixel 95// data. 96BitmapPlatformDevice* BitmapPlatformDevice::Create(CGContextRef context, 97 int width, 98 int height, 99 bool is_opaque) { 100 if (RasterDeviceTooBigToAllocate(width, height)) 101 return NULL; 102 103 SkBitmap bitmap; 104 // TODO: verify that the CG Context's pixels will have tight rowbytes or pass in the correct 105 // rowbytes for the case when context != NULL. 106 bitmap.setInfo(SkImageInfo::MakeN32(width, height, is_opaque ? kOpaque_SkAlphaType : kPremul_SkAlphaType)); 107 108 void* data; 109 if (context) { 110 data = CGBitmapContextGetData(context); 111 bitmap.setPixels(data); 112 } else { 113 if (!bitmap.tryAllocPixels()) 114 return NULL; 115 data = bitmap.getPixels(); 116 } 117 118 // If we were given data, then don't clobber it! 119#ifndef NDEBUG 120 if (!context && is_opaque) { 121 // To aid in finding bugs, we set the background color to something 122 // obviously wrong so it will be noticable when it is not cleared 123 bitmap.eraseARGB(255, 0, 255, 128); // bright bluish green 124 } 125#endif 126 127 if (!context) { 128 context = CGContextForData(data, width, height); 129 if (!context) 130 return NULL; 131 } else 132 CGContextRetain(context); 133 134 BitmapPlatformDevice* rv = new BitmapPlatformDevice(context, bitmap); 135 136 // The device object took ownership of the graphics context with its own 137 // CGContextRetain call. 138 CGContextRelease(context); 139 140 return rv; 141} 142 143BitmapPlatformDevice* BitmapPlatformDevice::CreateAndClear(int width, 144 int height, 145 bool is_opaque) { 146 BitmapPlatformDevice* device = Create(NULL, width, height, is_opaque); 147 if (!is_opaque) 148 device->clear(0); 149 return device; 150} 151 152BitmapPlatformDevice* BitmapPlatformDevice::CreateWithData(uint8_t* data, 153 int width, 154 int height, 155 bool is_opaque) { 156 CGContextRef context = NULL; 157 if (data) 158 context = CGContextForData(data, width, height); 159 160 BitmapPlatformDevice* rv = Create(context, width, height, is_opaque); 161 162 // The device object took ownership of the graphics context with its own 163 // CGContextRetain call. 164 if (context) 165 CGContextRelease(context); 166 167 return rv; 168} 169 170// The device will own the bitmap, which corresponds to also owning the pixel 171// data. Therefore, we do not transfer ownership to the SkBitmapDevice's bitmap. 172BitmapPlatformDevice::BitmapPlatformDevice( 173 CGContextRef context, const SkBitmap& bitmap) 174 : SkBitmapDevice(bitmap), 175 bitmap_context_(context), 176 config_dirty_(true), // Want to load the config next time. 177 transform_(SkMatrix::I()) { 178 SetPlatformDevice(this, this); 179 SkASSERT(bitmap_context_); 180 // Initialize the clip region to the entire bitmap. 181 182 SkIRect rect; 183 rect.set(0, 0, 184 CGBitmapContextGetWidth(bitmap_context_), 185 CGBitmapContextGetHeight(bitmap_context_)); 186 clip_region_ = SkRegion(rect); 187 CGContextRetain(bitmap_context_); 188 // We must save the state once so that we can use the restore/save trick 189 // in LoadConfig(). 190 CGContextSaveGState(bitmap_context_); 191} 192 193BitmapPlatformDevice::~BitmapPlatformDevice() { 194 if (bitmap_context_) 195 CGContextRelease(bitmap_context_); 196} 197 198CGContextRef BitmapPlatformDevice::GetBitmapContext() { 199 LoadConfig(); 200 return bitmap_context_; 201} 202 203void BitmapPlatformDevice::setMatrixClip(const SkMatrix& transform, 204 const SkRegion& region, 205 const SkClipStack&) { 206 SetMatrixClip(transform, region); 207} 208 209void BitmapPlatformDevice::DrawToNativeContext(CGContextRef context, int x, 210 int y, const CGRect* src_rect) { 211 bool created_dc = false; 212 if (!bitmap_context_) { 213 created_dc = true; 214 GetBitmapContext(); 215 } 216 217 // this should not make a copy of the bits, since we're not doing 218 // anything to trigger copy on write 219 CGImageRef image = CGBitmapContextCreateImage(bitmap_context_); 220 CGRect bounds; 221 bounds.origin.x = x; 222 bounds.origin.y = y; 223 if (src_rect) { 224 bounds.size.width = src_rect->size.width; 225 bounds.size.height = src_rect->size.height; 226 CGImageRef sub_image = CGImageCreateWithImageInRect(image, *src_rect); 227 CGContextDrawImage(context, bounds, sub_image); 228 CGImageRelease(sub_image); 229 } else { 230 bounds.size.width = width(); 231 bounds.size.height = height(); 232 CGContextDrawImage(context, bounds, image); 233 } 234 CGImageRelease(image); 235 236 if (created_dc) 237 ReleaseBitmapContext(); 238} 239 240SkBaseDevice* BitmapPlatformDevice::onCreateDevice(const SkImageInfo& info, 241 Usage /*usage*/) { 242 SkASSERT(info.colorType() == kN32_SkColorType); 243 return BitmapPlatformDevice::CreateAndClear(info.width(), info.height(), 244 info.isOpaque()); 245} 246 247// PlatformCanvas impl 248 249SkCanvas* CreatePlatformCanvas(CGContextRef ctx, int width, int height, 250 bool is_opaque, OnFailureType failureType) { 251 skia::RefPtr<SkBaseDevice> dev = skia::AdoptRef( 252 BitmapPlatformDevice::Create(ctx, width, height, is_opaque)); 253 return CreateCanvas(dev, failureType); 254} 255 256SkCanvas* CreatePlatformCanvas(int width, int height, bool is_opaque, 257 uint8_t* data, OnFailureType failureType) { 258 skia::RefPtr<SkBaseDevice> dev = skia::AdoptRef( 259 BitmapPlatformDevice::CreateWithData(data, width, height, is_opaque)); 260 return CreateCanvas(dev, failureType); 261} 262 263// Port of PlatformBitmap to mac 264 265PlatformBitmap::~PlatformBitmap() { 266 if (surface_) 267 CGContextRelease(surface_); 268} 269 270bool PlatformBitmap::Allocate(int width, int height, bool is_opaque) { 271 if (RasterDeviceTooBigToAllocate(width, height)) 272 return false; 273 274 if (!bitmap_.tryAllocN32Pixels(width, height, is_opaque)) 275 return false; 276 277 if (!is_opaque) 278 bitmap_.eraseColor(0); 279 280 surface_ = CGContextForData(bitmap_.getPixels(), bitmap_.width(), 281 bitmap_.height()); 282 return true; 283} 284 285} // namespace skia 286