OLD | NEW |
| (Empty) |
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 <stddef.h> | |
9 #include <time.h> | |
10 | |
11 #include "base/mac/mac_util.h" | |
12 #include "base/memory/ref_counted.h" | |
13 #include "skia/ext/bitmap_platform_device.h" | |
14 #include "skia/ext/platform_canvas.h" | |
15 #include "skia/ext/skia_utils_mac.h" | |
16 #include "third_party/skia/include/core/SkMatrix.h" | |
17 #include "third_party/skia/include/core/SkPath.h" | |
18 #include "third_party/skia/include/core/SkRect.h" | |
19 #include "third_party/skia/include/core/SkTypes.h" | |
20 | |
21 namespace skia { | |
22 | |
23 namespace { | |
24 | |
25 // Returns true if it is unsafe to attempt to allocate an offscreen buffer | |
26 // given these dimensions. | |
27 bool RasterDeviceTooBigToAllocate(int width, int height) { | |
28 | |
29 #ifndef SKIA_EXT_RASTER_DEVICE_ALLOCATION_MAX | |
30 #define SKIA_EXT_RASTER_DEVICE_ALLOCATION_MAX (2 * 256 * 1024 * 1024) | |
31 #endif | |
32 | |
33 int bytesPerPixel = 4; | |
34 int64_t bytes = (int64_t)width * height * bytesPerPixel; | |
35 return bytes > SKIA_EXT_RASTER_DEVICE_ALLOCATION_MAX; | |
36 } | |
37 | |
38 static CGContextRef CGContextForData(void* data, int width, int height) { | |
39 #define HAS_ARGB_SHIFTS(a, r, g, b) \ | |
40 (SK_A32_SHIFT == (a) && SK_R32_SHIFT == (r) \ | |
41 && SK_G32_SHIFT == (g) && SK_B32_SHIFT == (b)) | |
42 #if defined(SK_CPU_LENDIAN) && HAS_ARGB_SHIFTS(24, 16, 8, 0) | |
43 // Allocate a bitmap context with 4 components per pixel (BGRA). Apple | |
44 // recommends these flags for improved CG performance. | |
45 | |
46 // CGBitmapContextCreate returns NULL if width/height are 0. However, our | |
47 // callers expect to get a canvas back (which they later resize/reallocate) | |
48 // so we pin the dimensions here. | |
49 width = SkMax32(1, width); | |
50 height = SkMax32(1, height); | |
51 CGContextRef context = | |
52 CGBitmapContextCreate(data, width, height, 8, width * 4, | |
53 base::mac::GetSystemColorSpace(), | |
54 kCGImageAlphaPremultipliedFirst | | |
55 kCGBitmapByteOrder32Host); | |
56 #else | |
57 #error We require that Skia's and CoreGraphics's recommended \ | |
58 image memory layout match. | |
59 #endif | |
60 #undef HAS_ARGB_SHIFTS | |
61 | |
62 if (!context) | |
63 return NULL; | |
64 | |
65 // Change the coordinate system to match WebCore's | |
66 CGContextTranslateCTM(context, 0, height); | |
67 CGContextScaleCTM(context, 1.0, -1.0); | |
68 | |
69 return context; | |
70 } | |
71 | |
72 } // namespace | |
73 | |
74 void BitmapPlatformDevice::ReleaseBitmapContext() { | |
75 SkASSERT(bitmap_context_); | |
76 CGContextRelease(bitmap_context_); | |
77 bitmap_context_ = NULL; | |
78 } | |
79 | |
80 // Loads the specified Skia transform into the device context | |
81 static void LoadTransformToCGContext(CGContextRef context, | |
82 const SkMatrix& matrix) { | |
83 // CoreGraphics can concatenate transforms, but not reset the current one. | |
84 // So in order to get the required behavior here, we need to first make | |
85 // the current transformation matrix identity and only then load the new one. | |
86 | |
87 // Reset matrix to identity. | |
88 CGAffineTransform orig_cg_matrix = CGContextGetCTM(context); | |
89 CGAffineTransform orig_cg_matrix_inv = | |
90 CGAffineTransformInvert(orig_cg_matrix); | |
91 CGContextConcatCTM(context, orig_cg_matrix_inv); | |
92 | |
93 // assert that we have indeed returned to the identity Matrix. | |
94 SkASSERT(CGAffineTransformIsIdentity(CGContextGetCTM(context))); | |
95 | |
96 // Convert xform to CG-land. | |
97 // Our coordinate system is flipped to match WebKit's so we need to modify | |
98 // the xform to match that. | |
99 SkMatrix transformed_matrix = matrix; | |
100 SkScalar sy = -matrix.getScaleY(); | |
101 transformed_matrix.setScaleY(sy); | |
102 size_t height = CGBitmapContextGetHeight(context); | |
103 SkScalar ty = -matrix.getTranslateY(); // y axis is flipped. | |
104 transformed_matrix.setTranslateY(ty + (SkScalar)height); | |
105 | |
106 CGAffineTransform cg_matrix = | |
107 skia::SkMatrixToCGAffineTransform(transformed_matrix); | |
108 | |
109 // Load final transform into context. | |
110 CGContextConcatCTM(context, cg_matrix); | |
111 } | |
112 | |
113 static void LoadClippingRegionToCGContext(CGContextRef context, | |
114 const SkIRect& clip_bounds, | |
115 const SkMatrix& transformation) { | |
116 // CoreGraphics applies the current transform to clip rects, which is | |
117 // unwanted. Inverse-transform the rect before sending it to CG. This only | |
118 // works for translations and scaling, but not for rotations (but the | |
119 // viewport is never rotated anyway). | |
120 SkMatrix t; | |
121 bool did_invert = transformation.invert(&t); | |
122 if (!did_invert) | |
123 t.reset(); | |
124 | |
125 SkRect rect = SkRect::Make(clip_bounds); | |
126 t.mapRect(&rect); | |
127 SkIRect irect; | |
128 rect.round(&irect); | |
129 CGContextClipToRect(context, skia::SkIRectToCGRect(irect)); | |
130 } | |
131 | |
132 void BitmapPlatformDevice::LoadConfig(const SkMatrix& transform, | |
133 const SkIRect& clip_bounds) { | |
134 if (!bitmap_context_) | |
135 return; // Nothing to do. | |
136 | |
137 // We must restore and then save the state of the graphics context since the | |
138 // calls to Load the clipping region to the context are strictly cummulative, | |
139 // i.e., you can't replace a clip rect, other than with a save/restore. | |
140 // But this implies that no other changes to the state are done elsewhere. | |
141 // If we ever get to need to change this, then we must replace the clip rect | |
142 // calls in LoadClippingRegionToCGContext() with an image mask instead. | |
143 CGContextRestoreGState(bitmap_context_); | |
144 CGContextSaveGState(bitmap_context_); | |
145 LoadTransformToCGContext(bitmap_context_, transform); | |
146 LoadClippingRegionToCGContext(bitmap_context_, clip_bounds, transform); | |
147 } | |
148 | |
149 | |
150 // We use this static factory function instead of the regular constructor so | |
151 // that we can create the pixel data before calling the constructor. This is | |
152 // required so that we can call the base class' constructor with the pixel | |
153 // data. | |
154 BitmapPlatformDevice* BitmapPlatformDevice::Create(CGContextRef context, | |
155 int width, | |
156 int height, | |
157 bool is_opaque, | |
158 bool do_clear) { | |
159 if (RasterDeviceTooBigToAllocate(width, height)) | |
160 return NULL; | |
161 | |
162 SkBitmap bitmap; | |
163 // TODO: verify that the CG Context's pixels will have tight rowbytes or pass
in the correct | |
164 // rowbytes for the case when context != NULL. | |
165 bitmap.setInfo(SkImageInfo::MakeN32(width, height, is_opaque ? kOpaque_SkAlpha
Type : kPremul_SkAlphaType)); | |
166 | |
167 void* data; | |
168 if (context) { | |
169 data = CGBitmapContextGetData(context); | |
170 bitmap.setPixels(data); | |
171 } else { | |
172 if (!bitmap.tryAllocPixels()) | |
173 return NULL; | |
174 data = bitmap.getPixels(); | |
175 } | |
176 if (do_clear) | |
177 memset(data, 0, bitmap.getSafeSize()); | |
178 | |
179 // If we were given data, then don't clobber it! | |
180 #ifndef NDEBUG | |
181 if (!context && is_opaque) { | |
182 // To aid in finding bugs, we set the background color to something | |
183 // obviously wrong so it will be noticable when it is not cleared | |
184 bitmap.eraseARGB(255, 0, 255, 128); // bright bluish green | |
185 } | |
186 #endif | |
187 | |
188 if (!context) { | |
189 context = CGContextForData(data, width, height); | |
190 if (!context) | |
191 return NULL; | |
192 } else | |
193 CGContextRetain(context); | |
194 | |
195 BitmapPlatformDevice* rv = new BitmapPlatformDevice(context, bitmap); | |
196 | |
197 // The device object took ownership of the graphics context with its own | |
198 // CGContextRetain call. | |
199 CGContextRelease(context); | |
200 | |
201 return rv; | |
202 } | |
203 | |
204 BitmapPlatformDevice* BitmapPlatformDevice::CreateWithData(uint8_t* data, | |
205 int width, | |
206 int height, | |
207 bool is_opaque) { | |
208 CGContextRef context = NULL; | |
209 if (data) | |
210 context = CGContextForData(data, width, height); | |
211 | |
212 BitmapPlatformDevice* rv = Create(context, width, height, is_opaque, false); | |
213 | |
214 // The device object took ownership of the graphics context with its own | |
215 // CGContextRetain call. | |
216 if (context) | |
217 CGContextRelease(context); | |
218 | |
219 return rv; | |
220 } | |
221 | |
222 // The device will own the bitmap, which corresponds to also owning the pixel | |
223 // data. Therefore, we do not transfer ownership to the SkBitmapDevice's bitmap. | |
224 BitmapPlatformDevice::BitmapPlatformDevice( | |
225 CGContextRef context, const SkBitmap& bitmap) | |
226 : SkBitmapDevice(bitmap), | |
227 bitmap_context_(context) { | |
228 SetPlatformDevice(this, this); | |
229 SkASSERT(bitmap_context_); | |
230 // Initialize the clip region to the entire bitmap. | |
231 | |
232 SkIRect rect; | |
233 rect.set(0, 0, | |
234 CGBitmapContextGetWidth(bitmap_context_), | |
235 CGBitmapContextGetHeight(bitmap_context_)); | |
236 CGContextRetain(bitmap_context_); | |
237 // We must save the state once so that we can use the restore/save trick | |
238 // in LoadConfig(). | |
239 CGContextSaveGState(bitmap_context_); | |
240 } | |
241 | |
242 BitmapPlatformDevice::~BitmapPlatformDevice() { | |
243 if (bitmap_context_) | |
244 CGContextRelease(bitmap_context_); | |
245 } | |
246 | |
247 NativeDrawingContext BitmapPlatformDevice::BeginPlatformPaint( | |
248 const SkMatrix& transform, | |
249 const SkIRect& clip_bounds) { | |
250 LoadConfig(transform, clip_bounds); | |
251 return bitmap_context_; | |
252 } | |
253 | |
254 SkBaseDevice* BitmapPlatformDevice::onCreateDevice(const CreateInfo& cinfo, | |
255 const SkPaint*) { | |
256 const SkImageInfo& info = cinfo.fInfo; | |
257 const bool do_clear = !info.isOpaque(); | |
258 SkASSERT(info.colorType() == kN32_SkColorType); | |
259 return Create(NULL, info.width(), info.height(), info.isOpaque(), do_clear); | |
260 } | |
261 | |
262 // PlatformCanvas impl | |
263 | |
264 std::unique_ptr<SkCanvas> CreatePlatformCanvasWithPixels( | |
265 int width, | |
266 int height, | |
267 bool is_opaque, | |
268 uint8_t* data, | |
269 OnFailureType failureType) { | |
270 sk_sp<SkBaseDevice> dev( | |
271 BitmapPlatformDevice::CreateWithData(data, width, height, is_opaque)); | |
272 return CreateCanvas(dev, failureType); | |
273 } | |
274 | |
275 } // namespace skia | |
OLD | NEW |