[CG] Rasterized scaling of transformed SVG shapes with gradient fill and -webkit...
authortimothy_horton@apple.com <timothy_horton@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 18 Jan 2012 18:49:51 +0000 (18:49 +0000)
committertimothy_horton@apple.com <timothy_horton@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 18 Jan 2012 18:49:51 +0000 (18:49 +0000)
https://bugs.webkit.org/show_bug.cgi?id=76482
<rdar://problem/10415483>

Reviewed by Simon Fraser.

Scale the CGLayer used when filling or stroking a shadowed path or rect with
a gradient in GraphicsContextCG. Previously, the CGLayer was created and rendered
into at the untransformed size of the shape, leading to pixelation when it was
then scaled up and drawn into the destination.

Add AffineTransform::mapSize() to map a size through a transformation.

Test: svg/custom/transform-with-shadow-and-gradient.svg

* platform/graphics/cg/GraphicsContextCG.cpp:
(WebCore::GraphicsContext::fillPath):
(WebCore::GraphicsContext::strokePath):
(WebCore::GraphicsContext::fillRect):
(WebCore::GraphicsContext::strokeRect):
* platform/graphics/transforms/AffineTransform.cpp:
(WebCore::AffineTransform::mapSize): Added.
* platform/graphics/transforms/AffineTransform.h:

Add a test that ensures that SVG shapes are drawn crisply when transformed
if they have both a gradient fill and -webkit-svg-shadow applied.

* platform/mac/svg/custom/transform-with-shadow-and-gradient-expected.png: Added.
* platform/mac/svg/custom/transform-with-shadow-and-gradient-expected.txt: Added.
* svg/custom/transform-with-shadow-and-gradient.svg: Added.

git-svn-id: http://svn.webkit.org/repository/webkit/trunk@105296 268f45cc-cd09-0410-ab3c-d52691b4dbfc

LayoutTests/ChangeLog
LayoutTests/platform/mac/svg/custom/transform-with-shadow-and-gradient-expected.png [new file with mode: 0644]
LayoutTests/platform/mac/svg/custom/transform-with-shadow-and-gradient-expected.txt [new file with mode: 0644]
LayoutTests/svg/custom/transform-with-shadow-and-gradient.svg [new file with mode: 0644]
Source/WebCore/ChangeLog
Source/WebCore/platform/graphics/cg/GraphicsContextCG.cpp
Source/WebCore/platform/graphics/transforms/AffineTransform.cpp
Source/WebCore/platform/graphics/transforms/AffineTransform.h

index fa069a4..973e2a1 100644 (file)
@@ -1,3 +1,18 @@
+2012-01-18  Tim Horton  <timothy_horton@apple.com>
+
+        [CG] Rasterized scaling of transformed SVG shapes with gradient fill and -webkit-svg-shadow applied
+        https://bugs.webkit.org/show_bug.cgi?id=76482
+        <rdar://problem/10415483>
+
+        Reviewed by Simon Fraser.
+
+        Add a test that ensures that SVG shapes are drawn crisply when transformed
+        if they have both a gradient fill and -webkit-svg-shadow applied.
+
+        * platform/mac/svg/custom/transform-with-shadow-and-gradient-expected.png: Added.
+        * platform/mac/svg/custom/transform-with-shadow-and-gradient-expected.txt: Added.
+        * svg/custom/transform-with-shadow-and-gradient.svg: Added.
+
 2012-01-18  Dominic Mazzoni  <dmazzoni@google.com>
 
         Accessibility: Chromium needs methods to scroll an object into view or to a specific location.
diff --git a/LayoutTests/platform/mac/svg/custom/transform-with-shadow-and-gradient-expected.png b/LayoutTests/platform/mac/svg/custom/transform-with-shadow-and-gradient-expected.png
new file mode 100644 (file)
index 0000000..e46945a
Binary files /dev/null and b/LayoutTests/platform/mac/svg/custom/transform-with-shadow-and-gradient-expected.png differ
diff --git a/LayoutTests/platform/mac/svg/custom/transform-with-shadow-and-gradient-expected.txt b/LayoutTests/platform/mac/svg/custom/transform-with-shadow-and-gradient-expected.txt
new file mode 100644 (file)
index 0000000..0ff0ff6
--- /dev/null
@@ -0,0 +1,12 @@
+layer at (0,0) size 800x600
+  RenderView at (0,0) size 800x600
+layer at (0,0) size 800x600
+  RenderSVGRoot {svg} at (18,16) size 542x544
+    RenderSVGHiddenContainer {defs} at (0,0) size 0x0
+      RenderSVGResourceLinearGradient {linearGradient} [id="gradient"] [gradientUnits=objectBoundingBox] [start=(0,0)] [end=(1,0)]
+        RenderSVGGradientStop {stop} [offset=0.00] [color=#0000FF]
+        RenderSVGGradientStop {stop} [offset=1.00] [color=#008000]
+    RenderSVGPath {circle} at (20,20) size 176x176 [transform={m=((4.00,0.00)(0.00,4.00)) t=(0.00,0.00)}] [fill={[type=LINEAR-GRADIENT] [id="gradient"]}] [cx=25.00] [cy=25.00] [r=20.00]
+    RenderSVGRect {rect} at (320,16) size 288x288 [transform={m=((32.00,0.00)(0.00,32.00)) t=(0.00,0.00)}] [fill={[type=LINEAR-GRADIENT] [id="gradient"]}] [x=10.00] [y=0.50] [width=5.00] [height=5.00]
+    RenderSVGPath {circle} at (18,318) size 180x180 [transform={m=((4.00,0.00)(0.00,4.00)) t=(0.00,0.00)}] [stroke={[type=LINEAR-GRADIENT] [id="gradient"]}] [fill={[type=SOLID] [color=#00000000]}] [cx=25.00] [cy=100.00] [r=20.00]
+    RenderSVGRect {rect} at (304,304) size 320x296 [transform={m=((32.00,0.00)(0.00,32.00)) t=(0.00,0.00)}] [stroke={[type=LINEAR-GRADIENT] [id="gradient"]}] [fill={[type=SOLID] [color=#00000000]}] [x=10.00] [y=10.00] [width=5.00] [height=5.00]
diff --git a/LayoutTests/svg/custom/transform-with-shadow-and-gradient.svg b/LayoutTests/svg/custom/transform-with-shadow-and-gradient.svg
new file mode 100644 (file)
index 0000000..35ccf37
--- /dev/null
@@ -0,0 +1,13 @@
+<svg xmlns="http://www.w3.org/2000/svg">
+    <title>None of the shapes should be pixelated, even when zoomed.</title>
+    <defs>
+        <linearGradient id="gradient">
+            <stop offset="0%" stop-color="blue"></stop>
+            <stop offset="100%" stop-color="green"></stop>
+        </linearGradient>
+    </defs>
+    <circle transform="scale(4,4)" style="-webkit-svg-shadow: black 1px 1px 1px;" fill="url(#gradient)" cx="25" cy="25" r="20"></circle>
+    <rect transform="scale(32,32)" style="-webkit-svg-shadow: black 1px 1px 1px;" fill="url(#gradient)" x="10" y="0.5" width="5" height="5"></rect>
+    <circle transform="scale(4,4)" style="-webkit-svg-shadow: black 1px 1px 1px;" stroke="url(#gradient)" fill="transparent" cx="25" cy="100" r="20"></circle>
+    <rect transform="scale(32,32)" style="-webkit-svg-shadow: black 1px 1px 1px;" stroke="url(#gradient)" fill="transparent" x="10" y="10" width="5" height="5"></rect>
+</svg>
index df1eadd..e6afd82 100644 (file)
@@ -1,3 +1,29 @@
+2012-01-18  Tim Horton  <timothy_horton@apple.com>
+
+        [CG] Rasterized scaling of transformed SVG shapes with gradient fill and -webkit-svg-shadow applied
+        https://bugs.webkit.org/show_bug.cgi?id=76482
+        <rdar://problem/10415483>
+
+        Reviewed by Simon Fraser.
+
+        Scale the CGLayer used when filling or stroking a shadowed path or rect with
+        a gradient in GraphicsContextCG. Previously, the CGLayer was created and rendered
+        into at the untransformed size of the shape, leading to pixelation when it was
+        then scaled up and drawn into the destination.
+
+        Add AffineTransform::mapSize() to map a size through a transformation.
+
+        Test: svg/custom/transform-with-shadow-and-gradient.svg
+
+        * platform/graphics/cg/GraphicsContextCG.cpp:
+        (WebCore::GraphicsContext::fillPath):
+        (WebCore::GraphicsContext::strokePath):
+        (WebCore::GraphicsContext::fillRect):
+        (WebCore::GraphicsContext::strokeRect):
+        * platform/graphics/transforms/AffineTransform.cpp:
+        (WebCore::AffineTransform::mapSize): Added.
+        * platform/graphics/transforms/AffineTransform.h:
+
 2012-01-18  Dominic Mazzoni  <dmazzoni@google.com>
 
         Accessibility: Chromium needs methods to scroll an object into view or to a specific location.
index 20ffba6..1fa9141 100644 (file)
@@ -630,9 +630,12 @@ void GraphicsContext::fillPath(const Path& path)
     if (m_state.fillGradient) {
         if (hasShadow()) {
             FloatRect rect = path.fastBoundingRect();
-            CGLayerRef layer = CGLayerCreateWithContext(context, CGSizeMake(rect.width(), rect.height()), 0);
+            FloatSize layerSize = getCTM().mapSize(rect.size());
+
+            CGLayerRef layer = CGLayerCreateWithContext(context, layerSize, 0);
             CGContextRef layerContext = CGLayerGetContext(layer);
 
+            CGContextScaleCTM(layerContext, layerSize.width() / rect.width(), layerSize.height() / rect.height());
             CGContextTranslateCTM(layerContext, -rect.x(), -rect.y());
             CGContextBeginPath(layerContext);
             CGContextAddPath(layerContext, path.platformPath());
@@ -644,7 +647,7 @@ void GraphicsContext::fillPath(const Path& path)
                 CGContextClip(layerContext);
 
             m_state.fillGradient->paint(layerContext);
-            CGContextDrawLayerAtPoint(context, CGPointMake(rect.x(), rect.y()), layer);
+            CGContextDrawLayerInRect(context, rect, layer);
             CGLayerRelease(layer);
         } else {
             CGContextBeginPath(context);
@@ -687,10 +690,12 @@ void GraphicsContext::strokePath(const Path& path)
             FloatRect rect = path.fastBoundingRect();
             float lineWidth = strokeThickness();
             float doubleLineWidth = lineWidth * 2;
-            float layerWidth = ceilf(rect.width() + doubleLineWidth);
-            float layerHeight = ceilf(rect.height() + doubleLineWidth);
+            float adjustedWidth = ceilf(rect.width() + doubleLineWidth);
+            float adjustedHeight = ceilf(rect.height() + doubleLineWidth);
+
+            FloatSize layerSize = getCTM().mapSize(FloatSize(adjustedWidth, adjustedHeight));
 
-            CGLayerRef layer = CGLayerCreateWithContext(context, CGSizeMake(layerWidth, layerHeight), 0);
+            CGLayerRef layer = CGLayerCreateWithContext(context, layerSize, 0);
             CGContextRef layerContext = CGLayerGetContext(layer);
             CGContextSetLineWidth(layerContext, lineWidth);
 
@@ -699,6 +704,7 @@ void GraphicsContext::strokePath(const Path& path)
             // the layer on the left and top sides.
             float translationX = lineWidth - rect.x();
             float translationY = lineWidth - rect.y();
+            CGContextScaleCTM(layerContext, layerSize.width() / adjustedWidth, layerSize.height() / adjustedHeight);
             CGContextTranslateCTM(layerContext, translationX, translationY);
 
             CGContextAddPath(layerContext, path.platformPath());
@@ -709,7 +715,7 @@ void GraphicsContext::strokePath(const Path& path)
 
             float destinationX = roundf(rect.x() - lineWidth);
             float destinationY = roundf(rect.y() - lineWidth);
-            CGContextDrawLayerAtPoint(context, CGPointMake(destinationX, destinationY), layer);
+            CGContextDrawLayerInRect(context, CGRectMake(destinationX, destinationY, adjustedWidth, adjustedHeight), layer);
             CGLayerRelease(layer);
         } else {
             CGContextSaveGState(context);
@@ -747,16 +753,19 @@ void GraphicsContext::fillRect(const FloatRect& rect)
     if (m_state.fillGradient) {
         CGContextSaveGState(context);
         if (hasShadow()) {
-            CGLayerRef layer = CGLayerCreateWithContext(context, CGSizeMake(rect.width(), rect.height()), 0);
+            FloatSize layerSize = getCTM().mapSize(rect.size());
+
+            CGLayerRef layer = CGLayerCreateWithContext(context, layerSize, 0);
             CGContextRef layerContext = CGLayerGetContext(layer);
 
+            CGContextScaleCTM(layerContext, layerSize.width() / rect.width(), layerSize.height() / rect.height());
             CGContextTranslateCTM(layerContext, -rect.x(), -rect.y());
             CGContextAddRect(layerContext, rect);
             CGContextClip(layerContext);
 
             CGContextConcatCTM(layerContext, m_state.fillGradient->gradientSpaceTransform());
             m_state.fillGradient->paint(layerContext);
-            CGContextDrawLayerAtPoint(context, CGPointMake(rect.x(), rect.y()), layer);
+            CGContextDrawLayerInRect(context, rect, layer);
             CGLayerRelease(layer);
         } else {
             CGContextClipToRect(context, rect);
@@ -1088,9 +1097,11 @@ void GraphicsContext::strokeRect(const FloatRect& rect, float lineWidth)
     if (m_state.strokeGradient) {
         if (hasShadow()) {
             const float doubleLineWidth = lineWidth * 2;
-            const float layerWidth = ceilf(rect.width() + doubleLineWidth);
-            const float layerHeight = ceilf(rect.height() + doubleLineWidth);
-            CGLayerRef layer = CGLayerCreateWithContext(context, CGSizeMake(layerWidth, layerHeight), 0);
+            float adjustedWidth = ceilf(rect.width() + doubleLineWidth);
+            float adjustedHeight = ceilf(rect.height() + doubleLineWidth);
+            FloatSize layerSize = getCTM().mapSize(FloatSize(adjustedWidth, adjustedHeight));
+
+            CGLayerRef layer = CGLayerCreateWithContext(context, layerSize, 0);
 
             CGContextRef layerContext = CGLayerGetContext(layer);
             m_state.strokeThickness = lineWidth;
@@ -1101,6 +1112,7 @@ void GraphicsContext::strokeRect(const FloatRect& rect, float lineWidth)
             // the layer on the left and top sides.
             const float translationX = lineWidth - rect.x();
             const float translationY = lineWidth - rect.y();
+            CGContextScaleCTM(layerContext, layerSize.width() / adjustedWidth, layerSize.height() / adjustedHeight);
             CGContextTranslateCTM(layerContext, translationX, translationY);
 
             CGContextAddRect(layerContext, rect);
@@ -1111,7 +1123,7 @@ void GraphicsContext::strokeRect(const FloatRect& rect, float lineWidth)
 
             const float destinationX = roundf(rect.x() - lineWidth);
             const float destinationY = roundf(rect.y() - lineWidth);
-            CGContextDrawLayerAtPoint(context, CGPointMake(destinationX, destinationY), layer);
+            CGContextDrawLayerInRect(context, CGRectMake(destinationX, destinationY, adjustedWidth, adjustedHeight), layer);
             CGLayerRelease(layer);
         } else {
             CGContextSaveGState(context);
index 60c8df3..4131ca4 100644 (file)
@@ -250,6 +250,22 @@ FloatPoint AffineTransform::mapPoint(const FloatPoint& point) const
     return FloatPoint(narrowPrecisionToFloat(x2), narrowPrecisionToFloat(y2));
 }
 
+IntSize AffineTransform::mapSize(const IntSize& size) const
+{
+    double width2 = size.width() * xScale();
+    double height2 = size.height() * yScale();
+
+    return IntSize(lround(width2), lround(height2));
+}
+
+FloatSize AffineTransform::mapSize(const FloatSize& size) const
+{
+    double width2 = size.width() * xScale();
+    double height2 = size.height() * yScale();
+
+    return FloatSize(narrowPrecisionToFloat(width2), narrowPrecisionToFloat(height2));
+}
+
 IntRect AffineTransform::mapRect(const IntRect &rect) const
 {
     return enclosingIntRect(mapRect(FloatRect(rect)));
index d3970da..7d83f59 100644 (file)
@@ -76,6 +76,10 @@ public:
 
     FloatPoint mapPoint(const FloatPoint&) const;
 
+    IntSize mapSize(const IntSize&) const;
+
+    FloatSize mapSize(const FloatSize&) const;
+
     // Rounds the resulting mapped rectangle out. This is helpful for bounding
     // box computations but may not be what is wanted in other contexts.
     IntRect mapRect(const IntRect&) const;