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 17namespace { 18 19// Draws an NSImage or an NSImageRep with a given size into a SkBitmap. 20SkBitmap 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 87namespace gfx { 88 89CGAffineTransform 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 104SkRect 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 111CGRect 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 119CGRect 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. 128SkColor 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. 138CGColorRef 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 146SkColor 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. 159NSColor* 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 166NSColor* 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 173NSColor* 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 185SkBitmap 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 217SkBitmap NSImageToSkBitmapWithColorSpace( 218 NSImage* image, bool is_opaque, CGColorSpaceRef color_space) { 219 return NSImageOrNSImageRepToSkBitmapWithColorSpace( 220 image, nil, [image size], is_opaque, color_space); 221} 222 223SkBitmap 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 231NSBitmapImageRep* SkBitmapToNSBitmapImageRep(const SkBitmap& skiaBitmap) { 232 base::ScopedCFTypeRef<CGColorSpaceRef> color_space( 233 CGColorSpaceCreateWithName(kCGColorSpaceGenericRGB)); 234 return SkBitmapToNSBitmapImageRepWithColorSpace(skiaBitmap, color_space); 235} 236 237NSBitmapImageRep* 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 250NSImage* 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 262NSImage* SkBitmapToNSImage(const SkBitmap& skiaBitmap) { 263 base::ScopedCFTypeRef<CGColorSpaceRef> colorSpace( 264 CGColorSpaceCreateWithName(kCGColorSpaceGenericRGB)); 265 return SkBitmapToNSImageWithColorSpace(skiaBitmap, colorSpace.get()); 266} 267 268SkiaBitLocker::SkiaBitLocker(SkCanvas* canvas) 269 : canvas_(canvas), 270 userClipRectSpecified_(false), 271 cgContext_(0), 272 bitmapScaleFactor_(1), 273 useDeviceBits_(false), 274 bitmapIsDummy_(false) { 275} 276 277SkiaBitLocker::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 290SkiaBitLocker::~SkiaBitLocker() { 291 releaseIfNeeded(); 292 if (userClipRectSpecified_) 293 canvas_->restore(); 294} 295 296SkIRect 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 } 323foundTop: 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 } 336foundBottom: 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 } 349foundLeft: 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 } 362foundRight: 363 return bounds; 364} 365 366// This must be called to balance calls to cgContext 367void 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 // Neutralize the global matrix by concatenating the inverse. In the 380 // future, Skia may provide some mechanism to set the device portion of 381 // the matrix to identity without clobbering any hosting matrix (e.g., the 382 // picture's matrix). 383 const SkMatrix& skMatrix = canvas_->getTotalMatrix(); 384 SkMatrix inverse; 385 if (!skMatrix.invert(&inverse)) 386 return; 387 canvas_->save(); 388 canvas_->concat(inverse); 389 canvas_->translate(bounds.x() + bitmapOffset_.x(), 390 bounds.y() + bitmapOffset_.y()); 391 canvas_->scale(1.f / bitmapScaleFactor_, 1.f / bitmapScaleFactor_); 392 canvas_->drawBitmap(subset, 0, 0); 393 canvas_->restore(); 394 } 395 CGContextRelease(cgContext_); 396 cgContext_ = 0; 397 useDeviceBits_ = false; 398 bitmapIsDummy_ = false; 399} 400 401CGContextRef SkiaBitLocker::cgContext() { 402 SkIRect clip_bounds; 403 if (!canvas_->getClipDeviceBounds(&clip_bounds)) { 404 // If the clip is empty, then there is nothing to draw. The caller may 405 // attempt to draw (to-be-clipped) results, so ensure there is a dummy 406 // non-NULL CGContext to use. 407 bitmapIsDummy_ = true; 408 clip_bounds = SkIRect::MakeXYWH(0, 0, 1, 1); 409 } 410 411 SkBaseDevice* device = canvas_->getTopDevice(); 412 DCHECK(device); 413 if (!device) 414 return 0; 415 416 releaseIfNeeded(); // This flushes any prior bitmap use 417 418 // remember the top/left, in case we need to compose this later 419 bitmapOffset_.set(clip_bounds.x(), clip_bounds.y()); 420 421 // Now make clip_bounds be relative to the current layer/device 422 clip_bounds.offset(-device->getOrigin()); 423 424 const SkBitmap& deviceBits = device->accessBitmap(true); 425 426 // Only draw directly if we have pixels, and we're only rect-clipped. 427 // If not, we allocate an offscreen and draw into that, relying on the 428 // compositing step to apply skia's clip. 429 useDeviceBits_ = deviceBits.getPixels() && 430 canvas_->isClipRect() && 431 !bitmapIsDummy_; 432 if (useDeviceBits_) { 433 bool result = deviceBits.extractSubset(&bitmap_, clip_bounds); 434 DCHECK(result); 435 if (!result) 436 return 0; 437 bitmap_.lockPixels(); 438 } else { 439 bool result = bitmap_.tryAllocN32Pixels( 440 SkScalarCeilToInt(bitmapScaleFactor_ * clip_bounds.width()), 441 SkScalarCeilToInt(bitmapScaleFactor_ * clip_bounds.height())); 442 DCHECK(result); 443 if (!result) 444 return 0; 445 bitmap_.eraseColor(0); 446 } 447 base::ScopedCFTypeRef<CGColorSpaceRef> colorSpace( 448 CGColorSpaceCreateDeviceRGB()); 449 cgContext_ = CGBitmapContextCreate(bitmap_.getPixels(), bitmap_.width(), 450 bitmap_.height(), 8, bitmap_.rowBytes(), colorSpace, 451 kCGBitmapByteOrder32Host | kCGImageAlphaPremultipliedFirst); 452 DCHECK(cgContext_); 453 454 SkMatrix matrix = canvas_->getTotalMatrix(); 455 matrix.postTranslate(-SkIntToScalar(bitmapOffset_.x()), 456 -SkIntToScalar(bitmapOffset_.y())); 457 matrix.postScale(bitmapScaleFactor_, -bitmapScaleFactor_); 458 matrix.postTranslate(0, SkIntToScalar(bitmap_.height())); 459 460 CGContextConcatCTM(cgContext_, SkMatrixToCGAffineTransform(matrix)); 461 462 return cgContext_; 463} 464 465bool SkiaBitLocker::hasEmptyClipRegion() const { 466 return canvas_->isClipEmpty(); 467} 468 469} // namespace gfx 470