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

Time:11-28

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

CodePudding user response:

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

CodePudding user response:

You could also simplify your svg by using a compound path.

The "cut out" area is defined by a sub path with an opposite path direction.

In your case:

// outer circle - clockwise
M 128 64
a 64 64 0 0 1 0 128
a 64 64 0 0 1 0 -128
z

// innercircle - counter clockwise
M 128 96
a 32 32 180 1 0 0 64
a 32 32 180 1 0 0 -64
z  

You could also do it the other way round: counterclockwise for outer shape, clockwise for the inner ones.

body{
background:#222
}
<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">
 <path
d="
M 128 64
a 64 64 0 0 1 0 128
a 64 64 0 0 1 0 -128
z
M 128 96
a 32 32 180 1 0 0 64
a 32 32 180 1 0 0 -64
z"
fill="#ff0000" />
</svg>

This way you don't need any additional fill-rule - even less capable applications like image viewers can render these svgs flawlessly.
This concept (clipping by alternating path directions) is actually around since the very first Bézier drawing technologies (e.g post-script).

You can create compound paths in every vector editor (e.g. Adobe illustrator, inkscape etc) by applying a path operation like subtract/extract.

Further reading: The Winding Order of the Fill Rule

  • Related