SVG Gradients with ObjectBoundingBox units and gradientTransform with rotation - svg

Trying to replicate an SVG gradient with canvas drawing in javascript i came across this use case:
<svg xmlns="http://www.w3.org/2000/svg" width="760" height="760"
xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 380 380">
<defs>
<linearGradient id="three_stops_4" gradientTransform="rotate(90)">
<stop offset="0%" style="stop-color: #ffcc00;"/>
<stop offset="33.3%" style="stop-color: #cc6699; stop-opacity: 1"/>
<stop offset="100%" style="stop-color: #66cc99; stop-opacity: 1"/>
</linearGradient>
<polygon id="base" points="5,5 35,5 35,40 5,40 5,35 30,35 30,10 5,10" />
<rect width="35" height="80" id="base2" />
</defs>
<polygon transform="translate(0, 45)" points="5,5 80,5 80,40 5,40 5,35 75,35 75,10 5,10" style="fill: url(#three_stops_4); stroke: black;"/>
</svg>
That render like this:
Now those are the specs:
https://www.w3.org/TR/SVG11/pservers.html#LinearGradientElementGradientUnitsAttribute
What i do not understand is if:
1) the object bounding box transformation is applied to the gradient coordinates and then the gradient transform is applied.
OR
2) the gradientTransform is applied to the object bounding box and the the gradient coordinates are transformed.
The specs seems to say to take in the consideration the first option, but the point is that while the gradient should stretch from the whole width of the object, i clearly see it in the rendering that being rotated it stretches for the height of the object.
There is an additional stretching ( compression ) that seems to come from the object bounding box aspect ratio, applied after the rotation.
Does anyone know how exactly the renderer should behave?

It says in the spec (under gradientTransform):
This additional transformation matrix is post-multiplied to (i.e., inserted to the right of) any previously defined transformations, including the implicit transformation necessary to convert from object bounding box units to user space.
So depending on how you like to think about matrix multiplication, both answers are correct. In my way of thinking about it (transforms are applied right to left) #2 is correct. The gradient transform is applied first. Then the objectBoundingBox transform is applied. In pseudo code:
ctx.transform(gradientTransform)
ctx.transform(objectBoundingBoxTransform)
But it also depends on what 2D rendering library you are using, and whether you are using pre- or post- multiplication. Some rendering libraries let you set a transform matrix on the gradient object. For example, in Android you can do:
LinearGradient grad = new LinearGradient(...);
grad.setLocalMatrix(gradientTransformMatrix);

The accepted answer is the one that guided me to the solution.
The problem was translating some SVG peculiarities to JS canvas rendering.
That is hard and sometimes not possible at all with simple gradients.
For gradients that get applied to a filled shape, and that are boundingbox units and also have a gradientTrasnfrom attribute, you should:
var t = myGradientTransform;
MakeAPathForMyShape(ctx);
ctx.transform(width, 0, 0, height, minX, minY);
ctx.transform(t[0], t[1], t[2], t[3], t[4], t[5], t[6]);
ctx.fill();
This works most of the time if you are using ctx.fill, does not work if you are using fillRect or fillText, because you cannot put transformations in the middle of the path traced and the fill operation.
Now let's talk of the stroke.
If you use the code above with stroke, you are going to apply the gradient transform to your strokeWidth, thing that maybe you do not want to.
If the scaling is not uniform between X and Y, you cannot compensate with changing ctx.strokeWidth;
A solution i had to fallback to, is to create a pattern and apply that.
So i calculate a bounding box for my shape, and i create a canvas with the same size.
Then make a path as big as the canvas, using its perimeter, and i use the above code on that context.
At this point i have a rect, as big as my shape, with the transformed gradient applied over.
Now i create a pattern from it:
ctx.strokeStyle = ctx.createPattern(tmpCanvas, 'no-repeat');
ctx.stroke();
in this way i can apply the transform to the gradient and not to the stroke size.
In the research i also evaluated the idea of transforming the normal of the gradient and find back again the points that would generate a transformed gradient in a non transformed space. This can work for linear gradients, but not for radial gradients, that get an ellipse shape and do need a classic trasnform to work.

Related

How to auto-brighten an SVG node using SVG only?

I would like to be able to specify some kind of transformation that would, given an arbitrary SVG node remap all of its pixel values to cover the full (0-100% or 0-255) range of intensities while avoiding clipping.
The feComponentTransfer filter with feFuncX linear mapping functions almost offers what I want but seems to lack the ability to refer to the global maximum intensity of the input node, is there some clever way around it ?
There is no "auto-brighten" feature that will do what you want.
You would have have to do it yourself by reading all the colours yourself with javascript and work out the appropriate brighten/saturate value.
But as a good-enough approach, a saturate filter with a value of 200% might get something close to what you want.
svg rect:hover {
filter: saturate(200%);
}
<svg>
<rect width="50%" height="100%" fill="cadetblue"/>
<rect x="50%" width="50%" height="100%" fill="cornsilk"/>
</svg>

What does inkscape:transform-center-x/y means?

I am using Inkscape to make SVG image and a little confused about the "transform-center-x" attribute like below:
<circle
style="display:inline;fill:#0000ff;fill-opacity:1;stroke:#000000;stroke-width:1.13386"
id="beacon-21737"
cx="-121.04593"
cy="42.20393"
r="1.9999999"
inkscape:transform-center-x="-0.6614634"
inkscape:transform-center-y="-10.318751"
inkscape:label="beacon"
transform="rotate(-90)">
</circle>
It seems not equal to rotate(angle, x, y). Please help me understand the meaning of the "transform-center-x/y".
This is a property of the grafical interface. If you click twice on a grafical object, you can rotate or skew it around a center indicated by a cross:
The cross can be moved by dragging it. Its position is stored in the inkscape:transform-center attribute. The value is in coordinates relative to the center of the bounding box of the grafical object. This position will also be used for other transforms, for example when you use the Object -> Transformation... dialog.
The SVG namespace transform will not reflect that center. Inkscape has an internal optimization algorithm to express rotations and other transforms, so the grafical and the standardized center might not coincide.
As always, other renderers will simply ignore tags and attributes in the inkscape namespace.

SVG-animation on hover - first steps, how to?

I'd like to do an animation to an element when hovering it.
As I do use svg-elements for both situations (standard and hover-state) I guess I must somehow manipulate the first svg-element when hovering it by editing the svg-code inline.
I basically'd need a starting point there:
How would I "redraw" in an animated manner the hover-image and not just swap it?
Do I need a 3rd party library (which)?
If I had multiple of these situations, how would I keep my code clean by not having 10 svg-codes inline within my html-source?
Thanks for your answer(s)!
The code for the svg-image(s) is here
<svg id="Ebene_1" data-name="Ebene 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1280 800">
<defs><style>.cls-1,.cls-2{fill:none;stroke:#000;}.cls-1{stroke-miterlimit:10;stroke-width:7px;}.cls-2{stroke-linejoin:bevel;stroke-width:5px;}</style></defs>
<title>arrows_demoZeichenfläche 1</title>
<line class="cls-1" x1="325.5" y1="333" x2="325.5" y2="539"/>
<polyline class="cls-2" points="242 455.67 325.75 539.42 409.42 455.75"/><path class="cls-1" d="M670.5,135.79c0,11.62-8,11.73-8,23.35s8,11.68,8,23.3-8,11.65-8,23.28,8,11.64,8,23.26-8,11.63-8,23.25,8,11.63,8,23.25-8,11.63-8,23.25,8,11.63,8,23.25-8,11.62-8,23.25,8,11.62,8,23.25-8,11.63-8,23.25,8,11.63,8,23.25-8,11.62-8,23.25,8,11.62,8,23.25-8,11.63-8,23.25,8,11.63,8,23.25v31"/>
<polyline class="cls-2" points="587 455.67 670.75 539.42 754.42 455.75"/></svg>
You could use JavaScript to manipulate the value of the points attribute, but such changes would be sudden, so the change would look like a stop motion film.
Like this Codepen, what you could do is give a path element a stroke-dasharray that is equal to the getTotalLength of the path in order to "erase" the straight line (of the arrow) off the page, then quickly switch the value of the d attribute, and then "redraw" the line back onto the page?
However, I don't believe that's what you're looking for. I believe HTML5 Canvas, with my limited knowledge about it, would be the more feasible option for what you're trying to accomplish.
Actually, I guess it might be possible using a CSS3 3D transform, like so. The problem, however, is that the line doesn't have any depth, so when you initially set rotateX(90deg) on the path in CSS the line becomes invisible instead of appearing as a straight line...

SVG Transformation

I want to move every shape present on S.V.G 600px down, as my previous S.V.G's initial coordinates was at "bottom-left" but in my new S.V.G, it's at the "top-left" of S.V.G, so to display the shapes of previous S.V.G on new S.V.G I just used to add the Previous S.V.G height i.e. 600px into Y-translation and it's working fine with shape like:
<g id="shape3165-80" transform="translate(105.375,-208.875)"></g>
As the new points are (105.375, 391.125) which is correct as from bottom it's 208.875 above.
But in the case of rotated shapes, it's not showing on correct position as on rotation it's translation point had been changed, for example:
In case of:
<g id="shape3164-77" transform="translate(263.251,868.355) rotate(180)"></g>
so on adding 600px it's coordinates becomes (263.251, 1468.355) which display them out of S.V.G box.
So I want to find:
The initial translate point of the shape before the rotation of 180deg.
Then add 600px to it.
Then again rotate it with 180deg.
So my question is am I doing it in right manner? Would you help me to find out the solution or formula for the same?
Because the shape is rotated 180 degrees, you will need to subtract 600 from the Y coordinate instead of adding 600.
So the second example should be:
<g id="shape3164-77" transform="translate(263.251,268.355) rotate(180)"></g>
Alternatively, add your 600 before the other transforms:
<g id="shape3164-77" transform="translate(263.251,868.355) rotate(180) translate(0,600)"></g>
(Because of the way the transform attribute works, you can think of transforms being applied from right to left. So to have one applied first, it has to be put at the end).

SVG Fill and Transform

I understand that fill is applied after transform, but I don't understand why. I've looked in the spec and cannot find information about why this might be.
I'd like to know if there is a way to have the fill applied to a group of elements, some using transform: rotate in a consistent manner. To illustrate the problem see the example code below:
<svg width='38' height='18' viewBox='0 0 38 18' xmlns='http://www.w3.org/2000/svg'>
<defs>
<linearGradient id='gradientFill' x2='0' y2='100%' gradientUnits='userSpaceOnUse'>
<stop stop-color='hsl(0, 0%, 0%)' />
<stop offset='100%' stop-color='hsl(0, 0%, 100%)' />
</linearGradient>
<path id='shape' d='M0,0m9,0c-3.92,0,-7.24,2.51,-8.48,6h3.29c0.83,-1.92,2.8,-3.56,5.19,-3c2.52,0.59,3,3.55,3,6c0,3.6,-0.6,6,-3,6s-3,-2.4,-3,-6h-6c0,4.97,4.03,9,9,9c4.97,0,9,-4.03,9,-9s-4.03,-9,-9,-9z'/>
<g id='test'>
<use xlink:href='#shape'></use>
<use transform='translate(20,0) rotate(180,9,9)' xlink:href='#shape'></use>
</g>
</defs>
<use class='logo' xlink:href='#test' fill="url(#gradientFill)"></use>
</svg>
As you see, on the non-rotated element the gradient runs top to bottom, as expected. However on the rotated element the gradient is also rotated so that it runs from bottom to top. This seems inconsistent to me, as the fill is applied to a group and not the individual objects.
My ultimate desire is to have the linear gradient flow from top to bottom on both shapes, but I'd like to continue using the use and transform method, if possible.
Can someone point me in the right direction in regards to the spec, and also suggest a solution to accomplish my goal?
Attributes like "fill" are not commands that operate on an object, they are properties that are used by the object when rendering and are inherited by an object's children. Just the same as CSS rules.
To achieve the effect you want, use Michael's solution, or create a second gradient that has it's x,y and x2,y2 flipped to match the 180 degree rotation.
That you apply transforms first is stated in the SVG specification for gradients when it talks about gradientUnits...
If gradientUnits="userSpaceOnUse", ‘x1’, ‘y1’, ‘x2’ and ‘y2’ represent values in the coordinate system that results from taking the current user coordinate system in place at the time when the gradient element is referenced (i.e., the user coordinate system for the element referencing the gradient element via a ‘fill’ or ‘stroke’ property) and then applying the transform specified by attribute ‘gradientTransform’.
If you apply a transform to the element it changes the element's co-ordinate system and that therefore affects the gradient too.
You should define your shape as a clip-path and then apply it to a rect that is filled with the gradient you want. You could also do it with a filter, although that's a little more complicated.

Resources