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/skia_utils_mac.h" | |
6 | |
7 #import <AppKit/AppKit.h> | |
8 | |
9 #include "base/logging.h" | |
10 #include "base/mac/scoped_cftyperef.h" | |
11 #include "base/mac/scoped_nsobject.h" | |
12 #include "base/memory/scoped_ptr.h" | |
13 #include "skia/ext/bitmap_platform_device_mac.h" | |
14 #include "third_party/skia/include/core/SkRegion.h" | |
15 #include "third_party/skia/include/utils/mac/SkCGUtils.h" | |
16 | |
17 namespace { | |
18 | |
19 // Draws an NSImage or an NSImageRep with a given size into a SkBitmap. | |
20 SkBitmap NSImageOrNSImageRepToSkBitmapWithColorSpace( | |
21 NSImage* image, | |
22 NSImageRep* image_rep, | |
23 NSSize size, | |
24 bool is_opaque, | |
25 CGColorSpaceRef color_space) { | |
26 // Only image or image_rep should be provided, not both. | |
27 DCHECK((image != 0) ^ (image_rep != 0)); | |
28 | |
29 SkBitmap bitmap; | |
30 if (!bitmap.tryAllocN32Pixels(size.width, size.height, is_opaque)) | |
31 return bitmap; // Return |bitmap| which should respond true to isNull(). | |
32 | |
33 | |
34 void* data = bitmap.getPixels(); | |
35 | |
36 // Allocate a bitmap context with 4 components per pixel (BGRA). Apple | |
37 // recommends these flags for improved CG performance. | |
38 #define HAS_ARGB_SHIFTS(a, r, g, b) \ | |
39 (SK_A32_SHIFT == (a) && SK_R32_SHIFT == (r) \ | |
40 && SK_G32_SHIFT == (g) && SK_B32_SHIFT == (b)) | |
41 #if defined(SK_CPU_LENDIAN) && HAS_ARGB_SHIFTS(24, 16, 8, 0) | |
42 base::ScopedCFTypeRef<CGContextRef> context(CGBitmapContextCreate( | |
43 data, | |
44 size.width, | |
45 size.height, | |
46 8, | |
47 size.width * 4, | |
48 color_space, | |
49 kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Host)); | |
50 #else | |
51 #error We require that Skia's and CoreGraphics's recommended \ | |
52 image memory layout match. | |
53 #endif | |
54 #undef HAS_ARGB_SHIFTS | |
55 | |
56 // Something went really wrong. Best guess is that the bitmap data is invalid. | |
57 DCHECK(context); | |
58 | |
59 [NSGraphicsContext saveGraphicsState]; | |
60 | |
61 NSGraphicsContext* context_cocoa = | |
62 [NSGraphicsContext graphicsContextWithGraphicsPort:context flipped:NO]; | |
63 [NSGraphicsContext setCurrentContext:context_cocoa]; | |
64 | |
65 NSRect drawRect = NSMakeRect(0, 0, size.width, size.height); | |
66 if (image) { | |
67 [image drawInRect:drawRect | |
68 fromRect:NSZeroRect | |
69 operation:NSCompositeCopy | |
70 fraction:1.0]; | |
71 } else { | |
72 [image_rep drawInRect:drawRect | |
73 fromRect:NSZeroRect | |
74 operation:NSCompositeCopy | |
75 fraction:1.0 | |
76 respectFlipped:NO | |
77 hints:nil]; | |
78 } | |
79 | |
80 [NSGraphicsContext restoreGraphicsState]; | |
81 | |
82 return bitmap; | |
83 } | |
84 | |
85 } // namespace | |
86 | |
87 namespace gfx { | |
88 | |
89 CGAffineTransform SkMatrixToCGAffineTransform(const SkMatrix& matrix) { | |
90 // CGAffineTransforms don't support perspective transforms, so make sure | |
91 // we don't get those. | |
92 DCHECK(matrix[SkMatrix::kMPersp0] == 0.0f); | |
93 DCHECK(matrix[SkMatrix::kMPersp1] == 0.0f); | |
94 DCHECK(matrix[SkMatrix::kMPersp2] == 1.0f); | |
95 | |
96 return CGAffineTransformMake(matrix[SkMatrix::kMScaleX], | |
97 matrix[SkMatrix::kMSkewY], | |
98 matrix[SkMatrix::kMSkewX], | |
99 matrix[SkMatrix::kMScaleY], | |
100 matrix[SkMatrix::kMTransX], | |
101 matrix[SkMatrix::kMTransY]); | |
102 } | |
103 | |
104 SkRect CGRectToSkRect(const CGRect& rect) { | |
105 SkRect sk_rect = { | |
106 rect.origin.x, rect.origin.y, CGRectGetMaxX(rect), CGRectGetMaxY(rect) | |
107 }; | |
108 return sk_rect; | |
109 } | |
110 | |
111 CGRect SkIRectToCGRect(const SkIRect& rect) { | |
112 CGRect cg_rect = { | |
113 { rect.fLeft, rect.fTop }, | |
114 { rect.fRight - rect.fLeft, rect.fBottom - rect.fTop } | |
115 }; | |
116 return cg_rect; | |
117 } | |
118 | |
119 CGRect SkRectToCGRect(const SkRect& rect) { | |
120 CGRect cg_rect = { | |
121 { rect.fLeft, rect.fTop }, | |
122 { rect.fRight - rect.fLeft, rect.fBottom - rect.fTop } | |
123 }; | |
124 return cg_rect; | |
125 } | |
126 | |
127 // Converts CGColorRef to the ARGB layout Skia expects. | |
128 SkColor CGColorRefToSkColor(CGColorRef color) { | |
129 DCHECK(CGColorGetNumberOfComponents(color) == 4); | |
130 const CGFloat* components = CGColorGetComponents(color); | |
131 return SkColorSetARGB(SkScalarRoundToInt(255.0 * components[3]), // alpha | |
132 SkScalarRoundToInt(255.0 * components[0]), // red | |
133 SkScalarRoundToInt(255.0 * components[1]), // green | |
134 SkScalarRoundToInt(255.0 * components[2])); // blue | |
135 } | |
136 | |
137 // Converts ARGB to CGColorRef. | |
138 CGColorRef CGColorCreateFromSkColor(SkColor color) { | |
139 return CGColorCreateGenericRGB(SkColorGetR(color) / 255.0, | |
140 SkColorGetG(color) / 255.0, | |
141 SkColorGetB(color) / 255.0, | |
142 SkColorGetA(color) / 255.0); | |
143 } | |
144 | |
145 // Converts NSColor to ARGB | |
146 SkColor NSDeviceColorToSkColor(NSColor* color) { | |
147 DCHECK([color colorSpace] == [NSColorSpace genericRGBColorSpace] || | |
148 [color colorSpace] == [NSColorSpace deviceRGBColorSpace]); | |
149 CGFloat red, green, blue, alpha; | |
150 color = [color colorUsingColorSpace:[NSColorSpace deviceRGBColorSpace]]; | |
151 [color getRed:&red green:&green blue:&blue alpha:&alpha]; | |
152 return SkColorSetARGB(SkScalarRoundToInt(255.0 * alpha), | |
153 SkScalarRoundToInt(255.0 * red), | |
154 SkScalarRoundToInt(255.0 * green), | |
155 SkScalarRoundToInt(255.0 * blue)); | |
156 } | |
157 | |
158 // Converts ARGB to NSColor. | |
159 NSColor* SkColorToCalibratedNSColor(SkColor color) { | |
160 return [NSColor colorWithCalibratedRed:SkColorGetR(color) / 255.0 | |
161 green:SkColorGetG(color) / 255.0 | |
162 blue:SkColorGetB(color) / 255.0 | |
163 alpha:SkColorGetA(color) / 255.0]; | |
164 } | |
165 | |
166 NSColor* SkColorToDeviceNSColor(SkColor color) { | |
167 return [NSColor colorWithDeviceRed:SkColorGetR(color) / 255.0 | |
168 green:SkColorGetG(color) / 255.0 | |
169 blue:SkColorGetB(color) / 255.0 | |
170 alpha:SkColorGetA(color) / 255.0]; | |
171 } | |
172 | |
173 NSColor* SkColorToSRGBNSColor(SkColor color) { | |
174 const CGFloat components[] = { | |
175 SkColorGetR(color) / 255.0, | |
176 SkColorGetG(color) / 255.0, | |
177 SkColorGetB(color) / 255.0, | |
178 SkColorGetA(color) / 255.0 | |
179 }; | |
180 return [NSColor colorWithColorSpace:[NSColorSpace sRGBColorSpace] | |
181 components:components | |
182 count:4]; | |
183 } | |
184 | |
185 SkBitmap CGImageToSkBitmap(CGImageRef image) { | |
186 if (!image) | |
187 return SkBitmap(); | |
188 | |
189 int width = CGImageGetWidth(image); | |
190 int height = CGImageGetHeight(image); | |
191 | |
192 scoped_ptr<SkBaseDevice> device( | |
193 skia::BitmapPlatformDevice::Create(NULL, width, height, false)); | |
194 | |
195 CGContextRef context = skia::GetBitmapContext(device.get()); | |
196 | |
197 // We need to invert the y-axis of the canvas so that Core Graphics drawing | |
198 // happens right-side up. Skia has an upper-left origin and CG has a lower- | |
199 // left one. | |
200 CGContextScaleCTM(context, 1.0, -1.0); | |
201 CGContextTranslateCTM(context, 0, -height); | |
202 | |
203 // We want to copy transparent pixels from |image|, instead of blending it | |
204 // onto uninitialized pixels. | |
205 CGContextSetBlendMode(context, kCGBlendModeCopy); | |
206 | |
207 CGRect rect = CGRectMake(0, 0, width, height); | |
208 CGContextDrawImage(context, rect, image); | |
209 | |
210 // Because |device| will be cleaned up and will take its pixels with it, we | |
211 // copy it to the stack and return it. | |
212 SkBitmap bitmap = device->accessBitmap(false); | |
213 | |
214 return bitmap; | |
215 } | |
216 | |
217 SkBitmap NSImageToSkBitmapWithColorSpace( | |
218 NSImage* image, bool is_opaque, CGColorSpaceRef color_space) { | |
219 return NSImageOrNSImageRepToSkBitmapWithColorSpace( | |
220 image, nil, [image size], is_opaque, color_space); | |
221 } | |
222 | |
223 SkBitmap NSImageRepToSkBitmapWithColorSpace(NSImageRep* image_rep, | |
224 NSSize size, | |
225 bool is_opaque, | |
226 CGColorSpaceRef color_space) { | |
227 return NSImageOrNSImageRepToSkBitmapWithColorSpace( | |
228 nil, image_rep, size, is_opaque, color_space); | |
229 } | |
230 | |
231 NSBitmapImageRep* SkBitmapToNSBitmapImageRep(const SkBitmap& skiaBitmap) { | |
232 base::ScopedCFTypeRef<CGColorSpaceRef> color_space( | |
233 CGColorSpaceCreateWithName(kCGColorSpaceGenericRGB)); | |
234 return SkBitmapToNSBitmapImageRepWithColorSpace(skiaBitmap, color_space); | |
235 } | |
236 | |
237 NSBitmapImageRep* SkBitmapToNSBitmapImageRepWithColorSpace( | |
238 const SkBitmap& skiaBitmap, | |
239 CGColorSpaceRef colorSpace) { | |
240 // First convert SkBitmap to CGImageRef. | |
241 base::ScopedCFTypeRef<CGImageRef> cgimage( | |
242 SkCreateCGImageRefWithColorspace(skiaBitmap, colorSpace)); | |
243 | |
244 // Now convert to NSBitmapImageRep. | |
245 base::scoped_nsobject<NSBitmapImageRep> bitmap( | |
246 [[NSBitmapImageRep alloc] initWithCGImage:cgimage]); | |
247 return [bitmap.release() autorelease]; | |
248 } | |
249 | |
250 NSImage* SkBitmapToNSImageWithColorSpace(const SkBitmap& skiaBitmap, | |
251 CGColorSpaceRef colorSpace) { | |
252 if (skiaBitmap.isNull()) | |
253 return nil; | |
254 | |
255 base::scoped_nsobject<NSImage> image([[NSImage alloc] init]); | |
256 [image addRepresentation: | |
257 SkBitmapToNSBitmapImageRepWithColorSpace(skiaBitmap, colorSpace)]; | |
258 [image setSize:NSMakeSize(skiaBitmap.width(), skiaBitmap.height())]; | |
259 return [image.release() autorelease]; | |
260 } | |
261 | |
262 NSImage* SkBitmapToNSImage(const SkBitmap& skiaBitmap) { | |
263 base::ScopedCFTypeRef<CGColorSpaceRef> colorSpace( | |
264 CGColorSpaceCreateWithName(kCGColorSpaceGenericRGB)); | |
265 return SkBitmapToNSImageWithColorSpace(skiaBitmap, colorSpace.get()); | |
266 } | |
267 | |
268 SkiaBitLocker::SkiaBitLocker(SkCanvas* canvas) | |
269 : canvas_(canvas), | |
270 userClipRectSpecified_(false), | |
271 cgContext_(0), | |
272 bitmapScaleFactor_(1), | |
273 useDeviceBits_(false), | |
274 bitmapIsDummy_(false) { | |
275 } | |
276 | |
277 SkiaBitLocker::SkiaBitLocker(SkCanvas* canvas, | |
278 const SkIRect& userClipRect, | |
279 SkScalar bitmapScaleFactor) | |
280 : canvas_(canvas), | |
281 userClipRectSpecified_(true), | |
282 cgContext_(0), | |
283 bitmapScaleFactor_(bitmapScaleFactor), | |
284 useDeviceBits_(false), | |
285 bitmapIsDummy_(false) { | |
286 canvas_->save(); | |
287 canvas_->clipRect(SkRect::MakeFromIRect(userClipRect)); | |
288 } | |
289 | |
290 SkiaBitLocker::~SkiaBitLocker() { | |
291 releaseIfNeeded(); | |
292 if (userClipRectSpecified_) | |
293 canvas_->restore(); | |
294 } | |
295 | |
296 SkIRect SkiaBitLocker::computeDirtyRect() { | |
297 // If the user specified a clip region, assume that it was tight and that the | |
298 // dirty rect is approximately the whole bitmap. | |
299 if (userClipRectSpecified_) | |
300 return SkIRect::MakeWH(bitmap_.width(), bitmap_.height()); | |
301 | |
302 // Find the bits that were drawn to. | |
303 SkAutoLockPixels lockedPixels(bitmap_); | |
304 const uint32_t* pixelBase | |
305 = reinterpret_cast<uint32_t*>(bitmap_.getPixels()); | |
306 int rowPixels = bitmap_.rowBytesAsPixels(); | |
307 int width = bitmap_.width(); | |
308 int height = bitmap_.height(); | |
309 SkIRect bounds; | |
310 bounds.fTop = 0; | |
311 int x; | |
312 int y = -1; | |
313 const uint32_t* pixels = pixelBase; | |
314 while (++y < height) { | |
315 for (x = 0; x < width; ++x) { | |
316 if (pixels[x]) { | |
317 bounds.fTop = y; | |
318 goto foundTop; | |
319 } | |
320 } | |
321 pixels += rowPixels; | |
322 } | |
323 foundTop: | |
324 bounds.fBottom = height; | |
325 y = height; | |
326 pixels = pixelBase + rowPixels * (y - 1); | |
327 while (--y > bounds.fTop) { | |
328 for (x = 0; x < width; ++x) { | |
329 if (pixels[x]) { | |
330 bounds.fBottom = y + 1; | |
331 goto foundBottom; | |
332 } | |
333 } | |
334 pixels -= rowPixels; | |
335 } | |
336 foundBottom: | |
337 bounds.fLeft = 0; | |
338 x = -1; | |
339 while (++x < width) { | |
340 pixels = pixelBase + rowPixels * bounds.fTop; | |
341 for (y = bounds.fTop; y < bounds.fBottom; ++y) { | |
342 if (pixels[x]) { | |
343 bounds.fLeft = x; | |
344 goto foundLeft; | |
345 } | |
346 pixels += rowPixels; | |
347 } | |
348 } | |
349 foundLeft: | |
350 bounds.fRight = width; | |
351 x = width; | |
352 while (--x > bounds.fLeft) { | |
353 pixels = pixelBase + rowPixels * bounds.fTop; | |
354 for (y = bounds.fTop; y < bounds.fBottom; ++y) { | |
355 if (pixels[x]) { | |
356 bounds.fRight = x + 1; | |
357 goto foundRight; | |
358 } | |
359 pixels += rowPixels; | |
360 } | |
361 } | |
362 foundRight: | |
363 return bounds; | |
364 } | |
365 | |
366 // This must be called to balance calls to cgContext | |
367 void SkiaBitLocker::releaseIfNeeded() { | |
368 if (!cgContext_) | |
369 return; | |
370 if (useDeviceBits_) { | |
371 bitmap_.unlockPixels(); | |
372 } else if (!bitmapIsDummy_) { | |
373 // Find the bits that were drawn to. | |
374 SkIRect bounds = computeDirtyRect(); | |
375 SkBitmap subset; | |
376 if (!bitmap_.extractSubset(&subset, bounds)) { | |
377 return; | |
378 } | |
379 subset.setImmutable(); // Prevents a defensive copy inside Skia. | |
380 canvas_->save(); | |
381 canvas_->setMatrix(SkMatrix::I()); // Reset back to device space. | |
382 canvas_->translate(bounds.x() + bitmapOffset_.x(), | |
383 bounds.y() + bitmapOffset_.y()); | |
384 canvas_->scale(1.f / bitmapScaleFactor_, 1.f / bitmapScaleFactor_); | |
385 canvas_->drawBitmap(subset, 0, 0); | |
386 canvas_->restore(); | |
387 } | |
388 CGContextRelease(cgContext_); | |
389 cgContext_ = 0; | |
390 useDeviceBits_ = false; | |
391 bitmapIsDummy_ = false; | |
392 } | |
393 | |
394 CGContextRef SkiaBitLocker::cgContext() { | |
395 SkIRect clip_bounds; | |
396 if (!canvas_->getClipDeviceBounds(&clip_bounds)) { | |
397 // If the clip is empty, then there is nothing to draw. The caller may | |
398 // attempt to draw (to-be-clipped) results, so ensure there is a dummy | |
399 // non-NULL CGContext to use. | |
400 bitmapIsDummy_ = true; | |
401 clip_bounds = SkIRect::MakeXYWH(0, 0, 1, 1); | |
402 } | |
403 | |
404 SkBaseDevice* device = canvas_->getTopDevice(); | |
405 DCHECK(device); | |
406 if (!device) | |
407 return 0; | |
408 | |
409 releaseIfNeeded(); // This flushes any prior bitmap use | |
410 | |
411 // remember the top/left, in case we need to compose this later | |
412 bitmapOffset_.set(clip_bounds.x(), clip_bounds.y()); | |
413 | |
414 // Now make clip_bounds be relative to the current layer/device | |
415 clip_bounds.offset(-device->getOrigin()); | |
416 | |
417 const SkBitmap& deviceBits = device->accessBitmap(true); | |
418 | |
419 // Only draw directly if we have pixels, and we're only rect-clipped. | |
420 // If not, we allocate an offscreen and draw into that, relying on the | |
421 // compositing step to apply skia's clip. | |
422 useDeviceBits_ = deviceBits.getPixels() && | |
423 canvas_->isClipRect() && | |
424 !bitmapIsDummy_; | |
425 if (useDeviceBits_) { | |
426 bool result = deviceBits.extractSubset(&bitmap_, clip_bounds); | |
427 DCHECK(result); | |
428 if (!result) | |
429 return 0; | |
430 bitmap_.lockPixels(); | |
431 } else { | |
432 bool result = bitmap_.tryAllocN32Pixels( | |
433 SkScalarCeilToInt(bitmapScaleFactor_ * clip_bounds.width()), | |
434 SkScalarCeilToInt(bitmapScaleFactor_ * clip_bounds.height())); | |
435 DCHECK(result); | |
436 if (!result) | |
437 return 0; | |
438 bitmap_.eraseColor(0); | |
439 } | |
440 base::ScopedCFTypeRef<CGColorSpaceRef> colorSpace( | |
441 CGColorSpaceCreateDeviceRGB()); | |
442 cgContext_ = CGBitmapContextCreate(bitmap_.getPixels(), bitmap_.width(), | |
443 bitmap_.height(), 8, bitmap_.rowBytes(), colorSpace, | |
444 kCGBitmapByteOrder32Host | kCGImageAlphaPremultipliedFirst); | |
445 DCHECK(cgContext_); | |
446 | |
447 SkMatrix matrix = canvas_->getTotalMatrix(); | |
448 matrix.postTranslate(-SkIntToScalar(bitmapOffset_.x()), | |
449 -SkIntToScalar(bitmapOffset_.y())); | |
450 matrix.postScale(bitmapScaleFactor_, -bitmapScaleFactor_); | |
451 matrix.postTranslate(0, SkIntToScalar(bitmap_.height())); | |
452 | |
453 CGContextConcatCTM(cgContext_, SkMatrixToCGAffineTransform(matrix)); | |
454 | |
455 return cgContext_; | |
456 } | |
457 | |
458 bool SkiaBitLocker::hasEmptyClipRegion() const { | |
459 return canvas_->isClipEmpty(); | |
460 } | |
461 | |
462 } // namespace gfx | |
OLD | NEW |