Search code examples
svgmaskmacos-mail-app

iOS/macOS Mail in dark mode inverts SVG mask colors, causing masking behavior to change


I have an SVG image that uses masking to cut out a hole in another shape. I've simplified the complex shapes involved down to this representative example:

<?xml version="1.0" encoding="UTF-8" standalone="no" ?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="256" height="256" viewBox="0 0 256 256" xml:space="preserve">
  <defs>
    <mask id="mask">
      <rect x="0" y="0" width="256" height="256" fill="#ffffff" />
      <circle cx="128" cy="128" r="32" fill="#000000" />
    </mask>
  </defs>
  <circle mask="url(#mask)" cx="128" cy="128" r="64" fill="#ff0000" />
</svg>

This works as expected, i.e., draws the following donut shape, in pretty much every context:

enter image description here

However, if I inline the SVG in an email and view it in macOS Mail (16.0, comes with Monterey 12.3.1) or iOS Mail while dark mode is enabled, I get this peculiar version:

enter image description here

What appears to be happening is that these two apps are trying to guess at a dark-mode interpretation of the SVG, and have erroneously changed the mask's #ffffff to a very dark grey (#232323, if I had to guess, which is the background color of the application itself in dark mode) and #000000 to #ffffff, thereby mostly but not entirely inverting the masking behavior.

This seems like a bug in Mail. Is there a way to tell Mail to not mess with the colors defined in the mask? I tried a couple workarounds, but they were insufficient:

  • using prefers-color-scheme to define the mask's colors so I can "pre-invert" it for dark mode, which works here but breaks the image in every other context
  • obscuring the mask coloration enough that it won't try to invert it (e.g. by using linear-gradient(#ffffff,#ffffff) instead of just #ffffff), which just breaks the images everywhere

Solution

  • Working off of ccprog's suggestion, I got this SVG to work as expected:

    <?xml version="1.0" encoding="UTF-8" standalone="no" ?>
    <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="256" height="256" viewBox="0 0 256 256" xml:space="preserve">
      <defs>
        <mask id="mask" mask-type="alpha">
          <path
            d="
              M 0 0
              L 256 0
              L 256 256
              L 0 256
              L 0 0
              M 128 96
              a 32 32 180 1 1 0 64
              a 32 32 180 1 1 0 -64
              z
            "
            fill-rule="evenodd"
          />
        </mask>
      </defs>
      <circle mask="url(#mask)" cx="128" cy="128" r="64" fill="#ff0000" />
    </svg>
    

    In addition to the switch to mask-type (so that we are masking using something that iOS/macOS Mail won't try to invert), the mask had to be redefined. In particular, it had to fill in everywhere that isn't in the circle, using fill-rule="evenodd" to get it to fill in the desired areas. I couldn't just switch the existing shapes to use opacity, because the circle would be want to be opacity 0 (in order to specify that the circle should be cut out) and the rect would come through behind it with opacity 1, making the mask a no-op.

    The new mask path, if rendered in black against a white background, looks like this:

    enter image description here