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.allocN32Pixels(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 cgContext_(0) { 271 } 272 273 SkiaBitLocker::~SkiaBitLocker() { 274 releaseIfNeeded(); 275 } 276 277 // This must be called to balance calls to cgContext 278 void SkiaBitLocker::releaseIfNeeded() { 279 if (!cgContext_) 280 return; 281 if (useDeviceBits_) { 282 bitmap_.unlockPixels(); 283 } else { 284 // Find the bits that were drawn to. 285 SkAutoLockPixels lockedPixels(bitmap_); 286 const uint32_t* pixelBase 287 = reinterpret_cast<uint32_t*>(bitmap_.getPixels()); 288 int rowPixels = bitmap_.rowBytesAsPixels(); 289 int width = bitmap_.width(); 290 int height = bitmap_.height(); 291 SkIRect bounds; 292 bounds.fTop = 0; 293 int x; 294 int y = -1; 295 const uint32_t* pixels = pixelBase; 296 while (++y < height) { 297 for (x = 0; x < width; ++x) { 298 if (pixels[x]) { 299 bounds.fTop = y; 300 goto foundTop; 301 } 302 } 303 pixels += rowPixels; 304 } 305 foundTop: 306 bounds.fBottom = height; 307 y = height; 308 pixels = pixelBase + rowPixels * (y - 1); 309 while (--y > bounds.fTop) { 310 for (x = 0; x < width; ++x) { 311 if (pixels[x]) { 312 bounds.fBottom = y + 1; 313 goto foundBottom; 314 } 315 } 316 pixels -= rowPixels; 317 } 318 foundBottom: 319 bounds.fLeft = 0; 320 x = -1; 321 while (++x < width) { 322 pixels = pixelBase + rowPixels * bounds.fTop; 323 for (y = bounds.fTop; y < bounds.fBottom; ++y) { 324 if (pixels[x]) { 325 bounds.fLeft = x; 326 goto foundLeft; 327 } 328 pixels += rowPixels; 329 } 330 } 331 foundLeft: 332 bounds.fRight = width; 333 x = width; 334 while (--x > bounds.fLeft) { 335 pixels = pixelBase + rowPixels * bounds.fTop; 336 for (y = bounds.fTop; y < bounds.fBottom; ++y) { 337 if (pixels[x]) { 338 bounds.fRight = x + 1; 339 goto foundRight; 340 } 341 pixels += rowPixels; 342 } 343 } 344 foundRight: 345 SkBitmap subset; 346 if (!bitmap_.extractSubset(&subset, bounds)) { 347 return; 348 } 349 // Neutralize the global matrix by concatenating the inverse. In the 350 // future, Skia may provide some mechanism to set the device portion of 351 // the matrix to identity without clobbering any hosting matrix (e.g., the 352 // picture's matrix). 353 const SkMatrix& skMatrix = canvas_->getTotalMatrix(); 354 SkMatrix inverse; 355 if (!skMatrix.invert(&inverse)) 356 return; 357 canvas_->save(); 358 canvas_->concat(inverse); 359 canvas_->drawBitmap(subset, bounds.x() + bitmapOffset_.x(), 360 bounds.y() + bitmapOffset_.y()); 361 canvas_->restore(); 362 } 363 CGContextRelease(cgContext_); 364 cgContext_ = 0; 365 } 366 367 CGContextRef SkiaBitLocker::cgContext() { 368 SkIRect clip_bounds; 369 if (!canvas_->getClipDeviceBounds(&clip_bounds)) 370 return 0; // the clip is empty, nothing to draw 371 372 SkBaseDevice* device = canvas_->getTopDevice(); 373 DCHECK(device); 374 if (!device) 375 return 0; 376 377 releaseIfNeeded(); // This flushes any prior bitmap use 378 379 // remember the top/left, in case we need to compose this later 380 bitmapOffset_.set(clip_bounds.x(), clip_bounds.y()); 381 382 // Now make clip_bounds be relative to the current layer/device 383 clip_bounds.offset(-device->getOrigin()); 384 385 const SkBitmap& deviceBits = device->accessBitmap(true); 386 387 // Only draw directly if we have pixels, and we're only rect-clipped. 388 // If not, we allocate an offscreen and draw into that, relying on the 389 // compositing step to apply skia's clip. 390 useDeviceBits_ = deviceBits.getPixels() && canvas_->isClipRect(); 391 if (useDeviceBits_) { 392 if (!deviceBits.extractSubset(&bitmap_, clip_bounds)) 393 return 0; 394 bitmap_.lockPixels(); 395 } else { 396 if (!bitmap_.allocN32Pixels(clip_bounds.width(), clip_bounds.height())) 397 return 0; 398 bitmap_.eraseColor(0); 399 } 400 base::ScopedCFTypeRef<CGColorSpaceRef> colorSpace( 401 CGColorSpaceCreateDeviceRGB()); 402 cgContext_ = CGBitmapContextCreate(bitmap_.getPixels(), bitmap_.width(), 403 bitmap_.height(), 8, bitmap_.rowBytes(), colorSpace, 404 kCGBitmapByteOrder32Host | kCGImageAlphaPremultipliedFirst); 405 406 SkMatrix matrix = canvas_->getTotalMatrix(); 407 matrix.postTranslate(-SkIntToScalar(bitmapOffset_.x()), 408 -SkIntToScalar(bitmapOffset_.y())); 409 matrix.postScale(1, -1); 410 matrix.postTranslate(0, SkIntToScalar(bitmap_.height())); 411 412 CGContextConcatCTM(cgContext_, SkMatrixToCGAffineTransform(matrix)); 413 414 return cgContext_; 415 } 416 417 } // namespace gfx 418