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.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/SkPath.h"
17 #include "third_party/skia/include/core/SkRegion.h"
18 #include "third_party/skia/include/core/SkTypes.h"
19 #include "third_party/skia/include/core/SkUtils.h"
25 static CGContextRef
CGContextForData(void* data
, int width
, int height
) {
26 #define HAS_ARGB_SHIFTS(a, r, g, b) \
27 (SK_A32_SHIFT == (a) && SK_R32_SHIFT == (r) \
28 && SK_G32_SHIFT == (g) && SK_B32_SHIFT == (b))
29 #if defined(SK_CPU_LENDIAN) && HAS_ARGB_SHIFTS(24, 16, 8, 0)
30 // Allocate a bitmap context with 4 components per pixel (BGRA). Apple
31 // recommends these flags for improved CG performance.
33 // CGBitmapContextCreate returns NULL if width/height are 0. However, our
34 // callers expect to get a canvas back (which they later resize/reallocate)
35 // so we pin the dimensions here.
36 width
= SkMax32(1, width
);
37 height
= SkMax32(1, height
);
38 CGContextRef context
=
39 CGBitmapContextCreate(data
, width
, height
, 8, width
* 4,
40 base::mac::GetSystemColorSpace(),
41 kCGImageAlphaPremultipliedFirst
|
42 kCGBitmapByteOrder32Host
);
44 #error We require that Skia's and CoreGraphics's recommended \
45 image memory layout match.
47 #undef HAS_ARGB_SHIFTS
52 // Change the coordinate system to match WebCore's
53 CGContextTranslateCTM(context
, 0, height
);
54 CGContextScaleCTM(context
, 1.0, -1.0);
61 void BitmapPlatformDevice::ReleaseBitmapContext() {
62 SkASSERT(bitmap_context_
);
63 CGContextRelease(bitmap_context_
);
64 bitmap_context_
= NULL
;
67 void BitmapPlatformDevice::SetMatrixClip(
68 const SkMatrix
& transform
,
69 const SkRegion
& region
) {
70 transform_
= transform
;
71 clip_region_
= region
;
75 // Loads the specified Skia transform into the device context
76 static void LoadTransformToCGContext(CGContextRef context
,
77 const SkMatrix
& matrix
) {
78 // CoreGraphics can concatenate transforms, but not reset the current one.
79 // So in order to get the required behavior here, we need to first make
80 // the current transformation matrix identity and only then load the new one.
82 // Reset matrix to identity.
83 CGAffineTransform orig_cg_matrix
= CGContextGetCTM(context
);
84 CGAffineTransform orig_cg_matrix_inv
=
85 CGAffineTransformInvert(orig_cg_matrix
);
86 CGContextConcatCTM(context
, orig_cg_matrix_inv
);
88 // assert that we have indeed returned to the identity Matrix.
89 SkASSERT(CGAffineTransformIsIdentity(CGContextGetCTM(context
)));
91 // Convert xform to CG-land.
92 // Our coordinate system is flipped to match WebKit's so we need to modify
93 // the xform to match that.
94 SkMatrix transformed_matrix
= matrix
;
95 SkScalar sy
= -matrix
.getScaleY();
96 transformed_matrix
.setScaleY(sy
);
97 size_t height
= CGBitmapContextGetHeight(context
);
98 SkScalar ty
= -matrix
.getTranslateY(); // y axis is flipped.
99 transformed_matrix
.setTranslateY(ty
+ (SkScalar
)height
);
101 CGAffineTransform cg_matrix
=
102 gfx::SkMatrixToCGAffineTransform(transformed_matrix
);
104 // Load final transform into context.
105 CGContextConcatCTM(context
, cg_matrix
);
108 // Loads a SkRegion into the CG context.
109 static void LoadClippingRegionToCGContext(CGContextRef context
,
110 const SkRegion
& region
,
111 const SkMatrix
& transformation
) {
112 if (region
.isEmpty()) {
113 // region can be empty, in which case everything will be clipped.
116 CGContextClipToRect(context
, gfx::SkRectToCGRect(rect
));
117 } else if (region
.isRect()) {
118 // CoreGraphics applies the current transform to clip rects, which is
119 // unwanted. Inverse-transform the rect before sending it to CG. This only
120 // works for translations and scaling, but not for rotations (but the
121 // viewport is never rotated anyway).
123 bool did_invert
= transformation
.invert(&t
);
126 // Do the transformation.
128 rect
.set(region
.getBounds());
132 CGContextClipToRect(context
, gfx::SkIRectToCGRect(irect
));
136 region
.getBoundaryPath(&path
);
137 // Clip. Note that windows clipping regions are not affected by the
138 // transform so apply it manually.
139 path
.transform(transformation
);
140 // TODO(playmobil): Implement.
142 // LoadPathToDC(context, path);
143 // hrgn = PathToRegion(context);
147 void BitmapPlatformDevice::LoadConfig() {
148 if (!config_dirty_
|| !bitmap_context_
)
149 return; // Nothing to do.
150 config_dirty_
= false;
152 // We must restore and then save the state of the graphics context since the
153 // calls to Load the clipping region to the context are strictly cummulative,
154 // i.e., you can't replace a clip rect, other than with a save/restore.
155 // But this implies that no other changes to the state are done elsewhere.
156 // If we ever get to need to change this, then we must replace the clip rect
157 // calls in LoadClippingRegionToCGContext() with an image mask instead.
158 CGContextRestoreGState(bitmap_context_
);
159 CGContextSaveGState(bitmap_context_
);
160 LoadTransformToCGContext(bitmap_context_
, transform_
);
161 LoadClippingRegionToCGContext(bitmap_context_
, clip_region_
, transform_
);
165 // We use this static factory function instead of the regular constructor so
166 // that we can create the pixel data before calling the constructor. This is
167 // required so that we can call the base class' constructor with the pixel
169 BitmapPlatformDevice
* BitmapPlatformDevice::Create(CGContextRef context
,
174 if (RasterDeviceTooBigToAllocate(width
, height
))
178 // TODO: verify that the CG Context's pixels will have tight rowbytes or pass in the correct
179 // rowbytes for the case when context != NULL.
180 bitmap
.setInfo(SkImageInfo::MakeN32(width
, height
, is_opaque
? kOpaque_SkAlphaType
: kPremul_SkAlphaType
));
184 data
= CGBitmapContextGetData(context
);
185 bitmap
.setPixels(data
);
187 if (!bitmap
.tryAllocPixels())
189 data
= bitmap
.getPixels();
192 memset(data
, 0, bitmap
.getSafeSize());
194 // If we were given data, then don't clobber it!
196 if (!context
&& is_opaque
) {
197 // To aid in finding bugs, we set the background color to something
198 // obviously wrong so it will be noticable when it is not cleared
199 bitmap
.eraseARGB(255, 0, 255, 128); // bright bluish green
204 context
= CGContextForData(data
, width
, height
);
208 CGContextRetain(context
);
210 BitmapPlatformDevice
* rv
= new BitmapPlatformDevice(context
, bitmap
);
212 // The device object took ownership of the graphics context with its own
213 // CGContextRetain call.
214 CGContextRelease(context
);
219 BitmapPlatformDevice
* BitmapPlatformDevice::CreateWithData(uint8_t* data
,
223 CGContextRef context
= NULL
;
225 context
= CGContextForData(data
, width
, height
);
227 BitmapPlatformDevice
* rv
= Create(context
, width
, height
, is_opaque
, false);
229 // The device object took ownership of the graphics context with its own
230 // CGContextRetain call.
232 CGContextRelease(context
);
237 // The device will own the bitmap, which corresponds to also owning the pixel
238 // data. Therefore, we do not transfer ownership to the SkBitmapDevice's bitmap.
239 BitmapPlatformDevice::BitmapPlatformDevice(
240 CGContextRef context
, const SkBitmap
& bitmap
)
241 : SkBitmapDevice(bitmap
),
242 bitmap_context_(context
),
243 config_dirty_(true), // Want to load the config next time.
244 transform_(SkMatrix::I()) {
245 SetPlatformDevice(this, this);
246 SkASSERT(bitmap_context_
);
247 // Initialize the clip region to the entire bitmap.
251 CGBitmapContextGetWidth(bitmap_context_
),
252 CGBitmapContextGetHeight(bitmap_context_
));
253 clip_region_
= SkRegion(rect
);
254 CGContextRetain(bitmap_context_
);
255 // We must save the state once so that we can use the restore/save trick
257 CGContextSaveGState(bitmap_context_
);
260 BitmapPlatformDevice::~BitmapPlatformDevice() {
262 CGContextRelease(bitmap_context_
);
265 CGContextRef
BitmapPlatformDevice::GetBitmapContext() {
267 return bitmap_context_
;
270 void BitmapPlatformDevice::setMatrixClip(const SkMatrix
& transform
,
271 const SkRegion
& region
,
272 const SkClipStack
&) {
273 SetMatrixClip(transform
, region
);
276 SkBaseDevice
* BitmapPlatformDevice::onCreateDevice(const CreateInfo
& cinfo
,
278 const SkImageInfo
& info
= cinfo
.fInfo
;
279 const bool do_clear
= !info
.isOpaque();
280 SkASSERT(info
.colorType() == kN32_SkColorType
);
281 return Create(NULL
, info
.width(), info
.height(), info
.isOpaque(), do_clear
);
284 // PlatformCanvas impl
286 SkCanvas
* CreatePlatformCanvas(CGContextRef ctx
, int width
, int height
,
287 bool is_opaque
, OnFailureType failureType
) {
288 const bool do_clear
= false;
289 skia::RefPtr
<SkBaseDevice
> dev
= skia::AdoptRef(
290 BitmapPlatformDevice::Create(ctx
, width
, height
, is_opaque
, do_clear
));
291 return CreateCanvas(dev
, failureType
);
294 SkCanvas
* CreatePlatformCanvas(int width
, int height
, bool is_opaque
,
295 uint8_t* data
, OnFailureType failureType
) {
296 skia::RefPtr
<SkBaseDevice
> dev
= skia::AdoptRef(
297 BitmapPlatformDevice::CreateWithData(data
, width
, height
, is_opaque
));
298 return CreateCanvas(dev
, failureType
);
301 // Port of PlatformBitmap to mac
303 PlatformBitmap::~PlatformBitmap() {
305 CGContextRelease(surface_
);
308 bool PlatformBitmap::Allocate(int width
, int height
, bool is_opaque
) {
309 if (RasterDeviceTooBigToAllocate(width
, height
))
312 if (!bitmap_
.tryAllocN32Pixels(width
, height
, is_opaque
))
316 bitmap_
.eraseColor(0);
318 surface_
= CGContextForData(bitmap_
.getPixels(), bitmap_
.width(),