2 * Copyright (C) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 Apple Inc. All rights reserved.
3 * Copyright (C) 2008 Eric Seidel <eric@webkit.org>
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
8 * 1. Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 * notice, this list of conditions and the following disclaimer in the
12 * documentation and/or other materials provided with the distribution.
14 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
15 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
17 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
18 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
19 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
20 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
21 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
22 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
24 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 #define _USE_MATH_DEFINES 1
29 #include "GraphicsContextCG.h"
31 #include "AffineTransform.h"
32 #include "FloatConversion.h"
33 #include "GraphicsContextPlatformPrivateCG.h"
34 #include "ImageBuffer.h"
35 #include "ImageOrientation.h"
39 #include "ShadowBlur.h"
40 #include "SubimageCacheWithTimer.h"
42 #include <CoreGraphics/CoreGraphics.h>
43 #include <wtf/MathExtras.h>
44 #include <wtf/RetainPtr.h>
47 #include "WebCoreSystemInterface.h"
51 #include <WebKitSystemInterface/WebKitSystemInterface.h>
55 #include <CoreGraphics/CGContextGState.h>
56 #include <wtf/HashMap.h>
61 CG_EXTERN void CGContextSetCTM(CGContextRef, CGAffineTransform);
62 CG_EXTERN CGAffineTransform CGContextGetBaseCTM(CGContextRef);
64 #endif // !PLATFORM(IOS)
66 // FIXME: The following using declaration should be in <wtf/HashFunctions.h>.
67 using WTF::pairIntHash;
69 // FIXME: The following using declaration should be in <wtf/HashTraits.h>.
70 using WTF::GenericHashTraits;
74 static void setCGFillColor(CGContextRef context, const Color& color, ColorSpace colorSpace)
76 CGContextSetFillColorWithColor(context, cachedCGColor(color, colorSpace));
79 static void setCGStrokeColor(CGContextRef context, const Color& color, ColorSpace colorSpace)
81 CGContextSetStrokeColorWithColor(context, cachedCGColor(color, colorSpace));
84 CGColorSpaceRef deviceRGBColorSpaceRef()
86 static CGColorSpaceRef deviceSpace = CGColorSpaceCreateDeviceRGB();
90 CGColorSpaceRef sRGBColorSpaceRef()
93 return deviceRGBColorSpaceRef();
95 static CGColorSpaceRef sRGBSpace = CGColorSpaceCreateWithName(kCGColorSpaceSRGB);
97 // Out-of-date CG installations will not honor kCGColorSpaceSRGB. This logic avoids
98 // causing a crash under those conditions. Since the default color space in Windows
99 // is sRGB, this all works out nicely.
101 sRGBSpace = deviceRGBColorSpaceRef();
102 #endif // PLATFORM(WIN)
104 #endif // PLATFORM(IOS)
108 void setStrokeAndFillColor(CGContextRef context, CGColorRef color)
110 CGContextSetStrokeColorWithColor(context, color);
111 CGContextSetFillColorWithColor(context, color);
113 #endif // PLATFORM(IOS)
115 #if PLATFORM(WIN) || PLATFORM(IOS)
116 CGColorSpaceRef linearRGBColorSpaceRef()
118 // FIXME: Windows should be able to use linear sRGB, this is tracked by http://webkit.org/b/80000.
119 return deviceRGBColorSpaceRef();
123 void GraphicsContext::platformInit(CGContextRef cgContext, bool shouldUseContextColors)
125 m_data = new GraphicsContextPlatformPrivate(cgContext);
126 setPaintingDisabled(!cgContext);
129 m_state.shouldUseContextColors = shouldUseContextColors;
130 if (shouldUseContextColors) {
132 UNUSED_PARAM(shouldUseContextColors);
134 // Make sure the context starts in sync with our state.
135 setPlatformFillColor(fillColor(), fillColorSpace());
136 setPlatformStrokeColor(strokeColor(), strokeColorSpace());
137 setPlatformStrokeThickness(strokeThickness());
144 void GraphicsContext::platformDestroy()
149 CGContextRef GraphicsContext::platformContext() const
151 ASSERT(!paintingDisabled());
152 ASSERT(m_data->m_cgContext);
153 return m_data->m_cgContext.get();
156 void GraphicsContext::savePlatformState()
158 // Note: Do not use this function within this class implementation, since we want to avoid the extra
159 // save of the secondary context (in GraphicsContextPlatformPrivateCG.h).
160 CGContextSaveGState(platformContext());
164 void GraphicsContext::restorePlatformState()
166 // Note: Do not use this function within this class implementation, since we want to avoid the extra
167 // restore of the secondary context (in GraphicsContextPlatformPrivateCG.h).
168 CGContextRestoreGState(platformContext());
170 m_data->m_userToDeviceTransformKnownToBeIdentity = false;
173 void GraphicsContext::drawNativeImage(PassNativeImagePtr imagePtr, const FloatSize& imageSize, ColorSpace styleColorSpace, const FloatRect& destRect, const FloatRect& srcRect, CompositeOperator op, BlendMode blendMode, ImageOrientation orientation)
175 RetainPtr<CGImageRef> image(imagePtr);
177 float currHeight = orientation.usesWidthAsHeight() ? CGImageGetWidth(image.get()) : CGImageGetHeight(image.get());
178 if (currHeight <= srcRect.y())
181 CGContextRef context = platformContext();
182 CGContextSaveGState(context);
185 // Anti-aliasing is on by default on the iPhone. Need to turn it off when drawing images.
186 CGContextSetShouldAntialias(context, false);
189 bool shouldUseSubimage = false;
191 // If the source rect is a subportion of the image, then we compute an inflated destination rect that will hold the entire image
192 // and then set a clip to the portion that we want to display.
193 FloatRect adjustedDestRect = destRect;
195 if (srcRect.size() != imageSize) {
196 CGInterpolationQuality interpolationQuality = CGContextGetInterpolationQuality(context);
197 // When the image is scaled using high-quality interpolation, we create a temporary CGImage
198 // containing only the portion we want to display. We need to do this because high-quality
199 // interpolation smoothes sharp edges, causing pixels from outside the source rect to bleed
200 // into the destination rect. See <rdar://problem/6112909>.
201 shouldUseSubimage = (interpolationQuality != kCGInterpolationNone) && (srcRect.size() != destRect.size() || !getCTM().isIdentityOrTranslationOrFlipped());
202 float xScale = srcRect.width() / destRect.width();
203 float yScale = srcRect.height() / destRect.height();
204 if (shouldUseSubimage) {
205 FloatRect subimageRect = srcRect;
206 float leftPadding = srcRect.x() - floorf(srcRect.x());
207 float topPadding = srcRect.y() - floorf(srcRect.y());
209 subimageRect.move(-leftPadding, -topPadding);
210 adjustedDestRect.move(-leftPadding / xScale, -topPadding / yScale);
212 subimageRect.setWidth(ceilf(subimageRect.width() + leftPadding));
213 adjustedDestRect.setWidth(subimageRect.width() / xScale);
215 subimageRect.setHeight(ceilf(subimageRect.height() + topPadding));
216 adjustedDestRect.setHeight(subimageRect.height() / yScale);
219 image = subimageCache().getSubimage(image.get(), subimageRect);
221 image = adoptCF(CGImageCreateWithImageInRect(image.get(), subimageRect));
223 if (currHeight < srcRect.maxY()) {
224 ASSERT(CGImageGetHeight(image.get()) == currHeight - CGRectIntegral(srcRect).origin.y);
225 adjustedDestRect.setHeight(CGImageGetHeight(image.get()) / yScale);
228 adjustedDestRect.setLocation(FloatPoint(destRect.x() - srcRect.x() / xScale, destRect.y() - srcRect.y() / yScale));
229 adjustedDestRect.setSize(FloatSize(imageSize.width() / xScale, imageSize.height() / yScale));
232 if (!destRect.contains(adjustedDestRect))
233 CGContextClipToRect(context, destRect);
236 // If the image is only partially loaded, then shrink the destination rect that we're drawing into accordingly.
237 if (!shouldUseSubimage && currHeight < imageSize.height())
238 adjustedDestRect.setHeight(adjustedDestRect.height() * currHeight / imageSize.height());
241 // Align to pixel boundaries
242 adjustedDestRect = roundToDevicePixels(adjustedDestRect);
245 setPlatformCompositeOperation(op, blendMode);
247 // ImageOrientation expects the origin to be at (0, 0)
248 CGContextTranslateCTM(context, adjustedDestRect.x(), adjustedDestRect.y());
249 adjustedDestRect.setLocation(FloatPoint());
251 if (orientation != DefaultImageOrientation) {
252 CGContextConcatCTM(context, orientation.transformFromDefault(adjustedDestRect.size()));
253 if (orientation.usesWidthAsHeight()) {
254 // The destination rect will have it's width and height already reversed for the orientation of
255 // the image, as it was needed for page layout, so we need to reverse it back here.
256 adjustedDestRect = FloatRect(adjustedDestRect.x(), adjustedDestRect.y(), adjustedDestRect.height(), adjustedDestRect.width());
261 CGContextTranslateCTM(context, 0, adjustedDestRect.height());
262 CGContextScaleCTM(context, 1, -1);
264 // Adjust the color space.
265 image = Image::imageWithColorSpace(image.get(), styleColorSpace);
268 CGContextDrawImage(context, adjustedDestRect, image.get());
270 CGContextRestoreGState(context);
273 // Draws a filled rectangle with a stroked border.
274 void GraphicsContext::drawRect(const FloatRect& rect, float borderThickness)
276 // FIXME: this function does not handle patterns and gradients
277 // like drawPath does, it probably should.
278 if (paintingDisabled())
281 ASSERT(!rect.isEmpty());
283 CGContextRef context = platformContext();
285 CGContextFillRect(context, rect);
287 if (strokeStyle() != NoStroke) {
288 // We do a fill of four rects to simulate the stroke of a border.
289 Color oldFillColor = fillColor();
290 if (oldFillColor != strokeColor())
291 setCGFillColor(context, strokeColor(), strokeColorSpace());
293 FloatRect(rect.x(), rect.y(), rect.width(), borderThickness),
294 FloatRect(rect.x(), rect.maxY() - borderThickness, rect.width(), borderThickness),
295 FloatRect(rect.x(), rect.y() + borderThickness, borderThickness, rect.height() - 2 * borderThickness),
296 FloatRect(rect.maxX() - borderThickness, rect.y() + borderThickness, borderThickness, rect.height() - 2 * borderThickness)
298 CGContextFillRects(context, rects, 4);
299 if (oldFillColor != strokeColor())
300 setCGFillColor(context, oldFillColor, fillColorSpace());
304 // This is only used to draw borders.
305 void GraphicsContext::drawLine(const FloatPoint& point1, const FloatPoint& point2)
307 if (paintingDisabled())
310 if (strokeStyle() == NoStroke)
313 float width = strokeThickness();
315 FloatPoint p1 = point1;
316 FloatPoint p2 = point2;
317 bool isVerticalLine = (p1.x() == p2.x());
319 // For odd widths, we add in 0.5 to the appropriate x/y so that the float arithmetic
320 // works out. For example, with a border width of 3, KHTML will pass us (y1+y2)/2, e.g.,
321 // (50+53)/2 = 103/2 = 51 when we want 51.5. It is always true that an even width gave
322 // us a perfect position, but an odd width gave us a position that is off by exactly 0.5.
323 if (strokeStyle() == DottedStroke || strokeStyle() == DashedStroke) {
324 if (isVerticalLine) {
333 if (((int)width) % 2) {
334 if (isVerticalLine) {
335 // We're a vertical line. Adjust our x.
339 // We're a horizontal line. Adjust our y.
346 switch (strokeStyle()) {
350 case WavyStroke: // FIXME: https://bugs.webkit.org/show_bug.cgi?id=94112 - Needs platform support.
353 patWidth = (int)width;
356 patWidth = 3 * (int)width;
360 CGContextRef context = platformContext();
362 if (shouldAntialias()) {
363 bool willAntialias = false;
365 // Force antialiasing on for line patterns as they don't look good with it turned off (<rdar://problem/5459772>).
366 willAntialias = patWidth;
368 CGContextSetShouldAntialias(context, willAntialias);
372 CGContextSaveGState(context);
374 // Do a rect fill of our endpoints. This ensures we always have the
375 // appearance of being a border. We then draw the actual dotted/dashed line.
376 setCGFillColor(context, strokeColor(), strokeColorSpace()); // The save/restore make it safe to mutate the fill color here without setting it back to the old color.
377 if (isVerticalLine) {
378 CGContextFillRect(context, FloatRect(p1.x() - width / 2, p1.y() - width, width, width));
379 CGContextFillRect(context, FloatRect(p2.x() - width / 2, p2.y(), width, width));
381 CGContextFillRect(context, FloatRect(p1.x() - width, p1.y() - width / 2, width, width));
382 CGContextFillRect(context, FloatRect(p2.x(), p2.y() - width / 2, width, width));
385 // Example: 80 pixels with a width of 30 pixels.
386 // Remainder is 20. The maximum pixels of line we could paint
387 // will be 50 pixels.
388 int distance = (isVerticalLine ? (int)(point2.y() - point1.y()) : (point2.x() - point1.x())) - 2*(int)width;
389 int remainder = distance % patWidth;
390 int coverage = distance - remainder;
391 int numSegments = coverage / patWidth;
393 float patternOffset = 0.0f;
394 // Special case 1px dotted borders for speed.
396 patternOffset = 1.0f;
398 bool evenNumberOfSegments = !(numSegments % 2);
400 evenNumberOfSegments = !evenNumberOfSegments;
401 if (evenNumberOfSegments) {
403 patternOffset += patWidth - remainder;
404 patternOffset += remainder / 2;
406 patternOffset = patWidth / 2;
409 patternOffset = (patWidth - remainder)/2;
413 const CGFloat dottedLine[2] = { static_cast<CGFloat>(patWidth), static_cast<CGFloat>(patWidth) };
414 CGContextSetLineDash(context, patternOffset, dottedLine, 2);
417 CGContextBeginPath(context);
418 CGContextMoveToPoint(context, p1.x(), p1.y());
419 CGContextAddLineToPoint(context, p2.x(), p2.y());
421 CGContextStrokePath(context);
424 CGContextRestoreGState(context);
426 if (shouldAntialias())
427 CGContextSetShouldAntialias(context, true);
431 void GraphicsContext::drawJoinedLines(CGPoint points[], unsigned count, bool antialias, CGLineCap lineCap)
433 if (paintingDisabled() || !count)
436 CGContextRef context = platformContext();
437 float width = CGContextGetLineWidth(context);
439 CGContextSaveGState(context);
441 CGContextSetShouldAntialias(context, antialias);
443 CGContextSetLineWidth(context, width < 1 ? 1 : width);
445 CGContextBeginPath(context);
447 CGContextSetLineCap(context, lineCap);
449 CGContextMoveToPoint(context, points[0].x, points[0].y);
451 for (unsigned i = 1; i < count; ++i)
452 CGContextAddLineToPoint(context, points[i].x, points[i].y);
454 CGContextStrokePath(context);
456 CGContextRestoreGState(context);
460 void GraphicsContext::drawEllipse(const FloatRect& rect)
462 if (paintingDisabled())
466 path.addEllipse(rect);
470 static void addConvexPolygonToPath(Path& path, size_t numberOfPoints, const FloatPoint* points)
472 ASSERT(numberOfPoints > 0);
474 path.moveTo(points[0]);
475 for (size_t i = 1; i < numberOfPoints; ++i)
476 path.addLineTo(points[i]);
480 void GraphicsContext::drawConvexPolygon(size_t numberOfPoints, const FloatPoint* points, bool antialiased)
482 if (paintingDisabled())
485 if (numberOfPoints <= 1)
488 CGContextRef context = platformContext();
490 if (antialiased != shouldAntialias())
491 CGContextSetShouldAntialias(context, antialiased);
494 addConvexPolygonToPath(path, numberOfPoints, points);
497 if (antialiased != shouldAntialias())
498 CGContextSetShouldAntialias(context, shouldAntialias());
501 void GraphicsContext::clipConvexPolygon(size_t numberOfPoints, const FloatPoint* points, bool antialias)
503 if (paintingDisabled())
506 if (numberOfPoints <= 1)
509 CGContextRef context = platformContext();
511 if (antialias != shouldAntialias())
512 CGContextSetShouldAntialias(context, antialias);
515 addConvexPolygonToPath(path, numberOfPoints, points);
516 clipPath(path, RULE_NONZERO);
518 if (antialias != shouldAntialias())
519 CGContextSetShouldAntialias(context, shouldAntialias());
522 void GraphicsContext::applyStrokePattern()
524 CGContextRef cgContext = platformContext();
525 AffineTransform userToBaseCTM = AffineTransform(wkGetUserToBaseCTM(cgContext));
527 RetainPtr<CGPatternRef> platformPattern = adoptCF(m_state.strokePattern->createPlatformPattern(userToBaseCTM));
528 if (!platformPattern)
531 RetainPtr<CGColorSpaceRef> patternSpace = adoptCF(CGColorSpaceCreatePattern(0));
532 CGContextSetStrokeColorSpace(cgContext, patternSpace.get());
534 const CGFloat patternAlpha = 1;
535 CGContextSetStrokePattern(cgContext, platformPattern.get(), &patternAlpha);
538 void GraphicsContext::applyFillPattern()
540 CGContextRef cgContext = platformContext();
541 AffineTransform userToBaseCTM = AffineTransform(wkGetUserToBaseCTM(cgContext));
543 RetainPtr<CGPatternRef> platformPattern = adoptCF(m_state.fillPattern->createPlatformPattern(userToBaseCTM));
544 if (!platformPattern)
547 RetainPtr<CGColorSpaceRef> patternSpace = adoptCF(CGColorSpaceCreatePattern(0));
548 CGContextSetFillColorSpace(cgContext, patternSpace.get());
550 const CGFloat patternAlpha = 1;
551 CGContextSetFillPattern(cgContext, platformPattern.get(), &patternAlpha);
554 static inline bool calculateDrawingMode(const GraphicsContextState& state, CGPathDrawingMode& mode)
556 bool shouldFill = state.fillPattern || state.fillColor.alpha();
557 bool shouldStroke = state.strokePattern || (state.strokeStyle != NoStroke && state.strokeColor.alpha());
558 bool useEOFill = state.fillRule == RULE_EVENODD;
563 mode = kCGPathEOFillStroke;
565 mode = kCGPathFillStroke;
566 } else { // fill, no stroke
568 mode = kCGPathEOFill;
573 // Setting mode to kCGPathStroke even if shouldStroke is false. In that case, we return false and mode will not be used,
574 // but the compiler will not complain about an uninitialized variable.
575 mode = kCGPathStroke;
578 return shouldFill || shouldStroke;
581 void GraphicsContext::drawPath(const Path& path)
583 if (paintingDisabled() || path.isEmpty())
586 CGContextRef context = platformContext();
587 const GraphicsContextState& state = m_state;
589 if (state.fillGradient || state.strokeGradient) {
590 // We don't have any optimized way to fill & stroke a path using gradients
591 // FIXME: Be smarter about this.
597 CGContextBeginPath(context);
598 CGContextAddPath(context, path.platformPath());
600 if (state.fillPattern)
602 if (state.strokePattern)
603 applyStrokePattern();
605 CGPathDrawingMode drawingMode;
606 if (calculateDrawingMode(state, drawingMode))
607 CGContextDrawPath(context, drawingMode);
610 static inline void fillPathWithFillRule(CGContextRef context, WindRule fillRule)
612 if (fillRule == RULE_EVENODD)
613 CGContextEOFillPath(context);
615 CGContextFillPath(context);
618 void GraphicsContext::fillPath(const Path& path)
620 if (paintingDisabled() || path.isEmpty())
623 CGContextRef context = platformContext();
625 if (m_state.fillGradient) {
627 FloatRect rect = path.fastBoundingRect();
628 FloatSize layerSize = getCTM().mapSize(rect.size());
630 CGLayerRef layer = CGLayerCreateWithContext(context, layerSize, 0);
631 CGContextRef layerContext = CGLayerGetContext(layer);
633 CGContextScaleCTM(layerContext, layerSize.width() / rect.width(), layerSize.height() / rect.height());
634 CGContextTranslateCTM(layerContext, -rect.x(), -rect.y());
635 CGContextBeginPath(layerContext);
636 CGContextAddPath(layerContext, path.platformPath());
637 CGContextConcatCTM(layerContext, m_state.fillGradient->gradientSpaceTransform());
639 if (fillRule() == RULE_EVENODD)
640 CGContextEOClip(layerContext);
642 CGContextClip(layerContext);
644 m_state.fillGradient->paint(layerContext);
645 CGContextDrawLayerInRect(context, rect, layer);
646 CGLayerRelease(layer);
648 CGContextBeginPath(context);
649 CGContextAddPath(context, path.platformPath());
650 CGContextSaveGState(context);
651 CGContextConcatCTM(context, m_state.fillGradient->gradientSpaceTransform());
653 if (fillRule() == RULE_EVENODD)
654 CGContextEOClip(context);
656 CGContextClip(context);
658 m_state.fillGradient->paint(this);
659 CGContextRestoreGState(context);
665 CGContextBeginPath(context);
666 CGContextAddPath(context, path.platformPath());
668 if (m_state.fillPattern)
670 fillPathWithFillRule(context, fillRule());
673 void GraphicsContext::strokePath(const Path& path)
675 if (paintingDisabled() || path.isEmpty())
678 CGContextRef context = platformContext();
680 CGContextBeginPath(context);
681 CGContextAddPath(context, path.platformPath());
683 if (m_state.strokeGradient) {
685 FloatRect rect = path.fastBoundingRect();
686 float lineWidth = strokeThickness();
687 float doubleLineWidth = lineWidth * 2;
688 float adjustedWidth = ceilf(rect.width() + doubleLineWidth);
689 float adjustedHeight = ceilf(rect.height() + doubleLineWidth);
691 FloatSize layerSize = getCTM().mapSize(FloatSize(adjustedWidth, adjustedHeight));
693 CGLayerRef layer = CGLayerCreateWithContext(context, layerSize, 0);
694 CGContextRef layerContext = CGLayerGetContext(layer);
695 CGContextSetLineWidth(layerContext, lineWidth);
697 // Compensate for the line width, otherwise the layer's top-left corner would be
698 // aligned with the rect's top-left corner. This would result in leaving pixels out of
699 // the layer on the left and top sides.
700 float translationX = lineWidth - rect.x();
701 float translationY = lineWidth - rect.y();
702 CGContextScaleCTM(layerContext, layerSize.width() / adjustedWidth, layerSize.height() / adjustedHeight);
703 CGContextTranslateCTM(layerContext, translationX, translationY);
705 CGContextAddPath(layerContext, path.platformPath());
706 CGContextReplacePathWithStrokedPath(layerContext);
707 CGContextClip(layerContext);
708 CGContextConcatCTM(layerContext, m_state.strokeGradient->gradientSpaceTransform());
709 m_state.strokeGradient->paint(layerContext);
711 float destinationX = roundf(rect.x() - lineWidth);
712 float destinationY = roundf(rect.y() - lineWidth);
713 CGContextDrawLayerInRect(context, CGRectMake(destinationX, destinationY, adjustedWidth, adjustedHeight), layer);
714 CGLayerRelease(layer);
716 CGContextSaveGState(context);
717 CGContextReplacePathWithStrokedPath(context);
718 CGContextClip(context);
719 CGContextConcatCTM(context, m_state.strokeGradient->gradientSpaceTransform());
720 m_state.strokeGradient->paint(this);
721 CGContextRestoreGState(context);
726 if (m_state.strokePattern)
727 applyStrokePattern();
728 CGContextStrokePath(context);
731 void GraphicsContext::fillRect(const FloatRect& rect)
733 if (paintingDisabled())
736 CGContextRef context = platformContext();
738 if (m_state.fillGradient) {
739 CGContextSaveGState(context);
741 FloatSize layerSize = getCTM().mapSize(rect.size());
743 CGLayerRef layer = CGLayerCreateWithContext(context, layerSize, 0);
744 CGContextRef layerContext = CGLayerGetContext(layer);
746 CGContextScaleCTM(layerContext, layerSize.width() / rect.width(), layerSize.height() / rect.height());
747 CGContextTranslateCTM(layerContext, -rect.x(), -rect.y());
748 CGContextAddRect(layerContext, rect);
749 CGContextClip(layerContext);
751 CGContextConcatCTM(layerContext, m_state.fillGradient->gradientSpaceTransform());
752 m_state.fillGradient->paint(layerContext);
753 CGContextDrawLayerInRect(context, rect, layer);
754 CGLayerRelease(layer);
756 CGContextClipToRect(context, rect);
757 CGContextConcatCTM(context, m_state.fillGradient->gradientSpaceTransform());
758 m_state.fillGradient->paint(this);
760 CGContextRestoreGState(context);
764 if (m_state.fillPattern)
767 bool drawOwnShadow = !isAcceleratedContext() && hasBlurredShadow() && !m_state.shadowsIgnoreTransforms; // Don't use ShadowBlur for canvas yet.
769 // Turn off CG shadows.
770 CGContextSaveGState(context);
771 CGContextSetShadowWithColor(platformContext(), CGSizeZero, 0, 0);
773 ShadowBlur contextShadow(m_state);
774 contextShadow.drawRectShadow(this, FloatRoundedRect(rect));
777 CGContextFillRect(context, rect);
780 CGContextRestoreGState(context);
783 void GraphicsContext::fillRect(const FloatRect& rect, const Color& color, ColorSpace colorSpace)
785 if (paintingDisabled())
788 CGContextRef context = platformContext();
789 Color oldFillColor = fillColor();
790 ColorSpace oldColorSpace = fillColorSpace();
792 if (oldFillColor != color || oldColorSpace != colorSpace)
793 setCGFillColor(context, color, colorSpace);
795 bool drawOwnShadow = !isAcceleratedContext() && hasBlurredShadow() && !m_state.shadowsIgnoreTransforms; // Don't use ShadowBlur for canvas yet.
797 // Turn off CG shadows.
798 CGContextSaveGState(context);
799 CGContextSetShadowWithColor(platformContext(), CGSizeZero, 0, 0);
801 ShadowBlur contextShadow(m_state);
802 contextShadow.drawRectShadow(this, FloatRoundedRect(rect));
805 CGContextFillRect(context, rect);
808 CGContextRestoreGState(context);
810 if (oldFillColor != color || oldColorSpace != colorSpace)
811 setCGFillColor(context, oldFillColor, oldColorSpace);
814 void GraphicsContext::platformFillRoundedRect(const FloatRoundedRect& rect, const Color& color, ColorSpace colorSpace)
816 if (paintingDisabled())
819 CGContextRef context = platformContext();
820 Color oldFillColor = fillColor();
821 ColorSpace oldColorSpace = fillColorSpace();
823 if (oldFillColor != color || oldColorSpace != colorSpace)
824 setCGFillColor(context, color, colorSpace);
826 bool drawOwnShadow = !isAcceleratedContext() && hasBlurredShadow() && !m_state.shadowsIgnoreTransforms; // Don't use ShadowBlur for canvas yet.
828 // Turn off CG shadows.
829 CGContextSaveGState(context);
830 CGContextSetShadowWithColor(platformContext(), CGSizeZero, 0, 0);
832 ShadowBlur contextShadow(m_state);
833 contextShadow.drawRectShadow(this, rect);
836 const FloatRect& r = rect.rect();
837 const FloatRoundedRect::Radii& radii = rect.radii();
838 bool equalWidths = (radii.topLeft().width() == radii.topRight().width() && radii.topRight().width() == radii.bottomLeft().width() && radii.bottomLeft().width() == radii.bottomRight().width());
839 bool equalHeights = (radii.topLeft().height() == radii.bottomLeft().height() && radii.bottomLeft().height() == radii.topRight().height() && radii.topRight().height() == radii.bottomRight().height());
840 bool hasCustomFill = m_state.fillGradient || m_state.fillPattern;
841 if (!hasCustomFill && equalWidths && equalHeights && radii.topLeft().width() * 2 == r.width() && radii.topLeft().height() * 2 == r.height())
842 CGContextFillEllipseInRect(context, r);
845 path.addRoundedRect(rect);
850 CGContextRestoreGState(context);
852 if (oldFillColor != color || oldColorSpace != colorSpace)
853 setCGFillColor(context, oldFillColor, oldColorSpace);
856 void GraphicsContext::fillRectWithRoundedHole(const FloatRect& rect, const FloatRoundedRect& roundedHoleRect, const Color& color, ColorSpace colorSpace)
858 if (paintingDisabled())
861 CGContextRef context = platformContext();
866 if (!roundedHoleRect.radii().isZero())
867 path.addRoundedRect(roundedHoleRect);
869 path.addRect(roundedHoleRect.rect());
871 WindRule oldFillRule = fillRule();
872 Color oldFillColor = fillColor();
873 ColorSpace oldFillColorSpace = fillColorSpace();
875 setFillRule(RULE_EVENODD);
876 setFillColor(color, colorSpace);
878 // fillRectWithRoundedHole() assumes that the edges of rect are clipped out, so we only care about shadows cast around inside the hole.
879 bool drawOwnShadow = !isAcceleratedContext() && hasBlurredShadow() && !m_state.shadowsIgnoreTransforms;
881 // Turn off CG shadows.
882 CGContextSaveGState(context);
883 CGContextSetShadowWithColor(platformContext(), CGSizeZero, 0, 0);
885 ShadowBlur contextShadow(m_state);
886 contextShadow.drawInsetShadow(this, rect, roundedHoleRect);
892 CGContextRestoreGState(context);
894 setFillRule(oldFillRule);
895 setFillColor(oldFillColor, oldFillColorSpace);
898 void GraphicsContext::clip(const FloatRect& rect)
900 if (paintingDisabled())
902 CGContextClipToRect(platformContext(), rect);
906 void GraphicsContext::clipOut(const FloatRect& rect)
908 if (paintingDisabled())
911 // FIXME: Using CGRectInfinite is much faster than getting the clip bounding box. However, due
912 // to <rdar://problem/12584492>, CGRectInfinite can't be used with an accelerated context that
913 // has certain transforms that aren't just a translation or a scale. And due to <rdar://problem/14634453>
914 // we cannot use it in for a printing context either.
915 const AffineTransform& ctm = getCTM();
916 bool canUseCGRectInfinite = !wkCGContextIsPDFContext(platformContext()) && (!isAcceleratedContext() || (!ctm.b() && !ctm.c()));
917 CGRect rects[2] = { canUseCGRectInfinite ? CGRectInfinite : CGContextGetClipBoundingBox(platformContext()), rect };
918 CGContextBeginPath(platformContext());
919 CGContextAddRects(platformContext(), rects, 2);
920 CGContextEOClip(platformContext());
923 void GraphicsContext::clipPath(const Path& path, WindRule clipRule)
925 if (paintingDisabled())
928 // Why does clipping to an empty path do nothing?
929 // Why is this different from GraphicsContext::clip(const Path&).
933 CGContextRef context = platformContext();
935 CGContextBeginPath(platformContext());
936 CGContextAddPath(platformContext(), path.platformPath());
938 if (clipRule == RULE_EVENODD)
939 CGContextEOClip(context);
941 CGContextClip(context);
944 IntRect GraphicsContext::clipBounds() const
946 return enclosingIntRect(CGContextGetClipBoundingBox(platformContext()));
949 void GraphicsContext::beginPlatformTransparencyLayer(float opacity)
951 if (paintingDisabled())
956 CGContextRef context = platformContext();
957 CGContextSetAlpha(context, opacity);
958 CGContextBeginTransparencyLayer(context, 0);
959 m_data->m_userToDeviceTransformKnownToBeIdentity = false;
962 void GraphicsContext::endPlatformTransparencyLayer()
964 if (paintingDisabled())
966 CGContextRef context = platformContext();
967 CGContextEndTransparencyLayer(context);
972 bool GraphicsContext::supportsTransparencyLayers()
977 static void applyShadowOffsetWorkaroundIfNeeded(const GraphicsContext& context, CGFloat& xOffset, CGFloat& yOffset)
980 UNUSED_PARAM(context);
981 UNUSED_PARAM(xOffset);
982 UNUSED_PARAM(yOffset);
984 if (context.isAcceleratedContext())
987 if (wkCGContextDrawsWithCorrectShadowOffsets(context.platformContext()))
990 // Work around <rdar://problem/5539388> by ensuring that the offsets will get truncated
991 // to the desired integer. Also see: <rdar://problem/10056277>
992 static const CGFloat extraShadowOffset = narrowPrecisionToCGFloat(1.0 / 128);
994 xOffset += extraShadowOffset;
995 else if (xOffset < 0)
996 xOffset -= extraShadowOffset;
999 yOffset += extraShadowOffset;
1000 else if (yOffset < 0)
1001 yOffset -= extraShadowOffset;
1005 void GraphicsContext::setPlatformShadow(const FloatSize& offset, float blur, const Color& color, ColorSpace colorSpace)
1007 if (paintingDisabled())
1010 // FIXME: we could avoid the shadow setup cost when we know we'll render the shadow ourselves.
1012 CGFloat xOffset = offset.width();
1013 CGFloat yOffset = offset.height();
1014 CGFloat blurRadius = blur;
1015 CGContextRef context = platformContext();
1017 if (!m_state.shadowsIgnoreTransforms) {
1018 CGAffineTransform userToBaseCTM = wkGetUserToBaseCTM(context);
1020 CGFloat A = userToBaseCTM.a * userToBaseCTM.a + userToBaseCTM.b * userToBaseCTM.b;
1021 CGFloat B = userToBaseCTM.a * userToBaseCTM.c + userToBaseCTM.b * userToBaseCTM.d;
1023 CGFloat D = userToBaseCTM.c * userToBaseCTM.c + userToBaseCTM.d * userToBaseCTM.d;
1025 CGFloat smallEigenvalue = narrowPrecisionToCGFloat(sqrt(0.5 * ((A + D) - sqrt(4 * B * C + (A - D) * (A - D)))));
1027 blurRadius = blur * smallEigenvalue;
1029 CGSize offsetInBaseSpace = CGSizeApplyAffineTransform(offset, userToBaseCTM);
1031 xOffset = offsetInBaseSpace.width;
1032 yOffset = offsetInBaseSpace.height;
1035 // Extreme "blur" values can make text drawing crash or take crazy long times, so clamp
1036 blurRadius = std::min(blurRadius, narrowPrecisionToCGFloat(1000.0));
1038 applyShadowOffsetWorkaroundIfNeeded(*this, xOffset, yOffset);
1040 // Check for an invalid color, as this means that the color was not set for the shadow
1041 // and we should therefore just use the default shadow color.
1042 if (!color.isValid())
1043 CGContextSetShadow(context, CGSizeMake(xOffset, yOffset), blurRadius);
1045 CGContextSetShadowWithColor(context, CGSizeMake(xOffset, yOffset), blurRadius, cachedCGColor(color, colorSpace));
1048 void GraphicsContext::clearPlatformShadow()
1050 if (paintingDisabled())
1052 CGContextSetShadowWithColor(platformContext(), CGSizeZero, 0, 0);
1055 void GraphicsContext::setMiterLimit(float limit)
1057 if (paintingDisabled())
1059 CGContextSetMiterLimit(platformContext(), limit);
1062 void GraphicsContext::setAlpha(float alpha)
1064 if (paintingDisabled())
1066 CGContextSetAlpha(platformContext(), alpha);
1069 void GraphicsContext::clearRect(const FloatRect& r)
1071 if (paintingDisabled())
1073 CGContextClearRect(platformContext(), r);
1076 void GraphicsContext::strokeRect(const FloatRect& rect, float lineWidth)
1078 if (paintingDisabled())
1081 CGContextRef context = platformContext();
1083 if (m_state.strokeGradient) {
1085 const float doubleLineWidth = lineWidth * 2;
1086 float adjustedWidth = ceilf(rect.width() + doubleLineWidth);
1087 float adjustedHeight = ceilf(rect.height() + doubleLineWidth);
1088 FloatSize layerSize = getCTM().mapSize(FloatSize(adjustedWidth, adjustedHeight));
1090 CGLayerRef layer = CGLayerCreateWithContext(context, layerSize, 0);
1092 CGContextRef layerContext = CGLayerGetContext(layer);
1093 m_state.strokeThickness = lineWidth;
1094 CGContextSetLineWidth(layerContext, lineWidth);
1096 // Compensate for the line width, otherwise the layer's top-left corner would be
1097 // aligned with the rect's top-left corner. This would result in leaving pixels out of
1098 // the layer on the left and top sides.
1099 const float translationX = lineWidth - rect.x();
1100 const float translationY = lineWidth - rect.y();
1101 CGContextScaleCTM(layerContext, layerSize.width() / adjustedWidth, layerSize.height() / adjustedHeight);
1102 CGContextTranslateCTM(layerContext, translationX, translationY);
1104 CGContextAddRect(layerContext, rect);
1105 CGContextReplacePathWithStrokedPath(layerContext);
1106 CGContextClip(layerContext);
1107 CGContextConcatCTM(layerContext, m_state.strokeGradient->gradientSpaceTransform());
1108 m_state.strokeGradient->paint(layerContext);
1110 const float destinationX = roundf(rect.x() - lineWidth);
1111 const float destinationY = roundf(rect.y() - lineWidth);
1112 CGContextDrawLayerInRect(context, CGRectMake(destinationX, destinationY, adjustedWidth, adjustedHeight), layer);
1113 CGLayerRelease(layer);
1115 CGContextSaveGState(context);
1116 setStrokeThickness(lineWidth);
1117 CGContextAddRect(context, rect);
1118 CGContextReplacePathWithStrokedPath(context);
1119 CGContextClip(context);
1120 CGContextConcatCTM(context, m_state.strokeGradient->gradientSpaceTransform());
1121 m_state.strokeGradient->paint(this);
1122 CGContextRestoreGState(context);
1127 if (m_state.strokePattern)
1128 applyStrokePattern();
1130 // Using CGContextAddRect and CGContextStrokePath to stroke rect rather than
1131 // convenience functions (CGContextStrokeRect/CGContextStrokeRectWithWidth).
1132 // The convenience functions currently (in at least OSX 10.9.4) fail to
1133 // apply some attributes of the graphics state in certain cases
1134 // (as identified in https://bugs.webkit.org/show_bug.cgi?id=132948)
1135 CGContextSaveGState(context);
1136 setStrokeThickness(lineWidth);
1138 CGContextAddRect(context, rect);
1139 CGContextStrokePath(context);
1141 CGContextRestoreGState(context);
1144 void GraphicsContext::setLineCap(LineCap cap)
1146 if (paintingDisabled())
1150 CGContextSetLineCap(platformContext(), kCGLineCapButt);
1153 CGContextSetLineCap(platformContext(), kCGLineCapRound);
1156 CGContextSetLineCap(platformContext(), kCGLineCapSquare);
1161 void GraphicsContext::setLineDash(const DashArray& dashes, float dashOffset)
1163 if (dashOffset < 0) {
1165 for (size_t i = 0; i < dashes.size(); ++i)
1166 length += static_cast<float>(dashes[i]);
1168 dashOffset = fmod(dashOffset, length) + length;
1170 CGContextSetLineDash(platformContext(), dashOffset, dashes.data(), dashes.size());
1173 void GraphicsContext::setLineJoin(LineJoin join)
1175 if (paintingDisabled())
1179 CGContextSetLineJoin(platformContext(), kCGLineJoinMiter);
1182 CGContextSetLineJoin(platformContext(), kCGLineJoinRound);
1185 CGContextSetLineJoin(platformContext(), kCGLineJoinBevel);
1190 void GraphicsContext::clip(const Path& path, WindRule fillRule)
1192 if (paintingDisabled())
1194 CGContextRef context = platformContext();
1196 // CGContextClip does nothing if the path is empty, so in this case, we
1197 // instead clip against a zero rect to reduce the clipping region to
1198 // nothing - which is the intended behavior of clip() if the path is empty.
1200 CGContextClipToRect(context, CGRectZero);
1202 CGContextBeginPath(context);
1203 CGContextAddPath(context, path.platformPath());
1204 if (fillRule == RULE_NONZERO)
1205 CGContextClip(context);
1207 CGContextEOClip(context);
1212 void GraphicsContext::canvasClip(const Path& path, WindRule fillRule)
1214 clip(path, fillRule);
1217 void GraphicsContext::clipOut(const Path& path)
1219 if (paintingDisabled())
1222 CGContextBeginPath(platformContext());
1223 CGContextAddRect(platformContext(), CGContextGetClipBoundingBox(platformContext()));
1224 if (!path.isEmpty())
1225 CGContextAddPath(platformContext(), path.platformPath());
1226 CGContextEOClip(platformContext());
1229 void GraphicsContext::scale(const FloatSize& size)
1231 if (paintingDisabled())
1233 CGContextScaleCTM(platformContext(), size.width(), size.height());
1234 m_data->scale(size);
1235 m_data->m_userToDeviceTransformKnownToBeIdentity = false;
1238 void GraphicsContext::rotate(float angle)
1240 if (paintingDisabled())
1242 CGContextRotateCTM(platformContext(), angle);
1243 m_data->rotate(angle);
1244 m_data->m_userToDeviceTransformKnownToBeIdentity = false;
1247 void GraphicsContext::translate(float x, float y)
1249 if (paintingDisabled())
1251 CGContextTranslateCTM(platformContext(), x, y);
1252 m_data->translate(x, y);
1253 m_data->m_userToDeviceTransformKnownToBeIdentity = false;
1256 void GraphicsContext::concatCTM(const AffineTransform& transform)
1258 if (paintingDisabled())
1260 CGContextConcatCTM(platformContext(), transform);
1261 m_data->concatCTM(transform);
1262 m_data->m_userToDeviceTransformKnownToBeIdentity = false;
1265 void GraphicsContext::setCTM(const AffineTransform& transform)
1267 if (paintingDisabled())
1269 CGContextSetCTM(platformContext(), transform);
1270 m_data->setCTM(transform);
1271 m_data->m_userToDeviceTransformKnownToBeIdentity = false;
1274 AffineTransform GraphicsContext::getCTM(IncludeDeviceScale includeScale) const
1276 if (paintingDisabled())
1277 return AffineTransform();
1279 // The CTM usually includes the deviceScaleFactor except in WebKit 1 when the
1280 // content is non-composited, since the scale factor is integrated at a lower
1281 // level. To guarantee the deviceScale is included, we can use this CG API.
1282 if (includeScale == DefinitelyIncludeDeviceScale)
1283 return CGContextGetUserSpaceToDeviceSpaceTransform(platformContext());
1285 return CGContextGetCTM(platformContext());
1288 FloatRect GraphicsContext::roundToDevicePixels(const FloatRect& rect, RoundingMode roundingMode)
1290 // It is not enough just to round to pixels in device space. The rotation part of the
1291 // affine transform matrix to device space can mess with this conversion if we have a
1292 // rotating image like the hands of the world clock widget. We just need the scale, so
1293 // we get the affine transform matrix and extract the scale.
1295 if (m_data->m_userToDeviceTransformKnownToBeIdentity)
1296 return roundedIntRect(rect);
1298 CGAffineTransform deviceMatrix = CGContextGetUserSpaceToDeviceSpaceTransform(platformContext());
1299 if (CGAffineTransformIsIdentity(deviceMatrix)) {
1300 m_data->m_userToDeviceTransformKnownToBeIdentity = true;
1301 return roundedIntRect(rect);
1304 float deviceScaleX = sqrtf(deviceMatrix.a * deviceMatrix.a + deviceMatrix.b * deviceMatrix.b);
1305 float deviceScaleY = sqrtf(deviceMatrix.c * deviceMatrix.c + deviceMatrix.d * deviceMatrix.d);
1307 CGPoint deviceOrigin = CGPointMake(rect.x() * deviceScaleX, rect.y() * deviceScaleY);
1308 CGPoint deviceLowerRight = CGPointMake((rect.x() + rect.width()) * deviceScaleX,
1309 (rect.y() + rect.height()) * deviceScaleY);
1311 deviceOrigin.x = roundf(deviceOrigin.x);
1312 deviceOrigin.y = roundf(deviceOrigin.y);
1313 if (roundingMode == RoundAllSides) {
1314 deviceLowerRight.x = roundf(deviceLowerRight.x);
1315 deviceLowerRight.y = roundf(deviceLowerRight.y);
1317 deviceLowerRight.x = deviceOrigin.x + roundf(rect.width() * deviceScaleX);
1318 deviceLowerRight.y = deviceOrigin.y + roundf(rect.height() * deviceScaleY);
1321 // Don't let the height or width round to 0 unless either was originally 0
1322 if (deviceOrigin.y == deviceLowerRight.y && rect.height())
1323 deviceLowerRight.y += 1;
1324 if (deviceOrigin.x == deviceLowerRight.x && rect.width())
1325 deviceLowerRight.x += 1;
1327 FloatPoint roundedOrigin = FloatPoint(deviceOrigin.x / deviceScaleX, deviceOrigin.y / deviceScaleY);
1328 FloatPoint roundedLowerRight = FloatPoint(deviceLowerRight.x / deviceScaleX, deviceLowerRight.y / deviceScaleY);
1329 return FloatRect(roundedOrigin, roundedLowerRight - roundedOrigin);
1332 FloatRect GraphicsContext::computeLineBoundsForText(const FloatPoint& point, float width, bool printing)
1336 return computeLineBoundsAndAntialiasingModeForText(point, width, printing, dummyBool, dummyColor);
1339 void GraphicsContext::drawLineForText(const FloatPoint& point, float width, bool printing, bool doubleLines)
1342 widths.append(width);
1344 drawLinesForText(point, widths, printing, doubleLines);
1347 void GraphicsContext::drawLinesForText(const FloatPoint& point, const DashArray& widths, bool printing, bool doubleLines)
1349 if (paintingDisabled())
1352 if (widths.size() <= 0)
1355 Color localStrokeColor(strokeColor());
1357 bool shouldAntialiasLine;
1358 FloatRect bounds = computeLineBoundsAndAntialiasingModeForText(point, widths.last(), printing, shouldAntialiasLine, localStrokeColor);
1359 bool fillColorIsNotEqualToStrokeColor = fillColor() != localStrokeColor;
1361 Vector<CGRect, 4> dashBounds;
1362 ASSERT(!(widths.size() % 2));
1363 dashBounds.reserveInitialCapacity(dashBounds.size() / 2);
1364 for (size_t i = 0; i < widths.size(); i += 2)
1365 dashBounds.append(CGRectMake(bounds.x() + widths[i], bounds.y(), widths[i+1] - widths[i], bounds.height()));
1368 // The space between double underlines is equal to the height of the underline
1369 for (size_t i = 0; i < widths.size(); i += 2)
1370 dashBounds.append(CGRectMake(bounds.x() + widths[i], bounds.y() + 2 * bounds.height(), widths[i+1] - widths[i], bounds.height()));
1374 if (m_state.shouldUseContextColors)
1376 if (fillColorIsNotEqualToStrokeColor)
1377 setCGFillColor(platformContext(), localStrokeColor, strokeColorSpace());
1379 CGContextFillRects(platformContext(), dashBounds.data(), dashBounds.size());
1382 if (m_state.shouldUseContextColors)
1384 if (fillColorIsNotEqualToStrokeColor)
1385 setCGFillColor(platformContext(), fillColor(), fillColorSpace());
1388 void GraphicsContext::setURLForRect(const URL& link, const IntRect& destRect)
1391 if (paintingDisabled())
1394 RetainPtr<CFURLRef> urlRef = link.createCFURL();
1398 CGContextRef context = platformContext();
1400 // Get the bounding box to handle clipping.
1401 CGRect box = CGContextGetClipBoundingBox(context);
1403 IntRect intBox((int)box.origin.x, (int)box.origin.y, (int)box.size.width, (int)box.size.height);
1404 IntRect rect = destRect;
1405 rect.intersect(intBox);
1407 CGPDFContextSetURLForRect(context, urlRef.get(),
1408 CGRectApplyAffineTransform(rect, CGContextGetCTM(context)));
1411 UNUSED_PARAM(destRect);
1415 void GraphicsContext::setImageInterpolationQuality(InterpolationQuality mode)
1417 if (paintingDisabled())
1420 CGInterpolationQuality quality = kCGInterpolationDefault;
1422 case InterpolationDefault:
1423 quality = kCGInterpolationDefault;
1425 case InterpolationNone:
1426 quality = kCGInterpolationNone;
1428 case InterpolationLow:
1429 quality = kCGInterpolationLow;
1431 case InterpolationMedium:
1432 quality = kCGInterpolationMedium;
1434 case InterpolationHigh:
1435 quality = kCGInterpolationHigh;
1438 CGContextSetInterpolationQuality(platformContext(), quality);
1441 InterpolationQuality GraphicsContext::imageInterpolationQuality() const
1443 if (paintingDisabled())
1444 return InterpolationDefault;
1446 CGInterpolationQuality quality = CGContextGetInterpolationQuality(platformContext());
1448 case kCGInterpolationDefault:
1449 return InterpolationDefault;
1450 case kCGInterpolationNone:
1451 return InterpolationNone;
1452 case kCGInterpolationLow:
1453 return InterpolationLow;
1454 case kCGInterpolationMedium:
1455 return InterpolationMedium;
1456 case kCGInterpolationHigh:
1457 return InterpolationHigh;
1459 return InterpolationDefault;
1462 void GraphicsContext::setAllowsFontSmoothing(bool allowsFontSmoothing)
1464 UNUSED_PARAM(allowsFontSmoothing);
1466 CGContextRef context = platformContext();
1467 CGContextSetAllowsFontSmoothing(context, allowsFontSmoothing);
1471 void GraphicsContext::setIsCALayerContext(bool isLayerContext)
1474 m_data->m_contextFlags |= IsLayerCGContext;
1476 m_data->m_contextFlags &= ~IsLayerCGContext;
1479 bool GraphicsContext::isCALayerContext() const
1481 return m_data->m_contextFlags & IsLayerCGContext;
1484 void GraphicsContext::setIsAcceleratedContext(bool isAccelerated)
1487 m_data->m_contextFlags |= IsAcceleratedCGContext;
1489 m_data->m_contextFlags &= ~IsAcceleratedCGContext;
1492 bool GraphicsContext::isAcceleratedContext() const
1494 return m_data->m_contextFlags & IsAcceleratedCGContext;
1497 void GraphicsContext::setPlatformTextDrawingMode(TextDrawingModeFlags mode)
1499 if (paintingDisabled())
1502 CGContextRef context = platformContext();
1505 CGContextSetTextDrawingMode(context, kCGTextFill);
1507 case TextModeStroke:
1508 CGContextSetTextDrawingMode(context, kCGTextStroke);
1510 case TextModeFill | TextModeStroke:
1511 CGContextSetTextDrawingMode(context, kCGTextFillStroke);
1518 void GraphicsContext::setPlatformStrokeColor(const Color& color, ColorSpace colorSpace)
1520 if (paintingDisabled())
1522 setCGStrokeColor(platformContext(), color, colorSpace);
1525 void GraphicsContext::setPlatformStrokeThickness(float thickness)
1527 if (paintingDisabled())
1529 CGContextSetLineWidth(platformContext(), thickness);
1532 void GraphicsContext::setPlatformFillColor(const Color& color, ColorSpace colorSpace)
1534 if (paintingDisabled())
1536 setCGFillColor(platformContext(), color, colorSpace);
1539 void GraphicsContext::setPlatformShouldAntialias(bool enable)
1541 if (paintingDisabled())
1543 CGContextSetShouldAntialias(platformContext(), enable);
1546 void GraphicsContext::setPlatformShouldSmoothFonts(bool enable)
1548 if (paintingDisabled())
1550 CGContextSetShouldSmoothFonts(platformContext(), enable);
1553 void GraphicsContext::setPlatformCompositeOperation(CompositeOperator mode, BlendMode blendMode)
1555 if (paintingDisabled())
1558 CGBlendMode target = kCGBlendModeNormal;
1559 if (blendMode != BlendModeNormal) {
1560 switch (blendMode) {
1561 case BlendModeMultiply:
1562 target = kCGBlendModeMultiply;
1564 case BlendModeScreen:
1565 target = kCGBlendModeScreen;
1567 case BlendModeOverlay:
1568 target = kCGBlendModeOverlay;
1570 case BlendModeDarken:
1571 target = kCGBlendModeDarken;
1573 case BlendModeLighten:
1574 target = kCGBlendModeLighten;
1576 case BlendModeColorDodge:
1577 target = kCGBlendModeColorDodge;
1579 case BlendModeColorBurn:
1580 target = kCGBlendModeColorBurn;
1582 case BlendModeHardLight:
1583 target = kCGBlendModeHardLight;
1585 case BlendModeSoftLight:
1586 target = kCGBlendModeSoftLight;
1588 case BlendModeDifference:
1589 target = kCGBlendModeDifference;
1591 case BlendModeExclusion:
1592 target = kCGBlendModeExclusion;
1595 target = kCGBlendModeHue;
1597 case BlendModeSaturation:
1598 target = kCGBlendModeSaturation;
1600 case BlendModeColor:
1601 target = kCGBlendModeColor;
1603 case BlendModeLuminosity:
1604 target = kCGBlendModeLuminosity;
1611 case CompositeClear:
1612 target = kCGBlendModeClear;
1615 target = kCGBlendModeCopy;
1617 case CompositeSourceOver:
1618 // kCGBlendModeNormal
1620 case CompositeSourceIn:
1621 target = kCGBlendModeSourceIn;
1623 case CompositeSourceOut:
1624 target = kCGBlendModeSourceOut;
1626 case CompositeSourceAtop:
1627 target = kCGBlendModeSourceAtop;
1629 case CompositeDestinationOver:
1630 target = kCGBlendModeDestinationOver;
1632 case CompositeDestinationIn:
1633 target = kCGBlendModeDestinationIn;
1635 case CompositeDestinationOut:
1636 target = kCGBlendModeDestinationOut;
1638 case CompositeDestinationAtop:
1639 target = kCGBlendModeDestinationAtop;
1642 target = kCGBlendModeXOR;
1644 case CompositePlusDarker:
1645 target = kCGBlendModePlusDarker;
1647 case CompositePlusLighter:
1648 target = kCGBlendModePlusLighter;
1650 case CompositeDifference:
1651 target = kCGBlendModeDifference;
1655 CGContextSetBlendMode(platformContext(), target);
1658 void GraphicsContext::platformApplyDeviceScaleFactor(float deviceScaleFactor)
1660 // CoreGraphics expects the base CTM of a HiDPI context to have the scale factor applied to it.
1661 // Failing to change the base level CTM will cause certain CG features, such as focus rings,
1662 // to draw with a scale factor of 1 rather than the actual scale factor.
1663 wkSetBaseCTM(platformContext(), CGAffineTransformScale(CGContextGetBaseCTM(platformContext()), deviceScaleFactor, deviceScaleFactor));
1666 void GraphicsContext::platformFillEllipse(const FloatRect& ellipse)
1668 if (paintingDisabled())
1671 // CGContextFillEllipseInRect only supports solid colors.
1672 if (m_state.fillGradient || m_state.fillPattern) {
1673 fillEllipseAsPath(ellipse);
1677 CGContextRef context = platformContext();
1678 CGContextFillEllipseInRect(context, ellipse);
1681 void GraphicsContext::platformStrokeEllipse(const FloatRect& ellipse)
1683 if (paintingDisabled())
1686 // CGContextStrokeEllipseInRect only supports solid colors.
1687 if (m_state.strokeGradient || m_state.strokePattern) {
1688 strokeEllipseAsPath(ellipse);
1692 CGContextRef context = platformContext();
1693 CGContextStrokeEllipseInRect(context, ellipse);