Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
323 views
in Technique[技术] by (71.8m points)

javascript - What is the efficient way to calculate human eye contrast difference for RGB values?

In order to check if two colors in grayscale will be too close to be distinguished by the human eye. I want to be able to generate a warning to the user if 'dangerous' colors are picked. Hence based on the result we can decide if for people with bad eye sight we should change one of the two colors to white or black to enhance the readable contrast. For example the Hex colours #9d5fb0 (purple) and #318261 (green) will turn into almost the same grey tone. Seen in HSB the B value is just 1% different from the other and therefor the healthy human eye cannot really see the difference. Or for the same the 8-Bit K value in this case differs 2%.

I have learned the luminance method is the more sophisticated way for judging grey tones the way the human eye sees colors. Yet how to do this programatically is beyond my current understanding. I could write it either PHP or JS once I understand the math.

In order to either pick values from CSS, from a screen pixel or from a file as image object, I guess we should always handle the input as RGB right?

something like:

$result = grayScaleDifference('#9d5fb0','#318261');

or

$result = 8bitK_difference('#9d5fb0','#318261');

or

$result = luminanceDifference('#9d5fb0','#318261');

So what is the best script style formula to compare them without changing or converting the actual image or color objects?

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

FOLLOW-UP ANSWER

I'm posting this as a followup answer to not only clarify my initial answer (which I also just edited), but also to add code snippets of the various concepts. Each step in the R′G′B′to Y process is important, and also must be in the order described or the results will fail.

DEFINITIONS:

sRGB: sRGB is a tristimulus color model which is the standard for the Web, and used on most computer monitors. It uses the same primaries and white point as Rec709, the standard for HDTV. sRGB differs from Rec709 only in the transfer curve, often referred to as gamma.

Gamma: This is a curve used with various methods of image coding for storage and transmission. It is often similar to the perception curve of human vision. In digital, gamma's effect is to give more weight to the darker areas of an image such that they are defined by more bits in order to avoid artifacts such as "banding".

Luminance: (notated L or Y): a linear measure or representation of light (i.e. NO gamma curve). As a measure it is usually cd/m2. As a representation, it's Y as in CIEXYZ, and commonly 0 (black) to 100 (white). Luminance features spectral weighting, based on human perception of different wavelengths of light. However, luminance is linear in terms of lightness/darkness - that is if 100 photons of light measures 10, then 20 would be 200 photons of light.

L* (aka Lstar): Perceptual Lightness, as defined by CIELAB (L*a*b*) Where luminance is linear in terms of the quantity of light, L* is based on perception, and so is nonlinear in terms of light quantities, with a curve intended to match the human eye's photopic vision (approx. gamma is ^0.43).

Luminance vs L:* 0 and 100 are the same in both luminance (written Y or L) and Lightness (written L*), but in the middle they are very different. What we identify as middle grey is in the very middle of L* at 50, but that relates to 18.4 in Luminance (Y). In sRGB that's #777777 or 46.7%.

Contrast: The term for defining a difference between two L or two Y values. There are multiple methods and standards for contrast. One common method is Weber contrast, which is ΔL/L. Contrast is usually stated as a ratio (3:1) or a percentage (70%).

DERIVING LUMINANCE (Y) FROM sRGB

STEP ZERO (un - HEX)

If needed, convert a HEX color value to a triplet of integer values where #00 = 0 and #FF = 255.

STEP ONE (8 bit to decimal)

Convert 8 bit sRGB values to decimal by dividing by 255:

?? R′decimal = R′8bit / 255 ?? ?? G′decimal = G′8bit / 255 ?? ?? B′decimal = B′8bit / 255

If your sRGB values are 16 bit then convert to decimal by dividing by 65535.

STEP TWO (Linearize, Simple Version)

Raise each color channel to the power of 2.2, the same as an sRGB display. This is fine for most applications. But if you need to make multiple ound trips into and out of sRGB gamma encoded space, then use the more accurate versions below.

?? R′^2.2 = Rlin ?? G′^2.2 = Glin ?? B′^2.2 = Blin

STEP TWO (Linearize, Accurate Version)

Use this version instead of the simple ^2.2 version above if you are doing image manipulations and multiple round trips in and out of gamma encoded space.

function sRGBtoLin(colorChannel) {
        // Send this function a decimal sRGB gamma encoded color value
        // between 0.0 and 1.0, and it returns a linearized value.

    if ( colorChannel <= 0.04045 ) {
            return colorChannel / 12.92;
        } else {
            return Math.pow((( colorChannel + 0.055)/1.055),2.4));
        }
    }

EDIT TO ADD CLARIFICATION: the sRGB linearization I cited above above uses the correct threshold from the official IEC standard, while the old WCAG2 math uses an incorrect threshold (a known, open bug). Nevertheless, the threshold difference does not affect the WCAG 2 results, which are instead plagued by other factors.

STEP THREE (Spectrally Weighted Luminance)

The normal human eye has three types of cones that are sensitive to red, green, and blue light. But our spectral sensitivity is not uniform, as we are most sensitive to green (555 nm), and blue is a distant last place. Luminance is spectrally weighted to reflect this using the following coefficients:

?? Rlin * 0.2126 + Glin * 0.7152 + Blin * 0.0722 = Y = L

Multiply each linearized color channel by their coefficient and sum them all together to find L, Luminance.

STEP FOUR (Contrast Determination)

There are many different means to determine contrast, and various standards as well. Some equations work better than others depending on the specific application.

WCAG 2.x
The current web page contrast guideline listed in the WCAG 2.0 and 2.1 is simple contrast with an offset:

?? C = ((Llighter + 0.05) / (Ldarker + 0.05)) : 1

This gives a ratio, and the WCAG specifies 3:1 for non-text, and 4.5:1 for text to meet the "AA" level.

However, it is a weak example for a variety of reasons. I'm on record as pointing out the flaws in a current GitHub issue (WCAG #695) and have been researching alternatives.

EDIT TO ADD (Jan 2021):

The replacement to the old WCAG 2 contrast is the APCA:

"Advanced Perceptual Contrast Algorithm"

A part of the new WCAG 3. It is a substantial leap forward. While stable I still consider it beta, and because it is a bit more complicated, probably better to link to the SAPC/APCA GitHub repo for the time being.

Some other previously developed contrast methods in the literature:

Modified Weber
The Hwang/Peli Modified Weber provides a better assessment of contrast as it applies to computer monitors / sRGB.

?? C = (Llighter – Ldarker) / (Llighter + 0.1)

Note that I chose the flare factor of 0.1 instead of 0.05 based on some recent experiments. That value is TBD though, and a different value might be better.

LAB Difference
Another alternative that I happen to like more than others is converting the linearized luminance (L) to L* which is Perceptual Lightness, then just subtracting one from the other to find the difference.

Convert Y to L*:

function YtoLstar(Y) {
        // Send this function a luminance value between 0.0 and 1.0,
        // and it returns L* - perceptual lightness

    if ( Y <= (216/24389) {       // The CIE standard states 0.008856 but 216/24389 is the intent for 0.008856451679036
            return Y * (24389/27);  // The CIE standard states 903.3, but 24389/27 is the intent, making 903.296296296296296
        } else {
            return Math.pow(Y,(1/3)) * 116 - 16;
        }
    }

Once you've converted L to L*, then a useful contrast figure is simply:

??? C = Llighter – Ldarker**

The results here may need to be scaled to be similar to other methods. A scaling of about 1.6 or 1.7 seems to work well.

There are a number of other methods for determining contrast, but these are the most common. Some applications though will do better with other contrast methods. Some others are Michaelson Contrast, Perceptual Contrast Length (PCL), and Bowman/Sapolinski.

ALSO, if you are looking for color differences beyond the luminance or lightness differences, then CIELAB has some useful methods in this regard.

SIDE NOTES:

Averaging RGB No Bueno!

OP 2x2p mentioned a commonly cited equation for making a greyscale of a color as:

??? GRAY = round((R + G + B) / 3);

He pointed out how inaccurate it seemed, and indeed — it is completely wrong. The spectral weighting of R, G, and B is substantial and cannot be overlooked. GREEN is a higher luminance than BLUE by an ORDER OF MAGNITUDE. You cannot just sum all three channels together and divide by three and get anything close to the actual luminance of a particular color.

I believe the confusion over this may have come from a color control known as HSI (Hue, Saturation, Intensity). But this control is not (and never intended to be) perceptually uniform!!! HSI, like HSV, are just "conveniences" for manipulating color values in a computer. Neither are perceptually uniform, and the math they use is strictly for supporting an "easy" way to adjust color values in software.

OP's Sample Colors

2x2p posted his code using '#318261','#9d5fb0' as test colors. Here's how they look on my spreadsheet, along with each value in every step along the process of conversion (using the "accurate" sRGB method):

enter image description here

Both are close to middle grey of #777777. Notice also that while the luminance L is just 18, the perceptual lightness L* is 50.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...