misterioss
misterioss

Reputation: 451

How can I merge two shapes in svg?

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: like this

Upvotes: 19

Views: 18572

Answers (6)

Stephan Hoyer
Stephan Hoyer

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

Pascal
Pascal

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

Bal&#225;zs Herczeg
Bal&#225;zs Herczeg

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

devuxer
devuxer

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:

  1. It defines a white rectangle called "canvas" that is the same size as the SVG.
  2. It defines the two shapes that are to be combined ("shape1" and "shape2").
  3. It defines a mask for each shape that combines the canvas (which has a fill of white) with the shape (which has a fill of black by default). Note that when you apply a mask to a shape, the part of the shape that corresponds to the white area of the mask is shown, while the part that corresponds with black part is hidden.
  4. It draws each shape with the the mask of the other shape applied.

Upvotes: 21

Michael Mullany
Michael Mullany

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

Paul LeBeau
Paul LeBeau

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

Related Questions