Reputation: 451
I have two shapes: circle and rectangle. Want to convert them into one figure. Are there any ways to do that in SVG code?
<svg width="400" height="400">
<defs>
<g id="shape" fill="none" stroke="red">
<rect x="40" y="50" width="40" height="70" />
<circle cx="50" cy="50" r="50" />
</g>
</defs>
<use xlink:href="#shape" x="50" y="50" />
<use xlink:href="#shape" x="200" y="50" />
</svg>
Like this:
Upvotes: 19
Views: 18572
Reputation: 4928
For everyone who wants both fill and border, here is a combination of devuxers and pascals posts
<svg width="500" height="300" xmlns="http://www.w3.org/2000/svg">
<defs>
<rect id="canvas" width="100%" height="100%" fill="white" />
<rect id="rect" x="50" y="60" width="40" height="70" />
<circle id="circle" cx="50" cy="50" r="40" />
<clipPath id="shape">
<use href="#rect" />
<use href="#circle" />
</clipPath>
<mask id="maskRect">
<use href="#canvas" />
<use href="#rect" />
</mask>
<mask id="maskCircle">
<use href="#canvas" />
<use href="#circle" />
</mask>
</defs>
<rect width="100%" height="100%" fill="red" clip-path="url(#shape)" />
<use href="#rect" stroke="green" stroke-width="2" fill="none" mask="url(#maskCircle)" />
<use href="#circle" stroke="green" stroke-width="2" fill="none" mask="url(#maskRect)" />
</svg>
Upvotes: 4
Reputation: 2299
Note: Be aware that this solution is only giving you the outer part of the contour.
A little variation of @devuxer's answer. A shadow can be created by a mask that can then be applied to a black shape. In https://stackoverflow.com/a/50884092/275195 two masks are created with set theory (A-B)+(B-A)
. However in the rendering engine a pixel raster is used for masks and antialiasing is applied. This leads to half-transparent pixels in the mask at the boundary of A
and B
. This can be avoided by applying the shaped to one mask like this (basically outline of A+B
minus A+B
):
<svg>
<!-- Masks seem to be pre-filled with black -->
<mask id="union">
<!-- Whatever is white in the mask will be visible of the shape the mask is applied to. -->
<rect id="A" x="20" y="20" width="50" height="100" stroke="white" fill="none"></rect>
<!-- Putting a second (, third, ...) shape into the mask will draw the shape over any existing shapes. -->
<circle id="B" cx="80" cy="60" r="30" stroke="white" fill="none"></circle>
<!-- Now we delete the inner part of the shapes by drawing black over them. -->
<rect id="Ainner" x="20" y="20" width="50" height="100" fill="black"></rect>
<circle id="Binner" cx="80" cy="60" r="30" fill="black"></circle>
</mask>
<!-- Applied to a full-screen red rectangle it cuts out the strokes from the mask. -->
<rect id="A+B" x="0" y="0" width="100%" height="100%" fill="red" mask="url(#union)"></rect>
</svg>
Upvotes: 4
Reputation: 129
With the feMorphology
filter you can grow and shrink objects (MDN). Combining them, you can create a simply reusable outline: make an increased copy of the original, paint it, and place it under the shrunk original.
<svg xmlns="http://www.w3.org/2000/svg" width="400" height="400">
<defs>
<filter id="outline">
<!-- Make a copy of the original,
grow it by half the stroke width,
call it outer -->
<feMorphology
operator="dilate"
radius="1"
result="outer"
/>
<!-- Make another copy of the original,
shrink it by half the stroke width,
call it inner -->
<feMorphology
in="SourceGraphic"
operator="erode"
radius="1"
result="inner"
/>
<!-- Create a color layer... -->
<feFlood
flood-color="black"
flood-opacity="1"
result="outlineColor"
/>
<!-- ...and crop it by outer -->
<feComposite
operator="in"
in="outlineColor"
in2="outer"
result="coloredOuter"
/>
<!-- Place the shrunk original over it -->
<feComposite
operator="over"
in="inner"
in2="coloredOuter"
/>
</filter>
</defs>
<g filter="url(#outline)">
<rect x="50" y="60" width="40" height="70" fill="red" />
<circle cx="60" cy="60" r="50" fill="red" />
</g>
</svg>
If you want to have it transparent, replace the operator in the last feComposite
with xor
:
<!-- Knock off the shrunk one -->
<feComposite
operator="xor"
in="inner"
in2="coloredOuter"
/>
The only caveat: fatter strokes - ie larger dilate and erode radii - on curves may look distorted, with 1px + 1px it is still fine.
Upvotes: 1
Reputation: 42354
For anyone looking for the answer to the actual question of how to combine two outlined shapes into a single outlined shape (rather than putting a drop shadow on the combined shape), here is a possible solution:
<svg width="400" height="400">
<defs>
<rect id="canvas" width="100%" height="100%" fill="white" />
<rect id="shape1" x="40" y="50" width="40" height="70" />
<circle id="shape2" cx="50" cy="50" r="50" />
<mask id="shape1-cutout">
<use href="#canvas" />
<use href="#shape1" />
</mask>
<mask id="shape2-cutout">
<use href="#canvas" />
<use href="#shape2" />
</mask>
</defs>
<use href="#shape1" stroke="red" fill="none" mask="url(#shape2-cutout)" />
<use href="#shape2" stroke="red" fill="none" mask="url(#shape1-cutout)" />
</svg>
This essentially draws the circle with the rectangle shape cut out of it and draws the rectangle with the circle cut out of it. When you place these "punched out" shapes one on top of the other, you get what appears to be a single outlined shape.
Here's what the SVG actually does:
Upvotes: 21
Reputation: 31715
What's wrong with just a dropshadow on a group around the shapes?
<svg width="400" height="400">
<defs>
<filter id="shadow">
<feGaussianBlur in="SourceAlpha" stdDeviation="3"/>
<feOffset dx="3" dy="3"/>
<feMerge>
<feMergeNode/>
<feMergeNode in="SourceGraphic"/>
</feMerge>
</filter>
</defs>
<g filter="url(#shadow)">
<rect x="40" y="50" width="40" height="70" fill="red"/>
<circle cx="50" cy="50" r="50" fill="red"/>
</g>
</svg>
Upvotes: 9
Reputation: 101800
You can make a <mask>
or a <clipPath>
from the two shapes and then use that to mask a third shape. You can then apply your drop shadow to that.
<svg width="400" height="400">
<defs>
<clipPath id="shape">
<rect x="40" y="50" width="40" height="70" />
<circle cx="50" cy="50" r="50" />
</clipPath>
<filter id="shadow">
<feGaussianBlur in="SourceAlpha" stdDeviation="3"/>
<feOffset dx="3" dy="3"/>
<feMerge>
<feMergeNode/>
<feMergeNode in="SourceGraphic"/>
</feMerge>
</filter>
</defs>
<g filter="url(#shadow)">
<rect width="100%" height="100%" fill="red"
clip-path="url(#shape)"/>
</g>
</svg>
Note: if you are wondering why we are applying the drop shadow to a parent <g>
here, it is because if we applied it directly to the <rect>
, the drop shadow would be subject to the clip also.
Upvotes: 11