Blink SDL_Surface to white

Hi, someone can show me how to change the colors of a SDL_Surface?
For exemple, in old games of metal slug when shoot at boss its images blink in white and turn back to normal.

I want to make something like this but using an auxiliary surface which will be converted to texture.

So, how can I do that?

If you’re interested in the formula for manually calculating pixel colors to achieve this effect, it looks like this:

final.r := round(source.r + (dest.r - source^.r) / scale * level);
final.g := round(source.g + (dest.g - source^.g) / scale * level);
final.b := round(source.b + (dest.b - source^.b) / scale * level);
  • final — final pixel color,
  • source — source color (e.g. any sprite pixel color),
  • dest — dest color (e.g. white),
  • scale — the range of available levels, can be arbitrary (e.g. 100 if you want a percentage scale, or 255 if you want a uint8 range),
  • level — mix level you want to use (from the [0,scale] range).

You choose the scale once, and with the level parameter you determine how close the source color should be to the target color. For level equal to 0, the final color will be the same as source, for level equal to scale, the final color will be equal to dest.

You can use this code to interpolate two colors to get any effect you want — it can be a sprite blink, but it can also be blending one image into another. Below is an example of such a blending effect, in my old test application:

lerp images

1 Like

Initially I just think to set all colors of surface to white, keeping the colorkey intact, but what you showme is better than I thought.
Thank you, I will try to reproduce that in another hour.

I thought you meant the sprite’s animated transition from original color to white and then the other way around. That is, blink is supposed to be a multi-frame transition, in the form of simple animation.

If you need the sprite to be rendered in its original form in one frame, and all white in the next (without a smooth transition), then you can just set all pixels other than ColorKey to white, because it will work as you want and by the way should work faster than the specified code by me (no floating point math).

However, if you use surfaces with a limited number of colors (i.e. color palettes), then this type of animation can be done by swapping palettes (instead of modifying all pixel data) as in the good old days.

2 Likes

Ok, so I will choose just change the pallet of suface. In my case, is just recreate another texture (white pixels).
Thanks again!

Even then, this blending formula only works when the RGB values are linear, so it cannot be done directly on sRGB bytes for instance. It seems like in your animation it is incorrect blending.

This formula is useful where the pixel data is uint8 for each channel (standard RGB). Normally, I used this method in window applications, to animate various interface elements, created from typical bitmaps or PNGs, where the pixel data is stored in memory as an array of BGR or ABGR uint8 values.

It works the way it’s supposed to work.

I also used this method in (an unfinished) pseudo-game called Deep Platformer, to smoothly change the colors of the level layers and the background gradient. Both effects work simultaneously in the staff scene (see the video in the attachment).

deep platformer stuff.mkv (1.8 MB)

When the pixel data is 8-bit in each channel, that channel value is not linear, so a linear formula cannot be used for blending. The byte 00 is used for when the channel has 0% brightness, and the byte FF is used for when the channel has 100% brightness. The halfway between these two bytes as 7F or 80 is incorrect, because that corresponds to about 21% brightness. The correct halfway blend of channels with 00 and FF is BB or BC, which corresponds to about 50% brightness.

In your animation, the halfway blend looks like this:
image

The correct blend is supposed to look like this:
image

An example of correct linear blending (along with lookup tables for conversion) of 24-bit sRGB colors:

const uint16_t sRGBtolinear[256] = {
   0,   1,   2,   3,   4,   5,   6,   7,   8,   9,  10,  11,  12,  13,  14,  16,
  17,  18,  20,  21,  23,  25,  26,  28,  30,  32,  34,  36,  38,  40,  43,  45,
  48,  50,  53,  55,  58,  61,  64,  67,  70,  73,  76,  80,  83,  86,  90,  94,
  97, 101, 105, 109, 113, 117, 122, 126, 130, 135, 139, 144, 149, 154, 159, 164,
 169, 174, 179, 185, 190, 196, 202, 208, 213, 219, 226, 232, 238, 244, 251, 258,
 264, 271, 278, 285, 292, 299, 307, 314, 321, 329, 337, 345, 353, 361, 369, 377,
 385, 394, 402, 411, 420, 429, 438, 447, 456, 465, 475, 484, 494, 504, 514, 524,
 534, 544, 554, 565, 575, 586, 597, 608, 619, 630, 641, 652, 664, 676, 687, 699,
 711, 723, 735, 748, 760, 773, 785, 798, 811, 824, 837, 850, 864, 877, 891, 905,
 919, 933, 947, 961, 975, 990,1005,1019,1034,1049,1064,1080,1095,1111,1126,1142,
1158,1174,1190,1206,1223,1239,1256,1273,1290,1307,1324,1341,1359,1377,1394,1412,
1430,1448,1466,1485,1503,1522,1541,1560,1579,1598,1617,1637,1657,1676,1696,1716,
1736,1757,1777,1798,1818,1839,1860,1881,1903,1924,1945,1967,1989,2011,2033,2055,
2078,2100,2123,2146,2169,2192,2215,2238,2262,2286,2309,2333,2357,2382,2406,2431,
2455,2480,2505,2530,2556,2581,2607,2632,2658,2684,2710,2737,2763,2790,2816,2843,
2870,2897,2925,2952,2980,3008,3036,3064,3092,3120,3149,3178,3207,3236,3265,3294,
};

const uint8_t lineartosRGB[3295] = {
0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,14,15,
16,17,17,18,19,19,20,21,21,22,22,23,23,24,24,25,25,26,26,27,27,28,28,29,29,30,30,31,31,31,
32,32,33,33,33,34,34,34,35,35,36,36,36,37,37,37,38,38,38,39,39,39,40,40,40,41,41,41,42,42,42,43,43,43,43,44,44,44,45,45,45,45,46,46,46,47,47,47,47,
48,48,48,48,49,49,49,49,50,50,50,50,51,51,51,51,52,52,52,52,53,53,53,53,54,54,54,54,55,55,55,55,55,56,56,56,56,57,57,57,57,57,58,58,58,58,59,59,59,59,59,60,60,60,60,60,61,61,61,61,61,62,62,62,62,62,63,63,63,63,63,
64,64,64,64,64,65,65,65,65,65,66,66,66,66,66,66,67,67,67,67,67,68,68,68,68,68,68,69,69,69,69,69,70,70,70,70,70,70,71,71,71,71,71,71,72,72,72,72,72,72,73,73,73,73,73,73,74,74,74,74,74,74,75,75,75,75,75,75,76,76,76,76,76,76,76,77,77,77,77,77,77,78,78,78,78,78,78,78,79,79,79,79,79,79,
80,80,80,80,80,80,80,81,81,81,81,81,81,81,82,82,82,82,82,82,82,83,83,83,83,83,83,83,84,84,84,84,84,84,84,85,85,85,85,85,85,85,86,86,86,86,86,86,86,86,87,87,87,87,87,87,87,88,88,88,88,88,88,88,88,89,89,89,89,89,89,89,90,90,90,90,90,90,90,90,91,91,91,91,91,91,91,91,92,92,92,92,92,92,92,92,93,93,93,93,93,93,93,93,94,94,94,94,94,94,94,94,95,95,95,95,95,95,95,95,95,
96,96,96,96,96,96,96,96,97,97,97,97,97,97,97,97,97,98,98,98,98,98,98,98,98,99,99,99,99,99,99,99,99,99,100,100,100,100,100,100,100,100,100,101,101,101,101,101,101,101,101,101,102,102,102,102,102,102,102,102,102,103,103,103,103,103,103,103,103,103,104,104,104,104,104,104,104,104,104,105,105,105,105,105,105,105,105,105,105,106,106,106,106,106,106,106,106,106,107,107,107,107,107,107,107,107,107,107,108,108,108,108,108,108,108,108,108,109,109,109,109,109,109,109,109,109,109,110,110,110,110,110,110,110,110,110,110,111,111,111,111,111,111,111,111,111,111,
112,112,112,112,112,112,112,112,112,112,113,113,113,113,113,113,113,113,113,113,113,114,114,114,114,114,114,114,114,114,114,115,115,115,115,115,115,115,115,115,115,116,116,116,116,116,116,116,116,116,116,116,117,117,117,117,117,117,117,117,117,117,117,118,118,118,118,118,118,118,118,118,118,118,119,119,119,119,119,119,119,119,119,119,119,120,120,120,120,120,120,120,120,120,120,120,121,121,121,121,121,121,121,121,121,121,121,122,122,122,122,122,122,122,122,122,122,122,123,123,123,123,123,123,123,123,123,123,123,123,124,124,124,124,124,124,124,124,124,124,124,125,125,125,125,125,125,125,125,125,125,125,125,126,126,126,126,126,126,126,126,126,126,126,126,127,127,127,127,127,127,127,127,127,127,127,127,
128,128,128,128,128,128,128,128,128,128,128,128,129,129,129,129,129,129,129,129,129,129,129,129,130,130,130,130,130,130,130,130,130,130,130,130,131,131,131,131,131,131,131,131,131,131,131,131,132,132,132,132,132,132,132,132,132,132,132,132,132,133,133,133,133,133,133,133,133,133,133,133,133,134,134,134,134,134,134,134,134,134,134,134,134,134,135,135,135,135,135,135,135,135,135,135,135,135,135,136,136,136,136,136,136,136,136,136,136,136,136,136,137,137,137,137,137,137,137,137,137,137,137,137,137,138,138,138,138,138,138,138,138,138,138,138,138,138,139,139,139,139,139,139,139,139,139,139,139,139,139,139,140,140,140,140,140,140,140,140,140,140,140,140,140,141,141,141,141,141,141,141,141,141,141,141,141,141,141,142,142,142,142,142,142,142,142,142,142,142,142,142,143,143,143,143,143,143,143,143,143,143,143,143,143,143,
144,144,144,144,144,144,144,144,144,144,144,144,144,144,145,145,145,145,145,145,145,145,145,145,145,145,145,145,146,146,146,146,146,146,146,146,146,146,146,146,146,146,147,147,147,147,147,147,147,147,147,147,147,147,147,147,147,148,148,148,148,148,148,148,148,148,148,148,148,148,148,149,149,149,149,149,149,149,149,149,149,149,149,149,149,149,150,150,150,150,150,150,150,150,150,150,150,150,150,150,151,151,151,151,151,151,151,151,151,151,151,151,151,151,151,152,152,152,152,152,152,152,152,152,152,152,152,152,152,152,153,153,153,153,153,153,153,153,153,153,153,153,153,153,153,154,154,154,154,154,154,154,154,154,154,154,154,154,154,154,154,155,155,155,155,155,155,155,155,155,155,155,155,155,155,155,156,156,156,156,156,156,156,156,156,156,156,156,156,156,156,157,157,157,157,157,157,157,157,157,157,157,157,157,157,157,157,158,158,158,158,158,158,158,158,158,158,158,158,158,158,158,158,159,159,159,159,159,159,159,159,159,159,159,159,159,159,159,
160,160,160,160,160,160,160,160,160,160,160,160,160,160,160,160,161,161,161,161,161,161,161,161,161,161,161,161,161,161,161,161,161,162,162,162,162,162,162,162,162,162,162,162,162,162,162,162,162,163,163,163,163,163,163,163,163,163,163,163,163,163,163,163,163,164,164,164,164,164,164,164,164,164,164,164,164,164,164,164,164,164,165,165,165,165,165,165,165,165,165,165,165,165,165,165,165,165,166,166,166,166,166,166,166,166,166,166,166,166,166,166,166,166,166,167,167,167,167,167,167,167,167,167,167,167,167,167,167,167,167,167,
168,168,168,168,168,168,168,168,168,168,168,168,168,168,168,168,168,169,169,169,169,169,169,169,169,169,169,169,169,169,169,169,169,169,170,170,170,170,170,170,170,170,170,170,170,170,170,170,170,170,170,171,171,171,171,171,171,171,171,171,171,171,171,171,171,171,171,171,171,172,172,172,172,172,172,172,172,172,172,172,172,172,172,172,172,172,173,173,173,173,173,173,173,173,173,173,173,173,173,173,173,173,173,173,174,174,174,174,174,174,174,174,174,174,174,174,174,174,174,174,174,174,175,175,175,175,175,175,175,175,175,175,175,175,175,175,175,175,175,175,
176,176,176,176,176,176,176,176,176,176,176,176,176,176,176,176,176,176,177,177,177,177,177,177,177,177,177,177,177,177,177,177,177,177,177,177,178,178,178,178,178,178,178,178,178,178,178,178,178,178,178,178,178,178,179,179,179,179,179,179,179,179,179,179,179,179,179,179,179,179,179,179,179,180,180,180,180,180,180,180,180,180,180,180,180,180,180,180,180,180,180,181,181,181,181,181,181,181,181,181,181,181,181,181,181,181,181,181,181,181,182,182,182,182,182,182,182,182,182,182,182,182,182,182,182,182,182,182,182,183,183,183,183,183,183,183,183,183,183,183,183,183,183,183,183,183,183,183,
184,184,184,184,184,184,184,184,184,184,184,184,184,184,184,184,184,184,184,185,185,185,185,185,185,185,185,185,185,185,185,185,185,185,185,185,185,185,186,186,186,186,186,186,186,186,186,186,186,186,186,186,186,186,186,186,186,186,187,187,187,187,187,187,187,187,187,187,187,187,187,187,187,187,187,187,187,188,188,188,188,188,188,188,188,188,188,188,188,188,188,188,188,188,188,188,188,189,189,189,189,189,189,189,189,189,189,189,189,189,189,189,189,189,189,189,189,190,190,190,190,190,190,190,190,190,190,190,190,190,190,190,190,190,190,190,190,191,191,191,191,191,191,191,191,191,191,191,191,191,191,191,191,191,191,191,191,
192,192,192,192,192,192,192,192,192,192,192,192,192,192,192,192,192,192,192,192,193,193,193,193,193,193,193,193,193,193,193,193,193,193,193,193,193,193,193,193,194,194,194,194,194,194,194,194,194,194,194,194,194,194,194,194,194,194,194,194,194,195,195,195,195,195,195,195,195,195,195,195,195,195,195,195,195,195,195,195,195,196,196,196,196,196,196,196,196,196,196,196,196,196,196,196,196,196,196,196,196,196,197,197,197,197,197,197,197,197,197,197,197,197,197,197,197,197,197,197,197,197,197,198,198,198,198,198,198,198,198,198,198,198,198,198,198,198,198,198,198,198,198,198,199,199,199,199,199,199,199,199,199,199,199,199,199,199,199,199,199,199,199,199,199,
200,200,200,200,200,200,200,200,200,200,200,200,200,200,200,200,200,200,200,200,200,200,201,201,201,201,201,201,201,201,201,201,201,201,201,201,201,201,201,201,201,201,201,202,202,202,202,202,202,202,202,202,202,202,202,202,202,202,202,202,202,202,202,202,202,203,203,203,203,203,203,203,203,203,203,203,203,203,203,203,203,203,203,203,203,203,203,204,204,204,204,204,204,204,204,204,204,204,204,204,204,204,204,204,204,204,204,204,205,205,205,205,205,205,205,205,205,205,205,205,205,205,205,205,205,205,205,205,205,205,205,206,206,206,206,206,206,206,206,206,206,206,206,206,206,206,206,206,206,206,206,206,206,207,207,207,207,207,207,207,207,207,207,207,207,207,207,207,207,207,207,207,207,207,207,
208,208,208,208,208,208,208,208,208,208,208,208,208,208,208,208,208,208,208,208,208,208,209,209,209,209,209,209,209,209,209,209,209,209,209,209,209,209,209,209,209,209,209,209,209,210,210,210,210,210,210,210,210,210,210,210,210,210,210,210,210,210,210,210,210,210,210,210,211,211,211,211,211,211,211,211,211,211,211,211,211,211,211,211,211,211,211,211,211,211,211,212,212,212,212,212,212,212,212,212,212,212,212,212,212,212,212,212,212,212,212,212,212,212,213,213,213,213,213,213,213,213,213,213,213,213,213,213,213,213,213,213,213,213,213,213,213,214,214,214,214,214,214,214,214,214,214,214,214,214,214,214,214,214,214,214,214,214,214,214,215,215,215,215,215,215,215,215,215,215,215,215,215,215,215,215,215,215,215,215,215,215,215,215,
216,216,216,216,216,216,216,216,216,216,216,216,216,216,216,216,216,216,216,216,216,216,216,217,217,217,217,217,217,217,217,217,217,217,217,217,217,217,217,217,217,217,217,217,217,217,217,218,218,218,218,218,218,218,218,218,218,218,218,218,218,218,218,218,218,218,218,218,218,218,218,219,219,219,219,219,219,219,219,219,219,219,219,219,219,219,219,219,219,219,219,219,219,219,219,220,220,220,220,220,220,220,220,220,220,220,220,220,220,220,220,220,220,220,220,220,220,220,220,221,221,221,221,221,221,221,221,221,221,221,221,221,221,221,221,221,221,221,221,221,221,221,221,222,222,222,222,222,222,222,222,222,222,222,222,222,222,222,222,222,222,222,222,222,222,222,222,222,223,223,223,223,223,223,223,223,223,223,223,223,223,223,223,223,223,223,223,223,223,223,223,223,223,
224,224,224,224,224,224,224,224,224,224,224,224,224,224,224,224,224,224,224,224,224,224,224,224,225,225,225,225,225,225,225,225,225,225,225,225,225,225,225,225,225,225,225,225,225,225,225,225,225,226,226,226,226,226,226,226,226,226,226,226,226,226,226,226,226,226,226,226,226,226,226,226,226,226,227,227,227,227,227,227,227,227,227,227,227,227,227,227,227,227,227,227,227,227,227,227,227,227,227,228,228,228,228,228,228,228,228,228,228,228,228,228,228,228,228,228,228,228,228,228,228,228,228,228,228,229,229,229,229,229,229,229,229,229,229,229,229,229,229,229,229,229,229,229,229,229,229,229,229,229,230,230,230,230,230,230,230,230,230,230,230,230,230,230,230,230,230,230,230,230,230,230,230,230,230,230,231,231,231,231,231,231,231,231,231,231,231,231,231,231,231,231,231,231,231,231,231,231,231,231,231,231,
232,232,232,232,232,232,232,232,232,232,232,232,232,232,232,232,232,232,232,232,232,232,232,232,232,232,233,233,233,233,233,233,233,233,233,233,233,233,233,233,233,233,233,233,233,233,233,233,233,233,233,233,234,234,234,234,234,234,234,234,234,234,234,234,234,234,234,234,234,234,234,234,234,234,234,234,234,234,235,235,235,235,235,235,235,235,235,235,235,235,235,235,235,235,235,235,235,235,235,235,235,235,235,235,236,236,236,236,236,236,236,236,236,236,236,236,236,236,236,236,236,236,236,236,236,236,236,236,236,236,236,237,237,237,237,237,237,237,237,237,237,237,237,237,237,237,237,237,237,237,237,237,237,237,237,237,237,238,238,238,238,238,238,238,238,238,238,238,238,238,238,238,238,238,238,238,238,238,238,238,238,238,238,238,239,239,239,239,239,239,239,239,239,239,239,239,239,239,239,239,239,239,239,239,239,239,239,239,239,239,239,
240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,240,241,241,241,241,241,241,241,241,241,241,241,241,241,241,241,241,241,241,241,241,241,241,241,241,241,241,241,241,242,242,242,242,242,242,242,242,242,242,242,242,242,242,242,242,242,242,242,242,242,242,242,242,242,242,242,243,243,243,243,243,243,243,243,243,243,243,243,243,243,243,243,243,243,243,243,243,243,243,243,243,243,243,243,244,244,244,244,244,244,244,244,244,244,244,244,244,244,244,244,244,244,244,244,244,244,244,244,244,244,244,245,245,245,245,245,245,245,245,245,245,245,245,245,245,245,245,245,245,245,245,245,245,245,245,245,245,245,245,246,246,246,246,246,246,246,246,246,246,246,246,246,246,246,246,246,246,246,246,246,246,246,246,246,246,246,246,247,247,247,247,247,247,247,247,247,247,247,247,247,247,247,247,247,247,247,247,247,247,247,247,247,247,247,247,
248,248,248,248,248,248,248,248,248,248,248,248,248,248,248,248,248,248,248,248,248,248,248,248,248,248,248,248,248,249,249,249,249,249,249,249,249,249,249,249,249,249,249,249,249,249,249,249,249,249,249,249,249,249,249,249,249,250,250,250,250,250,250,250,250,250,250,250,250,250,250,250,250,250,250,250,250,250,250,250,250,250,250,250,250,250,251,251,251,251,251,251,251,251,251,251,251,251,251,251,251,251,251,251,251,251,251,251,251,251,251,251,251,251,251,252,252,252,252,252,252,252,252,252,252,252,252,252,252,252,252,252,252,252,252,252,252,252,252,252,252,252,252,252,253,253,253,253,253,253,253,253,253,253,253,253,253,253,253,253,253,253,253,253,253,253,253,253,253,253,253,253,253,254,254,254,254,254,254,254,254,254,254,254,254,254,254,254,254,254,254,254,254,254,254,254,254,254,254,254,254,254,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
};
 int a=0xFF8000; int b=0x00FF80; int c=0x8000;
 int d=0;
 ((uint8_t*)&d)[0]=lineartosRGB[(sRGBtolinear[((uint8_t*)&a)[0]]*(0x10000-c)+sRGBtolinear[((uint8_t*)&b)[0]]*(c))>>16];
 ((uint8_t*)&d)[1]=lineartosRGB[(sRGBtolinear[((uint8_t*)&a)[1]]*(0x10000-c)+sRGBtolinear[((uint8_t*)&b)[1]]*(c))>>16];
 ((uint8_t*)&d)[2]=lineartosRGB[(sRGBtolinear[((uint8_t*)&a)[2]]*(0x10000-c)+sRGBtolinear[((uint8_t*)&b)[2]]*(c))>>16];

This blending code results in the halfway blend between FF8000 and 00FF80 being BCCD5C.

A dithering between FF8000 and 00FF80 is in the middle. On the left side is BCCD5C, the right side has 80C040 which would have been the incorrect blending.

image

1 Like

I understand what you want to convey, however my formula is meant to be used to mix colors without considering/correcting brightness. To the naked eye, it doesn’t make much difference, but such calculations are simple and fast. Blending can be done in many ways, as well as color→grayscale conversion and other things, so you can’t say that a given algorithm is wrong, but at most that there are others, better (for some reason).

Thanks for sharing your knowledge and code.

“Incorrect” blending seems to be very common and not always noticeable. As far as I understand CSS is even specified to do it the “wrong” way.

When drawing a gradient between two colors it’s not always obvious you want to do the blending in linear space. Sometimes it looks better, sometimes it doesn’t.

shades2

In the example where you blend halfway between two images I can see the difference but I wouldn’t have been able to tell which one is correct.

In a game I’m working on I do color→grayscale conversion on the background when showing the in-game menu. Before I tried to do it the “correct” way the grayscale colours would look like they had different brightness compared to the original. Since it went from colour to grayscale immediately it was very noticeable.

Note that when converting color→grayscale it’s not enough to take the average of the colour components (Red, Green and Blue) even if they are in linear space because all colour components are not perceived as equally bright.

When scrolling the page I noticed an interesting effect in the dithered area. While the page was scrolling the dithered area flickered to a green colour similar to the one on the right. I wonder if this is a flaw with my monitor or if it’s the browsers that do blending incorrectly (perhaps due to non-integer aligned pixel coordinates). I see the same effect in both Firefox and Chrome. The colour also looks wrong when scaling the image (by scaling the page) but I guess that is a different issue.

EDIT: I tried the same on another computer with Edge on Windows and Firefox on Linux and there was the same glitching behaviour but even more noticeable (the green was much more pure).
I also tried it on an old Samsung tablet and there was no glitching when moving the image but instead the dithered area looked green all the time (similar to the colour on the right). I guess this is because the tablet uses 4 physical pixels per image pixel and interpolates “incorrectly” which results in the wrong colour as you described.

I’ve used various color processing functions many times in the past — blending/mixing, lightening, darkening, greyscale — and they were usually simple solutions that gave decent results and didn’t require complicated calculations (so they were fast).

For grayscale I also used (and still use) a simple way:

shade := source.r * 0.299 + source.g * 0.587 + source.b * 0.114;

final.r := shade;
final.g := shade;
final.b := shade;

To get darker shade of the color, the formula is as follows (level in range [0,255]):

level := 255 - level;

final.r := source.r * level shr 8;
final.g := source.g * level shr 8;
final.b := source.b * level shr 8;

For brighter shade formula also is quite simple (level in range [0,255]):

level := 255 - level;

final.r := round(source.r * level / 255 + 255 - level);
final.g := round(source.g * level / 255 + 255 - level);
final.b := round(source.b * level / 255 + 255 - level);

All of these snippets produce decent results and work relatively quickly. If the end result is satisfactory (it doesn’t have to be perfect), then simple solutions are perfect. And if someone needs better solutions, because simple ones are visually unsatisfactory, then they should adapt the algorithm to their requirements.

1 Like

I did the same originally, except that I used the coefficients from the second formula in the section that you linked to. It was okay but I wasn’t entirely satisfied because it looked like different colours changed brightness by different amounts.

So as an improvement I convert the sRGB to linear and do the grayscale calculation in the linear space instead before converting back to sRGB. You can compare the result below:

anim1

The places were I think it’s most noticeable are:

  • The lower-left door. In the old version it looked like the brightness changed compared to the surrounding green.
  • The red colour on the person. It looked too dark in the old version. The new version still doesn’t look perfect for me on my monitor but it’s a clear improvement.
  • The diagonal parts of the fence.

The problems and improvements are more noticeable when the graphics is scaled up to a larger size.


The above is not the entire truth. In the real game I actually blend the gray colour with the original colour to get something that is more grayish but still has quite a bit of colour. In the old version I just did it using the naive approach directly on the sRGB values. In the new version I do it on the linear values before converting back to sRGB.

anim2

The above uses 70% original colour and 30% grayscale but this is something I might change in the future…


I agree with what you said about simple solutions often being good enough but in this particular situation I wasn’t satisfied so I think it was worth doing a bit of extra work to improve it.

Keep in mind that the color component multipliers (in my example) can be adjusted to achieve different results. There is also another way to do it — sum the color components and divide by 3. This solution also produces grayscale, but the result is unique.

Anyway, the best solution is always the one that satisfies you and that’s all that matters. :wink:

I wouldn’t expect to be able to improve it a lot by choosing different multipliers. At least not when I want perceived brightness. Adjusting the multipliers alone won’t be able to compensate for the non-linear nature of the sRGB color space.

Taking the average. That’s the same as using 1.0/3.0 as the multiplier for all three colour components. For my purposes this would not have been an improvement.

average

But it’s simple, and it “works”, so it might be good enough for some purposes :man_shrugging:

Ironically, your subjective gradient demonstration seems to use incorrect blending for the text smoothing. This is one of the most controversial use cases of incorrect blending, where many renderer vendors used subjective considerations and ended up with distorted text rendering (some users even tried to tell me that all of my screens are broken just because I have a strong preference for correct blending in text). https://fontgammatest.coosucks.repl.co demonstrates the color blending in the browser’s text renderer. It is one of the many reasons that I consider most text renderers to be of low quality.

Taking the brightness of a color is not quite as objective as linear blending, but the canonical brightness weights for linear channels are supposed to be 0.2126, 0.7152, and 0.0722 for RGB respectively. Is that what you have used?

Indeed using simple integer operations can be useful in cases where a software graphical effect is desired instead of a specific linear operation. The xor 0xFFFFFF is a quick way to get a single pixel text cursor. In case of multiplicative brightness change, a simple bitshift or multiplication by n is actually an approximation of a linear brightness multiplication by n^2.2, since sRGB scale is similar to 2.2 gamma. This can be used to darken a large area or dark single pixel border (which won’t scale to 2 pixels until around 288dpi) with (x>>1)&0x7F7F7F. For bright single pixel border I might use ((x&0x7F7F7F)<<1)|(((x&0x808080)>>7)*0xFF). However, as soon as an additive operation is involved, such as in color blending, there is no longer an accurate approximation in fast integer operations, and the level of accuracy required depends on the particular use case.

OK, before you start picking me apart into too many pieces I have to admit that I don’t consider myself an expert on this. I only learned about it recently.

I used this page to generate the gradients. It has lots of useful information too. I recommend people who don’t know much about this subject to read it if they want to know more.

Then I just put everything together in GIMP, so if you should blame anyone for the text then you should blame GIMP. To be fair, I used a very old version of GIMP to do this so it is possible that more recent versions do it differently, I have no idea.

In my game I use approximations to convert to and from linear space. I got them from Guy Davidson’s talk: Everything you know about colour is wrong I can recommend this too. It’s a very good introduction on the subject.

Yes, that is the exact values I have used in my game.

1 Like