PSA on mischievous opacity

Because alpha=0.5 is not always half the color

Paul Danyliuk
Prototypr

--

Yesterday I was doing some mocking-up for my upcoming Android app (that’s despite the fact that I had rage-quit indie development 🙂). I drew some black text with 87% opacity, as prescribed by the Material spec, and added an 87% black line below. That’s when I noticed something was off:

Clearly, the text looked lighter than the underline. At first, I thought this was a bug. Affinity Designer for Windows, the tool I’m using for everything design, is a relatively new app, and it still has issues here and there. I started googling for its text rendering problems, but found this passage instead:

— from Affinity Designer help

And that’s how I learned about the thing called blend gamma.

Okay, I assume you know what opacity (alpha) is. To everyone working with digital imagery, it is obvious that 100% opacity (alpha=1) means the object completely overdraws its background, and 0% opacity (alpha=0) means it’s completely invisible. Now, what about all those values in between? Usually one would assume that during alpha blending, values of each pixel’s resulting R, G, and B components are calculated linearly between the corresponding pixel values of the object and its background, in proportion dictated by the object’s alpha. For example, drawing semi-transparent rgba(0, 0, 0, 50%) black over solid white background would result in perfectly gray rgb(50%, 50%, 50%) pixels.

Usually, that’s the case. But not always.

If you never designed for print, you probably never cared about color profiles, and never really worked with any color space other than sRGB (standard red-green-blue) or settings other than 8 bit per channel. You know by heart that each pixel’s color on screen is encoded by RGB values in range 0–255, and the higher the value for each channel is, the brighter the corresponding subpixel shines. And due to sRGB being the default color profile pretty much everywhere — in the browser, on your phone, in Photoshop etc. — you’ve got used to the fact that images just look the same in every app and on each device, and you expect nothing less from how color universally behaves.

The catch is that in sRGB color space those 0–255 ranges are not linear. Or, actually, they seem linear to us humans, while in terms of actual quantity of light (i.e., number of photons) they are more on a logarithmic side. The relation between actual light (intensity) and RGB-encoded value in the 0–255 range is a power law function with its exponent parameter being referred to as gamma. This transformation (called gamma encoding) is applied so that 8 bits of each channel are used more optimally to encode human-discernible shades.

I know this is confusing already. There’s a video that explains this all nicely:

With certain simplifications, the gamma value in sRGB color space is 2.2.

How is alpha blending affected?

Quick answer: it works just as you’d expect, as long as blend gamma is also 2.2.

Long answer. By default, blending a semi-transparent object (source) onto a solid background (destination) would work intuitively, interpolating RGB values linearly between the source and destination pixels in relation to alpha:

Blending blacks (linear gradient of 0% to 100% opacity) onto white background: resulting ‘grayness’ is also linear with regard to RGB values. Blend gamma = 2.2.

However, if blend gamma setting is different from 2.2, your tool will recalculate the output as if the absolute intensity was gamma-encoded with a different exponent value. For example, when blend gamma is 1.0, no exponentiation takes place, and instead of being linear encoding-wise, the blend result is linear intensity-wise:

Same gradient as above, but with blend gamma = 1.0.

Same thing, animated (notice how “unblended” area stays the same):

But why would I use a different blend gamma?

The video above perfectly explains why “linear” RGB blending with default gamma may produce subjectively bad results (seriously, go watch it if you haven’t already!) The problem is best seen when you blend two opposite colors. Notice the darker outline where cyan fades to red:

Intensity drop when blending with default gamma (left). Using gamma = 1.0 (right) results in smoother blend.

I don’t know about you, but this blew my mind. My whole life was a lie!

And I don’t even want to dig into how blending is affected when I’m working in LAB, CMYK, 16 or 32-bit RGB, or with different color profiles — that would be the end of my sanity.

Back to the 87% black text problem

I think you guessed by now what was the culprit. While Affinity Designer created all other layers with blend gamma = 2.2, for text layers this setting was overridden with 1.45. That’s why translucent text appeared lighter than a shape, a pixel layer, or a smart object of the same color and opacity. If you’re thinking it’s the problem with Affinity Designer, it’s not — actually Photoshop came up with this first:

Photoshop > color settings > more options

Why 1.45? As an answer on Adobe forums says, 1.45 was empirically found to be optimal for text antialiasing. Gamma 2.2 would make light on dark too thin and dark on light too fat, and 1.0 would make it vice versa.

Text antialiasing is still more of an art than a science.
— Chris Cox, Adobe

I don’t know about antialiasing, but gamma = 1.0 sure looks best for contrasting text on color. For grayscale though, it fades to light too fast.

Help, I’m getting paranoid!

My first compulsion after learning all this was to go and check on opacity everywhere I could. What kind of 87% did I measure against? Was the Material spec affected? Android text rendering affected? Web rendering affected?

Left: image from the Material spec; right: testing in Affinity Designer

I tested blend gamma in a few apps and places. Here’s the verdict:

  • Material spec: 2.2
  • Material spec sticker sheet: 2.2
  • Translucent text in Android (android:alpha and hex): 2.2
  • Translucent text in web (Chrome): 2.2 with minor deviations
  • Adobe Photoshop: text is set to 1.45, other layers have gamma = 2.2.
    It is possible to setup blend gamma for text layers and other layers in the settings, but those settings are applied globally on your machine, not to a document (meaning if you send the file to someone, they’ll have to change the settings as well.)
  • Adobe Illustrator: 2.2
    No blend gamma settings found.
  • Affinity Designer: text is set to 1.45, other layers have gamma = 2.2.
    Defaults can be changed in the settings. Also, you can change blend gamma for individual objects/layers, which is super handy.
  • Figma (in Chrome): 2.2
  • Paint.NET: 2.2
  • Other popular editors (e.g. Sketch), iOS, other browsers: no idea, couldn’t check.
    Assuming Affinity Photo having the same behavior as Designer.
  • Some people say in higher fidelity modes blend gamma doesn’t have any effect. I only checked in Affinity Designer: blend gamma is always 1.0 in RGB/32 (HDR) color format, but still works as usual in RGB/16.

So, as you can see, almost all apps and devices perform alpha blending in a familiar, linear RGB value way. Exceptions are Photoshop and Affinity apps, which make translucent text lighter unless you fiddle with settings. In any case, I advise you to check your alpha blending results with the good old trusty color picker just in case, and see if they match your assumptions.

Conclusions

Opacity is astonishingly non-trivial. In most cases it works as expected, e.g. 50% opacity just takes you halfway between source and destination. But sometimes the settings for this are different, and it may take quite a while until you notice. It’s always a good idea to check whether that opacity slider doesn’t behave strangely in your tool of choice.

On the other hand, taming blend gamma is an essential piece of knowledge for digital artists and designers alike, which may help you make beautiful color effects. It’s a good idea to use smaller values for nice soft blurs, but stick to 2.2 when designing prototypes for web and devices.

--

--