// Copyright (c) 2012 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "skia/ext/bitmap_platform_device_mac.h" #import #include #include #include "base/mac/mac_util.h" #include "base/memory/ref_counted.h" #include "skia/ext/bitmap_platform_device.h" #include "skia/ext/platform_canvas.h" #include "skia/ext/skia_utils_mac.h" #include "third_party/skia/include/core/SkMatrix.h" #include "third_party/skia/include/core/SkPath.h" #include "third_party/skia/include/core/SkRegion.h" #include "third_party/skia/include/core/SkTypes.h" #include "third_party/skia/include/core/SkUtils.h" namespace skia { namespace { static CGContextRef CGContextForData(void* data, int width, int height) { #define HAS_ARGB_SHIFTS(a, r, g, b) \ (SK_A32_SHIFT == (a) && SK_R32_SHIFT == (r) \ && SK_G32_SHIFT == (g) && SK_B32_SHIFT == (b)) #if defined(SK_CPU_LENDIAN) && HAS_ARGB_SHIFTS(24, 16, 8, 0) // Allocate a bitmap context with 4 components per pixel (BGRA). Apple // recommends these flags for improved CG performance. // CGBitmapContextCreate returns NULL if width/height are 0. However, our // callers expect to get a canvas back (which they later resize/reallocate) // so we pin the dimensions here. width = SkMax32(1, width); height = SkMax32(1, height); CGContextRef context = CGBitmapContextCreate(data, width, height, 8, width * 4, base::mac::GetSystemColorSpace(), kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Host); #else #error We require that Skia's and CoreGraphics's recommended \ image memory layout match. #endif #undef HAS_ARGB_SHIFTS if (!context) return NULL; // Change the coordinate system to match WebCore's CGContextTranslateCTM(context, 0, height); CGContextScaleCTM(context, 1.0, -1.0); return context; } } // namespace void BitmapPlatformDevice::ReleaseBitmapContext() { SkASSERT(bitmap_context_); CGContextRelease(bitmap_context_); bitmap_context_ = NULL; } void BitmapPlatformDevice::SetMatrixClip( const SkMatrix& transform, const SkRegion& region) { transform_ = transform; clip_region_ = region; config_dirty_ = true; } // Loads the specified Skia transform into the device context static void LoadTransformToCGContext(CGContextRef context, const SkMatrix& matrix) { // CoreGraphics can concatenate transforms, but not reset the current one. // So in order to get the required behavior here, we need to first make // the current transformation matrix identity and only then load the new one. // Reset matrix to identity. CGAffineTransform orig_cg_matrix = CGContextGetCTM(context); CGAffineTransform orig_cg_matrix_inv = CGAffineTransformInvert(orig_cg_matrix); CGContextConcatCTM(context, orig_cg_matrix_inv); // assert that we have indeed returned to the identity Matrix. SkASSERT(CGAffineTransformIsIdentity(CGContextGetCTM(context))); // Convert xform to CG-land. // Our coordinate system is flipped to match WebKit's so we need to modify // the xform to match that. SkMatrix transformed_matrix = matrix; SkScalar sy = -matrix.getScaleY(); transformed_matrix.setScaleY(sy); size_t height = CGBitmapContextGetHeight(context); SkScalar ty = -matrix.getTranslateY(); // y axis is flipped. transformed_matrix.setTranslateY(ty + (SkScalar)height); CGAffineTransform cg_matrix = skia::SkMatrixToCGAffineTransform(transformed_matrix); // Load final transform into context. CGContextConcatCTM(context, cg_matrix); } // Loads a SkRegion into the CG context. static void LoadClippingRegionToCGContext(CGContextRef context, const SkRegion& region, const SkMatrix& transformation) { if (region.isEmpty()) { // region can be empty, in which case everything will be clipped. SkRect rect; rect.setEmpty(); CGContextClipToRect(context, skia::SkRectToCGRect(rect)); } else if (region.isRect()) { // CoreGraphics applies the current transform to clip rects, which is // unwanted. Inverse-transform the rect before sending it to CG. This only // works for translations and scaling, but not for rotations (but the // viewport is never rotated anyway). SkMatrix t; bool did_invert = transformation.invert(&t); if (!did_invert) t.reset(); // Do the transformation. SkRect rect; rect.set(region.getBounds()); t.mapRect(&rect); SkIRect irect; rect.round(&irect); CGContextClipToRect(context, skia::SkIRectToCGRect(irect)); } else { // It is complex. SkPath path; region.getBoundaryPath(&path); // Clip. Note that windows clipping regions are not affected by the // transform so apply it manually. path.transform(transformation); // TODO(playmobil): Implement. SkASSERT(false); // LoadPathToDC(context, path); // hrgn = PathToRegion(context); } } void BitmapPlatformDevice::LoadConfig() { if (!config_dirty_ || !bitmap_context_) return; // Nothing to do. config_dirty_ = false; // We must restore and then save the state of the graphics context since the // calls to Load the clipping region to the context are strictly cummulative, // i.e., you can't replace a clip rect, other than with a save/restore. // But this implies that no other changes to the state are done elsewhere. // If we ever get to need to change this, then we must replace the clip rect // calls in LoadClippingRegionToCGContext() with an image mask instead. CGContextRestoreGState(bitmap_context_); CGContextSaveGState(bitmap_context_); LoadTransformToCGContext(bitmap_context_, transform_); LoadClippingRegionToCGContext(bitmap_context_, clip_region_, transform_); } // We use this static factory function instead of the regular constructor so // that we can create the pixel data before calling the constructor. This is // required so that we can call the base class' constructor with the pixel // data. BitmapPlatformDevice* BitmapPlatformDevice::Create(CGContextRef context, int width, int height, bool is_opaque, bool do_clear) { if (RasterDeviceTooBigToAllocate(width, height)) return NULL; SkBitmap bitmap; // TODO: verify that the CG Context's pixels will have tight rowbytes or pass in the correct // rowbytes for the case when context != NULL. bitmap.setInfo(SkImageInfo::MakeN32(width, height, is_opaque ? kOpaque_SkAlphaType : kPremul_SkAlphaType)); void* data; if (context) { data = CGBitmapContextGetData(context); bitmap.setPixels(data); } else { if (!bitmap.tryAllocPixels()) return NULL; data = bitmap.getPixels(); } if (do_clear) memset(data, 0, bitmap.getSafeSize()); // If we were given data, then don't clobber it! #ifndef NDEBUG if (!context && is_opaque) { // To aid in finding bugs, we set the background color to something // obviously wrong so it will be noticable when it is not cleared bitmap.eraseARGB(255, 0, 255, 128); // bright bluish green } #endif if (!context) { context = CGContextForData(data, width, height); if (!context) return NULL; } else CGContextRetain(context); BitmapPlatformDevice* rv = new BitmapPlatformDevice(context, bitmap); // The device object took ownership of the graphics context with its own // CGContextRetain call. CGContextRelease(context); return rv; } BitmapPlatformDevice* BitmapPlatformDevice::CreateWithData(uint8_t* data, int width, int height, bool is_opaque) { CGContextRef context = NULL; if (data) context = CGContextForData(data, width, height); BitmapPlatformDevice* rv = Create(context, width, height, is_opaque, false); // The device object took ownership of the graphics context with its own // CGContextRetain call. if (context) CGContextRelease(context); return rv; } // The device will own the bitmap, which corresponds to also owning the pixel // data. Therefore, we do not transfer ownership to the SkBitmapDevice's bitmap. BitmapPlatformDevice::BitmapPlatformDevice( CGContextRef context, const SkBitmap& bitmap) : SkBitmapDevice(bitmap), bitmap_context_(context), config_dirty_(true), // Want to load the config next time. transform_(SkMatrix::I()) { SetPlatformDevice(this, this); SkASSERT(bitmap_context_); // Initialize the clip region to the entire bitmap. SkIRect rect; rect.set(0, 0, CGBitmapContextGetWidth(bitmap_context_), CGBitmapContextGetHeight(bitmap_context_)); clip_region_ = SkRegion(rect); CGContextRetain(bitmap_context_); // We must save the state once so that we can use the restore/save trick // in LoadConfig(). CGContextSaveGState(bitmap_context_); } BitmapPlatformDevice::~BitmapPlatformDevice() { if (bitmap_context_) CGContextRelease(bitmap_context_); } CGContextRef BitmapPlatformDevice::GetBitmapContext() { LoadConfig(); return bitmap_context_; } void BitmapPlatformDevice::setMatrixClip(const SkMatrix& transform, const SkRegion& region, const SkClipStack&) { SetMatrixClip(transform, region); } SkBaseDevice* BitmapPlatformDevice::onCreateDevice(const CreateInfo& cinfo, const SkPaint*) { const SkImageInfo& info = cinfo.fInfo; const bool do_clear = !info.isOpaque(); SkASSERT(info.colorType() == kN32_SkColorType); return Create(NULL, info.width(), info.height(), info.isOpaque(), do_clear); } // PlatformCanvas impl SkCanvas* CreatePlatformCanvas(CGContextRef ctx, int width, int height, bool is_opaque, OnFailureType failureType) { const bool do_clear = false; skia::RefPtr dev = skia::AdoptRef( BitmapPlatformDevice::Create(ctx, width, height, is_opaque, do_clear)); return CreateCanvas(dev, failureType); } SkCanvas* CreatePlatformCanvas(int width, int height, bool is_opaque, uint8_t* data, OnFailureType failureType) { skia::RefPtr dev = skia::AdoptRef( BitmapPlatformDevice::CreateWithData(data, width, height, is_opaque)); return CreateCanvas(dev, failureType); } } // namespace skia