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 bitmap.setConfig(SkBitmap::kARGB_8888_Config, 31 size.width, 32 size.height, 33 0, 34 is_opaque ? kOpaque_SkAlphaType : kPremul_SkAlphaType); 35 36 if (!bitmap.allocPixels()) 37 return bitmap; // Return |bitmap| which should respond true to isNull(). 38 39 40 void* data = bitmap.getPixels(); 41 42 // Allocate a bitmap context with 4 components per pixel (BGRA). Apple 43 // recommends these flags for improved CG performance. 44#define HAS_ARGB_SHIFTS(a, r, g, b) \ 45 (SK_A32_SHIFT == (a) && SK_R32_SHIFT == (r) \ 46 && SK_G32_SHIFT == (g) && SK_B32_SHIFT == (b)) 47#if defined(SK_CPU_LENDIAN) && HAS_ARGB_SHIFTS(24, 16, 8, 0) 48 base::ScopedCFTypeRef<CGContextRef> context(CGBitmapContextCreate( 49 data, 50 size.width, 51 size.height, 52 8, 53 size.width * 4, 54 color_space, 55 kCGImageAlphaPremultipliedFirst | 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 // Something went really wrong. Best guess is that the bitmap data is invalid. 63 DCHECK(context); 64 65 [NSGraphicsContext saveGraphicsState]; 66 67 NSGraphicsContext* context_cocoa = 68 [NSGraphicsContext graphicsContextWithGraphicsPort:context flipped:NO]; 69 [NSGraphicsContext setCurrentContext:context_cocoa]; 70 71 NSRect drawRect = NSMakeRect(0, 0, size.width, size.height); 72 if (image) { 73 [image drawInRect:drawRect 74 fromRect:NSZeroRect 75 operation:NSCompositeCopy 76 fraction:1.0]; 77 } else { 78 [image_rep drawInRect:drawRect 79 fromRect:NSZeroRect 80 operation:NSCompositeCopy 81 fraction:1.0 82 respectFlipped:NO 83 hints:nil]; 84 } 85 86 [NSGraphicsContext restoreGraphicsState]; 87 88 return bitmap; 89} 90 91} // namespace 92 93namespace gfx { 94 95CGAffineTransform SkMatrixToCGAffineTransform(const SkMatrix& matrix) { 96 // CGAffineTransforms don't support perspective transforms, so make sure 97 // we don't get those. 98 DCHECK(matrix[SkMatrix::kMPersp0] == 0.0f); 99 DCHECK(matrix[SkMatrix::kMPersp1] == 0.0f); 100 DCHECK(matrix[SkMatrix::kMPersp2] == 1.0f); 101 102 return CGAffineTransformMake(matrix[SkMatrix::kMScaleX], 103 matrix[SkMatrix::kMSkewY], 104 matrix[SkMatrix::kMSkewX], 105 matrix[SkMatrix::kMScaleY], 106 matrix[SkMatrix::kMTransX], 107 matrix[SkMatrix::kMTransY]); 108} 109 110SkRect CGRectToSkRect(const CGRect& rect) { 111 SkRect sk_rect = { 112 rect.origin.x, rect.origin.y, CGRectGetMaxX(rect), CGRectGetMaxY(rect) 113 }; 114 return sk_rect; 115} 116 117CGRect SkIRectToCGRect(const SkIRect& rect) { 118 CGRect cg_rect = { 119 { rect.fLeft, rect.fTop }, 120 { rect.fRight - rect.fLeft, rect.fBottom - rect.fTop } 121 }; 122 return cg_rect; 123} 124 125CGRect SkRectToCGRect(const SkRect& rect) { 126 CGRect cg_rect = { 127 { rect.fLeft, rect.fTop }, 128 { rect.fRight - rect.fLeft, rect.fBottom - rect.fTop } 129 }; 130 return cg_rect; 131} 132 133// Converts CGColorRef to the ARGB layout Skia expects. 134SkColor CGColorRefToSkColor(CGColorRef color) { 135 DCHECK(CGColorGetNumberOfComponents(color) == 4); 136 const CGFloat* components = CGColorGetComponents(color); 137 return SkColorSetARGB(SkScalarRound(255.0 * components[3]), // alpha 138 SkScalarRound(255.0 * components[0]), // red 139 SkScalarRound(255.0 * components[1]), // green 140 SkScalarRound(255.0 * components[2])); // blue 141} 142 143// Converts ARGB to CGColorRef. 144CGColorRef CGColorCreateFromSkColor(SkColor color) { 145 return CGColorCreateGenericRGB(SkColorGetR(color) / 255.0, 146 SkColorGetG(color) / 255.0, 147 SkColorGetB(color) / 255.0, 148 SkColorGetA(color) / 255.0); 149} 150 151// Converts NSColor to ARGB 152SkColor NSDeviceColorToSkColor(NSColor* color) { 153 DCHECK([color colorSpace] == [NSColorSpace genericRGBColorSpace] || 154 [color colorSpace] == [NSColorSpace deviceRGBColorSpace]); 155 CGFloat red, green, blue, alpha; 156 color = [color colorUsingColorSpace:[NSColorSpace deviceRGBColorSpace]]; 157 [color getRed:&red green:&green blue:&blue alpha:&alpha]; 158 return SkColorSetARGB(SkScalarRound(255.0 * alpha), 159 SkScalarRound(255.0 * red), 160 SkScalarRound(255.0 * green), 161 SkScalarRound(255.0 * blue)); 162} 163 164// Converts ARGB to NSColor. 165NSColor* SkColorToCalibratedNSColor(SkColor color) { 166 return [NSColor colorWithCalibratedRed:SkColorGetR(color) / 255.0 167 green:SkColorGetG(color) / 255.0 168 blue:SkColorGetB(color) / 255.0 169 alpha:SkColorGetA(color) / 255.0]; 170} 171 172NSColor* SkColorToDeviceNSColor(SkColor color) { 173 return [NSColor colorWithDeviceRed:SkColorGetR(color) / 255.0 174 green:SkColorGetG(color) / 255.0 175 blue:SkColorGetB(color) / 255.0 176 alpha:SkColorGetA(color) / 255.0]; 177} 178 179NSColor* SkColorToSRGBNSColor(SkColor color) { 180 const CGFloat components[] = { 181 SkColorGetR(color) / 255.0, 182 SkColorGetG(color) / 255.0, 183 SkColorGetB(color) / 255.0, 184 SkColorGetA(color) / 255.0 185 }; 186 return [NSColor colorWithColorSpace:[NSColorSpace sRGBColorSpace] 187 components:components 188 count:4]; 189} 190 191SkBitmap CGImageToSkBitmap(CGImageRef image) { 192 if (!image) 193 return SkBitmap(); 194 195 int width = CGImageGetWidth(image); 196 int height = CGImageGetHeight(image); 197 198 scoped_ptr<SkBaseDevice> device( 199 skia::BitmapPlatformDevice::Create(NULL, width, height, false)); 200 201 CGContextRef context = skia::GetBitmapContext(device.get()); 202 203 // We need to invert the y-axis of the canvas so that Core Graphics drawing 204 // happens right-side up. Skia has an upper-left origin and CG has a lower- 205 // left one. 206 CGContextScaleCTM(context, 1.0, -1.0); 207 CGContextTranslateCTM(context, 0, -height); 208 209 // We want to copy transparent pixels from |image|, instead of blending it 210 // onto uninitialized pixels. 211 CGContextSetBlendMode(context, kCGBlendModeCopy); 212 213 CGRect rect = CGRectMake(0, 0, width, height); 214 CGContextDrawImage(context, rect, image); 215 216 // Because |device| will be cleaned up and will take its pixels with it, we 217 // copy it to the stack and return it. 218 SkBitmap bitmap = device->accessBitmap(false); 219 220 return bitmap; 221} 222 223SkBitmap NSImageToSkBitmapWithColorSpace( 224 NSImage* image, bool is_opaque, CGColorSpaceRef color_space) { 225 return NSImageOrNSImageRepToSkBitmapWithColorSpace( 226 image, nil, [image size], is_opaque, color_space); 227} 228 229SkBitmap NSImageRepToSkBitmapWithColorSpace(NSImageRep* image_rep, 230 NSSize size, 231 bool is_opaque, 232 CGColorSpaceRef color_space) { 233 return NSImageOrNSImageRepToSkBitmapWithColorSpace( 234 nil, image_rep, size, is_opaque, color_space); 235} 236 237NSBitmapImageRep* SkBitmapToNSBitmapImageRep(const SkBitmap& skiaBitmap) { 238 base::ScopedCFTypeRef<CGColorSpaceRef> color_space( 239 CGColorSpaceCreateWithName(kCGColorSpaceGenericRGB)); 240 return SkBitmapToNSBitmapImageRepWithColorSpace(skiaBitmap, color_space); 241} 242 243NSBitmapImageRep* SkBitmapToNSBitmapImageRepWithColorSpace( 244 const SkBitmap& skiaBitmap, 245 CGColorSpaceRef colorSpace) { 246 // First convert SkBitmap to CGImageRef. 247 base::ScopedCFTypeRef<CGImageRef> cgimage( 248 SkCreateCGImageRefWithColorspace(skiaBitmap, colorSpace)); 249 250 // Now convert to NSBitmapImageRep. 251 base::scoped_nsobject<NSBitmapImageRep> bitmap( 252 [[NSBitmapImageRep alloc] initWithCGImage:cgimage]); 253 return [bitmap.release() autorelease]; 254} 255 256NSImage* SkBitmapToNSImageWithColorSpace(const SkBitmap& skiaBitmap, 257 CGColorSpaceRef colorSpace) { 258 if (skiaBitmap.isNull()) 259 return nil; 260 261 base::scoped_nsobject<NSImage> image([[NSImage alloc] init]); 262 [image addRepresentation: 263 SkBitmapToNSBitmapImageRepWithColorSpace(skiaBitmap, colorSpace)]; 264 [image setSize:NSMakeSize(skiaBitmap.width(), skiaBitmap.height())]; 265 return [image.release() autorelease]; 266} 267 268NSImage* SkBitmapToNSImage(const SkBitmap& skiaBitmap) { 269 base::ScopedCFTypeRef<CGColorSpaceRef> colorSpace( 270 CGColorSpaceCreateWithName(kCGColorSpaceGenericRGB)); 271 return SkBitmapToNSImageWithColorSpace(skiaBitmap, colorSpace.get()); 272} 273 274SkiaBitLocker::SkiaBitLocker(SkCanvas* canvas) 275 : canvas_(canvas), 276 cgContext_(0) { 277} 278 279SkiaBitLocker::~SkiaBitLocker() { 280 releaseIfNeeded(); 281} 282 283// This must be called to balance calls to cgContext 284void SkiaBitLocker::releaseIfNeeded() { 285 if (!cgContext_) 286 return; 287 if (useDeviceBits_) { 288 bitmap_.unlockPixels(); 289 } else { 290 // Find the bits that were drawn to. 291 SkAutoLockPixels lockedPixels(bitmap_); 292 const uint32_t* pixelBase 293 = reinterpret_cast<uint32_t*>(bitmap_.getPixels()); 294 int rowPixels = bitmap_.rowBytesAsPixels(); 295 int width = bitmap_.width(); 296 int height = bitmap_.height(); 297 SkIRect bounds; 298 bounds.fTop = 0; 299 int x; 300 int y = -1; 301 const uint32_t* pixels = pixelBase; 302 while (++y < height) { 303 for (x = 0; x < width; ++x) { 304 if (pixels[x]) { 305 bounds.fTop = y; 306 goto foundTop; 307 } 308 } 309 pixels += rowPixels; 310 } 311foundTop: 312 bounds.fBottom = height; 313 y = height; 314 pixels = pixelBase + rowPixels * (y - 1); 315 while (--y > bounds.fTop) { 316 for (x = 0; x < width; ++x) { 317 if (pixels[x]) { 318 bounds.fBottom = y + 1; 319 goto foundBottom; 320 } 321 } 322 pixels -= rowPixels; 323 } 324foundBottom: 325 bounds.fLeft = 0; 326 x = -1; 327 while (++x < width) { 328 pixels = pixelBase + rowPixels * bounds.fTop; 329 for (y = bounds.fTop; y < bounds.fBottom; ++y) { 330 if (pixels[x]) { 331 bounds.fLeft = x; 332 goto foundLeft; 333 } 334 pixels += rowPixels; 335 } 336 } 337foundLeft: 338 bounds.fRight = width; 339 x = width; 340 while (--x > bounds.fLeft) { 341 pixels = pixelBase + rowPixels * bounds.fTop; 342 for (y = bounds.fTop; y < bounds.fBottom; ++y) { 343 if (pixels[x]) { 344 bounds.fRight = x + 1; 345 goto foundRight; 346 } 347 pixels += rowPixels; 348 } 349 } 350foundRight: 351 SkBitmap subset; 352 if (!bitmap_.extractSubset(&subset, bounds)) { 353 return; 354 } 355 // Neutralize the global matrix by concatenating the inverse. In the 356 // future, Skia may provide some mechanism to set the device portion of 357 // the matrix to identity without clobbering any hosting matrix (e.g., the 358 // picture's matrix). 359 const SkMatrix& skMatrix = canvas_->getTotalMatrix(); 360 SkMatrix inverse; 361 if (!skMatrix.invert(&inverse)) 362 return; 363 canvas_->save(); 364 canvas_->concat(inverse); 365 canvas_->drawBitmap(subset, bounds.fLeft, bounds.fTop); 366 canvas_->restore(); 367 } 368 CGContextRelease(cgContext_); 369 cgContext_ = 0; 370} 371 372CGContextRef SkiaBitLocker::cgContext() { 373 SkBaseDevice* device = canvas_->getTopDevice(); 374 DCHECK(device); 375 if (!device) 376 return 0; 377 releaseIfNeeded(); // This flushes any prior bitmap use 378 const SkBitmap& deviceBits = device->accessBitmap(true); 379 useDeviceBits_ = deviceBits.getPixels(); 380 if (useDeviceBits_) { 381 bitmap_ = deviceBits; 382 bitmap_.lockPixels(); 383 } else { 384 bitmap_.setConfig( 385 SkBitmap::kARGB_8888_Config, deviceBits.width(), deviceBits.height()); 386 bitmap_.allocPixels(); 387 bitmap_.eraseColor(0); 388 } 389 base::ScopedCFTypeRef<CGColorSpaceRef> colorSpace( 390 CGColorSpaceCreateDeviceRGB()); 391 cgContext_ = CGBitmapContextCreate(bitmap_.getPixels(), bitmap_.width(), 392 bitmap_.height(), 8, bitmap_.rowBytes(), colorSpace, 393 kCGBitmapByteOrder32Host | kCGImageAlphaPremultipliedFirst); 394 395 // Apply device matrix. 396 CGAffineTransform contentsTransform = CGAffineTransformMakeScale(1, -1); 397 contentsTransform = CGAffineTransformTranslate(contentsTransform, 0, 398 -device->height()); 399 CGContextConcatCTM(cgContext_, contentsTransform); 400 401 const SkIPoint& pt = device->getOrigin(); 402 // Skip applying the clip when not writing directly to device. 403 // They're applied in the offscreen case when the bitmap is drawn. 404 if (useDeviceBits_) { 405 // Apply clip in device coordinates. 406 CGMutablePathRef clipPath = CGPathCreateMutable(); 407 const SkRegion& clipRgn = canvas_->getTotalClip(); 408 if (clipRgn.isEmpty()) { 409 // CoreGraphics does not consider a newly created path to be empty. 410 // Explicitly set it to empty so the subsequent drawing is clipped out. 411 // It would be better to make the CGContext hidden if there was a CG 412 // call that does that. 413 CGPathAddRect(clipPath, 0, CGRectMake(0, 0, 0, 0)); 414 } 415 SkRegion::Iterator iter(clipRgn); 416 const SkIPoint& pt = device->getOrigin(); 417 for (; !iter.done(); iter.next()) { 418 SkIRect skRect = iter.rect(); 419 skRect.offset(-pt); 420 CGRect cgRect = SkIRectToCGRect(skRect); 421 CGPathAddRect(clipPath, 0, cgRect); 422 } 423 CGContextAddPath(cgContext_, clipPath); 424 CGContextClip(cgContext_); 425 CGPathRelease(clipPath); 426 } 427 428 // Apply content matrix. 429 SkMatrix skMatrix = canvas_->getTotalMatrix(); 430 skMatrix.postTranslate(-SkIntToScalar(pt.fX), -SkIntToScalar(pt.fY)); 431 CGAffineTransform affine = SkMatrixToCGAffineTransform(skMatrix); 432 CGContextConcatCTM(cgContext_, affine); 433 434 return cgContext_; 435} 436 437} // namespace gfx 438