Get Bounding Box of clipped element - svg

I have an SVG which has such some elements which look like so:
<svg>
<defs>
<symbol id="image">
<image
xlink:href="../png/SpongeBob.jpg"
y="35.02803"
x="-81.995514"
height="203.2"
width="361.24442" />
</symbol>
<clipPath id="clipPath4981" clipPathUnits="userSpaceOnUse">
<rect
y="35.02803"
x="-81.995514"
height="67.732658"
width="120.41361" />
</clipPath>
</defs>
<use
clip-path="url(#clipPath4981)"
y="0"
x="0"
xlink:href="#image" />
</svg>
As the docs say for getBBox():
Returns the tight bounding box in current user space (i.e., after application of the ‘transform’ attribute, if any) on the geometry of all contained graphics elements, exclusive of stroking, clipping […]
But I need to get the bounding box the the element, such that the clipping is taken in account.
Right now, the bounding box I get looks like that:
How can I compute such a Bounding Box?

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>

SVG viewBox: Exact order of translation and scaling

I am struggling to understand exactly how min-x and min-y on viewBox works, from a technical standpoint (without metaphors).
Two helpful resources I have spent quite a lot of time on:
SVG 1.1 (official specification) - 7.7 The ‘viewBox’ attribute
Understanding SVG Coordinate Systems and Transformations (Part 1) - by Sara Soueidan
According to the SVG 1.1 specification:
The value of the ‘viewBox’ attribute is a list of four numbers
, , and , separated by whitespace and/or
a comma, which specify a rectangle in user space which should be
mapped to the bounds of the viewport established by the given element,
taking into account attribute ‘preserveAspectRatio’.
And:
The effect of the ‘viewBox’ attribute is that the user agent
automatically supplies the appropriate transformation matrix to map
the specified rectangle in user space to the bounds of a designated
region (often, the viewport).
And:
(Note: in some cases the user agent will need to supply a translate
transformation in addition to a scale transformation. For example, on
an outermost svg element, a translate transformation will be needed if
the ‘viewBox’ attributes specifies values other than zero for
or .)
So, my expectation was that defining a viewBox is the same as:
First scaling the viewbox, so it fills the viewport (assuming same aspect ratio on viewport and viewBox)
Then translating the viewBox, so it is placed in the viewport according to min-x and min-y viewBox attributes.
If we look at Sara's two examples, starting here, that is not what seems to be happening.
In her first example (<svg width="800" height="600" viewbox="100 100 200 150">...</svg>), it looks like:
viewBox is placed according to min-x / min-y in viewport
viewBox is scaled to same size as viewport
viewBox origin is translated (moved) to coincide with viewport origin
In her second example however (<svg width="800" height="600" viewbox="-100 -100 400 300">...</svg>), it looks like a completely different order:
viewBox is scaled to same size as viewport
viewBox origin is translated (moved) somehow in the opposite direction of what viewBox min-x min-y indicates. It does not coincide with viewport origin - This is different from the first example
Thus, I recognize that I do not fully understand it, because technically it should work the same way in both cases.
Finally, in Sara's examples, I do not understand why the blue coordinate system (user coordinate system) does not itself move, to (100, 100) or (-100, -100) in viewport coordinate system. I thought viewBox was supposed to translate and scale the user coordinate system?
EDIT:
According to this SO answer, min-x and min-y does indeed follow my first set of steps. The viewBox origin is placed in the viewport according to min-x and min-y, and then translated so its origin is on top of viewport origin. It is then (before or after) scaled to fill viewport.
If that is correct, I have a hard time understanding why the blue user coordinate system in Sara's examples do not always end up with its origin on top of viewport origin. After all, viewBox should modify the user coordinate system.
The offset of the origin of the coordinates viewBox on the x-axis (min-x=70px)
<svg width="400" height="400" viewBox="70px, 0, 400px, 400px">
In the figure, the origin of user coordinates shifts to the right by 70px, thereby shifting the entire rectangular viewing areaviewBox (400 x 400px)to the right along the horizontal axis.
When this happens, the image of the SVG document fragment that is under the viewBox is captured and then the viewBox viewing area with the captured fragment is back aligned with the fixed user viewport area with the origin (0,0) in the upper left corner.
The coordinates of the figures are recalculated with the last shift of 70px to the left. Formally it turns out that in the fixed viewing area of the viewport when applying the viewBox the fragment of the SVG document has shifted to the left.
Live Demo
The offset of the origin of the viewBox along two axes
min-x=70px, min-y="70px"
<svg width="400" height="400" viewBox="70px, 70px, 400px, 400px">
For clarity, add another red rectangle at the bottom of the picture - 6
After transferring the origin to the viewBox, a rectangular 400 × 400 px SVG document fragment with a width and height count from the origin (70.70) gets into the viewBox.
Image capture occurs. Next, the origin of the viewBox (70,70) is combined with the origin of the viewport (0,0). The coordinates of the figures are recalculated.
Therefore, red rectangles 5 and 6 become fully visible. Everything that does not fall into this area is cut off. For example, part of the areas of colored circles 1,2 and 4.
Live Demo
Zoom using viewBox
The scale of the SVG document fragment depends on the aspect ratio: viewport andviewBox
If viewport /viewBox = 1, then the scale will be 1
If viewport /viewBox different from one, the scale will change in the direction of increase or decrease.
How does the increase in scale explains the figure below
One pixel viewBox stretches to two pixelsviewport
Live Demo
Zoom out svg image 1: 2
<svg width="400" height="400" version="1.1"
viewBox="0 0 800 800">
viewport / viewBox = 1/2
The viewBox captures a rectangular fragment 800 x 800 px, that is, the entire scope of the SVG viewport 400 x 400 px and an additional 400px each on the right and bottom of the viewport.
That is two pixels of the viewBox are compressed into one pixel of the viewport. Therefore the SVG image is reduced by half.
Live Demo
In the picture, a gray rectangle is an infinite SVG canvas.
The green rectangle is the viewport that the user sees on its display.
The yellow rectangle is the virtual viewBox area through which the user looks at the viewport.
viewBox can move along the coordinate axes of the infinitesvg canvas as in the positive direction x-min> 0; y-min> 0 and in the negative direction-x-min; -y-min
Image processing svg
Next comes the capture of a fragment of the SVG canvas, located under
the viewBox.
In the next step, the coordinate system of the viewBox is aligned
with the origin of the coordinate system of the viewport. And the
fragment captured by the viewBox image is passed back to the
viewport.
There is a process of negotiation and options are possible here:
If min-x = 0 andmin-y = 0, the width and height of the viewports are equal, respectively, to the width and height ofviewBoxs, then the fragment image does not move or scale.
If the viewBox is shifted to the right - min-x> 0, the image is shifted to the left. It is clear that by capturing an image to the right of the viewport and then combining it with the origin, we thereby shift the image to the left.
If the viewBox is shifted below the viewports - min-y> 0, the image will go up.
Based on this, there are thoughts that you can implement horizontal and vertical parallax without using CSS,JavaScript. To do this, simply move the viewBox along the SVG canvas, as shown in the figure below. Click the Start button.
<svg version="1.1" xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
width="600" height="360" viewBox="0 0 600 360" >
<title> Explanation horizontal of parallax viewBox </title>
<desc> animate the horizontal parallax by modifying a coordinate of the viewBox </desc>
<defs>
<g id="canvas-svg" stroke-width="2px">
<g id="canvas-frame1">
<rect id="v-port1" x="25" y="200" width="110" height="110" stroke="skyblue" fill="yellowgreen" />
<text id="t-port1" x="75" y="255" style="font-size: 16pt;">1 </text>
<text x="26" y="303" > 0 </text>
</g>
<g id="canvas-frame2">
<rect id="v-port2" x="135" y="200" width="110" height="110" stroke="skyblue" fill="dodgerblue" />
<text id="t-port2" x="185" y="255" style="font-size: 16pt;">2 </text>
<text x="136" y="303" > 1168 </text>
</g>
<g id="canvas-frame3">
<rect id="v-port3" x="245" y="200" width="110" height="110" stroke="skyblue" fill="crimson" />
<text id="t-port3" x="295" y="255" style="font-size: 16pt;">3 </text>
<text x="246" y="303" > 2336 </text>
</g>
<g id="canvas-frame4">
<rect id="v-port4" x="355" y="200" width="110" height="110" stroke="skyblue" fill="orange" />
<text id="t-port4" x="405" y="255" style="font-size: 16pt;">4 </text>
<text x="356" y="303" > 3504 </text>
</g>
<g id="canvas-frame5">
<rect id="v-port5" x="465" y="200" width="110" height="110" stroke="skyblue" stroke-width="1px" fill="yellow" />
<text id="t-port5" x="515" y="255" style="font-size: 16pt;">5 </text>
<text x="466" y="303" > 4672 </text>
</g>
</g>
</defs>
<g id="first-rect">
<rect x="25" y="25" width="110" height="110" stroke="skyblue" stroke-width="1px" fill="yellowgreen" />
<text x="75" y="85" style="font-size: 16pt;">1 </text>
<text x="26" y="135" > 0 </text>
</g>
<desc>The SVG canvas is infinite in size. In our example, user a viewport of SVG is in the leftmost position.</desc>
<use xlink:href ="#canvas-svg" x="0" y="0"> </use>
<desc> viewBox is moved along canvas SVG</desc>
<g id="viewBox1">
<rect id="v-box" x="25" y="200" width="110" height="110" stroke="skyblue" stroke-width="5px" fill="none" />
<text id="t-port1" x="45" y="225" style="font-size: 16pt; fill:blue;">viewBox </text>
<animateTransform attributeName="transform" type="translate" begin="startButton.click+0.5s" end="stopButton.click" dur="20s" from="0 0" to="440 0" repeatCount="indefinite" restart="whenNotActive" fill="freeze"/>
</g>
<desc> The image moves to the left viewport</desc>
<use xlink:href ="#canvas-svg" x="0" y="0">
<animateTransform attributeName="transform" type="translate" begin="startButton.click+0.5s" end="stopButton.click" dur="20s" from="0 -170" to="-440 -170" repeatCount="indefinite" restart="whenNotActive" fill="freeze" />
</use>
<desc> Grey background image of the canvas SVG</desc>
<g fill="#E5E5E5" stroke="#E5E5E5">
<rect x="135" y="0" width="465" height="195" />
<rect x="0" y="0" width="25" height="195" />
<rect x="0" y="0" width="135" height="30" />
<rect x="25" y="135" width="135" height="60" />
<rect x="0" y="315" width="600" height="85" />
<rect x="0" y="195" width="25" height="120" />
<rect x="575" y="195" width="25" height="120" />
</g>
<g stroke-width="1px" stroke-dasharray = "5 5">
<line x1="25" y1="140" x2="25" y2="195" stroke="blue" />
<line x1="135" y1="140" x2="135" y2="195" stroke="blue" stroke-width="1px" />
</g>
<g style="font-size: 16pt; fill:blue;">
<text x="45" y="170" > viewport </text>
<text x="15" y="20" style="font-size: 14pt;"> display the user's </text>
<text x="230" y="90" style="font-size: 40pt; fill:#1E90FF"> canvas SVG </text>
</g>
<g id="startButton">
<rect x="520" y="325" rx="8" ry="8" width="60" height="20" fill="#58AE2A" />
<text x="550" y="340" font-size="16" font-weight="bold" font-family="Arial" text-anchor="middle"
fill="white" >Start</text>
</g>
<g id="stopButton">
<rect x="450" y="325" rx="8" ry="8" width="60" height="20" fill="#1E90FF" />
<text x="480" y="340" font-size="16" font-weight="bold" font-family="Arial" text-anchor="middle"
fill="white" >Stop</text>
</g>
</svg>
I always mix up viewBox and viewport. So I'll try to avoid it where possible. And I don't fully understand if you want to setup the transformation matrix for the browser or for SVG. So I'll try to avoid it as well.
The viewBox attribute provides information to your browser about the size and the coordinate origin of your SVG graphics. It defines the window into the SVG. Only parts within the window will be visible.
So let's look at an example:
<svg width="800" height="600" viewbox="100 100 200 150">
This tells the browser that it should draw an SVG graphics that will have the dimension 800px by 600px – in the browser's coordinate system. So within the browser DOM, the SVG component will have that size.
The viewbox attribute then tells the browser that the relevant/visible part of the SVG graphics is 200pt by 150pt in size (in the SVG coordinate system). So the browser knows that it will need to apply a scaling of 400% to convert SVG coordinates to browser coordinates.
Furthermore, the viewbox attribute tells the browser that the point (100, 100) in the SVG coordinate system will be the top left corner of the visible SVG graphics window. So the browser will translate it accordingly.
Everything with smaller x and y values in the SVG coordinate system will be clipped, i.e. not visible, as it's outside the windows and outside the space the browser has created for the SVG. Similarly, everything to the right of the SVG coordinate 300 (100 + 200) and below the coordinate 250 (100 + 150) will be outside the window and not visible.

SVG border appears trimmed

I think I am missing an apparent offset issue in my very first svg here, The top and left borders are tirmmed (pointed by red arrow), Also if something like nested <g> or <symbol> is possible pleas let me know, Thanks. (screenshot in FF27).
The simplified code and a fiddle
<svg>
<defs>
<symbol id="ringCenters" style="fill:black;">
<circle cx="50" cy="50" r="2" />
/*...more circles*/
</symbol>
<symbol id="ring1" class="rings">
<path d="M99.9746,51.5943
A50 50 0 1 0
62.5254,98.4057"
stroke="green" />
<path d="M99.9746,51.5943
A50 50 0 0 1
62.5254,98.4057"
stroke="red" />
</symbol>
/*...more rings*/
</defs>
<use xlink:href="#ringCenters" x="10" y="10" />
/*...more rings*/
</svg>
.rings{fill:none;}
svg {
width:600px;
height:300px;
}
The stroke around a shape is always centered on the exact geometric border of the shape, and so extends beyond the shape by half the stroke width.
If for any reason you don't want to use overflow:visible, another option is therefore to just adjust the positions of your shape so that you have a bit of padding on the edges, equal to half the stroke width. (Of course, that assumes that the stroke-width will be the same every time you use the symbol.)
Note that you have to adjust the position of the <path> within the <symbol>, not just the position of the <symbol> within your SVG. That's because each reference to a symbol element is drawn as if it was a nested <svg> element within it's own viewport, with a fixed height, width, and "viewBox" coordinate system. You're not setting those attributes on the symbol, so they end up as the defaults for nested SVGs: width and height equal to 100% of the parent, and a default coordinate system with (0,0) at the top left corner of the viewport.
That "nested SVG" then gets positioned so that it's top left corner is at the (x,y) position specified in the <use> element, but as far as the drawing content within the symbol is concerned, the rectangular edges of that viewport are the edges of the drawing surface. Unless, of course, you specifically allow overflow, as #helderdarocha suggested.
(By the way, the symbols-are-drawn-as-nested-SVGs idea is probably why you needed to specify svg{ overflow:visible;} for Firefox, although it really should work by setting the property on the symbols directly; I'd call that a Firefox bug.)
On your other question: <g> elements can be nested multiple times without problem. For <symbol> elements, it's not so clear. The specs say that symbols are much like <g> elements except (a) they have viewports and (b) the symbol is not drawn directly, it is only available for reference by a <use> element.
Now, that suggests that <symbol>s can be nested like <g> elements. And on Chrome it works. On Firefox, not so much. I assume what is happening is that when Firefox copies the internal content of the outer <symbol>, it treats the internal <symbol> elements as if they were just symbol definitions, not as if they were renderings of those symbols.
I.e. code like this
<svg>
<defs>
<symbol id="complicated">
<symbol id="firstPart">
<path d="...."/>
</symbol>
<symbol id="secondPart">
<path d="...."/>
</symbol>
</symbol>
</defs>
<use xlink:href="#complicated"/>
</svg>
Gets rendered as if it was
<svg>
<defs>
<symbol id="complicated">
<symbol id="firstPart">
<path d="...."/>
</symbol>
<symbol id="secondPart">
<path d="...."/>
</symbol>
</symbol>
</defs>
<g> <!-- The <use> element is drawn as if it was a <g> element -->
<svg> <!-- The <symbol> element is drawn as if it was a nested <svg> -->
<symbol id="firstPart"> <!-- the internal symbol is copied as-is -->
<path d="...."/>
</symbol>
<symbol id="secondPart"> <!-- but symbols aren't drawn directly! -->
<path d="...."/>
</symbol>
<svg>
</g>
</svg>
...and that shouldn't really be rendered as anything at all. This, I wouldn't call a Firefox "bug", just a literal interpretation of the specs, which don't give any special treatment to nested symbol elements.
There is a way to get symbols to nest, however, and that's to use <use> elements within the symbol definition:
<svg>
<defs>
<symbol id="firstPart">
<path d="...."/>
</symbol>
<symbol id="secondPart">
<path d="...."/>
</symbol>
<symbol id="complicated">
<use xlink:href="#firstPart"/>
<use xlink:href="#secondPart"/>
</symbol>
</defs>
<use xlink:href="#complicated"/>
</svg>
Here's your fiddle updated with that structure: http://jsfiddle.net/vGNMu/6/
Half of your stroke is outside the viewBox. You can avoid clipping by using:
svg, symbol {
overflow: visible;
}
Or adding an overflow="visible" attribute to each symbol.
Your updated JSFiddle

SVG Mask re-use

I'm creating some SVG with D3.JS. I have a group of SVG elements that I repeat for each of the items in my data set. One of the elements that is repeated is a square avatar/profile image. I want to mask each of these images to make them rounded.
I thought that defining a mask in defs and then pointing each of the images at it would work, but it does not because their coordinates are different. Does this mean that I need to repeat the mask in each of the groups, like this:
<defs>
<mask id="#mask" ...>
<!-- ... --->
</mask>
</defs>
<g>
<rect ... />
<use id="uniqueMask1" xlink:href="#mask" transform="translate(10, 10)" />
<image x="10" y="10" ... mask="url(#uniqueMask1)" />
</g>
<!-- repeat multiple times -->
What I had hoped was to define the mask once, and then reference it from the <image mask="..." /> attribute. This doesn't seem possible though. Is there another approach that could work?
Define your mask using maskContentUnits="objectBoundingBox". In this mode, the coords you use are all relative to the object's bounding box. So for example, a circular mask covering the object would be:
<mask maskContentUnits="objectBoundingBox">
<circle cx="0.5" cy="0.5" r="0.5" fill="white">
</mask>

Resources