Home | History | Annotate | Download | only in ext
      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