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.
5 #include "skia/ext/bitmap_platform_device_mac.h"
7 #import <ApplicationServices/ApplicationServices.h>
10 #include "base/mac/mac_util.h"
11 #include "base/memory/ref_counted.h"
12 #include "skia/ext/bitmap_platform_device_data.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"
24 static 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.
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);
43 #error We require that Skia's and CoreGraphics's recommended \
44 image memory layout match.
46 #undef HAS_ARGB_SHIFTS
51 // Change the coordinate system to match WebCore's
52 CGContextTranslateCTM(context, 0, height);
53 CGContextScaleCTM(context, 1.0, -1.0);
60 BitmapPlatformDevice::BitmapPlatformDeviceData::BitmapPlatformDeviceData(
62 : bitmap_context_(bitmap),
63 config_dirty_(true), // Want to load the config next time.
64 transform_(SkMatrix::I()) {
65 SkASSERT(bitmap_context_);
66 // Initialize the clip region to the entire bitmap.
70 CGBitmapContextGetWidth(bitmap_context_),
71 CGBitmapContextGetHeight(bitmap_context_));
72 clip_region_ = SkRegion(rect);
73 CGContextRetain(bitmap_context_);
74 // We must save the state once so that we can use the restore/save trick
76 CGContextSaveGState(bitmap_context_);
79 BitmapPlatformDevice::BitmapPlatformDeviceData::~BitmapPlatformDeviceData() {
81 CGContextRelease(bitmap_context_);
84 void BitmapPlatformDevice::BitmapPlatformDeviceData::ReleaseBitmapContext() {
85 SkASSERT(bitmap_context_);
86 CGContextRelease(bitmap_context_);
87 bitmap_context_ = NULL;
90 void BitmapPlatformDevice::BitmapPlatformDeviceData::SetMatrixClip(
91 const SkMatrix& transform,
92 const SkRegion& region) {
93 transform_ = transform;
94 clip_region_ = region;
98 void BitmapPlatformDevice::BitmapPlatformDeviceData::LoadConfig() {
99 if (!config_dirty_ || !bitmap_context_)
100 return; // Nothing to do.
101 config_dirty_ = false;
103 // We must restore and then save the state of the graphics context since the
104 // calls to Load the clipping region to the context are strictly cummulative,
105 // i.e., you can't replace a clip rect, other than with a save/restore.
106 // But this implies that no other changes to the state are done elsewhere.
107 // If we ever get to need to change this, then we must replace the clip rect
108 // calls in LoadClippingRegionToCGContext() with an image mask instead.
109 CGContextRestoreGState(bitmap_context_);
110 CGContextSaveGState(bitmap_context_);
111 LoadTransformToCGContext(bitmap_context_, transform_);
112 LoadClippingRegionToCGContext(bitmap_context_, clip_region_, transform_);
116 // We use this static factory function instead of the regular constructor so
117 // that we can create the pixel data before calling the constructor. This is
118 // required so that we can call the base class' constructor with the pixel
120 BitmapPlatformDevice* BitmapPlatformDevice::Create(CGContextRef context,
124 if (RasterDeviceTooBigToAllocate(width, height))
128 // TODO: verify that the CG Context's pixels will have tight rowbytes or pass in the correct
129 // rowbytes for the case when context != NULL.
130 bitmap.setConfig(SkBitmap::kARGB_8888_Config, width, height, 0,
131 is_opaque ? kOpaque_SkAlphaType : kPremul_SkAlphaType);
135 data = CGBitmapContextGetData(context);
136 bitmap.setPixels(data);
138 if (!bitmap.allocPixels())
140 data = bitmap.getPixels();
143 // If we were given data, then don't clobber it!
145 if (!context && is_opaque) {
146 // To aid in finding bugs, we set the background color to something
147 // obviously wrong so it will be noticable when it is not cleared
148 bitmap.eraseARGB(255, 0, 255, 128); // bright bluish green
153 context = CGContextForData(data, width, height);
157 CGContextRetain(context);
159 BitmapPlatformDevice* rv = new BitmapPlatformDevice(
160 skia::AdoptRef(new BitmapPlatformDeviceData(context)), bitmap);
162 // The device object took ownership of the graphics context with its own
163 // CGContextRetain call.
164 CGContextRelease(context);
169 BitmapPlatformDevice* BitmapPlatformDevice::CreateAndClear(int width,
172 BitmapPlatformDevice* device = Create(NULL, width, height, is_opaque);
174 device->accessBitmap(true).eraseARGB(0, 0, 0, 0);
178 BitmapPlatformDevice* BitmapPlatformDevice::CreateWithData(uint8_t* data,
182 CGContextRef context = NULL;
184 context = CGContextForData(data, width, height);
186 BitmapPlatformDevice* rv = Create(context, width, height, is_opaque);
188 // The device object took ownership of the graphics context with its own
189 // CGContextRetain call.
191 CGContextRelease(context);
196 // The device will own the bitmap, which corresponds to also owning the pixel
197 // data. Therefore, we do not transfer ownership to the SkBitmapDevice's bitmap.
198 BitmapPlatformDevice::BitmapPlatformDevice(
199 const skia::RefPtr<BitmapPlatformDeviceData>& data, const SkBitmap& bitmap)
200 : SkBitmapDevice(bitmap),
202 SetPlatformDevice(this, this);
205 BitmapPlatformDevice::~BitmapPlatformDevice() {
208 CGContextRef BitmapPlatformDevice::GetBitmapContext() {
210 return data_->bitmap_context();
213 void BitmapPlatformDevice::setMatrixClip(const SkMatrix& transform,
214 const SkRegion& region,
215 const SkClipStack&) {
216 data_->SetMatrixClip(transform, region);
219 void BitmapPlatformDevice::DrawToNativeContext(CGContextRef context, int x,
220 int y, const CGRect* src_rect) {
221 bool created_dc = false;
222 if (!data_->bitmap_context()) {
227 // this should not make a copy of the bits, since we're not doing
228 // anything to trigger copy on write
229 CGImageRef image = CGBitmapContextCreateImage(data_->bitmap_context());
234 bounds.size.width = src_rect->size.width;
235 bounds.size.height = src_rect->size.height;
236 CGImageRef sub_image = CGImageCreateWithImageInRect(image, *src_rect);
237 CGContextDrawImage(context, bounds, sub_image);
238 CGImageRelease(sub_image);
240 bounds.size.width = width();
241 bounds.size.height = height();
242 CGContextDrawImage(context, bounds, image);
244 CGImageRelease(image);
247 data_->ReleaseBitmapContext();
250 SkBaseDevice* BitmapPlatformDevice::onCreateCompatibleDevice(
251 SkBitmap::Config config, int width, int height, bool isOpaque,
253 SkASSERT(config == SkBitmap::kARGB_8888_Config);
254 SkBaseDevice* bitmap_device = BitmapPlatformDevice::CreateAndClear(width,
257 return bitmap_device;
260 // PlatformCanvas impl
262 SkCanvas* CreatePlatformCanvas(CGContextRef ctx, int width, int height,
263 bool is_opaque, OnFailureType failureType) {
264 skia::RefPtr<SkBaseDevice> dev = skia::AdoptRef(
265 BitmapPlatformDevice::Create(ctx, width, height, is_opaque));
266 return CreateCanvas(dev, failureType);
269 SkCanvas* CreatePlatformCanvas(int width, int height, bool is_opaque,
270 uint8_t* data, OnFailureType failureType) {
271 skia::RefPtr<SkBaseDevice> dev = skia::AdoptRef(
272 BitmapPlatformDevice::CreateWithData(data, width, height, is_opaque));
273 return CreateCanvas(dev, failureType);
276 // Port of PlatformBitmap to mac
278 PlatformBitmap::~PlatformBitmap() {
280 CGContextRelease(surface_);
283 bool PlatformBitmap::Allocate(int width, int height, bool is_opaque) {
284 if (RasterDeviceTooBigToAllocate(width, height))
287 bitmap_.setConfig(SkBitmap::kARGB_8888_Config, width, height, width * 4,
288 is_opaque ? kOpaque_SkAlphaType : kPremul_SkAlphaType);
289 if (!bitmap_.allocPixels())
293 bitmap_.eraseColor(0);
295 surface_ = CGContextForData(bitmap_.getPixels(), bitmap_.width(),