Search code examples
svgbordermaskmasking

Creating a border with rounded corners using SVG masks results in lighter corners


I'm trying to recreate the look of an iOS app icon using SVG, but with an outline in case the icon is white on a white background.

Example:

<svg width="76" height="76">
  <defs>
    <mask id="myMask">
      <rect fill="#fff" rx="15" ry="15" width="76" height="76"/>
    </mask>
  </defs>
  <rect id="border" mask="url(#myMask)" fill="#000" x="0" y="0" width="76" height="76" />
  <rect id="image" mask="url(#myMask)" fill="#fff" x="1" y="1" width="74" height="74" />
</svg>

(https://jsfiddle.net/d4ngtuqa/1/)

To do this, I'm rendering a filled rectangle behind an image rendered 2x2 pixels smaller (or another rect in my simplified example) and then applying an SVG mask to both layers.

However, when I do this, the rounded corners of the border render lighter than the rest of the border. Is this a rendering bug or something? Is there an alternate approach that could avoid this?


Solution

  • You don't need to mask both the image and the border. Just mask the image, then draw a 1px black border on top of it.

    <svg width="76" height="76">
      <defs>
        <mask id="myMask">
          <rect fill="#fff" rx="15" ry="15" width="76" height="76"/>
        </mask>
      </defs>
      <rect id="image" mask="url(#myMask)" fill="#fff" x="0" y="0" width="76" height="76" />
      <rect id="border" fill="none" stroke="#000" stroke-width="1" x="0.5" y="0.5" width="75" height="75" rx="15" ry="15" />
    </svg>

    Note that, in order to make the 1px border as neat and clean as possible, we use coordinates for the rect that align to a half-pixel (0.5 and 75.5), so that the line falls cleanly within a line of pixels.