SVG gradient gradient units vs gradienttransform - not the same gradient - svg

I am trying to figure out conversion from gradienttransform to gradientunits. I am getting different gradient distribution. Why?
<svg width="540" height="540" xmlns="http://www.w3.org/2000/svg" >
<defs>
<linearGradient id="linear1" gradientTransform="rotate(45 0.5 0.5)" spreadMethod="pad">
<stop offset="0%" stop-color="gold"/>
<stop offset="100%" stop-color="blue"/>
</linearGradient>
<linearGradient id="linear2" x1="0%" y1="0%" x2="100%" y2="100%" spreadMethod="pad">
<stop offset="0%" stop-color="gold"/>
<stop offset="100%" stop-color="blue"/>
</linearGradient>
</defs>
<rect fill="url(#linear1)" x="0" y="0" width="270" height="270" />
<rect fill="url(#linear2)" x="0" y="270" width="270" height="270" />
</svg>
These 2 gradients are not the same:

because the distance between the left side and right side of a rectangle does not equal the distance from one corner to the opposite corner when you rotate it 45 degrees.
the left and right sides are 270 units apart (default x2 is 100% the others default to 0%).
the corners 270 * √2 units apart
So the distance between x1,y1 and x2,y2 is different in each case given that the rotation transform is distance invariant.

Related

Linear gradient across SVG line

I am wondering how to make linearGradient across (from top to bottom) the line, as opposed to the example below where the gradient goes along (from left to right) the line.
<svg xmlns="http://www.w3.org/2000/svg" version="1">
<defs>
<linearGradient id="e" x1="40" y1="210" x2="460" y2="210" gradientUnits="userSpaceOnUse">
<stop stop-color="steelblue" offset="0" />
<stop stop-color="red" offset="1" />
</linearGradient>
</defs>
<line x1="40" y1="210" x2="460" y2="210" stroke="url(#e)" stroke-width="30" />
</svg>
Changing y coordinates works nicely for an un-rotated line and linearGradient now goes across (from top to bottom) the line:
<svg xmlns="http://www.w3.org/2000/svg" version="1">
<defs>
<linearGradient id="e" x1="40" y1="195" x2="40" y2="225" gradientUnits="userSpaceOnUse">
<stop stop-color="steelblue" offset="0" />
<stop stop-color="red" offset="1" />
</linearGradient>
</defs>
<line x1="40" y1="210" x2="460" y2="210" stroke="url(#e)" stroke-width="30"/>
</svg>
But this doesn't work when rotated:
<svg xmlns="http://www.w3.org/2000/svg" version="1">
<defs>
<linearGradient id="e" x1="40" y1="235" x2="40" y2="265" gradientUnits="userSpaceOnUse">
<stop stop-color="steelblue" offset="0" />
<stop stop-color="red" offset="1" />
</linearGradient>
</defs>
<line x1="40" y1="210" x2="460" y2="290" stroke="url(#e)" stroke-width="30"/>
</svg>
And what I want to have is the rotated line with linear gradient across it.
Something like this:
<svg width="600" height="200" viewBox="0 190 600 200" xmlns="http://www.w3.org/2000/svg" version="1">
<defs>
<linearGradient id="e" x1="40" y1="210" x2="460" y2="290" gradientUnits="userSpaceOnUse">
<stop stop-color="steelblue" offset="0" />
<stop stop-color="red" offset="1" />
</linearGradient>
</defs>
<line x1="40" y1="210" x2="460" y2="290" stroke="url(#e)" stroke-width="30"/>
</svg>
The trick to your first case is to make the x1 y1,x2 y2 of the line match the x1 y1,x2 y2 coordinates of the linear gradient.
For the second case it is a little more involved Math-wise.
You have to create a line that is perpendicular to the first and has the length of the width of the desired line and also start at half the width from one of the points.
So in your case (in pseudocode!):
Step 1:
get the direction
dx=x2-x1;
dy=y2-y1;
dx,dy is now the direction from point 1 to point2
Step 2:
normalise the direction to length 1 by dividing dx and dy by the length of the line.
len=Math.sqrt(dx*dx+dy*dy);
dx=dx/len;
dy=dy/len;
Ofcourse this doesn't work if len=0, but because you gave me coords I don't have to worry about that now.
Step 3:
Find the perpendicular direction. This is actually very easy, but logically can be two directions. I'll just choose one.
temp=dx;
dx=-dy;
dy=temp;
If you want the other direction, just negate dx and dy. after this process.
dx=-dx;
dy=-dy;
dx, dy now holds the perpendicular direction.
Step 4:
multiply dx and dy by the desired width of the line, in your case 30. I have called this w.
dx=w*dx;
dy=w*dy;
Step 5:
To find the p1 and p2 for the gradient, take p1 from the line and add or subtract half the dx.
gradient_x1=x1+dx*0.5;
gradient_y1=y1+dx*0.5;
gradient_x2=x1-dx*0.5;
gradient_y2=y1-dx*0.5;
Now you can build your line up again.
To show you what I mean, I've plugged in your values and done the whole thing and I get this:
Your case: (x1="40" y1="210" x2="460" y2="290" w=30)
## STEP1 ##
dx: 420 dy:80
## STEP2 ##
dx: 0.9823385664224747 dy:0.1871121078899952
## STEP3 ##
dx: -0.1871121078899952 dy:0.9823385664224747
## STEP4 ##
dx: -5.613363236699856 dy:29.47015699267424
## STEP5 ##
gradient_x1=37.19331838165007
gradient_y1=224.7350784963371
gradient_x2=42.80668161834993
gradient_y2=195.2649215036629
so plug-in that in to your example:
<svg width="600" height="200" viewBox="0 190 600 200" xmlns="http://www.w3.org/2000/svg" version="1">
<defs>
<linearGradient id="e" x1="37.19331838165007" y1="224.7350784963371" x2="42.80668161834993" y2="195.2649215036629" gradientUnits="userSpaceOnUse">
<stop stop-color="steelblue" offset="0" />
<stop stop-color="red" offset="1" />
</linearGradient>
</defs>
<line x1="40" y1="210" x2="460" y2="290" stroke="url(#e)" stroke-width="30"/>
</svg>
Wrapping up
Luckily, we don't have to do all this calculating at all, since we have a computer and svg elements can be easily manipulated by javascript.
To get to the elements in svg with javascript it's most handy if they have an id.
Your gradient has an id="e", let's give your line and id="l".
After that it's a question of inserting a little script into the page
to take the x1 y1,x2 y2 from the line ("l") and calculating everything and put it in the gradient ("e"), and you get this:
<svg width="600" height="200" viewBox="0 190 600 200" xmlns="http://www.w3.org/2000/svg" version="1">
<defs>
<linearGradient id="e" x1="0" y1="0" x2="1" y2="1" gradientUnits="userSpaceOnUse">
<!-- put the coords on 0,0 1,1 it really doesn't matter, they will be calculated-->
<stop stop-color="steelblue" offset="0" />
<stop stop-color="red" offset="1" />
</linearGradient>
</defs>
<line id="l" x1="40" y1="270" x2="450" y2="210" stroke="url(#e)" stroke-width="30"/>
</svg>
<script>
var line=document.getElementById("l");
var x1=parseFloat(l.getAttribute("x1"));
var y1=parseFloat(l.getAttribute("y1"));
var x2=parseFloat(l.getAttribute("x2"));
var y2=parseFloat(l.getAttribute("y2"));
var w=parseFloat(l.getAttribute("stroke-width"));
// step 1
var dx=x2-x1;
var dy=y2-y1;
// step 2
len=Math.sqrt(dx*dx+dy*dy);
dx=dx/len;
dy=dy/len;
// step 3
var temp=dx;
dx=-dy;
dy=temp;
//step 4
dx=w*dx;
dy=w*dy;
//step 5
var gradient_x1=x1+dx*0.5;
var gradient_y1=y1+dy*0.5;
var gradient_x2=x1-dx*0.5;
var gradient_y2=y1-dy*0.5;
document.getElementById("e");
e.setAttribute("x1",gradient_x1);
e.setAttribute("y1",gradient_y1);
e.setAttribute("x2",gradient_x2);
e.setAttribute("y2",gradient_y2);
</script>
You can freely edit the beginning and endpoints of the line and even the stroke-width, the script will fix your gradient on the fly. To 'prove' this to you, that is exactly what I did. :)
Hope this helps.
Do you mean rotate the gradient? Then use gradientTransform
<svg xmlns="http://www.w3.org/2000/svg" version="1">
<defs>
<linearGradient id="e" x1="40" y1="210" x2="460" y2="210" gradientUnits="userSpaceOnUse" gradientTransform="rotate(90)">
<stop stop-color="steelblue" offset="0" />
<stop stop-color="red" offset="1" />
</linearGradient>
</defs>
<line x1="40" y1="210" x2="460" y2="210" stroke="url(#e)" stroke-width="30" />
</svg>

Why is this SVG ellipse not a gradient?

The following SVG should show an ellipse that has a gradient from blue to green. But it's all green. Why?
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:jfreesvg="http://www.jfree.org/jfreesvg/svg" width="2.0in" height="2.0in" text-rendering="auto" shape-rendering="auto" viewBox="0 0 1828800 1828800">
<defs>
<linearGradient id="783907957477109gp0" x1="0" y1="0" x2="0" y2="152400" gradientUnits="userSpaceOnUse">
<stop offset="0%" stop-color="rgb(0,0,255)"/>
<stop offset="100%" stop-color="rgb(0,255,0)"/>
</linearGradient>
</defs>
<ellipse cx="228600" cy="304800" rx="76200" ry="152400" style="fill: url(#783907957477109gp0); fill-opacity: 1.0" transform="matrix(1,0,0,1,0,0)"/>
</svg>
The gradient runs from 0 to 152400 in the y direction, anything bigger than y=152400 will be drawn in the last stop colour.
The ellipse y centre is 304800 so its lowest point is 304800 - 152400 = 152400 which is the gradient end point.

How to make a vertical cross browser linear gradient using svg (for modern browsers)?

I am having trouble making SVG vertical linear gradients work in IE 11
, so the question is - given the following svg how to make the gradient identified as Gradient1 work in such a way that it is a vertical gradient?
<?xml version="1.0" encoding="utf-8"?>
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 79.4 59.5" xml:space="preserve">
<rect x="2.4" y="29.8" width="74.6" height="27.4"/>
<g>
<defs>
<linearGradient id="Gradient1" gradientUnits="userSpaceOnUse" x1="100%" y1="0%" x2="100%" y2="100%" gradientTransform="rotate(45 50 50)">
<stop offset="0%" style="stop-color:#9AAFCC"/>
<stop offset="20%" style="stop-color:#557096"/>
<stop offset="35%" style="stop-color:#36557D"/>
<stop offset="49%" style="stop-color:#1E3F6B"/>
<stop offset="63%" style="stop-color:#0D305D"/>
<stop offset="87%" style="stop-color:#032756"/>
<stop offset="100%" style="stop-color:#002453"/>
</linearGradient>
</defs>
<rect x="2.4" y="2.4" class="st0" width="74.6" height="27.4" fill="url(#Gradient1)"/>
</g>
</svg>
I have of course tried to use a gradientTransform, what I have that works in Chrome (and presumably other browsers but not in IE) includes the following on the linearGradient element
x1="-383.9706" y1="317.1023" x2="-382.9706" y2="317.1023" gradientTransform="matrix(0 -27.3826 -27.3826 0 8722.7842 -10484.3682)"
however as soon as I try to do this in IE the gradient I have stops working and the rectangle just takes the first full stop color.
I am open to translating the gradient into CSS if it can be made to work in the svg if that is the only cross-browser solution available.
Your example works the same in Chrome and IE for me.
<svg viewBox="0 0 79.4 59.5">
<rect x="2.4" y="29.8" width="74.6" height="27.4"/>
<defs>
<linearGradient id="Gradient1" gradientUnits="userSpaceOnUse" x1="100%" y1="0%" x2="100%" y2="100%" gradientTransform="rotate(45 50 50)">
<stop offset="0%" style="stop-color:#9AAFCC"/>
<stop offset="20%" style="stop-color:#557096"/>
<stop offset="35%" style="stop-color:#36557D"/>
<stop offset="49%" style="stop-color:#1E3F6B"/>
<stop offset="63%" style="stop-color:#0D305D"/>
<stop offset="87%" style="stop-color:#032756"/>
<stop offset="100%" style="stop-color:#002453"/>
</linearGradient>
</defs>
<rect x="2.4" y="2.4" class="st0" width="74.6" height="27.4" fill="url(#Gradient1)"/>
</svg>
It's a linear gradient rotated 45 degrees as your markup requests.
If you want the gradient to be vertical, remove the gradientTransform.
<svg viewBox="0 0 79.4 59.5">
<rect x="2.4" y="29.8" width="74.6" height="27.4"/>
<defs>
<linearGradient id="Gradient1" gradientUnits="userSpaceOnUse" x1="100%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" style="stop-color:#9AAFCC"/>
<stop offset="20%" style="stop-color:#557096"/>
<stop offset="35%" style="stop-color:#36557D"/>
<stop offset="49%" style="stop-color:#1E3F6B"/>
<stop offset="63%" style="stop-color:#0D305D"/>
<stop offset="87%" style="stop-color:#032756"/>
<stop offset="100%" style="stop-color:#002453"/>
</linearGradient>
</defs>
<rect x="2.4" y="2.4" class="st0" width="74.6" height="27.4" fill="url(#Gradient1)"/>
</svg>

Calculating SVG Gradient Coordinates for Angled Repeating Lines

I'm trying to generate a parallelogram in SVG with repeating lines that are along the same angle as the left and right sides of the polygon. Something like this:
I got the repeating gradient to work, but I can't get the angle of the lines right. They're skewed from the angle of the bounding parallelogram:
I know I can manipulate the angle based on the (x1, y1) / (x2, y2) attributes of the gradient, but just playing with the numbers isn't doing it for me. How can I calculate which values to use for these attributes given a known angle?
Here's the SVG code I have right now:
<svg version="1.1" xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 1680 550" preserveAspectRatio="xMidYMin">
<defs>
<linearGradient id="StripedGradient" spreadMethod="repeat" x1="0%" x2="1%" y1="0%" y2="1%">
<stop stop-color="yellow" offset="0%" />
<stop stop-color="yellow" offset="15%" />
<stop stop-color="transparent" offset="15%" />
<stop stop-color="transparent" offset="100%" />
</linearGradient>
</defs>
<polygon fill="url(#StripedGradient)" points="0 550, 415 145, 610 145, 195 550" />
</svg>
This angle currently works out to be just under 45deg (44.301...), but the value could change at the discretion of the designer, so...
(I'm really new to SVG, as in I knew it existed, but I had never written inline SVG by hand until today.)
One fairly simple approach would be to define your gradient as a horizontal stripe. Then if you also use gradientUnits="userSpaceOnUse", you can use a gradientTransform to set your angle.
<svg version="1.1" xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 750 550" preserveAspectRatio="xMidYMin">
<defs>
<linearGradient id="StripedGradient" spreadMethod="repeat"
gradientUnits="userSpaceOnUse"
x1="0" y1="0" x2="0" y2="20"
gradientTransform="rotate(-44.301)">
<stop stop-color="red" offset="0%" />
<stop stop-color="red" offset="15%" />
<stop stop-color="transparent" offset="15%" />
<stop stop-color="transparent" offset="100%" />
</linearGradient>
</defs>
<polygon fill="url(#StripedGradient)" points="0 550, 415 145, 610 145, 195 550" />
</svg>

Can I define a linear gradient in SVG with a normal/bell-shaped blend?

Consider the following svg snippet:
<linearGradient id="redgradient" x1="0%" x2="0%" y1="0%" y2="100%">
<stop offset="0%" stop-color="#ffffff"/>
<stop offset="100%" stop-color="#ff0000"/>
</linearGradient>
<rect x="0" y="0" width="400" height="400" fill="url(#redgradient)"/>
Is there a way of specifying that the colours should be blended together using a bell shaped curve?
(Similar to the GDI+ LinearGradientBrush::SetBlendBellShape method)
The two rectangles below show the difference - left is without bell blend, right is with bell blend:
Or is it left to the SVG renderer to decide how the colors should be blended together?
gradientTransform cannot do this kind of transformation. You will need a
filter to apply a bell shaped transform to a red/white gradient.
.
Original on the left, transformed on the right. The curve isn't very fine grained (only 15 values), so you would probably want to use more terms for a large gradient.
<svg width="500px" height="500px" viewbox="0 0 800 800">
<defs>
<filter id="bellTransform" color-interpolation-filters="sRGB">
<feComponentTransfer>
<feFuncG type="table" tableValues=
"0.0000 0.0059 0.0166 0.0346 0.0628 0.1038 0.1586 0.2259 0.3011 0.3761 0.4406 0.4844 0.5000 0.5156 0.5594 0.6239 0.6989 0.7741 0.8414 0.8962 0.9372 0.9654 0.9834 0.9941 1.0000"/>
<feFuncB type="table" tableValues=
"0.0000 0.0059 0.0166 0.0346 0.0628 0.1038 0.1586 0.2259 0.3011 0.3761 0.4406 0.4844 0.5000 0.5156 0.5594 0.6239 0.6989 0.7741 0.8414 0.8962 0.9372 0.9654 0.9834 0.9941 1.0000"/>
</feComponentTransfer>
</filter>
</defs>
<linearGradient id="redgradient" x1="0%" x2="0%" y1="0%" y2="100%">
<stop offset="0%" stop-color="#ffffff"/>
<stop offset="100%" stop-color="#ff0000"/>
</linearGradient>
<rect filter="url(#bellTransform)" x="410" y="0" width="400" height="400" fill="url(#redgradient)"/>
<rect x="0" y="0" width="400" height="400" fill="url(#redgradient)"/>
</svg>
You can apply a gradientTransform to a linear gradient. I'm not sure how the available transforms map to your required effect.
If that doesn't work you can use the gradient as the input to a filter and perhaps end up with a similar effect. Here's an article which covers combining filters.

Resources