1// Copyright (c) 2006-2008 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/platform_device.h"
6#include "skia/ext/bitmap_platform_device.h"
7
8#import <ApplicationServices/ApplicationServices.h>
9#include "skia/ext/skia_utils_mac.h"
10#include "third_party/skia/include/core/SkMatrix.h"
11#include "third_party/skia/include/core/SkPath.h"
12#include "third_party/skia/include/core/SkTypes.h"
13#include "third_party/skia/include/core/SkUtils.h"
14
15namespace skia {
16
17CGContextRef GetBitmapContext(SkBaseDevice* device) {
18  PlatformDevice* platform_device = GetPlatformDevice(device);
19  if (platform_device)
20    return platform_device->GetBitmapContext();
21
22  return NULL;
23}
24
25CGContextRef PlatformDevice::BeginPlatformPaint() {
26  return GetBitmapContext();
27}
28
29void PlatformDevice::EndPlatformPaint() {
30  // Flushing will be done in onAccessBitmap.
31}
32
33// Set up the CGContextRef for peaceful coexistence with Skia
34void PlatformDevice::InitializeCGContext(CGContextRef context) {
35  // CG defaults to the same settings as Skia
36}
37
38// static
39void PlatformDevice::LoadPathToCGContext(CGContextRef context,
40                                         const SkPath& path) {
41  // instead of a persistent attribute of the context, CG specifies the fill
42  // type per call, so we just have to load up the geometry.
43  CGContextBeginPath(context);
44
45  SkPoint points[4] = { {0, 0}, {0, 0}, {0, 0}, {0, 0} };
46  SkPath::Iter iter(path, false);
47  for (SkPath::Verb verb = iter.next(points); verb != SkPath::kDone_Verb;
48       verb = iter.next(points)) {
49    switch (verb) {
50      case SkPath::kMove_Verb: {  // iter.next returns 1 point
51        CGContextMoveToPoint(context, points[0].fX, points[0].fY);
52        break;
53      }
54      case SkPath::kLine_Verb: {  // iter.next returns 2 points
55        CGContextAddLineToPoint(context, points[1].fX, points[1].fY);
56        break;
57      }
58      case SkPath::kQuad_Verb: {  // iter.next returns 3 points
59        CGContextAddQuadCurveToPoint(context, points[1].fX, points[1].fY,
60                                     points[2].fX, points[2].fY);
61        break;
62      }
63      case SkPath::kCubic_Verb: {  // iter.next returns 4 points
64        CGContextAddCurveToPoint(context, points[1].fX, points[1].fY,
65                                 points[2].fX, points[2].fY,
66                                 points[3].fX, points[3].fY);
67        break;
68      }
69      case SkPath::kClose_Verb: {  // iter.next returns 1 point (the last point)
70        break;
71      }
72      case SkPath::kDone_Verb:  // iter.next returns 0 points
73      default: {
74        SkASSERT(false);
75        break;
76      }
77    }
78  }
79  CGContextClosePath(context);
80}
81
82// static
83void PlatformDevice::LoadTransformToCGContext(CGContextRef context,
84                                              const SkMatrix& matrix) {
85  // CoreGraphics can concatenate transforms, but not reset the current one.
86  // So in order to get the required behavior here, we need to first make
87  // the current transformation matrix identity and only then load the new one.
88
89  // Reset matrix to identity.
90  CGAffineTransform orig_cg_matrix = CGContextGetCTM(context);
91  CGAffineTransform orig_cg_matrix_inv = CGAffineTransformInvert(
92      orig_cg_matrix);
93  CGContextConcatCTM(context, orig_cg_matrix_inv);
94
95  // assert that we have indeed returned to the identity Matrix.
96  SkASSERT(CGAffineTransformIsIdentity(CGContextGetCTM(context)));
97
98  // Convert xform to CG-land.
99  // Our coordinate system is flipped to match WebKit's so we need to modify
100  // the xform to match that.
101  SkMatrix transformed_matrix = matrix;
102  SkScalar sy = matrix.getScaleY() * (SkScalar)-1;
103  transformed_matrix.setScaleY(sy);
104  size_t height = CGBitmapContextGetHeight(context);
105  SkScalar ty = -matrix.getTranslateY(); // y axis is flipped.
106  transformed_matrix.setTranslateY(ty + (SkScalar)height);
107
108  CGAffineTransform cg_matrix = gfx::SkMatrixToCGAffineTransform(
109      transformed_matrix);
110
111  // Load final transform into context.
112  CGContextConcatCTM(context, cg_matrix);
113}
114
115// static
116void PlatformDevice::LoadClippingRegionToCGContext(
117         CGContextRef context,
118         const SkRegion& region,
119         const SkMatrix& transformation) {
120  if (region.isEmpty()) {
121    // region can be empty, in which case everything will be clipped.
122    SkRect rect;
123    rect.setEmpty();
124    CGContextClipToRect(context, gfx::SkRectToCGRect(rect));
125  } else if (region.isRect()) {
126    // CoreGraphics applies the current transform to clip rects, which is
127    // unwanted. Inverse-transform the rect before sending it to CG. This only
128    // works for translations and scaling, but not for rotations (but the
129    // viewport is never rotated anyway).
130    SkMatrix t;
131    bool did_invert = transformation.invert(&t);
132    if (!did_invert)
133      t.reset();
134    // Do the transformation.
135    SkRect rect;
136    rect.set(region.getBounds());
137    t.mapRect(&rect);
138    SkIRect irect;
139    rect.round(&irect);
140    CGContextClipToRect(context, gfx::SkIRectToCGRect(irect));
141  } else {
142    // It is complex.
143    SkPath path;
144    region.getBoundaryPath(&path);
145    // Clip. Note that windows clipping regions are not affected by the
146    // transform so apply it manually.
147    path.transform(transformation);
148    // TODO(playmobil): Implement.
149    SkASSERT(false);
150    // LoadPathToDC(context, path);
151    // hrgn = PathToRegion(context);
152  }
153}
154
155}  // namespace skia
156