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/bitmap_platform_device_mac.h"
      6 
      7 #import <ApplicationServices/ApplicationServices.h>
      8 #include <time.h>
      9 
     10 #include "base/mac/mac_util.h"
     11 #include "base/memory/ref_counted.h"
     12 #include "skia/ext/bitmap_platform_device.h"
     13 #include "skia/ext/platform_canvas.h"
     14 #include "skia/ext/skia_utils_mac.h"
     15 #include "third_party/skia/include/core/SkMatrix.h"
     16 #include "third_party/skia/include/core/SkRegion.h"
     17 #include "third_party/skia/include/core/SkTypes.h"
     18 #include "third_party/skia/include/core/SkUtils.h"
     19 
     20 namespace skia {
     21 
     22 namespace {
     23 
     24 static CGContextRef CGContextForData(void* data, int width, int height) {
     25 #define HAS_ARGB_SHIFTS(a, r, g, b) \
     26             (SK_A32_SHIFT == (a) && SK_R32_SHIFT == (r) \
     27              && SK_G32_SHIFT == (g) && SK_B32_SHIFT == (b))
     28 #if defined(SK_CPU_LENDIAN) && HAS_ARGB_SHIFTS(24, 16, 8, 0)
     29   // Allocate a bitmap context with 4 components per pixel (BGRA).  Apple
     30   // recommends these flags for improved CG performance.
     31 
     32   // CGBitmapContextCreate returns NULL if width/height are 0. However, our
     33   // callers expect to get a canvas back (which they later resize/reallocate)
     34   // so we pin the dimensions here.
     35   width = SkMax32(1, width);
     36   height = SkMax32(1, height);
     37   CGContextRef context =
     38       CGBitmapContextCreate(data, width, height, 8, width * 4,
     39                             base::mac::GetSystemColorSpace(),
     40                             kCGImageAlphaPremultipliedFirst |
     41                                 kCGBitmapByteOrder32Host);
     42 #else
     43 #error We require that Skia's and CoreGraphics's recommended \
     44        image memory layout match.
     45 #endif
     46 #undef HAS_ARGB_SHIFTS
     47 
     48   if (!context)
     49     return NULL;
     50 
     51   // Change the coordinate system to match WebCore's
     52   CGContextTranslateCTM(context, 0, height);
     53   CGContextScaleCTM(context, 1.0, -1.0);
     54 
     55   return context;
     56 }
     57 
     58 }  // namespace
     59 
     60 void BitmapPlatformDevice::ReleaseBitmapContext() {
     61   SkASSERT(bitmap_context_);
     62   CGContextRelease(bitmap_context_);
     63   bitmap_context_ = NULL;
     64 }
     65 
     66 void BitmapPlatformDevice::SetMatrixClip(
     67     const SkMatrix& transform,
     68     const SkRegion& region) {
     69   transform_ = transform;
     70   clip_region_ = region;
     71   config_dirty_ = true;
     72 }
     73 
     74 void BitmapPlatformDevice::LoadConfig() {
     75   if (!config_dirty_ || !bitmap_context_)
     76     return;  // Nothing to do.
     77   config_dirty_ = false;
     78 
     79   // We must restore and then save the state of the graphics context since the
     80   // calls to Load the clipping region to the context are strictly cummulative,
     81   // i.e., you can't replace a clip rect, other than with a save/restore.
     82   // But this implies that no other changes to the state are done elsewhere.
     83   // If we ever get to need to change this, then we must replace the clip rect
     84   // calls in LoadClippingRegionToCGContext() with an image mask instead.
     85   CGContextRestoreGState(bitmap_context_);
     86   CGContextSaveGState(bitmap_context_);
     87   LoadTransformToCGContext(bitmap_context_, transform_);
     88   LoadClippingRegionToCGContext(bitmap_context_, clip_region_, transform_);
     89 }
     90 
     91 
     92 // We use this static factory function instead of the regular constructor so
     93 // that we can create the pixel data before calling the constructor. This is
     94 // required so that we can call the base class' constructor with the pixel
     95 // data.
     96 BitmapPlatformDevice* BitmapPlatformDevice::Create(CGContextRef context,
     97                                                    int width,
     98                                                    int height,
     99                                                    bool is_opaque) {
    100   if (RasterDeviceTooBigToAllocate(width, height))
    101     return NULL;
    102 
    103   SkBitmap bitmap;
    104   // TODO: verify that the CG Context's pixels will have tight rowbytes or pass in the correct
    105   // rowbytes for the case when context != NULL.
    106   bitmap.setInfo(SkImageInfo::MakeN32(width, height, is_opaque ? kOpaque_SkAlphaType : kPremul_SkAlphaType));
    107 
    108   void* data;
    109   if (context) {
    110     data = CGBitmapContextGetData(context);
    111     bitmap.setPixels(data);
    112   } else {
    113     if (!bitmap.allocPixels())
    114       return NULL;
    115     data = bitmap.getPixels();
    116   }
    117 
    118   // If we were given data, then don't clobber it!
    119 #ifndef NDEBUG
    120   if (!context && is_opaque) {
    121     // To aid in finding bugs, we set the background color to something
    122     // obviously wrong so it will be noticable when it is not cleared
    123     bitmap.eraseARGB(255, 0, 255, 128);  // bright bluish green
    124   }
    125 #endif
    126 
    127   if (!context) {
    128     context = CGContextForData(data, width, height);
    129     if (!context)
    130       return NULL;
    131   } else
    132     CGContextRetain(context);
    133 
    134   BitmapPlatformDevice* rv = new BitmapPlatformDevice(context, bitmap);
    135 
    136   // The device object took ownership of the graphics context with its own
    137   // CGContextRetain call.
    138   CGContextRelease(context);
    139 
    140   return rv;
    141 }
    142 
    143 BitmapPlatformDevice* BitmapPlatformDevice::CreateAndClear(int width,
    144                                                            int height,
    145                                                            bool is_opaque) {
    146   BitmapPlatformDevice* device = Create(NULL, width, height, is_opaque);
    147   if (!is_opaque)
    148     device->clear(0);
    149   return device;
    150 }
    151 
    152 BitmapPlatformDevice* BitmapPlatformDevice::CreateWithData(uint8_t* data,
    153                                                            int width,
    154                                                            int height,
    155                                                            bool is_opaque) {
    156   CGContextRef context = NULL;
    157   if (data)
    158     context = CGContextForData(data, width, height);
    159 
    160   BitmapPlatformDevice* rv = Create(context, width, height, is_opaque);
    161 
    162   // The device object took ownership of the graphics context with its own
    163   // CGContextRetain call.
    164   if (context)
    165     CGContextRelease(context);
    166 
    167   return rv;
    168 }
    169 
    170 // The device will own the bitmap, which corresponds to also owning the pixel
    171 // data. Therefore, we do not transfer ownership to the SkBitmapDevice's bitmap.
    172 BitmapPlatformDevice::BitmapPlatformDevice(
    173     CGContextRef context, const SkBitmap& bitmap)
    174     : SkBitmapDevice(bitmap),
    175       bitmap_context_(context),
    176       config_dirty_(true),  // Want to load the config next time.
    177       transform_(SkMatrix::I()) {
    178   SetPlatformDevice(this, this);
    179   SkASSERT(bitmap_context_);
    180   // Initialize the clip region to the entire bitmap.
    181 
    182   SkIRect rect;
    183   rect.set(0, 0,
    184            CGBitmapContextGetWidth(bitmap_context_),
    185            CGBitmapContextGetHeight(bitmap_context_));
    186   clip_region_ = SkRegion(rect);
    187   CGContextRetain(bitmap_context_);
    188   // We must save the state once so that we can use the restore/save trick
    189   // in LoadConfig().
    190   CGContextSaveGState(bitmap_context_);
    191 }
    192 
    193 BitmapPlatformDevice::~BitmapPlatformDevice() {
    194   if (bitmap_context_)
    195     CGContextRelease(bitmap_context_);
    196 }
    197 
    198 CGContextRef BitmapPlatformDevice::GetBitmapContext() {
    199   LoadConfig();
    200   return bitmap_context_;
    201 }
    202 
    203 void BitmapPlatformDevice::setMatrixClip(const SkMatrix& transform,
    204                                          const SkRegion& region,
    205                                          const SkClipStack&) {
    206   SetMatrixClip(transform, region);
    207 }
    208 
    209 void BitmapPlatformDevice::DrawToNativeContext(CGContextRef context, int x,
    210                                                int y, const CGRect* src_rect) {
    211   bool created_dc = false;
    212   if (!bitmap_context_) {
    213     created_dc = true;
    214     GetBitmapContext();
    215   }
    216 
    217   // this should not make a copy of the bits, since we're not doing
    218   // anything to trigger copy on write
    219   CGImageRef image = CGBitmapContextCreateImage(bitmap_context_);
    220   CGRect bounds;
    221   bounds.origin.x = x;
    222   bounds.origin.y = y;
    223   if (src_rect) {
    224     bounds.size.width = src_rect->size.width;
    225     bounds.size.height = src_rect->size.height;
    226     CGImageRef sub_image = CGImageCreateWithImageInRect(image, *src_rect);
    227     CGContextDrawImage(context, bounds, sub_image);
    228     CGImageRelease(sub_image);
    229   } else {
    230     bounds.size.width = width();
    231     bounds.size.height = height();
    232     CGContextDrawImage(context, bounds, image);
    233   }
    234   CGImageRelease(image);
    235 
    236   if (created_dc)
    237     ReleaseBitmapContext();
    238 }
    239 
    240 SkBaseDevice* BitmapPlatformDevice::onCreateDevice(const SkImageInfo& info,
    241                                                    Usage /*usage*/) {
    242   SkASSERT(info.colorType() == kPMColor_SkColorType);
    243   return BitmapPlatformDevice::CreateAndClear(info.width(), info.height(),
    244                                                 info.isOpaque());
    245 }
    246 
    247 // PlatformCanvas impl
    248 
    249 SkCanvas* CreatePlatformCanvas(CGContextRef ctx, int width, int height,
    250                                bool is_opaque, OnFailureType failureType) {
    251   skia::RefPtr<SkBaseDevice> dev = skia::AdoptRef(
    252       BitmapPlatformDevice::Create(ctx, width, height, is_opaque));
    253   return CreateCanvas(dev, failureType);
    254 }
    255 
    256 SkCanvas* CreatePlatformCanvas(int width, int height, bool is_opaque,
    257                                uint8_t* data, OnFailureType failureType) {
    258   skia::RefPtr<SkBaseDevice> dev = skia::AdoptRef(
    259       BitmapPlatformDevice::CreateWithData(data, width, height, is_opaque));
    260   return CreateCanvas(dev, failureType);
    261 }
    262 
    263 // Port of PlatformBitmap to mac
    264 
    265 PlatformBitmap::~PlatformBitmap() {
    266   if (surface_)
    267     CGContextRelease(surface_);
    268 }
    269 
    270 bool PlatformBitmap::Allocate(int width, int height, bool is_opaque) {
    271   if (RasterDeviceTooBigToAllocate(width, height))
    272     return false;
    273 
    274   if (!bitmap_.allocN32Pixels(width, height, is_opaque))
    275     return false;
    276 
    277   if (!is_opaque)
    278     bitmap_.eraseColor(0);
    279 
    280   surface_ = CGContextForData(bitmap_.getPixels(), bitmap_.width(),
    281                               bitmap_.height());
    282   return true;
    283 }
    284 
    285 }  // namespace skia
    286