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.tryAllocPixels()) 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() == kN32_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_.tryAllocN32Pixels(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