gfx/2d/DrawTargetCG.cpp

branch
TOR_BUG_9701
changeset 8
97036ab72558
equal deleted inserted replaced
-1:000000000000 0:90ee90d3064e
1 /* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
2 * This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 #include "BorrowedContext.h"
6 #include "DataSurfaceHelpers.h"
7 #include "DrawTargetCG.h"
8 #include "Logging.h"
9 #include "SourceSurfaceCG.h"
10 #include "Rect.h"
11 #include "ScaledFontMac.h"
12 #include "Tools.h"
13 #include <vector>
14 #include <algorithm>
15 #include "MacIOSurface.h"
16 #include "FilterNodeSoftware.h"
17 #include "mozilla/Assertions.h"
18 #include "mozilla/Types.h" // for decltype
19 #include "mozilla/FloatingPoint.h"
20
21 using namespace std;
22
23 //CG_EXTERN void CGContextSetCompositeOperation (CGContextRef, PrivateCGCompositeMode);
24
25 // A private API that Cairo has been using for a long time
26 CG_EXTERN void CGContextSetCTM(CGContextRef, CGAffineTransform);
27
28 namespace mozilla {
29 namespace gfx {
30
31 static CGRect RectToCGRect(Rect r)
32 {
33 return CGRectMake(r.x, r.y, r.width, r.height);
34 }
35
36 CGBlendMode ToBlendMode(CompositionOp op)
37 {
38 CGBlendMode mode;
39 switch (op) {
40 case CompositionOp::OP_OVER:
41 mode = kCGBlendModeNormal;
42 break;
43 case CompositionOp::OP_ADD:
44 mode = kCGBlendModePlusLighter;
45 break;
46 case CompositionOp::OP_ATOP:
47 mode = kCGBlendModeSourceAtop;
48 break;
49 case CompositionOp::OP_OUT:
50 mode = kCGBlendModeSourceOut;
51 break;
52 case CompositionOp::OP_IN:
53 mode = kCGBlendModeSourceIn;
54 break;
55 case CompositionOp::OP_SOURCE:
56 mode = kCGBlendModeCopy;
57 break;
58 case CompositionOp::OP_DEST_IN:
59 mode = kCGBlendModeDestinationIn;
60 break;
61 case CompositionOp::OP_DEST_OUT:
62 mode = kCGBlendModeDestinationOut;
63 break;
64 case CompositionOp::OP_DEST_OVER:
65 mode = kCGBlendModeDestinationOver;
66 break;
67 case CompositionOp::OP_DEST_ATOP:
68 mode = kCGBlendModeDestinationAtop;
69 break;
70 case CompositionOp::OP_XOR:
71 mode = kCGBlendModeXOR;
72 break;
73 case CompositionOp::OP_MULTIPLY:
74 mode = kCGBlendModeMultiply;
75 break;
76 case CompositionOp::OP_SCREEN:
77 mode = kCGBlendModeScreen;
78 break;
79 case CompositionOp::OP_OVERLAY:
80 mode = kCGBlendModeOverlay;
81 break;
82 case CompositionOp::OP_DARKEN:
83 mode = kCGBlendModeDarken;
84 break;
85 case CompositionOp::OP_LIGHTEN:
86 mode = kCGBlendModeLighten;
87 break;
88 case CompositionOp::OP_COLOR_DODGE:
89 mode = kCGBlendModeColorDodge;
90 break;
91 case CompositionOp::OP_COLOR_BURN:
92 mode = kCGBlendModeColorBurn;
93 break;
94 case CompositionOp::OP_HARD_LIGHT:
95 mode = kCGBlendModeHardLight;
96 break;
97 case CompositionOp::OP_SOFT_LIGHT:
98 mode = kCGBlendModeSoftLight;
99 break;
100 case CompositionOp::OP_DIFFERENCE:
101 mode = kCGBlendModeDifference;
102 break;
103 case CompositionOp::OP_EXCLUSION:
104 mode = kCGBlendModeExclusion;
105 break;
106 case CompositionOp::OP_HUE:
107 mode = kCGBlendModeHue;
108 break;
109 case CompositionOp::OP_SATURATION:
110 mode = kCGBlendModeSaturation;
111 break;
112 case CompositionOp::OP_COLOR:
113 mode = kCGBlendModeColor;
114 break;
115 case CompositionOp::OP_LUMINOSITY:
116 mode = kCGBlendModeLuminosity;
117 break;
118 /*
119 case OP_CLEAR:
120 mode = kCGBlendModeClear;
121 break;*/
122 default:
123 mode = kCGBlendModeNormal;
124 }
125 return mode;
126 }
127
128 static CGInterpolationQuality
129 InterpolationQualityFromFilter(Filter aFilter)
130 {
131 switch (aFilter) {
132 default:
133 case Filter::LINEAR:
134 return kCGInterpolationLow;
135 case Filter::POINT:
136 return kCGInterpolationNone;
137 case Filter::GOOD:
138 return kCGInterpolationDefault;
139 }
140 }
141
142
143 DrawTargetCG::DrawTargetCG() : mCg(nullptr), mSnapshot(nullptr)
144 {
145 }
146
147 DrawTargetCG::~DrawTargetCG()
148 {
149 MarkChanged();
150
151 // We need to conditionally release these because Init can fail without initializing these.
152 if (mColorSpace)
153 CGColorSpaceRelease(mColorSpace);
154 if (mCg)
155 CGContextRelease(mCg);
156 }
157
158 BackendType
159 DrawTargetCG::GetType() const
160 {
161 // It may be worth spliting Bitmap and IOSurface DrawTarget
162 // into seperate classes.
163 if (GetContextType(mCg) == CG_CONTEXT_TYPE_IOSURFACE) {
164 return BackendType::COREGRAPHICS_ACCELERATED;
165 } else {
166 return BackendType::COREGRAPHICS;
167 }
168 }
169
170 TemporaryRef<SourceSurface>
171 DrawTargetCG::Snapshot()
172 {
173 if (!mSnapshot) {
174 if (GetContextType(mCg) == CG_CONTEXT_TYPE_IOSURFACE) {
175 return new SourceSurfaceCGIOSurfaceContext(this);
176 } else {
177 mSnapshot = new SourceSurfaceCGBitmapContext(this);
178 }
179 }
180
181 return mSnapshot;
182 }
183
184 TemporaryRef<DrawTarget>
185 DrawTargetCG::CreateSimilarDrawTarget(const IntSize &aSize, SurfaceFormat aFormat) const
186 {
187 // XXX: in thebes we use CGLayers to do this kind of thing. It probably makes sense
188 // to add that in somehow, but at a higher level
189 RefPtr<DrawTargetCG> newTarget = new DrawTargetCG();
190 if (newTarget->Init(GetType(), aSize, aFormat)) {
191 return newTarget;
192 } else {
193 return nullptr;
194 }
195 }
196
197 TemporaryRef<SourceSurface>
198 DrawTargetCG::CreateSourceSurfaceFromData(unsigned char *aData,
199 const IntSize &aSize,
200 int32_t aStride,
201 SurfaceFormat aFormat) const
202 {
203 RefPtr<SourceSurfaceCG> newSurf = new SourceSurfaceCG();
204
205 if (!newSurf->InitFromData(aData, aSize, aStride, aFormat)) {
206 return nullptr;
207 }
208
209 return newSurf;
210 }
211
212 // This function returns a retained CGImage that needs to be released after
213 // use. The reason for this is that we want to either reuse an existing CGImage
214 // or create a new one.
215 static CGImageRef
216 GetRetainedImageFromSourceSurface(SourceSurface *aSurface)
217 {
218 if (aSurface->GetType() == SurfaceType::COREGRAPHICS_IMAGE)
219 return CGImageRetain(static_cast<SourceSurfaceCG*>(aSurface)->GetImage());
220 else if (aSurface->GetType() == SurfaceType::COREGRAPHICS_CGCONTEXT)
221 return CGImageRetain(static_cast<SourceSurfaceCGContext*>(aSurface)->GetImage());
222
223 if (aSurface->GetType() == SurfaceType::DATA) {
224 DataSourceSurface* dataSource = static_cast<DataSourceSurface*>(aSurface);
225 return CreateCGImage(nullptr, dataSource->GetData(), dataSource->GetSize(),
226 dataSource->Stride(), dataSource->GetFormat());
227 }
228
229 MOZ_CRASH("unsupported source surface");
230 }
231
232 TemporaryRef<SourceSurface>
233 DrawTargetCG::OptimizeSourceSurface(SourceSurface *aSurface) const
234 {
235 if (aSurface->GetType() == SurfaceType::COREGRAPHICS_IMAGE ||
236 aSurface->GetType() == SurfaceType::COREGRAPHICS_CGCONTEXT) {
237 return aSurface;
238 }
239 return aSurface->GetDataSurface();
240 }
241
242 class UnboundnessFixer
243 {
244 CGRect mClipBounds;
245 CGLayerRef mLayer;
246 CGContextRef mCg;
247 public:
248 UnboundnessFixer() : mCg(nullptr) {}
249
250 CGContextRef Check(CGContextRef baseCg, CompositionOp blend, const Rect* maskBounds = nullptr)
251 {
252 if (!IsOperatorBoundByMask(blend)) {
253 mClipBounds = CGContextGetClipBoundingBox(baseCg);
254 // If we're entirely clipped out or if the drawing operation covers the entire clip then
255 // we don't need to create a temporary surface.
256 if (CGRectIsEmpty(mClipBounds) ||
257 (maskBounds && maskBounds->Contains(CGRectToRect(mClipBounds)))) {
258 return baseCg;
259 }
260
261 // TransparencyLayers aren't blended using the blend mode so
262 // we are forced to use CGLayers
263
264 //XXX: The size here is in default user space units, of the layer relative to the graphics context.
265 // is the clip bounds still correct if, for example, we have a scale applied to the context?
266 mLayer = CGLayerCreateWithContext(baseCg, mClipBounds.size, nullptr);
267 mCg = CGLayerGetContext(mLayer);
268 // CGContext's default to have the origin at the bottom left
269 // so flip it to the top left and adjust for the origin
270 // of the layer
271 CGContextTranslateCTM(mCg, -mClipBounds.origin.x, mClipBounds.origin.y + mClipBounds.size.height);
272 CGContextScaleCTM(mCg, 1, -1);
273
274 return mCg;
275 } else {
276 return baseCg;
277 }
278 }
279
280 void Fix(CGContextRef baseCg)
281 {
282 if (mCg) {
283 CGContextTranslateCTM(baseCg, 0, mClipBounds.size.height);
284 CGContextScaleCTM(baseCg, 1, -1);
285 mClipBounds.origin.y *= -1;
286 CGContextDrawLayerAtPoint(baseCg, mClipBounds.origin, mLayer);
287 CGContextRelease(mCg);
288 }
289 }
290 };
291
292 void
293 DrawTargetCG::DrawSurface(SourceSurface *aSurface,
294 const Rect &aDest,
295 const Rect &aSource,
296 const DrawSurfaceOptions &aSurfOptions,
297 const DrawOptions &aDrawOptions)
298 {
299 MarkChanged();
300
301 CGContextSaveGState(mCg);
302
303 CGContextSetBlendMode(mCg, ToBlendMode(aDrawOptions.mCompositionOp));
304 UnboundnessFixer fixer;
305 CGContextRef cg = fixer.Check(mCg, aDrawOptions.mCompositionOp, &aDest);
306 CGContextSetAlpha(cg, aDrawOptions.mAlpha);
307 CGContextSetShouldAntialias(cg, aDrawOptions.mAntialiasMode != AntialiasMode::NONE);
308
309 CGContextConcatCTM(cg, GfxMatrixToCGAffineTransform(mTransform));
310
311 CGContextSetInterpolationQuality(cg, InterpolationQualityFromFilter(aSurfOptions.mFilter));
312
313 CGImageRef image = GetRetainedImageFromSourceSurface(aSurface);
314
315 if (aSurfOptions.mFilter == Filter::POINT) {
316 CGImageRef subimage = CGImageCreateWithImageInRect(image, RectToCGRect(aSource));
317 CGImageRelease(image);
318
319 CGContextScaleCTM(cg, 1, -1);
320
321 CGRect flippedRect = CGRectMake(aDest.x, -(aDest.y + aDest.height),
322 aDest.width, aDest.height);
323
324 CGContextDrawImage(cg, flippedRect, subimage);
325 CGImageRelease(subimage);
326 } else {
327 CGRect destRect = CGRectMake(aDest.x, aDest.y, aDest.width, aDest.height);
328 CGContextClipToRect(cg, destRect);
329
330 float xScale = aSource.width / aDest.width;
331 float yScale = aSource.height / aDest.height;
332 CGContextTranslateCTM(cg, aDest.x - aSource.x / xScale, aDest.y - aSource.y / yScale);
333
334 CGRect adjustedDestRect = CGRectMake(0, 0, CGImageGetWidth(image) / xScale,
335 CGImageGetHeight(image) / yScale);
336
337 CGContextTranslateCTM(cg, 0, CGRectGetHeight(adjustedDestRect));
338 CGContextScaleCTM(cg, 1, -1);
339
340 CGContextDrawImage(cg, adjustedDestRect, image);
341 CGImageRelease(image);
342 }
343
344 fixer.Fix(mCg);
345
346 CGContextRestoreGState(mCg);
347 }
348
349 TemporaryRef<FilterNode>
350 DrawTargetCG::CreateFilter(FilterType aType)
351 {
352 return FilterNodeSoftware::Create(aType);
353 }
354
355 void
356 DrawTargetCG::DrawFilter(FilterNode *aNode,
357 const Rect &aSourceRect,
358 const Point &aDestPoint,
359 const DrawOptions &aOptions)
360 {
361 FilterNodeSoftware* filter = static_cast<FilterNodeSoftware*>(aNode);
362 filter->Draw(this, aSourceRect, aDestPoint, aOptions);
363 }
364
365 static CGColorRef ColorToCGColor(CGColorSpaceRef aColorSpace, const Color& aColor)
366 {
367 CGFloat components[4] = {aColor.r, aColor.g, aColor.b, aColor.a};
368 return CGColorCreate(aColorSpace, components);
369 }
370
371 class GradientStopsCG : public GradientStops
372 {
373 public:
374 MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(GradientStopsCG)
375 //XXX: The skia backend uses a vector and passes in aNumStops. It should do better
376 GradientStopsCG(GradientStop* aStops, uint32_t aNumStops, ExtendMode aExtendMode)
377 {
378 mExtend = aExtendMode;
379 if (aExtendMode == ExtendMode::CLAMP) {
380 //XXX: do the stops need to be in any particular order?
381 // what should we do about the color space here? we certainly shouldn't be
382 // recreating it all the time
383 std::vector<CGFloat> colors;
384 std::vector<CGFloat> offsets;
385 colors.reserve(aNumStops*4);
386 offsets.reserve(aNumStops);
387
388 for (uint32_t i = 0; i < aNumStops; i++) {
389 colors.push_back(aStops[i].color.r);
390 colors.push_back(aStops[i].color.g);
391 colors.push_back(aStops[i].color.b);
392 colors.push_back(aStops[i].color.a);
393
394 offsets.push_back(aStops[i].offset);
395 }
396
397 CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
398 mGradient = CGGradientCreateWithColorComponents(colorSpace,
399 &colors.front(),
400 &offsets.front(),
401 aNumStops);
402 CGColorSpaceRelease(colorSpace);
403 } else {
404 mGradient = nullptr;
405 mStops.reserve(aNumStops);
406 for (uint32_t i = 0; i < aNumStops; i++) {
407 mStops.push_back(aStops[i]);
408 }
409 }
410
411 }
412 virtual ~GradientStopsCG() {
413 if (mGradient)
414 CGGradientRelease(mGradient);
415 }
416 // Will always report BackendType::COREGRAPHICS, but it is compatible
417 // with BackendType::COREGRAPHICS_ACCELERATED
418 BackendType GetBackendType() const { return BackendType::COREGRAPHICS; }
419 // XXX this should be a union
420 CGGradientRef mGradient;
421 std::vector<GradientStop> mStops;
422 ExtendMode mExtend;
423 };
424
425 TemporaryRef<GradientStops>
426 DrawTargetCG::CreateGradientStops(GradientStop *aStops, uint32_t aNumStops,
427 ExtendMode aExtendMode) const
428 {
429 return new GradientStopsCG(aStops, aNumStops, aExtendMode);
430 }
431
432 static void
433 UpdateLinearParametersToIncludePoint(double *min_t, double *max_t,
434 CGPoint *start,
435 double dx, double dy,
436 double x, double y)
437 {
438 MOZ_ASSERT(IsFinite(x) && IsFinite(y));
439
440 /**
441 * Compute a parameter t such that a line perpendicular to the (dx,dy)
442 * vector, passing through (start->x + dx*t, start->y + dy*t), also
443 * passes through (x,y).
444 *
445 * Let px = x - start->x, py = y - start->y.
446 * t is given by
447 * (px - dx*t)*dx + (py - dy*t)*dy = 0
448 *
449 * Solving for t we get
450 * numerator = dx*px + dy*py
451 * denominator = dx^2 + dy^2
452 * t = numerator/denominator
453 *
454 * In CalculateRepeatingGradientParams we know the length of (dx,dy)
455 * is not zero. (This is checked in DrawLinearRepeatingGradient.)
456 */
457 double px = x - start->x;
458 double py = y - start->y;
459 double numerator = dx * px + dy * py;
460 double denominator = dx * dx + dy * dy;
461 double t = numerator / denominator;
462
463 if (*min_t > t) {
464 *min_t = t;
465 }
466 if (*max_t < t) {
467 *max_t = t;
468 }
469 }
470
471 /**
472 * Repeat the gradient line such that lines extended perpendicular to the
473 * gradient line at both start and end would completely enclose the drawing
474 * extents.
475 */
476 static void
477 CalculateRepeatingGradientParams(CGPoint *aStart, CGPoint *aEnd,
478 CGRect aExtents, int *aRepeatCount)
479 {
480 double t_min = INFINITY;
481 double t_max = -INFINITY;
482 double dx = aEnd->x - aStart->x;
483 double dy = aEnd->y - aStart->y;
484
485 double bounds_x1 = aExtents.origin.x;
486 double bounds_y1 = aExtents.origin.y;
487 double bounds_x2 = aExtents.origin.x + aExtents.size.width;
488 double bounds_y2 = aExtents.origin.y + aExtents.size.height;
489
490 UpdateLinearParametersToIncludePoint(&t_min, &t_max, aStart, dx, dy,
491 bounds_x1, bounds_y1);
492 UpdateLinearParametersToIncludePoint(&t_min, &t_max, aStart, dx, dy,
493 bounds_x2, bounds_y1);
494 UpdateLinearParametersToIncludePoint(&t_min, &t_max, aStart, dx, dy,
495 bounds_x2, bounds_y2);
496 UpdateLinearParametersToIncludePoint(&t_min, &t_max, aStart, dx, dy,
497 bounds_x1, bounds_y2);
498
499 MOZ_ASSERT(!isinf(t_min) && !isinf(t_max),
500 "The first call to UpdateLinearParametersToIncludePoint should have made t_min and t_max non-infinite.");
501
502 // Move t_min and t_max to the nearest usable integer to try to avoid
503 // subtle variations due to numerical instability, especially accidentally
504 // cutting off a pixel. Extending the gradient repetitions is always safe.
505 t_min = floor (t_min);
506 t_max = ceil (t_max);
507 aEnd->x = aStart->x + dx * t_max;
508 aEnd->y = aStart->y + dy * t_max;
509 aStart->x = aStart->x + dx * t_min;
510 aStart->y = aStart->y + dy * t_min;
511
512 *aRepeatCount = t_max - t_min;
513 }
514
515 static void
516 DrawLinearRepeatingGradient(CGContextRef cg, const LinearGradientPattern &aPattern, const CGRect &aExtents)
517 {
518 GradientStopsCG *stops = static_cast<GradientStopsCG*>(aPattern.mStops.get());
519 CGPoint startPoint = { aPattern.mBegin.x, aPattern.mBegin.y };
520 CGPoint endPoint = { aPattern.mEnd.x, aPattern.mEnd.y };
521
522 int repeatCount = 1;
523 // if we don't have a line then we can't extend it
524 if (aPattern.mEnd.x != aPattern.mBegin.x ||
525 aPattern.mEnd.y != aPattern.mBegin.y) {
526 CalculateRepeatingGradientParams(&startPoint, &endPoint, aExtents,
527 &repeatCount);
528 }
529
530 double scale = 1./repeatCount;
531
532 std::vector<CGFloat> colors;
533 std::vector<CGFloat> offsets;
534 colors.reserve(stops->mStops.size()*repeatCount*4);
535 offsets.reserve(stops->mStops.size()*repeatCount);
536
537 for (int j = 0; j < repeatCount; j++) {
538 for (uint32_t i = 0; i < stops->mStops.size(); i++) {
539 colors.push_back(stops->mStops[i].color.r);
540 colors.push_back(stops->mStops[i].color.g);
541 colors.push_back(stops->mStops[i].color.b);
542 colors.push_back(stops->mStops[i].color.a);
543
544 offsets.push_back((stops->mStops[i].offset + j)*scale);
545 }
546 }
547
548 CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
549 CGGradientRef gradient = CGGradientCreateWithColorComponents(colorSpace,
550 &colors.front(),
551 &offsets.front(),
552 repeatCount*stops->mStops.size());
553 CGColorSpaceRelease(colorSpace);
554
555 CGContextDrawLinearGradient(cg, gradient, startPoint, endPoint,
556 kCGGradientDrawsBeforeStartLocation | kCGGradientDrawsAfterEndLocation);
557 CGGradientRelease(gradient);
558 }
559
560 static CGPoint CGRectTopLeft(CGRect a)
561 { return a.origin; }
562 static CGPoint CGRectBottomLeft(CGRect a)
563 { return CGPointMake(a.origin.x, a.origin.y + a.size.height); }
564 static CGPoint CGRectTopRight(CGRect a)
565 { return CGPointMake(a.origin.x + a.size.width, a.origin.y); }
566 static CGPoint CGRectBottomRight(CGRect a)
567 { return CGPointMake(a.origin.x + a.size.width, a.origin.y + a.size.height); }
568
569 static CGFloat
570 CGPointDistance(CGPoint a, CGPoint b)
571 {
572 return hypot(a.x-b.x, a.y-b.y);
573 }
574
575 static void
576 DrawRadialRepeatingGradient(CGContextRef cg, const RadialGradientPattern &aPattern, const CGRect &aExtents)
577 {
578 GradientStopsCG *stops = static_cast<GradientStopsCG*>(aPattern.mStops.get());
579 CGPoint startCenter = { aPattern.mCenter1.x, aPattern.mCenter1.y };
580 CGFloat startRadius = aPattern.mRadius1;
581 CGPoint endCenter = { aPattern.mCenter2.x, aPattern.mCenter2.y };
582 CGFloat endRadius = aPattern.mRadius2;
583
584 // find the maximum distance from endCenter to a corner of aExtents
585 CGFloat minimumEndRadius = endRadius;
586 minimumEndRadius = max(minimumEndRadius, CGPointDistance(endCenter, CGRectTopLeft(aExtents)));
587 minimumEndRadius = max(minimumEndRadius, CGPointDistance(endCenter, CGRectBottomLeft(aExtents)));
588 minimumEndRadius = max(minimumEndRadius, CGPointDistance(endCenter, CGRectTopRight(aExtents)));
589 minimumEndRadius = max(minimumEndRadius, CGPointDistance(endCenter, CGRectBottomRight(aExtents)));
590
591 CGFloat length = endRadius - startRadius;
592 int repeatCount = 1;
593 while (endRadius < minimumEndRadius) {
594 endRadius += length;
595 repeatCount++;
596 }
597
598 while (startRadius-length >= 0) {
599 startRadius -= length;
600 repeatCount++;
601 }
602
603 double scale = 1./repeatCount;
604
605 std::vector<CGFloat> colors;
606 std::vector<CGFloat> offsets;
607 colors.reserve(stops->mStops.size()*repeatCount*4);
608 offsets.reserve(stops->mStops.size()*repeatCount);
609 for (int j = 0; j < repeatCount; j++) {
610 for (uint32_t i = 0; i < stops->mStops.size(); i++) {
611 colors.push_back(stops->mStops[i].color.r);
612 colors.push_back(stops->mStops[i].color.g);
613 colors.push_back(stops->mStops[i].color.b);
614 colors.push_back(stops->mStops[i].color.a);
615
616 offsets.push_back((stops->mStops[i].offset + j)*scale);
617 }
618 }
619
620 CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
621 CGGradientRef gradient = CGGradientCreateWithColorComponents(colorSpace,
622 &colors.front(),
623 &offsets.front(),
624 repeatCount*stops->mStops.size());
625 CGColorSpaceRelease(colorSpace);
626
627 //XXX: are there degenerate radial gradients that we should avoid drawing?
628 CGContextDrawRadialGradient(cg, gradient, startCenter, startRadius, endCenter, endRadius,
629 kCGGradientDrawsBeforeStartLocation | kCGGradientDrawsAfterEndLocation);
630 CGGradientRelease(gradient);
631 }
632
633 static void
634 DrawGradient(CGContextRef cg, const Pattern &aPattern, const CGRect &aExtents)
635 {
636 if (CGRectIsEmpty(aExtents)) {
637 return;
638 }
639
640 if (aPattern.GetType() == PatternType::LINEAR_GRADIENT) {
641 const LinearGradientPattern& pat = static_cast<const LinearGradientPattern&>(aPattern);
642 GradientStopsCG *stops = static_cast<GradientStopsCG*>(pat.mStops.get());
643 CGContextConcatCTM(cg, GfxMatrixToCGAffineTransform(pat.mMatrix));
644 if (stops->mExtend == ExtendMode::CLAMP) {
645
646 // XXX: we should take the m out of the properties of LinearGradientPatterns
647 CGPoint startPoint = { pat.mBegin.x, pat.mBegin.y };
648 CGPoint endPoint = { pat.mEnd.x, pat.mEnd.y };
649
650 // Canvas spec states that we should avoid drawing degenerate gradients (XXX: should this be in common code?)
651 //if (startPoint.x == endPoint.x && startPoint.y == endPoint.y)
652 // return;
653
654 CGContextDrawLinearGradient(cg, stops->mGradient, startPoint, endPoint,
655 kCGGradientDrawsBeforeStartLocation | kCGGradientDrawsAfterEndLocation);
656 } else if (stops->mExtend == ExtendMode::REPEAT) {
657 DrawLinearRepeatingGradient(cg, pat, aExtents);
658 }
659 } else if (aPattern.GetType() == PatternType::RADIAL_GRADIENT) {
660 const RadialGradientPattern& pat = static_cast<const RadialGradientPattern&>(aPattern);
661 CGContextConcatCTM(cg, GfxMatrixToCGAffineTransform(pat.mMatrix));
662 GradientStopsCG *stops = static_cast<GradientStopsCG*>(pat.mStops.get());
663 if (stops->mExtend == ExtendMode::CLAMP) {
664
665 // XXX: we should take the m out of the properties of RadialGradientPatterns
666 CGPoint startCenter = { pat.mCenter1.x, pat.mCenter1.y };
667 CGFloat startRadius = pat.mRadius1;
668 CGPoint endCenter = { pat.mCenter2.x, pat.mCenter2.y };
669 CGFloat endRadius = pat.mRadius2;
670
671 //XXX: are there degenerate radial gradients that we should avoid drawing?
672 CGContextDrawRadialGradient(cg, stops->mGradient, startCenter, startRadius, endCenter, endRadius,
673 kCGGradientDrawsBeforeStartLocation | kCGGradientDrawsAfterEndLocation);
674 } else if (stops->mExtend == ExtendMode::REPEAT) {
675 DrawRadialRepeatingGradient(cg, pat, aExtents);
676 }
677 } else {
678 assert(0);
679 }
680
681 }
682
683 static void
684 drawPattern(void *info, CGContextRef context)
685 {
686 CGImageRef image = static_cast<CGImageRef>(info);
687 CGRect rect = {{0, 0},
688 {static_cast<CGFloat>(CGImageGetWidth(image)),
689 static_cast<CGFloat>(CGImageGetHeight(image))}};
690 CGContextDrawImage(context, rect, image);
691 }
692
693 static void
694 releaseInfo(void *info)
695 {
696 CGImageRef image = static_cast<CGImageRef>(info);
697 CGImageRelease(image);
698 }
699
700 CGPatternCallbacks patternCallbacks = {
701 0,
702 drawPattern,
703 releaseInfo
704 };
705
706 static bool
707 isGradient(const Pattern &aPattern)
708 {
709 return aPattern.GetType() == PatternType::LINEAR_GRADIENT || aPattern.GetType() == PatternType::RADIAL_GRADIENT;
710 }
711
712 /* CoreGraphics patterns ignore the userspace transform so
713 * we need to multiply it in */
714 static CGPatternRef
715 CreateCGPattern(const Pattern &aPattern, CGAffineTransform aUserSpace)
716 {
717 const SurfacePattern& pat = static_cast<const SurfacePattern&>(aPattern);
718 // XXX: is .get correct here?
719 CGImageRef image = GetRetainedImageFromSourceSurface(pat.mSurface.get());
720 CGFloat xStep, yStep;
721 switch (pat.mExtendMode) {
722 case ExtendMode::CLAMP:
723 // The 1 << 22 comes from Webkit see Pattern::createPlatformPattern() in PatternCG.cpp for more info
724 xStep = static_cast<CGFloat>(1 << 22);
725 yStep = static_cast<CGFloat>(1 << 22);
726 break;
727 case ExtendMode::REFLECT:
728 assert(0);
729 case ExtendMode::REPEAT:
730 xStep = static_cast<CGFloat>(CGImageGetWidth(image));
731 yStep = static_cast<CGFloat>(CGImageGetHeight(image));
732 // webkit uses wkCGPatternCreateWithImageAndTransform a wrapper around CGPatternCreateWithImage2
733 // this is done to avoid pixel-cracking along pattern boundaries
734 // (see https://bugs.webkit.org/show_bug.cgi?id=53055)
735 // typedef enum {
736 // wkPatternTilingNoDistortion,
737 // wkPatternTilingConstantSpacingMinimalDistortion,
738 // wkPatternTilingConstantSpacing
739 // } wkPatternTiling;
740 // extern CGPatternRef (*wkCGPatternCreateWithImageAndTransform)(CGImageRef, CGAffineTransform, int);
741 }
742
743 //XXX: We should be using CGContextDrawTiledImage when we can. Even though it
744 // creates a pattern, it seems to go down a faster path than using a delegate
745 // like we do below
746 CGRect bounds = {
747 {0, 0,},
748 {static_cast<CGFloat>(CGImageGetWidth(image)), static_cast<CGFloat>(CGImageGetHeight(image))}
749 };
750 CGAffineTransform transform =
751 CGAffineTransformConcat(CGAffineTransformConcat(CGAffineTransformMakeScale(1,
752 -1),
753 GfxMatrixToCGAffineTransform(pat.mMatrix)),
754 aUserSpace);
755 transform = CGAffineTransformTranslate(transform, 0, -static_cast<float>(CGImageGetHeight(image)));
756 return CGPatternCreate(image, bounds, transform, xStep, yStep, kCGPatternTilingConstantSpacing,
757 true, &patternCallbacks);
758 }
759
760 static void
761 SetFillFromPattern(CGContextRef cg, CGColorSpaceRef aColorSpace, const Pattern &aPattern)
762 {
763 assert(!isGradient(aPattern));
764 if (aPattern.GetType() == PatternType::COLOR) {
765
766 const Color& color = static_cast<const ColorPattern&>(aPattern).mColor;
767 //XXX: we should cache colors
768 CGColorRef cgcolor = ColorToCGColor(aColorSpace, color);
769 CGContextSetFillColorWithColor(cg, cgcolor);
770 CGColorRelease(cgcolor);
771 } else if (aPattern.GetType() == PatternType::SURFACE) {
772
773 CGColorSpaceRef patternSpace;
774 patternSpace = CGColorSpaceCreatePattern (nullptr);
775 CGContextSetFillColorSpace(cg, patternSpace);
776 CGColorSpaceRelease(patternSpace);
777
778 CGPatternRef pattern = CreateCGPattern(aPattern, CGContextGetCTM(cg));
779 const SurfacePattern& pat = static_cast<const SurfacePattern&>(aPattern);
780 CGContextSetInterpolationQuality(cg, InterpolationQualityFromFilter(pat.mFilter));
781 CGFloat alpha = 1.;
782 CGContextSetFillPattern(cg, pattern, &alpha);
783 CGPatternRelease(pattern);
784 }
785 }
786
787 static void
788 SetStrokeFromPattern(CGContextRef cg, CGColorSpaceRef aColorSpace, const Pattern &aPattern)
789 {
790 assert(!isGradient(aPattern));
791 if (aPattern.GetType() == PatternType::COLOR) {
792 const Color& color = static_cast<const ColorPattern&>(aPattern).mColor;
793 //XXX: we should cache colors
794 CGColorRef cgcolor = ColorToCGColor(aColorSpace, color);
795 CGContextSetStrokeColorWithColor(cg, cgcolor);
796 CGColorRelease(cgcolor);
797 } else if (aPattern.GetType() == PatternType::SURFACE) {
798 CGColorSpaceRef patternSpace;
799 patternSpace = CGColorSpaceCreatePattern (nullptr);
800 CGContextSetStrokeColorSpace(cg, patternSpace);
801 CGColorSpaceRelease(patternSpace);
802
803 CGPatternRef pattern = CreateCGPattern(aPattern, CGContextGetCTM(cg));
804 const SurfacePattern& pat = static_cast<const SurfacePattern&>(aPattern);
805 CGContextSetInterpolationQuality(cg, InterpolationQualityFromFilter(pat.mFilter));
806 CGFloat alpha = 1.;
807 CGContextSetStrokePattern(cg, pattern, &alpha);
808 CGPatternRelease(pattern);
809 }
810
811 }
812
813 void
814 DrawTargetCG::MaskSurface(const Pattern &aSource,
815 SourceSurface *aMask,
816 Point aOffset,
817 const DrawOptions &aDrawOptions)
818 {
819 MarkChanged();
820
821 CGContextSaveGState(mCg);
822
823 CGContextSetBlendMode(mCg, ToBlendMode(aDrawOptions.mCompositionOp));
824 UnboundnessFixer fixer;
825 CGContextRef cg = fixer.Check(mCg, aDrawOptions.mCompositionOp);
826 CGContextSetAlpha(cg, aDrawOptions.mAlpha);
827 CGContextSetShouldAntialias(cg, aDrawOptions.mAntialiasMode != AntialiasMode::NONE);
828
829 CGContextConcatCTM(cg, GfxMatrixToCGAffineTransform(mTransform));
830 CGImageRef image = GetRetainedImageFromSourceSurface(aMask);
831
832 // use a negative-y so that the mask image draws right ways up
833 CGContextScaleCTM(cg, 1, -1);
834
835 IntSize size = aMask->GetSize();
836
837 CGContextClipToMask(cg, CGRectMake(aOffset.x, -(aOffset.y + size.height), size.width, size.height), image);
838
839 CGContextScaleCTM(cg, 1, -1);
840 if (isGradient(aSource)) {
841 // we shouldn't need to clip to an additional rectangle
842 // as the cliping to the mask should be sufficient.
843 DrawGradient(cg, aSource, CGRectMake(aOffset.x, aOffset.y, size.width, size.height));
844 } else {
845 SetFillFromPattern(cg, mColorSpace, aSource);
846 CGContextFillRect(cg, CGRectMake(aOffset.x, aOffset.y, size.width, size.height));
847 }
848
849 CGImageRelease(image);
850
851 fixer.Fix(mCg);
852
853 CGContextRestoreGState(mCg);
854 }
855
856
857
858 void
859 DrawTargetCG::FillRect(const Rect &aRect,
860 const Pattern &aPattern,
861 const DrawOptions &aDrawOptions)
862 {
863 MarkChanged();
864
865 CGContextSaveGState(mCg);
866
867 UnboundnessFixer fixer;
868 CGContextRef cg = fixer.Check(mCg, aDrawOptions.mCompositionOp, &aRect);
869 CGContextSetAlpha(mCg, aDrawOptions.mAlpha);
870 CGContextSetShouldAntialias(cg, aDrawOptions.mAntialiasMode != AntialiasMode::NONE);
871 CGContextSetBlendMode(mCg, ToBlendMode(aDrawOptions.mCompositionOp));
872
873 CGContextConcatCTM(cg, GfxMatrixToCGAffineTransform(mTransform));
874
875 if (isGradient(aPattern)) {
876 CGContextClipToRect(cg, RectToCGRect(aRect));
877 CGRect clipBounds = CGContextGetClipBoundingBox(cg);
878 DrawGradient(cg, aPattern, clipBounds);
879 } else {
880 if (aPattern.GetType() == PatternType::SURFACE && static_cast<const SurfacePattern&>(aPattern).mExtendMode != ExtendMode::REPEAT) {
881 // SetFillFromPattern can handle this case but using CGContextDrawImage
882 // should give us better performance, better output, smaller PDF and
883 // matches what cairo does.
884 const SurfacePattern& pat = static_cast<const SurfacePattern&>(aPattern);
885 CGImageRef image = GetRetainedImageFromSourceSurface(pat.mSurface.get());
886 CGContextClipToRect(cg, RectToCGRect(aRect));
887 CGContextConcatCTM(cg, GfxMatrixToCGAffineTransform(pat.mMatrix));
888 CGContextTranslateCTM(cg, 0, CGImageGetHeight(image));
889 CGContextScaleCTM(cg, 1, -1);
890
891 CGRect imageRect = CGRectMake(0, 0, CGImageGetWidth(image), CGImageGetHeight(image));
892
893 CGContextSetInterpolationQuality(cg, InterpolationQualityFromFilter(pat.mFilter));
894
895 CGContextDrawImage(cg, imageRect, image);
896 CGImageRelease(image);
897 } else {
898 SetFillFromPattern(cg, mColorSpace, aPattern);
899 CGContextFillRect(cg, RectToCGRect(aRect));
900 }
901 }
902
903 fixer.Fix(mCg);
904 CGContextRestoreGState(mCg);
905 }
906
907 void
908 DrawTargetCG::StrokeLine(const Point &p1, const Point &p2, const Pattern &aPattern, const StrokeOptions &aStrokeOptions, const DrawOptions &aDrawOptions)
909 {
910 if (!std::isfinite(p1.x) ||
911 !std::isfinite(p1.y) ||
912 !std::isfinite(p2.x) ||
913 !std::isfinite(p2.y)) {
914 return;
915 }
916
917 MarkChanged();
918
919 CGContextSaveGState(mCg);
920
921 UnboundnessFixer fixer;
922 CGContextRef cg = fixer.Check(mCg, aDrawOptions.mCompositionOp);
923 CGContextSetAlpha(mCg, aDrawOptions.mAlpha);
924 CGContextSetShouldAntialias(cg, aDrawOptions.mAntialiasMode != AntialiasMode::NONE);
925 CGContextSetBlendMode(mCg, ToBlendMode(aDrawOptions.mCompositionOp));
926
927 CGContextConcatCTM(cg, GfxMatrixToCGAffineTransform(mTransform));
928
929 CGContextBeginPath(cg);
930 CGContextMoveToPoint(cg, p1.x, p1.y);
931 CGContextAddLineToPoint(cg, p2.x, p2.y);
932
933 SetStrokeOptions(cg, aStrokeOptions);
934
935 if (isGradient(aPattern)) {
936 CGContextReplacePathWithStrokedPath(cg);
937 CGRect extents = CGContextGetPathBoundingBox(cg);
938 //XXX: should we use EO clip here?
939 CGContextClip(cg);
940 DrawGradient(cg, aPattern, extents);
941 } else {
942 SetStrokeFromPattern(cg, mColorSpace, aPattern);
943 CGContextStrokePath(cg);
944 }
945
946 fixer.Fix(mCg);
947 CGContextRestoreGState(mCg);
948 }
949
950 void
951 DrawTargetCG::StrokeRect(const Rect &aRect,
952 const Pattern &aPattern,
953 const StrokeOptions &aStrokeOptions,
954 const DrawOptions &aDrawOptions)
955 {
956 if (!aRect.IsFinite()) {
957 return;
958 }
959
960 MarkChanged();
961
962 CGContextSaveGState(mCg);
963
964 UnboundnessFixer fixer;
965 CGContextRef cg = fixer.Check(mCg, aDrawOptions.mCompositionOp);
966 CGContextSetAlpha(mCg, aDrawOptions.mAlpha);
967 CGContextSetShouldAntialias(cg, aDrawOptions.mAntialiasMode != AntialiasMode::NONE);
968 CGContextSetBlendMode(mCg, ToBlendMode(aDrawOptions.mCompositionOp));
969
970 CGContextConcatCTM(cg, GfxMatrixToCGAffineTransform(mTransform));
971
972 SetStrokeOptions(cg, aStrokeOptions);
973
974 if (isGradient(aPattern)) {
975 // There's no CGContextClipStrokeRect so we do it by hand
976 CGContextBeginPath(cg);
977 CGContextAddRect(cg, RectToCGRect(aRect));
978 CGContextReplacePathWithStrokedPath(cg);
979 CGRect extents = CGContextGetPathBoundingBox(cg);
980 //XXX: should we use EO clip here?
981 CGContextClip(cg);
982 DrawGradient(cg, aPattern, extents);
983 } else {
984 SetStrokeFromPattern(cg, mColorSpace, aPattern);
985 CGContextStrokeRect(cg, RectToCGRect(aRect));
986 }
987
988 fixer.Fix(mCg);
989 CGContextRestoreGState(mCg);
990 }
991
992
993 void
994 DrawTargetCG::ClearRect(const Rect &aRect)
995 {
996 MarkChanged();
997
998 CGContextSaveGState(mCg);
999 CGContextConcatCTM(mCg, GfxMatrixToCGAffineTransform(mTransform));
1000
1001 CGContextClearRect(mCg, RectToCGRect(aRect));
1002
1003 CGContextRestoreGState(mCg);
1004 }
1005
1006 void
1007 DrawTargetCG::Stroke(const Path *aPath, const Pattern &aPattern, const StrokeOptions &aStrokeOptions, const DrawOptions &aDrawOptions)
1008 {
1009 if (!aPath->GetBounds().IsFinite()) {
1010 return;
1011 }
1012
1013 MarkChanged();
1014
1015 CGContextSaveGState(mCg);
1016
1017 UnboundnessFixer fixer;
1018 CGContextRef cg = fixer.Check(mCg, aDrawOptions.mCompositionOp);
1019 CGContextSetAlpha(mCg, aDrawOptions.mAlpha);
1020 CGContextSetShouldAntialias(cg, aDrawOptions.mAntialiasMode != AntialiasMode::NONE);
1021 CGContextSetBlendMode(mCg, ToBlendMode(aDrawOptions.mCompositionOp));
1022
1023 CGContextConcatCTM(cg, GfxMatrixToCGAffineTransform(mTransform));
1024
1025
1026 CGContextBeginPath(cg);
1027
1028 assert(aPath->GetBackendType() == BackendType::COREGRAPHICS);
1029 const PathCG *cgPath = static_cast<const PathCG*>(aPath);
1030 CGContextAddPath(cg, cgPath->GetPath());
1031
1032 SetStrokeOptions(cg, aStrokeOptions);
1033
1034 if (isGradient(aPattern)) {
1035 CGContextReplacePathWithStrokedPath(cg);
1036 CGRect extents = CGContextGetPathBoundingBox(cg);
1037 //XXX: should we use EO clip here?
1038 CGContextClip(cg);
1039 DrawGradient(cg, aPattern, extents);
1040 } else {
1041 // XXX: we could put fill mode into the path fill rule if we wanted
1042
1043 SetStrokeFromPattern(cg, mColorSpace, aPattern);
1044 CGContextStrokePath(cg);
1045 }
1046
1047 fixer.Fix(mCg);
1048 CGContextRestoreGState(mCg);
1049 }
1050
1051 void
1052 DrawTargetCG::Fill(const Path *aPath, const Pattern &aPattern, const DrawOptions &aDrawOptions)
1053 {
1054 MarkChanged();
1055
1056 assert(aPath->GetBackendType() == BackendType::COREGRAPHICS);
1057
1058 CGContextSaveGState(mCg);
1059
1060 CGContextSetBlendMode(mCg, ToBlendMode(aDrawOptions.mCompositionOp));
1061 UnboundnessFixer fixer;
1062 CGContextRef cg = fixer.Check(mCg, aDrawOptions.mCompositionOp);
1063 CGContextSetAlpha(cg, aDrawOptions.mAlpha);
1064 CGContextSetShouldAntialias(cg, aDrawOptions.mAntialiasMode != AntialiasMode::NONE);
1065
1066 CGContextConcatCTM(cg, GfxMatrixToCGAffineTransform(mTransform));
1067
1068 CGContextBeginPath(cg);
1069 // XXX: we could put fill mode into the path fill rule if we wanted
1070 const PathCG *cgPath = static_cast<const PathCG*>(aPath);
1071
1072 if (isGradient(aPattern)) {
1073 // setup a clip to draw the gradient through
1074 CGRect extents;
1075 if (CGPathIsEmpty(cgPath->GetPath())) {
1076 // Adding an empty path will cause us not to clip
1077 // so clip everything explicitly
1078 CGContextClipToRect(mCg, CGRectZero);
1079 extents = CGRectZero;
1080 } else {
1081 CGContextAddPath(cg, cgPath->GetPath());
1082 extents = CGContextGetPathBoundingBox(cg);
1083 if (cgPath->GetFillRule() == FillRule::FILL_EVEN_ODD)
1084 CGContextEOClip(mCg);
1085 else
1086 CGContextClip(mCg);
1087 }
1088
1089 DrawGradient(cg, aPattern, extents);
1090 } else {
1091 CGContextAddPath(cg, cgPath->GetPath());
1092
1093 SetFillFromPattern(cg, mColorSpace, aPattern);
1094
1095 if (cgPath->GetFillRule() == FillRule::FILL_EVEN_ODD)
1096 CGContextEOFillPath(cg);
1097 else
1098 CGContextFillPath(cg);
1099 }
1100
1101 fixer.Fix(mCg);
1102 CGContextRestoreGState(mCg);
1103 }
1104
1105 CGRect ComputeGlyphsExtents(CGRect *bboxes, CGPoint *positions, CFIndex count, float scale)
1106 {
1107 CGFloat x1, x2, y1, y2;
1108 if (count < 1)
1109 return CGRectZero;
1110
1111 x1 = bboxes[0].origin.x + positions[0].x;
1112 x2 = bboxes[0].origin.x + positions[0].x + scale*bboxes[0].size.width;
1113 y1 = bboxes[0].origin.y + positions[0].y;
1114 y2 = bboxes[0].origin.y + positions[0].y + scale*bboxes[0].size.height;
1115
1116 // accumulate max and minimum coordinates
1117 for (int i = 1; i < count; i++) {
1118 x1 = min(x1, bboxes[i].origin.x + positions[i].x);
1119 y1 = min(y1, bboxes[i].origin.y + positions[i].y);
1120 x2 = max(x2, bboxes[i].origin.x + positions[i].x + scale*bboxes[i].size.width);
1121 y2 = max(y2, bboxes[i].origin.y + positions[i].y + scale*bboxes[i].size.height);
1122 }
1123
1124 CGRect extents = {{x1, y1}, {x2-x1, y2-y1}};
1125 return extents;
1126 }
1127
1128
1129 void
1130 DrawTargetCG::FillGlyphs(ScaledFont *aFont, const GlyphBuffer &aBuffer, const Pattern &aPattern, const DrawOptions &aDrawOptions,
1131 const GlyphRenderingOptions*)
1132 {
1133 MarkChanged();
1134
1135 assert(aBuffer.mNumGlyphs);
1136 CGContextSaveGState(mCg);
1137
1138 CGContextSetBlendMode(mCg, ToBlendMode(aDrawOptions.mCompositionOp));
1139 UnboundnessFixer fixer;
1140 CGContextRef cg = fixer.Check(mCg, aDrawOptions.mCompositionOp);
1141 CGContextSetAlpha(cg, aDrawOptions.mAlpha);
1142 CGContextSetShouldAntialias(cg, aDrawOptions.mAntialiasMode != AntialiasMode::NONE);
1143 if (aDrawOptions.mAntialiasMode != AntialiasMode::DEFAULT) {
1144 CGContextSetShouldSmoothFonts(cg, aDrawOptions.mAntialiasMode == AntialiasMode::SUBPIXEL);
1145 }
1146
1147 CGContextConcatCTM(cg, GfxMatrixToCGAffineTransform(mTransform));
1148
1149 ScaledFontMac* macFont = static_cast<ScaledFontMac*>(aFont);
1150
1151 //XXX: we should use a stack vector here when we have a class like that
1152 std::vector<CGGlyph> glyphs;
1153 std::vector<CGPoint> positions;
1154 glyphs.resize(aBuffer.mNumGlyphs);
1155 positions.resize(aBuffer.mNumGlyphs);
1156
1157 // Handle the flip
1158 CGContextScaleCTM(cg, 1, -1);
1159 // CGContextSetTextMatrix works differently with kCGTextClip && kCGTextFill
1160 // It seems that it transforms the positions with TextFill and not with TextClip
1161 // Therefore we'll avoid it. See also:
1162 // http://cgit.freedesktop.org/cairo/commit/?id=9c0d761bfcdd28d52c83d74f46dd3c709ae0fa69
1163
1164 for (unsigned int i = 0; i < aBuffer.mNumGlyphs; i++) {
1165 glyphs[i] = aBuffer.mGlyphs[i].mIndex;
1166 // XXX: CGPointMake might not be inlined
1167 positions[i] = CGPointMake(aBuffer.mGlyphs[i].mPosition.x,
1168 -aBuffer.mGlyphs[i].mPosition.y);
1169 }
1170
1171 //XXX: CGContextShowGlyphsAtPositions is 10.5+ for older versions use CGContextShowGlyphsWithAdvances
1172 if (isGradient(aPattern)) {
1173 CGContextSetTextDrawingMode(cg, kCGTextClip);
1174 CGRect extents;
1175 if (ScaledFontMac::CTFontDrawGlyphsPtr != nullptr) {
1176 CGRect *bboxes = new CGRect[aBuffer.mNumGlyphs];
1177 CTFontGetBoundingRectsForGlyphs(macFont->mCTFont, kCTFontDefaultOrientation,
1178 &glyphs.front(), bboxes, aBuffer.mNumGlyphs);
1179 extents = ComputeGlyphsExtents(bboxes, &positions.front(), aBuffer.mNumGlyphs, 1.0f);
1180 ScaledFontMac::CTFontDrawGlyphsPtr(macFont->mCTFont, &glyphs.front(),
1181 &positions.front(), aBuffer.mNumGlyphs, cg);
1182 delete bboxes;
1183 } else {
1184 CGRect *bboxes = new CGRect[aBuffer.mNumGlyphs];
1185 CGFontGetGlyphBBoxes(macFont->mFont, &glyphs.front(), aBuffer.mNumGlyphs, bboxes);
1186 extents = ComputeGlyphsExtents(bboxes, &positions.front(), aBuffer.mNumGlyphs, macFont->mSize);
1187
1188 CGContextSetFont(cg, macFont->mFont);
1189 CGContextSetFontSize(cg, macFont->mSize);
1190 CGContextShowGlyphsAtPositions(cg, &glyphs.front(), &positions.front(),
1191 aBuffer.mNumGlyphs);
1192 delete bboxes;
1193 }
1194 CGContextScaleCTM(cg, 1, -1);
1195 DrawGradient(cg, aPattern, extents);
1196 } else {
1197 //XXX: with CoreGraphics we can stroke text directly instead of going
1198 // through GetPath. It would be nice to add support for using that
1199 CGContextSetTextDrawingMode(cg, kCGTextFill);
1200 SetFillFromPattern(cg, mColorSpace, aPattern);
1201 if (ScaledFontMac::CTFontDrawGlyphsPtr != nullptr) {
1202 ScaledFontMac::CTFontDrawGlyphsPtr(macFont->mCTFont, &glyphs.front(),
1203 &positions.front(),
1204 aBuffer.mNumGlyphs, cg);
1205 } else {
1206 CGContextSetFont(cg, macFont->mFont);
1207 CGContextSetFontSize(cg, macFont->mSize);
1208 CGContextShowGlyphsAtPositions(cg, &glyphs.front(), &positions.front(),
1209 aBuffer.mNumGlyphs);
1210 }
1211 }
1212
1213 fixer.Fix(mCg);
1214 CGContextRestoreGState(cg);
1215 }
1216
1217 extern "C" {
1218 void
1219 CGContextResetClip(CGContextRef);
1220 };
1221
1222 void
1223 DrawTargetCG::CopySurface(SourceSurface *aSurface,
1224 const IntRect& aSourceRect,
1225 const IntPoint &aDestination)
1226 {
1227 MarkChanged();
1228
1229 if (aSurface->GetType() == SurfaceType::COREGRAPHICS_IMAGE ||
1230 aSurface->GetType() == SurfaceType::COREGRAPHICS_CGCONTEXT ||
1231 aSurface->GetType() == SurfaceType::DATA) {
1232 CGImageRef image = GetRetainedImageFromSourceSurface(aSurface);
1233
1234 // XXX: it might be more efficient for us to do the copy directly if we have access to the bits
1235
1236 CGContextSaveGState(mCg);
1237
1238 // CopySurface ignores the clip, so we need to use private API to temporarily reset it
1239 CGContextResetClip(mCg);
1240 CGRect destRect = CGRectMake(aDestination.x, aDestination.y,
1241 aSourceRect.width, aSourceRect.height);
1242 CGContextClipToRect(mCg, destRect);
1243
1244 CGContextSetBlendMode(mCg, kCGBlendModeCopy);
1245
1246 CGContextScaleCTM(mCg, 1, -1);
1247
1248 CGRect flippedRect = CGRectMake(aDestination.x - aSourceRect.x, -(aDestination.y - aSourceRect.y + double(CGImageGetHeight(image))),
1249 CGImageGetWidth(image), CGImageGetHeight(image));
1250
1251 // Quartz seems to copy A8 surfaces incorrectly if we don't initialize them
1252 // to transparent first.
1253 if (mFormat == SurfaceFormat::A8) {
1254 CGContextClearRect(mCg, flippedRect);
1255 }
1256 CGContextDrawImage(mCg, flippedRect, image);
1257
1258 CGContextRestoreGState(mCg);
1259 CGImageRelease(image);
1260 }
1261 }
1262
1263 void
1264 DrawTargetCG::DrawSurfaceWithShadow(SourceSurface *aSurface, const Point &aDest, const Color &aColor, const Point &aOffset, Float aSigma, CompositionOp aOperator)
1265 {
1266 MarkChanged();
1267
1268 CGImageRef image = GetRetainedImageFromSourceSurface(aSurface);
1269
1270 IntSize size = aSurface->GetSize();
1271 CGContextSaveGState(mCg);
1272 //XXX do we need to do the fixup here?
1273 CGContextSetBlendMode(mCg, ToBlendMode(aOperator));
1274
1275 CGContextScaleCTM(mCg, 1, -1);
1276
1277 CGRect flippedRect = CGRectMake(aDest.x, -(aDest.y + size.height),
1278 size.width, size.height);
1279
1280 CGColorRef color = ColorToCGColor(mColorSpace, aColor);
1281 CGSize offset = {aOffset.x, -aOffset.y};
1282 // CoreGraphics needs twice sigma as it's amount of blur
1283 CGContextSetShadowWithColor(mCg, offset, 2*aSigma, color);
1284 CGColorRelease(color);
1285
1286 CGContextDrawImage(mCg, flippedRect, image);
1287
1288 CGImageRelease(image);
1289 CGContextRestoreGState(mCg);
1290
1291 }
1292
1293 bool
1294 DrawTargetCG::Init(BackendType aType,
1295 unsigned char* aData,
1296 const IntSize &aSize,
1297 int32_t aStride,
1298 SurfaceFormat aFormat)
1299 {
1300 // XXX: we should come up with some consistent semantics for dealing
1301 // with zero area drawtargets
1302 if (aSize.width <= 0 || aSize.height <= 0 ||
1303 // 32767 is the maximum size supported by cairo
1304 // we clamp to that to make it easier to interoperate
1305 aSize.width > 32767 || aSize.height > 32767) {
1306 gfxWarning() << "Failed to Init() DrawTargetCG because of bad size.";
1307 mColorSpace = nullptr;
1308 mCg = nullptr;
1309 return false;
1310 }
1311
1312 //XXX: handle SurfaceFormat
1313
1314 //XXX: we'd be better off reusing the Colorspace across draw targets
1315 mColorSpace = CGColorSpaceCreateDeviceRGB();
1316
1317 if (aData == nullptr && aType != BackendType::COREGRAPHICS_ACCELERATED) {
1318 // XXX: Currently, Init implicitly clears, that can often be a waste of time
1319 size_t bufLen = BufferSizeFromStrideAndHeight(aStride, aSize.height);
1320 if (bufLen == 0) {
1321 mColorSpace = nullptr;
1322 mCg = nullptr;
1323 return false;
1324 }
1325 static_assert(sizeof(decltype(mData[0])) == 1,
1326 "mData.Realloc() takes an object count, so its objects must be 1-byte sized if we use bufLen");
1327 mData.Realloc(/* actually an object count */ bufLen);
1328 aData = static_cast<unsigned char*>(mData);
1329 memset(aData, 0, bufLen);
1330 }
1331
1332 mSize = aSize;
1333
1334 if (aType == BackendType::COREGRAPHICS_ACCELERATED) {
1335 RefPtr<MacIOSurface> ioSurface = MacIOSurface::CreateIOSurface(aSize.width, aSize.height);
1336 mCg = ioSurface->CreateIOSurfaceContext();
1337 // If we don't have the symbol for 'CreateIOSurfaceContext' mCg will be null
1338 // and we will fallback to software below
1339 }
1340
1341 mFormat = SurfaceFormat::B8G8R8A8;
1342
1343 if (!mCg || aType == BackendType::COREGRAPHICS) {
1344 int bitsPerComponent = 8;
1345
1346 CGBitmapInfo bitinfo;
1347 if (aFormat == SurfaceFormat::A8) {
1348 if (mColorSpace)
1349 CGColorSpaceRelease(mColorSpace);
1350 mColorSpace = nullptr;
1351 bitinfo = kCGImageAlphaOnly;
1352 mFormat = SurfaceFormat::A8;
1353 } else {
1354 bitinfo = kCGBitmapByteOrder32Host;
1355 if (aFormat == SurfaceFormat::B8G8R8X8) {
1356 bitinfo |= kCGImageAlphaNoneSkipFirst;
1357 mFormat = aFormat;
1358 } else {
1359 bitinfo |= kCGImageAlphaPremultipliedFirst;
1360 }
1361 }
1362 // XXX: what should we do if this fails?
1363 mCg = CGBitmapContextCreate (aData,
1364 mSize.width,
1365 mSize.height,
1366 bitsPerComponent,
1367 aStride,
1368 mColorSpace,
1369 bitinfo);
1370 }
1371
1372 assert(mCg);
1373 // CGContext's default to have the origin at the bottom left
1374 // so flip it to the top left
1375 CGContextTranslateCTM(mCg, 0, mSize.height);
1376 CGContextScaleCTM(mCg, 1, -1);
1377 // See Bug 722164 for performance details
1378 // Medium or higher quality lead to expensive interpolation
1379 // for canvas we want to use low quality interpolation
1380 // to have competitive performance with other canvas
1381 // implementation.
1382 // XXX: Create input parameter to control interpolation and
1383 // use the default for content.
1384 CGContextSetInterpolationQuality(mCg, kCGInterpolationLow);
1385
1386
1387 if (aType == BackendType::COREGRAPHICS_ACCELERATED) {
1388 // The bitmap backend uses callac to clear, we can't do that without
1389 // reading back the surface. This should trigger something equivilent
1390 // to glClear.
1391 ClearRect(Rect(0, 0, mSize.width, mSize.height));
1392 }
1393
1394 return true;
1395 }
1396
1397 void
1398 DrawTargetCG::Flush()
1399 {
1400 if (GetContextType(mCg) == CG_CONTEXT_TYPE_IOSURFACE) {
1401 CGContextFlush(mCg);
1402 }
1403 }
1404
1405 bool
1406 DrawTargetCG::Init(CGContextRef cgContext, const IntSize &aSize)
1407 {
1408 // XXX: we should come up with some consistent semantics for dealing
1409 // with zero area drawtargets
1410 if (aSize.width == 0 || aSize.height == 0) {
1411 mColorSpace = nullptr;
1412 mCg = nullptr;
1413 return false;
1414 }
1415
1416 //XXX: handle SurfaceFormat
1417
1418 //XXX: we'd be better off reusing the Colorspace across draw targets
1419 mColorSpace = CGColorSpaceCreateDeviceRGB();
1420
1421 mSize = aSize;
1422
1423 mCg = cgContext;
1424 CGContextRetain(mCg);
1425
1426 assert(mCg);
1427
1428 // CGContext's default to have the origin at the bottom left.
1429 // However, currently the only use of this function is to construct a
1430 // DrawTargetCG around a CGContextRef from a cairo quartz surface which
1431 // already has it's origin adjusted.
1432 //
1433 // CGContextTranslateCTM(mCg, 0, mSize.height);
1434 // CGContextScaleCTM(mCg, 1, -1);
1435
1436 mFormat = SurfaceFormat::B8G8R8A8;
1437 if (GetContextType(mCg) == CG_CONTEXT_TYPE_BITMAP) {
1438 CGColorSpaceRef colorspace;
1439 CGBitmapInfo bitinfo = CGBitmapContextGetBitmapInfo(mCg);
1440 colorspace = CGBitmapContextGetColorSpace (mCg);
1441 if (CGColorSpaceGetNumberOfComponents(colorspace) == 1) {
1442 mFormat = SurfaceFormat::A8;
1443 } else if ((bitinfo & kCGBitmapAlphaInfoMask) == kCGImageAlphaNoneSkipFirst) {
1444 mFormat = SurfaceFormat::B8G8R8X8;
1445 }
1446 }
1447
1448 return true;
1449 }
1450
1451 bool
1452 DrawTargetCG::Init(BackendType aType, const IntSize &aSize, SurfaceFormat &aFormat)
1453 {
1454 int32_t stride = GetAlignedStride<16>(aSize.width * BytesPerPixel(aFormat));
1455
1456 // Calling Init with aData == nullptr will allocate.
1457 return Init(aType, nullptr, aSize, stride, aFormat);
1458 }
1459
1460 TemporaryRef<PathBuilder>
1461 DrawTargetCG::CreatePathBuilder(FillRule aFillRule) const
1462 {
1463 RefPtr<PathBuilderCG> pb = new PathBuilderCG(aFillRule);
1464 return pb;
1465 }
1466
1467 void*
1468 DrawTargetCG::GetNativeSurface(NativeSurfaceType aType)
1469 {
1470 if ((aType == NativeSurfaceType::CGCONTEXT && GetContextType(mCg) == CG_CONTEXT_TYPE_BITMAP) ||
1471 (aType == NativeSurfaceType::CGCONTEXT_ACCELERATED && GetContextType(mCg) == CG_CONTEXT_TYPE_IOSURFACE)) {
1472 return mCg;
1473 } else {
1474 return nullptr;
1475 }
1476 }
1477
1478 void
1479 DrawTargetCG::Mask(const Pattern &aSource,
1480 const Pattern &aMask,
1481 const DrawOptions &aDrawOptions)
1482 {
1483 MarkChanged();
1484
1485 CGContextSaveGState(mCg);
1486
1487 if (isGradient(aMask)) {
1488 assert(0);
1489 } else {
1490 if (aMask.GetType() == PatternType::COLOR) {
1491 DrawOptions drawOptions(aDrawOptions);
1492 const Color& color = static_cast<const ColorPattern&>(aMask).mColor;
1493 drawOptions.mAlpha *= color.a;
1494 assert(0);
1495 // XXX: we need to get a rect that when transformed covers the entire surface
1496 //Rect
1497 //FillRect(rect, aSource, drawOptions);
1498 } else if (aMask.GetType() == PatternType::SURFACE) {
1499 const SurfacePattern& pat = static_cast<const SurfacePattern&>(aMask);
1500 CGImageRef mask = GetRetainedImageFromSourceSurface(pat.mSurface.get());
1501 Rect rect(0,0, CGImageGetWidth(mask), CGImageGetHeight(mask));
1502 // XXX: probably we need to do some flipping of the image or something
1503 CGContextClipToMask(mCg, RectToCGRect(rect), mask);
1504 FillRect(rect, aSource, aDrawOptions);
1505 CGImageRelease(mask);
1506 }
1507 }
1508
1509 CGContextRestoreGState(mCg);
1510 }
1511
1512 void
1513 DrawTargetCG::PushClipRect(const Rect &aRect)
1514 {
1515 CGContextSaveGState(mCg);
1516
1517 /* We go through a bit of trouble to temporarilly set the transform
1518 * while we add the path */
1519 CGAffineTransform previousTransform = CGContextGetCTM(mCg);
1520 CGContextConcatCTM(mCg, GfxMatrixToCGAffineTransform(mTransform));
1521 CGContextClipToRect(mCg, RectToCGRect(aRect));
1522 CGContextSetCTM(mCg, previousTransform);
1523 }
1524
1525
1526 void
1527 DrawTargetCG::PushClip(const Path *aPath)
1528 {
1529 CGContextSaveGState(mCg);
1530
1531 CGContextBeginPath(mCg);
1532 assert(aPath->GetBackendType() == BackendType::COREGRAPHICS);
1533
1534 const PathCG *cgPath = static_cast<const PathCG*>(aPath);
1535
1536 // Weirdly, CoreGraphics clips empty paths as all shown
1537 // but emtpy rects as all clipped. We detect this situation and
1538 // workaround it appropriately
1539 if (CGPathIsEmpty(cgPath->GetPath())) {
1540 // XXX: should we return here?
1541 CGContextClipToRect(mCg, CGRectZero);
1542 }
1543
1544
1545 /* We go through a bit of trouble to temporarilly set the transform
1546 * while we add the path. XXX: this could be improved if we keep
1547 * the CTM as resident state on the DrawTarget. */
1548 CGContextSaveGState(mCg);
1549 CGContextConcatCTM(mCg, GfxMatrixToCGAffineTransform(mTransform));
1550 CGContextAddPath(mCg, cgPath->GetPath());
1551 CGContextRestoreGState(mCg);
1552
1553 if (cgPath->GetFillRule() == FillRule::FILL_EVEN_ODD)
1554 CGContextEOClip(mCg);
1555 else
1556 CGContextClip(mCg);
1557 }
1558
1559 void
1560 DrawTargetCG::PopClip()
1561 {
1562 CGContextRestoreGState(mCg);
1563 }
1564
1565 void
1566 DrawTargetCG::MarkChanged()
1567 {
1568 if (mSnapshot) {
1569 if (mSnapshot->refCount() > 1) {
1570 // We only need to worry about snapshots that someone else knows about
1571 mSnapshot->DrawTargetWillChange();
1572 }
1573 mSnapshot = nullptr;
1574 }
1575 }
1576
1577 CGContextRef
1578 BorrowedCGContext::BorrowCGContextFromDrawTarget(DrawTarget *aDT)
1579 {
1580 if (aDT->GetType() == BackendType::COREGRAPHICS || aDT->GetType() == BackendType::COREGRAPHICS_ACCELERATED) {
1581 DrawTargetCG* cgDT = static_cast<DrawTargetCG*>(aDT);
1582 cgDT->MarkChanged();
1583
1584 // swap out the context
1585 CGContextRef cg = cgDT->mCg;
1586 cgDT->mCg = nullptr;
1587
1588 // save the state to make it easier for callers to avoid mucking with things
1589 CGContextSaveGState(cg);
1590
1591 CGContextConcatCTM(cg, GfxMatrixToCGAffineTransform(cgDT->mTransform));
1592
1593 return cg;
1594 }
1595 return nullptr;
1596 }
1597
1598 void
1599 BorrowedCGContext::ReturnCGContextToDrawTarget(DrawTarget *aDT, CGContextRef cg)
1600 {
1601 DrawTargetCG* cgDT = static_cast<DrawTargetCG*>(aDT);
1602
1603 CGContextRestoreGState(cg);
1604 cgDT->mCg = cg;
1605 }
1606
1607
1608 }
1609 }

mercurial