Keeping a Rotated Linear Gradient Unrotated when Shape is Rotated in SVG

可紊 提交于 2019-12-11 09:46:42

问题


(SVG or WPF XAML - I'm open to (and need) both. I don't suppose they are any different in implementation. Below example is in SVG).

I'm trying to find a way to use rotation on a linear gradient (in this case 270°), but then when the shape it fills is rotated, keep the linear gradient unchanged, as if the shape it is filling isn't rotated.

By way of example, this is what Microsoft Word can do. When filling an AutoShape with a linear gradient, you can uncheck "Rotate with shape" and then the linear gradient will stay in place no matter how you rotate the shape.

So, here's an example, I want to rotated the linear gradient by 270° on it's center (in the linearGradient definition, gradientTransform="rotate(270 0.5 0.5)"). After that, I want to rotate the path it fills 45° (in the path, transform="rotate(45 0.5 0.5)").

<svg width="960" height="540" xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg">
 <svg x="265.24353" y="133.125046" width="337.287231" height="211.206543" viewBox="0 0 337.287231 211.206543" style="overflow: visible;" >
    <defs>
    <linearGradient id="linear"  gradientTransform="rotate(270 0.5 0.5)" spreadMethod="pad" >
      <stop offset="0%" stop-color="#FF0000"/>
      <stop offset="48%" stop-color="#FF0000"/>
      <stop offset="48%" stop-color="#0070C0"/>
      <stop offset="100%" stop-color="#0070C0"/>
    </linearGradient>
  </defs>
  <path id="9" fill="url(#linear)"  transform="rotate(45 0.5 0.5)" stroke="#000000" stroke-width="5" stroke-linecap="round" d="M0,0L205.8147,0L205.8147,174.9073L0,174.9073Z"/>
  </svg>
</svg>

Okay, so this doesn't work, right? It's not supposed to. So I try subtracting 45° (path's rotation) from 270° (lin grad's rotation) for 225° and then in the linearGradient set gradientTransform="rotate(225 0.5 0.5)". That gets me close. Really close. So then I try lowering the angle and voilà, it's about 221° that seems to be correct. 4° lower than 270°-45°.

So I try this strategy again with a path rotation 65° - I know the linearGradient's rotation (originally at 270°) won't be 205°, but somewhere close to that, ah ha! It's 201° (or some floating point of that) 4° lower than 270°-65°.

How do I know it's 4° lower than 270°-65° lower than the subtraction in both cases? I eyeball the divider in the linearGradient to ensure it is horizontal.

So would there be a way to calculate the above regardless of linearGradient rotation angle or the shape's rotation angle? For example, if the liner gradient's rotation is 180° (which divides the above example vertically) or any other degree? If I could figure how to insert JS Fiddle here, I'd put more examples that can be run.

UPDATE (on WPF):

@Clemens provided some keen insight into how this can be accomplished in WPF by transforming the PathGeometry, not the Path. Awesome.

Here's the sample of transforming the Path, which is identical the SVG above.

<Path RenderTransformOrigin="0.5,0.5" Data="M0,0L205.8147,0L205.8147,174.9073L0,174.9073Z" Stroke="Black" StrokeLineJoin="Miter" StrokeThickness="5">
        <Path.RenderTransform>
            <TransformGroup>
                <RotateTransform Angle="45" CenterY="0.5" CenterX="0.5"/>
            </TransformGroup>
        </Path.RenderTransform>
        <Path.Fill>
            <LinearGradientBrush StartPoint="0,1" EndPoint="1,1" >
                <LinearGradientBrush.RelativeTransform>
                    <TransformGroup>
                        <RotateTransform Angle="221" CenterY="0.5" CenterX="0.5"/>
                    </TransformGroup>
                </LinearGradientBrush.RelativeTransform>
                <GradientStopCollection>
                    <GradientStop Offset="0" Color="#FF0000" />
                    <GradientStop Offset="0.48" Color="#FF0000" />
                    <GradientStop Offset="0.48" Color="#0070C0" />
                    <GradientStop Offset="1" Color="#0070C0" />
                </GradientStopCollection>
            </LinearGradientBrush>
        </Path.Fill>
    </Path>

And here's the way to transform just the PathGeometry - it works exactly as I need (well, except for the center of rotation, but I think I can resolve that).

<Path RenderTransformOrigin="0.5,0.5" Stroke="Black" StrokeLineJoin="Miter" StrokeThickness="5" Canvas.Left="15" Canvas.Top="23" >
        <Path.Data>
            <PathGeometry>
                <PathGeometry.Figures>
                    <PathFigure StartPoint="0,0" IsClosed="True">
                        <LineSegment Point="205.8147,0"/>
                        <LineSegment Point="205.8147,174.9073"/>
                        <LineSegment Point="0,174.9073"/>
                    </PathFigure>
                </PathGeometry.Figures>
                <PathGeometry.Transform>
                    <RotateTransform Angle="45" CenterY="0.5" CenterX="0.5"/>
                </PathGeometry.Transform>
            </PathGeometry>
        </Path.Data>
        <Path.Fill>
            <LinearGradientBrush StartPoint="0,1" EndPoint="1,1" >
                <LinearGradientBrush.RelativeTransform>
                    <TransformGroup>
                        <RotateTransform Angle="270" CenterY="0.5" CenterX="0.5"/>
                    </TransformGroup>
                </LinearGradientBrush.RelativeTransform>
                <GradientStopCollection>
                    <GradientStop Offset="0" Color="#FF0000" />
                    <GradientStop Offset="0.48" Color="#FF0000" />
                    <GradientStop Offset="0.48" Color="#0070C0" />
                    <GradientStop Offset="1" Color="#0070C0" />
                </GradientStopCollection>
            </LinearGradientBrush>
        </Path.Fill>
    </Path>

回答1:


SVG only:

The angles don't add up because of the way gradients are applied on an element. By default..

(...) It essentially scales the gradient to the size of your object, so you only have to specify coordinates in values from zero to one, and they're scaled to the size of your object automatically for you.

https://developer.mozilla.org/en-US/docs/Web/SVG/Tutorial/Gradients

Another way to look at it; the gradient is first applied to a square, and then stretched to fit your element's bounding box. See this example where all three elements have the same 45 degrees gradient:

<svg width="960" height="540" xmlns="http://www.w3.org/2000/svg" >
  <defs>
    <linearGradient id="linear" gradientTransform="rotate(45 0.5 0.5)" spreadMethod="pad">
      <stop offset="49%" stop-color="gold"/>
      <stop offset="51%" stop-color="blue"/>
    </linearGradient>
  </defs>
  
  <path fill="url(#linear)" d="M0,0   h170 v100 h-170"/>
  <path fill="url(#linear)" d="M210,0 h100 v100 h-100"/>
  <path fill="url(#linear)" d="M350,0 h100 v170 h-100"/>
</svg>

So in your case, where you want the gradient at a 225 degree angle, imagine squeezing the gradient back into a square form, and calculate what the angle looks like then.

Long story short, here is some code:

https://jsfiddle.net/bc4k4Lcv/



来源:https://stackoverflow.com/questions/47515810/keeping-a-rotated-linear-gradient-unrotated-when-shape-is-rotated-in-svg

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!