SVG Mask re-use - svg

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>

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>

Is there a way for SVG strokes to "skip" intersecting each other, the same sort of way that CSS has `text-decoration-skip-ink`?

I could probably manually fake it using a solid-edged drop shadow filter around the strokes, set to the background color, but that's neither resilient nor ideal.
Visually, instead of this:
I want to have this (if the circle is on top):
A posible solution would be creating a mask with a white rectangle and a black stroked <use> element that is using the circle.
Please note that the white rectangle is covering all the svg element and the stroke-width of the <use> element is wider than the stroke of the circle.
This way you create a hole in the rect that is letting you to see whatever you have in the background.
<svg fill="none" stroke="black" stroke-width="3">
<mask id="m">
<rect width="100%" height="100%" fill="white" />
<use xlink:href="#c" stroke-width="10" />
</mask>
<rect x="10" y="5" width="70" height="70" mask="url(#m)" />
<circle id="c" cx="80" cy="75" r="40" />
</svg>

SVG mutltiple transform - rotate then translate horizontally

Using an SVG I am trying to rotate it by 30 degrees and then translate that horizontally 100. The issue is that when I rotate the coordinate system, it is rotated and so when I try to translate I will be translating the object along the rotated x-axis which is now at 30 degrees to the horizontal.
Is there any way to translate horizontally after rotation?
<g transform="rotate(30 50 50) translate(100 0)">
<rect x="0" y="0" fill="blue" width="100" height="100" />
</g>
I want to create control buttons for rotate and translate up, down, left, right. These will create a string which will use data binding to the transform property of the svg and so transform the object sequentially. But the translations need to be relative to the viewport not how the object has been rotated.
I am using the vue.js framework for the data binding.
I also do not want to use css.
Thanks,
If you want the rotate to happen first, you have to put that last in your transform attribute.
<g transform="translate(100 0) rotate(30 50 50)">
<rect x="0" y="0" fill="blue" width="100" height="100" />
</g>
Why? Because the above snippet is equivalent to
<g transform="translate(100 0)">
<g transform="rotate(30 50 50)">
<rect x="0" y="0" fill="blue" width="100" height="100" />
</g>
</g>
You can think of transforms as happening from the inside out. The rectangle is affected by the inner rotate transform before the outer translate transform.
Or to think of it another way, the outer transform creates a new coordinate system that is shifted 100 to the right. Within that there is a separate coordinate system that is rotated 30 degrees. Into that the rectangle is drawn.

Get Bounding Box of clipped element

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?

Coordinate Origin for SVG Symbol

I would like to move a particular composite shape to a <Symbol> definition, and then re-use it. This helps make the SVG code neater, and provides some global control over the actual structure of that shape. The shape is symmetrical about a given mid-point; however, the x/y coordinates of subsequent <Use> statements need to reference the top-left corner rather than its natural original, and this means that all usage of the shape must be aware of its total size. Is there a way to position a Symbol by some origin other than its top-left corner?
Contrived example, purely to explain this better. The following concentric-circle Symbol has a natural origin. However, the subsequent Use statement has to offset its x/y coordinates by half the Symbol size in order to position it correctly (at 20,20 in this example). Ideally, the usage of the symbol should not have to know this information.
<defs>
<symbol id="ex">
<circle fill="green" cx="8" cy="8" r="8"/>
<circle fill="white" cx="8" cy="8" r="6"/>
<circle fill="green" cx="8" cy="8" r="4"/>
</symbol>
</defs>
<use xlink:href="#ex" x="12" y="12">
The downside to a symbol is that it cuts of rendering at the border of a viewport. (which is also an upside, since you can define a viewBox.) But you can avoid using it at all. Everything in a <defs> element is not rendered directly, so you can exchange the <symbol> for a <g> and center everything on the origin:
<defs>
<g id="ex">
<circle fill="green" cx="0" cy="0" r="8"/>
<circle fill="white" cx="0" cy="0" r="6"/>
<circle fill="green" cx="0" cy="0" r="4"/>
</g>
</defs>
<use xlink:href="#ex" x="12" y="12">
http://jsfiddle.net/nuzwn07n/
If I read your question correctly, you want to be able to say
<use xlink:href="#ex" x="20" y="20">
..and then the circle symbol is positioned with its center at [20,20]. The solution I propose requires you to know the size of the symbol, but only once (in the <symbol> declaration), and not on every <use> element.
1: In the <symbol>, put everything in a group which you translate so the center of the graphics lies on the top-left corner.
<symbol id="ex">
<g transform="translate(-8,-8)">
<circle ...
</g>
</symbol>
2: If you now <use> that symbol, you'll only see the quarter circle that's still within the symbol's "viewport". To display the whole circle, simply apply overflow="visible" to the <symbol>.
<symbol id="ex" overflow="visible">
<g transform="translate(-8,-8)">
<circle ...
</g>
</symbol>
http://jsfiddle.net/0ghucsrp/

Resources