| 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 |