29

I have found that reducing the opacity against a white background is a good way to find usable lighter and less saturated tints of a base color.

As an example take this picture of an orange color:

orange

The lower row shows the variants. The percentages are the opacities.

Having a white background and reducing the opacity is good when I search good tints, but I like to use equivalent 100% opaque RGB colors in the final product.

How do I calculate or otherwise find the equivalent RGB numbers, when the RGB numbers of the base color and the opacity are already selected?

I have tried color picker, but in Illustrator it at least gives only the base color.

6
  • Comments are not for extended discussion; this conversation has been moved to chat.
    – Ryan
    Jul 23, 2018 at 14:57
  • @JonasPraem Do you find the recent edits to your question properly reflect what you were asking? If so I will reopen the question. Thank you!
    – curious
    Jul 23, 2018 at 23:21
  • Yes the edits made is perfectly reflecting what I was looking for Jul 24, 2018 at 7:34
  • 2
    Why is this question bring down voted? It is a perfectly valid question imho.
    – filip
    Jul 24, 2018 at 12:26
  • 4
    The original question was written with a very strong developer POV. The question is now edited to be more understandable for designers, and more precise as well. I am a developer, not a designer - seems like I hit a language barrier. Jul 24, 2018 at 13:02

7 Answers 7

26

Use formula Y=255 - P*(255-X) where X is a RGB number, P=opacity (0...1), Y=new RGB number which should give the same appearance with 100% opacity as X gives with 100p% opacity against white background.

The formula is the general opacity formula, only simplified for this special case - the partially transparent top layer is against pure white.

Note: the white background should be a white object, not the artboard white. White background object is color managed.

If you are in Illustrator and want to copy the color with the color picker, make a copy of the partially transparent object and rasterize it. Select white background in the rasterizing dialog. Now the color picker gives the color, no calculations are needed.

6
  • 2
    Here is a python implementation to calculate this graphicdesign.stackexchange.com/questions/46867/…
    – joojaa
    Jul 24, 2018 at 12:21
  • I have a follow up question about how to implement this in Sass, on stack overflow, if anyone is interested: question Jul 24, 2018 at 12:29
  • RE: the special case (i.e. not white), doesn't this work for every case, since the formula is for one single channel, and one would need to do this for each channel? Or is the special case "the first 255" in the formula?
    – Yorik
    Jul 24, 2018 at 13:59
  • 3
    @Yorik the general formula for 2 layer image is Y=p*T+(1-p)*B where p is the opacity 0...1 of the top layer, T= rgb number of top layer color, B is the rgb number of fully opaque bottom layer. Y is the rgb number of the equivalent fully opaque color.
    – user82991
    Jul 24, 2018 at 14:14
  • JavaScript implementation of the general formula (thanks @user287001): const afterOpacity = (fg,o,bg=[255,255,255]) => fg.map((colFg,idx)=>o*colFg+(1-o)*bg[idx]) where fg is the foreground colour as [r,g,b], o is the opacity (0...1) and bg is the background colour (defaults to white if omitted) E.g. afterOpacity([255,0,0],0.5) gives [255,127.5,127.5]
    – Chris M
    Nov 27, 2019 at 14:25
8

I wrote a tool for this: https://github.com/igrmk/blec. For your specific case you may use it this way

blec white deadbeef

Last ef is the hex representation of an alpha channel. Or you may use it like this

blec white deadbe:0.75

Specifying the opacity as a decimal fraction.

Please note that the suggested formula Y = 255 - P * (255 - X) is not quite accurate due to a gamma correction. More accurate one would be Y = (255^G * (1 - P) + X^G * P) ^ (1 / G) where Y — resulting RGB component value, X — overlay RGB component value, P — its opacity, G — a value of a gamma. Most common value of a gamma is 2.2. The reason to include a gamma correction into the formula is to move components into linear space on calculating. Almost every RGB space that is used today interprets color components in a non-linear way in order to place more color information in 8 bits. Historically it was introduced to compensate a non-linearity of CRT displays.

Here is an example why a gamma correction is important for the blending. Let's take this red image

red

and this blue image

blue

Let's take first one fully opaque and set an opacity of the second image equal to x-axis like this

blue-on-white

Now let's blend them without a gamma correction (gamma = 1)

gamma=1

Let's enable a gamma correction and do the same (gamma = 2.2)

gamma=2.2

As you can see there is much more localized transition if we don't use a gamma correction. There are clearly darker colors in the center. If we use a gamma correction then the transition and the lightness become much more smooth.

Last gradient is built using a dithering.

dither

The image contains pixels of just two colors but a probability of blue linearly increases from 0 in the left to 1 in the right. The result looks much closer to image with gamma = 2.2 in terms of lightness and transition colors. Try to look at it from a distance. And this is what you probably expect from blending two colors. We literally blend them by mixing like aquarelle in this example. So gamma is very important thing for blending especially when opacity is close to 0.5.

Let's finally compare the blending of opaque red and blue with alpha of 0.5 where the effect of a gamma correction is maximum.

gamma=1 gamma=2.2 dithering

First image does not use a gamma correction, the second one uses a gamma of 2.2, and the third one uses a dithering. As you can see the first one is very different to other two (If you don't see it, read the notes below). So I advise to always use a gamma correction. If you use any decent image editor then most probably you are safe and a gamma correction is enabled by default.

Note 1: To compare a dithering to a blending you need to look at images at 100% scale so that every single pixel of the image occupies exactly one pixel of a screen. This is almost never the case if you use a mobile phone or a retina display. If you look at images at different scale probably you look at antialiased image. The antialiasing can lead to very inaccurate results because it doesn't use a gamma correction at the moment. I can confirm it for Chrome 83 on Android 10 and for the latest Safari on iOS 13. I guess it needs too much resources to do it right way. So it effectively decreases a gamma of a display to about 1.8 when you look at very contrast noisy images.

Note 2: Not every display is calibrated well. If you feel like an image with gamma of 2.2 and an image with a dithering produce different colors I have a bad news for you. You can check a gamma of you monitor here http://web.mit.edu/jmorzins/www/gamma/adilger/gamma.html. However if you use a mobile device it is better to use an application because an antialiasing can lead to very inaccurate results.

Here is a code to get these pictures https://pastebin.com/fHYtWrMb.

2
  • I guess you have at least one gear more than me. The stacking glass example is vague. Stacked against what? What is watched and where the light comes and goes? Gamma correction is in computers for on computer and display constructions based reasons, it's not in physical color materials, so glass do not make the formula clearer at all.
    – user82991
    May 7, 2020 at 9:45
  • @user287001 Thank you for pointing it out. Glass example was really bad. There are too many physical factors to consider to clarify it. I think now it looks much better. May 7, 2020 at 18:44
1

No need to go to all this technical calculations. You can instead use the swatches pannel. You save your original color as a swatch with the Global option ticked.

enter image description here

You can then use the color panel and select the percentage you need. This is does not use transparency, it is opaque.

enter image description here

When you need to see the RGB or Hex code for your final tint, you can then press on the rgb icon on the color panel and your you can see the RGB and Hex values.

enter image description here

1

I found this thread looking for the opposite process. I had the final color, and initial color (flattened in a png file) and I reversed the equation above to figure out how to find the opacity that gave the lighter shade:

P = ( -Y + 255 ) / ( 255 - X )

Where:

  • P = [P]ercent Opacity
  • Y = RGB of lighter shade
  • X = RGB of Darker shade
0

This is a python implementation of the solution by user82991:

def without_alpha(hex_color_string, alpha=0.5):
    hex_color_string = hex_color_string.lower().strip("#")
    if hex_color_string.startswith("0x"):
        hex_color_string = hex_color_string[2:]
    if len(hex_color_string) != 6:
        raise ValueError(f"Unexpected color string format: {hex_color_string}")
    result = "#"
    for i in 0, 2, 4:
        rgb_value = int("0x"+hex_color_string[i:i+2], 16)
        new_rgb_value = int(255 - alpha*(255-rgb_value))
        new_rgb_hex = hex(new_rgb_value).upper()
        result += new_rgb_hex[2:]
    return result

Examples:

without_alpha("#003AB0", alpha=0.5)  returns "#7F9CD7"  
without_alpha("FF80BF", alpha=0.5)   returns "#FFBFDF"  
0

In case someone else was as confused as I was: ForegroundColor: the color of the object you're looking at. For instance, if the object is the text "hello world" then ForegroundColor is the color used to draw that string. BackgroundColor: the color of the surface on which the object is being drawn. For instance, if the surface is a panel with a white color then BackgroundColor would be white. The conversation in this thread has been about changing the opacity of the background color, NOT the foreground color. I was interested in the opposite problem: given a string of text, I want to make it transparent. Here's the solution I wrote in Free Pascal that allows you to reduce the opacity of the foreground color OR the background color. The code is fairly easy to read and should be relatively straightforward to port over to other languages.

        Type

  { TTextualManager }
  OpacityPercentage = 1 .. 100;
  RGBRecord = Record
    R,G,B: Byte;
  End;
  
  function NewColor(const aForegroundColor,
                        aBackgroundColor : byte;
                  const Opacity          : OpacityPercentage;
                  const ChangeForeground : Boolean = True   ): byte;
  Const GammaCorrection = 2.2;
  {
    Following the suggestion by Maxim Kulikov in this same thread. Here is
    the relevant text:
    [...]  More accurate one would be Y = (255^G * (1 - P) + X^G * P) ^ (1 / G)
    where Y — resulting RGB component value, X — overlay RGB component value,
    P — its opacity, G — a value of a gamma. Most common value of a gamma is   
    2.2.
    The reason to include a gamma correction into the formula is to move components
   into linear space on calculating. Almost every RGB space that is used today
   interprets color components in a non-linear way in order to place more color
   information in 8 bits.
   Historically it was introduced to compensate a non-linearity of CRT displays.  
  }
  var aVal: Real;
      Op  : Real;
  begin
    //Make it easier on the user: let them deal with percentage 
    //between 1 and 100
    Op    := Opacity / 100;
    If ChangeForeground
    then aVal  := Power(aBackgroundcolor, GammaCorrection) * Op +
                 (100 - Op) * Power(aForegroundColor,GammaCorrection)
    else aVal  := Power(aForegroundColor, GammaCorrection) * Op + 
                 (100 - Op) * Power(aBackgroundColor,GammaCorrection)
    aVal  := Power(aVal, 1/GammaCorrection);
    Result := Min(255,Round(aVal));
  end;

  function ChangeFgOpacity(const aForegroundColor : TColor;
                           const aBackgroundColor : TColor;
                           const Opacity          : OpacityPercentage): TColor;
  var RGBForeground, RGBBackground, RGBResult: RGBRecord;
  begin
    With RGBForeground do
      RedGreenBlue(aForegroundColor, R, G,B);
    With RGBBackground do
      RedGreenBlue(aBackgroundColor, R, G,B);
    With RGBResult do
    begin
      R      := NewColor(RGBForeground.R, RGBBackground.R ,Opacity);
      G      := NewColor(RGBForeground.G, RGBBackground.G, Opacity);
      B      := NewColor(RGBForeground.B, RGBBackground.B, Opacity);
      Result := RGBToColor(R, G, B);
    end;
  End;
1
  • I think you did not understand the question. It IS about changing the foreground color, calculating an opaque tint instead of using transparency (over a white background).
    – Luciano
    Apr 20 at 10:02
0

You can also use the function from user553965 comment to recalculate source color after it was made transparent on white background with alpha= more than 1

without_alpha('#f6eab8', alpha=2)

# Expected output:
'#EDD571'

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.