How to make SVG scaling correctly? - svg

I have an SVG like this :
<svg
xmlns="http://www.w3.org/2000/svg"
id="demo"
style="bottom:0px;left:0px;right:0px;top:0px;margin:auto;position:absolute;"
viewBox="0 0 40 28">
<rect x="0" y="0" width="40" height="14" fill="blue"/>
<rect x="0" y="14" width="40" height="14" fill="green"/>
</svg>
I would expect whatever its size, it will contains only pure blue or pure green.
However, that's not the case :
The same with zoom on it :
As you can see, there this half-transparent white border between blue and green rectangle.
Where does it come from ? Can I avoid it ?
Thank you.

You can turn off antialiasing if you want via shapeRendering:crispEdges. You might not like the result if you apply it to things that do not consist solely of vertical or horizontal lines though.
The viewBox also forces the shape to maintain an aspect ratio which can cause the edges to be blank to maintain that aspect ratio. Again you can turn that off via preserveAspectRatio="none" but your shape will then distort to match the container aspect ratio.
<svg
xmlns="http://www.w3.org/2000/svg"
id="demo"
style="bottom:0px;left:0px;right:0px;top:0px;margin:auto;position:absolute;shape-rendering:crispEdges"
viewBox="0 0 40 28" preserveAspectRatio="none">
<rect x="0" y="0" width="40" height="14" fill="blue"/>
<rect x="0" y="14" width="40" height="14" fill="green"/>
</svg>

The issue is anti-aliasing. It's a characteristic of the renderer, not your SVG representation. There's no real answer, and there's not meant to be - a pixel is an image sample, not a little square.

Related

Reversing the flip and rotation of an image fill in a path that is flipped and rotated

I am needing some help understanding how to "unflip/unrotate" and image fill in SVG for a path. When I fill a path with an image and then rotate and fill the path with an image, the image also flips and rotates. But I'd like to keep the image upright and non-flipped, regardless of the rotation and flipping. The size of the picture is the bounding box of the rotated shape.
So, for example, say I have this path and this picture:
If the path is only rotated (in this case, 315 degrees), it's easy to unrotate the image by just reversing the angle in the pattern that is used for a fill (i.e. 45 degrees).
<svg name="rotate only" x="0" y="0" width="100.08" height="200" overflow="visible" fill="url(#fillImages0sp15)" stroke="#4472C4" stroke-miterlimit="8" stroke-width="2.25">
<defs>
<image id="bgImage" preserveAspectRatio="none" width="159.113" height="159.113" xlink:href="THE IMAGE URL"></image>
<pattern id="fillImages0sp15" x="-38.362" y="11.598" width="159.113" height="159.113" patternTransform="rotate(45,50.04,100)" patternUnits="userSpaceOnUse">
<use xlink:href="#bgImages0sp15"></use>
</pattern>
</defs>
<path d="M0,149.96 25.02,149.96 25.02,0 75.06,0 75.06,149.96 100.08,149.96 50.04,200 Z " transform="rotate(315,50.04,100)"></path>
</svg>
But if there any kind of flip on the path (horizontal, vertical, or both), it doesn't work by just reversing the transformation on the pattern used for the image fill. For example, if the image is rotated 315 degrees and flipped vertical, the path has transform="rotate(45,50.04,100) translate(0,200), scale(1,-1)" for flipping vertically. That works. But the image fill needs to get reset back to be upright and not flipped. So the patternTransform should just be the same transformation. But this isn't working. This is the result I get.
<svg name="flipV" x="0" y="0" width="100.08" height="200" overflow="visible" fill="url(#fillImages0sp14)" stroke="#4472C4" stroke-miterlimit="8" stroke-width="2.25">
<defs>
<image id="bgImages" preserveAspectRatio="none" width="159.113" height="159.113" xlink:href="THE IMAGE URL"></image>
<pattern id="fillImages0sp14" x="-20.671" y="-370.711" width="159.113" height="159.113" patternTransform="rotate(45,50.04,100) translate(0,200) scale(1,-1)" patternUnits="userSpaceOnUse">
<use xlink:href="#bgImages"></use>
</pattern>
</defs>
<path d="M0,149.96 25.02,149.96 25.02,0 75.06,0 75.06,149.96 100.08,149.96 50.04,200 Z " transform="rotate(45,50.04,100) translate(0,200) scale(1,-1)"></path>
</svg>
Notice the path has transform="rotate(45,50.04,100) translate(0,200) scale(1,-1) and the fill pattern with the image has patternTransform="rotate(45,50.04,100) translate(0,200) scale(1,-1). This produces the wrong results.
In fact, here's all of it. This is what I'm hoping to achieve:
Does anyone know how to set the patternTransform so that it can "unflip/unrotate" the filled image? Is it that the translate in the patternTransform needs to be calculated differently?
Rather than filling the arrow, <svg ... fill="url(#fillImages0sp14)", transforming it and then trying to somehow separate it from its fill, untransform that, and then refill it, I'd just display the image, but masked by the transformed arrow.
I don't understand why the orange border still shows up. I've made the black rectangle overly large (which helped), and I've changed the overflow="...", but neither made it disappear.
Edit: Your global stroke attributes were messing up the mask. Moving them to the displayed arrow (the only thing using that stroke) fixed the orange border issue.
P.S. xlink is deprecated. Just use href.
P.P.S. I had to add a final translate to center the transformed arrow over the image. It's easier and more accurate to move the center of the arrow to the center of the image first, and then do your transformations about that center.
<svg name="transformed" x="0" y="0" width="159.113" height="159.113" overflow="visible" xmlns="http://www.w3.org/2000/svg">
<defs>
<path id="Arrow" d="M0,149.96 25.02,149.96 25.02,0 75.06,0 75.06,149.96 100.08,149.96 50.04,200 Z" />
<use id="TransformedArrow" href="#Arrow" transform="translate(38.3625,-29.2893) rotate(45,50.04,100) translate(0,200) scale(1,-1)" />
<mask id="ArrowMask">
<!-- Everything under black will be invisible -->
<rect x="0" y="0" width="100%" height="100%" fill="black" />
<!-- Everything under white will be visible -->
<use href="#TransformedArrow" fill="white" />
</mask>
</defs>
<image width="159.113" height="159.113" mask="url(#ArrowMask)" href="https://i.stack.imgur.com/yDcGi.png" />
<use href="#TransformedArrow" fill="none" stroke="#4472C4" stroke-miterlimit="8" stroke-width="2.25" />
</svg>

How to automatically create the minimal size of the viewbox which fits for the complete content?

I have a simple or complex SVG graphic. For example a rotated rectangle.
Without calculating you cannot know the minimal size of the viewbox, where the graphic fits into completely.
<svg viewBox="0 0 30 30">
<rect x="20" y="0" width="100" height="20" transform="rotate(45)" fill="black" />
</svg>
The result is, that the graphic does not fit into the viewbox.
Is there any method, how to get an the minimal size of the viewbox, where the graphic is shown completely?
Ideally I do not want to declare a size/ratio of a viewbox. I just want that the minimal size is a result of the content of the SVG graphics.
Is there any disadvantage, when I do not declare the viewBox attribute at all?
Thanks for your help.
One way to do it is wrapping the transformed rectangle in a <g> element and then get the value of the bounding box for theG. Next you use the values of the bounding box (BB) to reset the viewBox of theSVG. I hope it helps.
// the bounding box for the wrapping g
let BB = theG.getBBox();
theSVG.setAttributeNS(null, "viewBox", `${BB.x} ${BB.y} ${BB.width} ${BB.height}`)
svg{border:1px solid}
<svg id="theSVG" viewBox="0 0 30 30" width="300">
<g id="theG">
<rect x="20" y="0" width="100" height="20" transform="rotate(45)" fill="black" />
</g>
</svg>

How to make SVG scale only on X axis?

I have the following SVG graphic that is currently scaling when the window is resized, but the aspect ratio is maintained. How could I get this to only scale on the X axis, and keep the Y at 80px?
<svg width="100%" viewBox="0 0 300 80">
<rect x="0" y="0" fill="yellow" height="80" width="100"/>
<rect x="100" y="0" fill="blue" height="80" width="100"/>
<rect x="200" y="0" fill="red" height="80" width="100"/>
</svg>
Thank you,
You have a couple of options. First, you could simply specify the height of the graphic, e.g. using CSS.
svg {
height: 80px;
width: 100%;
}
If that's not the effect you want, you can get more sophisticated with the preserveAspectRatio attribute. It's hard to say what value would work for you since it's not completely clear what you want (assuming the CSS approach above doesn't do it), but maybe something like:
<svg viewBox="0 0 300 80" preserveAspectRatio="none">
Check out the reference link for more details.

Make SVG element transparent (like a mask)

How can I use the polygon as a mask, which will make the area in the circle transparent?
I cannot manage it
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 100 100">
<circle cx="50" cy="50" r="50" />
<polygon points="20,30 25,20 80,40 80,60 25,80, 20,70 70,50"/>
</svg>
Masks are quite simple. They are described here: http://www.w3.org/TR/SVG11/masking.html#Masking
In your case, it is just a matter of adding a few lines.
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
<circle cx="50" cy="50" r="50" mask="url(#hole)"/>
<mask id="hole">
<rect x="0" y="0" width="100" height="100" fill="white"/>
<polygon points="20,30 25,20 80,40 80,60 25,80, 20,70 70,50" fill="black"/>
</mask>
</svg>
In the mask definition, we have to add a white rectangle the size of the circle to make the <circle> visible (white means opaque), and we make the <polygon> black (transparent) to create the hole.
Fiddle here
I'd say try using Inkscape to make an SVG file, draw your items on each other, select them both and use the Path->Exclusion menu to do so. Save your file and then you can look at the code of the .svg file to see what it did.

How I can get text with desired height and width?

I need to get text with desired height and width.
I tried to find something in documentation of svg but found only font-size and also I tried to use scale in such manner:
<text xmlns="http://www.w3.org/2000/svg" id="10996080909940" name="-1"
x="1782.9351809218" y="-751.796133712862" width="1" height="1" style="font:Arial;text-
anchor:start;stroke:#000000" transform="rotate(0) scale(2 2)"> SOME TEXT </text>
But I get too big size of text and in place not where I need.
If you mean you want the text to exactly fill an arbitrary width and height, then there isn't really an easy way to do it in SVG. You can't specifiy a width and height on the <text> element. At least not in the current SVG spec (1.1).
However there are several ways to achieve this effect with a bit of trickery.
One way is by using a transform, as you suggested:
<svg>
<text font-size="10px" font-family="Verdana" transform="translate(99,400) scale(3.5,13.7)">SQUASHED TEXT</text>
<rect x="100" y="300" width="300" height="100" fill="none" stroke="red" />
</svg>
A second way is by using an inner <svg> element and setting the viewBox to match the bounds of the text. You then set preserveAspectRatio="none".
<svg>
<svg x="100" y="100" width="300" height="100" viewBox="0.2 -7.3 86 7.3" preserveAspectRatio="none" overflow="visible">
<text font-size="10px" font-family="Verdana">SQUASHED TEXT</text>
</svg>
<rect x="100" y="100" width="300" height="100" fill="none" stroke="red" />
</svg>
This way is more verbose, but it has the advantage that once you have found the correct viewBox for a piece of text, you can make it fit any sized rectangle very easily. Just set the x,y,width and height of the inner <svg> to the size of the rectangle.
Demo here: http://jsfiddle.net/ZRgEF/3/

Resources