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
17namespace {
18
19// Draws an NSImage or an NSImageRep with a given size into a SkBitmap.
20SkBitmap 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
87namespace gfx {
88
89CGAffineTransform 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
104SkRect 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
111CGRect 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
119CGRect 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.
128SkColor 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.
138CGColorRef 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
146SkColor 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.
159NSColor* 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
166NSColor* 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
173NSColor* 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
185SkBitmap 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
217SkBitmap NSImageToSkBitmapWithColorSpace(
218    NSImage* image, bool is_opaque, CGColorSpaceRef color_space) {
219  return NSImageOrNSImageRepToSkBitmapWithColorSpace(
220      image, nil, [image size], is_opaque, color_space);
221}
222
223SkBitmap 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
231NSBitmapImageRep* SkBitmapToNSBitmapImageRep(const SkBitmap& skiaBitmap) {
232  base::ScopedCFTypeRef<CGColorSpaceRef> color_space(
233      CGColorSpaceCreateWithName(kCGColorSpaceGenericRGB));
234  return SkBitmapToNSBitmapImageRepWithColorSpace(skiaBitmap, color_space);
235}
236
237NSBitmapImageRep* 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
250NSImage* 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
262NSImage* SkBitmapToNSImage(const SkBitmap& skiaBitmap) {
263  base::ScopedCFTypeRef<CGColorSpaceRef> colorSpace(
264      CGColorSpaceCreateWithName(kCGColorSpaceGenericRGB));
265  return SkBitmapToNSImageWithColorSpace(skiaBitmap, colorSpace.get());
266}
267
268SkiaBitLocker::SkiaBitLocker(SkCanvas* canvas)
269    : canvas_(canvas),
270      userClipRectSpecified_(false),
271      cgContext_(0),
272      bitmapScaleFactor_(1),
273      useDeviceBits_(false),
274      bitmapIsDummy_(false) {
275}
276
277SkiaBitLocker::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
290SkiaBitLocker::~SkiaBitLocker() {
291  releaseIfNeeded();
292  if (userClipRectSpecified_)
293    canvas_->restore();
294}
295
296SkIRect 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  }
323foundTop:
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  }
336foundBottom:
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  }
349foundLeft:
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  }
362foundRight:
363  return bounds;
364}
365
366// This must be called to balance calls to cgContext
367void 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    // Neutralize the global matrix by concatenating the inverse. In the
380    // future, Skia may provide some mechanism to set the device portion of
381    // the matrix to identity without clobbering any hosting matrix (e.g., the
382    // picture's matrix).
383    const SkMatrix& skMatrix = canvas_->getTotalMatrix();
384    SkMatrix inverse;
385    if (!skMatrix.invert(&inverse))
386      return;
387    canvas_->save();
388    canvas_->concat(inverse);
389    canvas_->translate(bounds.x() + bitmapOffset_.x(),
390                       bounds.y() + bitmapOffset_.y());
391    canvas_->scale(1.f / bitmapScaleFactor_, 1.f / bitmapScaleFactor_);
392    canvas_->drawBitmap(subset, 0, 0);
393    canvas_->restore();
394  }
395  CGContextRelease(cgContext_);
396  cgContext_ = 0;
397  useDeviceBits_ = false;
398  bitmapIsDummy_ = false;
399}
400
401CGContextRef SkiaBitLocker::cgContext() {
402  SkIRect clip_bounds;
403  if (!canvas_->getClipDeviceBounds(&clip_bounds)) {
404    // If the clip is empty, then there is nothing to draw. The caller may
405    // attempt to draw (to-be-clipped) results, so ensure there is a dummy
406    // non-NULL CGContext to use.
407    bitmapIsDummy_ = true;
408    clip_bounds = SkIRect::MakeXYWH(0, 0, 1, 1);
409  }
410
411  SkBaseDevice* device = canvas_->getTopDevice();
412  DCHECK(device);
413  if (!device)
414    return 0;
415
416  releaseIfNeeded(); // This flushes any prior bitmap use
417
418  // remember the top/left, in case we need to compose this later
419  bitmapOffset_.set(clip_bounds.x(), clip_bounds.y());
420
421  // Now make clip_bounds be relative to the current layer/device
422  clip_bounds.offset(-device->getOrigin());
423
424  const SkBitmap& deviceBits = device->accessBitmap(true);
425
426  // Only draw directly if we have pixels, and we're only rect-clipped.
427  // If not, we allocate an offscreen and draw into that, relying on the
428  // compositing step to apply skia's clip.
429  useDeviceBits_ = deviceBits.getPixels() &&
430                   canvas_->isClipRect() &&
431                   !bitmapIsDummy_;
432  if (useDeviceBits_) {
433    bool result = deviceBits.extractSubset(&bitmap_, clip_bounds);
434    DCHECK(result);
435    if (!result)
436      return 0;
437    bitmap_.lockPixels();
438  } else {
439    bool result = bitmap_.tryAllocN32Pixels(
440        SkScalarCeilToInt(bitmapScaleFactor_ * clip_bounds.width()),
441        SkScalarCeilToInt(bitmapScaleFactor_ * clip_bounds.height()));
442    DCHECK(result);
443    if (!result)
444      return 0;
445    bitmap_.eraseColor(0);
446  }
447  base::ScopedCFTypeRef<CGColorSpaceRef> colorSpace(
448      CGColorSpaceCreateDeviceRGB());
449  cgContext_ = CGBitmapContextCreate(bitmap_.getPixels(), bitmap_.width(),
450    bitmap_.height(), 8, bitmap_.rowBytes(), colorSpace, 
451    kCGBitmapByteOrder32Host | kCGImageAlphaPremultipliedFirst);
452  DCHECK(cgContext_);
453
454  SkMatrix matrix = canvas_->getTotalMatrix();
455  matrix.postTranslate(-SkIntToScalar(bitmapOffset_.x()),
456                       -SkIntToScalar(bitmapOffset_.y()));
457  matrix.postScale(bitmapScaleFactor_, -bitmapScaleFactor_);
458  matrix.postTranslate(0, SkIntToScalar(bitmap_.height()));
459
460  CGContextConcatCTM(cgContext_, SkMatrixToCGAffineTransform(matrix));
461  
462  return cgContext_;
463}
464
465bool SkiaBitLocker::hasEmptyClipRegion() const {
466  return canvas_->isClipEmpty();
467}
468
469}  // namespace gfx
470