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