Як програмно розрахувати коефіцієнт контрастності між двома кольорами?


76

Досить прямо вперед, візьміть жовтий і білий:

back_color = {r:255,g:255,b:255}; //white
text_color = {r:255,g:255,b:0}; //yellow

Який закон фізики на Божій Землі універсальних констант робить той факт, що жовтий текст не можна читати на білому тлі, а синій?

Заради мого настроюваного віджета я спробував усі можливі кольорові моделі, для яких знайшов функції перетворення; жоден з них не може сказати, що зелений може бути на білому, а жовтий - на основі просто числових порівнянь.

Я подивився Adsense (який створений Буддою всього Інтернету) і здогадався, що вони зробили, вони зробили пресети та обчислили відстань кольорових клітин. Я не можу цього зробити. Мої користувачі мають право вибирати навіть найбільш запальні, неестетичні комбінації сітківки, якщо текст все ще можна прочитати.


Тож ваші користувачі можуть вибрати будь-які два кольори, якщо вони контрастні?
DanRedux

Я вважаю це цікавим з технічної точки зору, але більш практично, якщо ваші користувачі мають "право" вибирати будь-які кольори, чому вам взагалі цікаво, чи можна їх читати? Чи не від них залежить, як правильно це зрозуміти?
nnnnnn

@nnnnnn Мені насправді байдуже, які кольори вони вибирають, вони можуть змішувати все, що завгодно, але я дбаю про те, наскільки читабельною є (c) 2012 Company Inc.
Silviu-Marian

Я встановив jsfiddle, щоб на власні очі переконатися, наскільки точними були відповіді, і вони бачать, що можуть досить добре передбачити читабельність: jsfiddle.net/UVUZC
mowwwalker

Якщо хтось пропустив відповідь ШаоКана, це посилання .
Silviu-Marian

Відповіді:


98

Згідно з Вікіпедією, при перетворенні на представлення яскравості у відтінках сірого "потрібно отримати значення її червоного, зеленого та синього" та змішати їх у такій пропорції: R: 30% G: 59% B: 11%

Тому білий буде мати 100% яскравості, а жовтий - 89%. У той же час, зелений має лише 59%. 11% - майже в чотири рази менше, ніж 41% різниці!

І навіть лайм ( #00ff00) не годиться для читання великої кількості текстів.

IMHO для гарного контрасту яскравість кольорів повинна відрізнятися принаймні на 50%. І цю яскравість слід вимірювати як перетворену в градації сірого.

upd : Нещодавно знайшов всеосяжний інструмент для того, що в Інтернеті, який для того, щоб використовувати формулу з документа w3. Порогові значення можна взяти з # 1.4 Ось реалізація для цієї більш просунутої речі.

function luminanace(r, g, b) {
    var a = [r, g, b].map(function (v) {
        v /= 255;
        return v <= 0.03928
            ? v / 12.92
            : Math.pow( (v + 0.055) / 1.055, 2.4 );
    });
    return a[0] * 0.2126 + a[1] * 0.7152 + a[2] * 0.0722;
}
function contrast(rgb1, rgb2) {
    var lum1 = luminanace(rgb1[0], rgb1[1], rgb1[2]);
    var lum2 = luminanace(rgb2[0], rgb2[1], rgb2[2]);
    var brightest = Math.max(lum1, lum2);
    var darkest = Math.min(lum1, lum2);
    return (brightest + 0.05)
         / (darkest + 0.05);
}
contrast([255, 255, 255], [255, 255, 0]); // 1.074 for yellow
contrast([255, 255, 255], [0, 0, 255]); // 8.592 for blue
// minimal recommended contrast ratio is 4.5, or 3 for larger font-sizes

Для отримання додаткової інформації перевірте документацію WCAG 2.0 про те, як обчислити це значення.


2
+1 Це правда. У реальному житті - перехід у градації сірого для перевірки читабельності дизайну є обов’язковим завданням (спеціально для логотипів).
Роко С. Бульян

Я виберу цю відповідь як рішення, оскільки вона є найбільш вичерпною, і я не можу вибрати двох рішень; але я б обрав відповідь HondaSuzukiShaolinShaorma, оскільки вона надає готовий до використання код.
Silviu-Marian

11
Хоча contrast([255, 255, 255], [0, 0, 255])повертає 8,592, ті самі цифри, contrast([0, 0, 255], [255, 255, 255])повернені назад, повертають 0,116 - тобто 1/8,592. Щоб отримати коефіцієнт контрастності від 1 до 21, вам потрібно буде розділити 1 на вихід, якщо вихід <1. function contrast(rgb1, rgb2) { var result = (luminanace(rgb1[0], rgb1[1], rgb1[2]) + 0.05) / (luminanace(rgb2[0], rgb2[1], rgb2[2]) + 0.05); if (result < 1) result = 1/result; return result; }
mattsoave

3
Ось невелика помилка: return (luminanace(rgb1[0], rgb1[1], rgb1[2]) + 0.05) / (luminanace(rgb2[0], rgb2[1], rgb2[2]) + 0.05); . Ви повинні розділити більшу величину на меншу. Краще скористайтеся цим:l1=luminanace(rgb1[0], rgb1[1], rgb1[2]) + 0.05; l2=luminanace(rgb2[0], rgb2[1], rgb2[2]) + 0.05; return (Math.max(l1,l2) / Math.min(l1,l2));
зулук

1
@zuluk, ти повністю маєш рацію. Відредаговано. w3.org/TR/WCAG20-TECHS/G17.html#G17-procedure
Брайан Рейнер

27

Існують різні способи розрахунку контрасту, але загальним способом є така формула:

brightness = (299*R + 587*G + 114*B) / 1000

Ви робите це для обох кольорів, а потім берете різницю. Це, очевидно, дає набагато більший контраст для синього на білому, ніж жовтого на білому.


3
лише для довідки, очевидно, це з w3c trendct.org/2016/01/22/… w3.org/TR/AERT#color-contrast
Коді Моніз

2
Тільки FYI, коефіцієнти, показані вище (299 * R + 587 * G + 114 * B), відносяться до NTSC і застаріли / неправильні для веб-кольорів, які є sRGB. Правильні коефіцієнти sRGB складають 0,2126 * R + 0,7152 * G + 0,0722 * B (Зверніть увагу, що для sRGB компоненти RGB спочатку повинні бути лінеаризовані).
Myndex,

2

Нещодавно я натрапив на відповідь на цій сторінці і за допомогою коду створив скрипт для Adobe Illustrator для розрахунку коефіцієнта контрастності.

Тут ви можете побачити результат: http://screencast.com/t/utT481Ut

Деякі скорочені позначення сценарію, наведені вище, викликають у мене незрозумілість, а де не працюють у розширеному сценарії Adobe. Тому я вважав, що було б непогано поділитися своїм вдосконаленням / інтерпретацією коду, яким поділився kirilloid.

function luminance(r, g, b) {
    var colorArray = [r, g, b];
    var colorFactor;
    var i;
    for (i = 0; i < colorArray.length; i++) {
        colorFactor = colorArray[i] / 255;
        if (colorFactor <= 0.03928) {
            colorFactor = colorFactor / 12.92;
        } else {
            colorFactor = Math.pow(((colorFactor + 0.055) / 1.055), 2.4);
        }
        colorArray[i] = colorFactor;
    }
    return (colorArray[0] * 0.2126 + colorArray[1] * 0.7152 + colorArray[2] * 0.0722) + 0.05;
}

І звичайно, вам потрібно викликати цю функцію

в межах циклу for я отримую всі кольори з мого об’єкта ілюстратора

//just a snippet here to demonstrate the notation
var selection = app.activeDocument.selection;
for (i = 0; i < selection.length; i++) {
   red[i] = selection[i].fillColor.red;
   //I left out the rest,because it would become to long
}

//this can then be used to calculate the contrast ratio.
var foreGround = luminance(red[0], green[0], blue[0]);
var background = luminance(red[1], green[1], blue[1]);
luminanceValue = foreGround / background;
luminanceValue = round(luminanceValue, 2);

//for rounding the numbers I use this function:
function round(number, decimals) {
   return +(Math.round(number + "e+" + decimals) + "e-" + decimals);
}

Більше інформації про коефіцієнт контрастності: http://webaim.org/resources/contrastchecker/


1

На основі відповіді на кірилоїд:

Сервісна служба Angular, яка буде обчислювати контраст і люмінесценцію, передаючи шістнадцяткове значення:

.service('ColorContrast', [function() {
var self = this;

/**
 * Return iluminance value (base for getting the contrast)
 */
self.calculateIlluminance = function(hexColor) {
    return calculateIluminance(hexColor);
};

/**
 * Calculate contrast value to white
 */
self.contrastToWhite = function(hexColor){
    var whiteIlluminance = 1;
    var illuminance = calculateIlluminance(hexColor);
    return whiteIlluminance / illuminance;
};

/**
* Bool if there is enough contrast to white
*/
self.isContrastOkToWhite = function(hexColor){
    return self.contrastToWhite(hexColor) > 4.5;
};

/**
 * Convert HEX color to RGB
 */
var hex2Rgb = function(hex) {
    var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
    return result ? {
        r: parseInt(result[1], 16),
        g: parseInt(result[2], 16),
        b: parseInt(result[3], 16)
    } : null;
};

/**
 * Calculate iluminance
 */
var calculateIlluminance = function(hexColor) {
    var rgbColor = hex2Rgb(hexColor);
    var r = rgbColor.r, g = rgbColor.g, b = rgbColor.b;
    var a = [r, g, b].map(function(v) {
        v /= 255;
        return (v <= 0.03928) ?
            v / 12.92 :
            Math.pow(((v + 0.055) / 1.055), 2.4);
    });
    return a[0] * 0.2126 + a[1] * 0.7152 + a[2] * 0.0722;
};

}]);

1

const getLuminanace = (values) => {
  const rgb = values.map((v) => {
    const val = v / 255;
    return val <= 0.03928 ? val / 12.92 : ((val + 0.055) / 1.055) ** 2.4;
  });
  return Number((0.2126 * rgb[0] + 0.7152 * rgb[1] + 0.0722 * rgb[2]).toFixed(3));
};

const getContrastRatio = (colorA, colorB) => {
  const lumA = getLuminanace(colorA);
  const lumB = getLuminanace(colorB);

  return (Math.max(lumA, lumB) + 0.05) / (Math.min(lumA, lumB) + 0.05);
};

// usage:
const back_color = [255,255,255]; //white
const text_color = [255,255,0]; //yellow

getContrastRatio(back_color, text_color); // 1.0736196319018405


0
module.exports = function colorcontrast (hex) {
    var color = {};

    color.contrast = function(rgb) {
        // check if we are receiving an element or element background-color
        if (rgb instanceof jQuery) {
            // get element background-color
            rgb = rgb.css('background-color');
        } else if (typeof rgb !== 'string') {
            return;
        }

        // Strip everything except the integers eg. "rgb(" and ")" and " "
        rgb = rgb.split(/\(([^)]+)\)/)[1].replace(/ /g, '');

        // map RGB values to variables
        var r = parseInt(rgb.split(',')[0], 10),
            g = parseInt(rgb.split(',')[1], 10),
            b = parseInt(rgb.split(',')[2], 10),
            a;

        // if RGBA, map alpha to variable (not currently in use)
        if (rgb.split(',')[3] !== null) {
            a = parseInt(rgb.split(',')[3], 10);
        }

        // calculate contrast of color (standard grayscale algorithmic formula)
        var contrast = (Math.round(r * 299) + Math.round(g * 587) + Math.round(b * 114)) / 1000;

        return (contrast >= 128) ? 'black' : 'white';
    };

    // Return public interface
    return color;

};

Це виглядає добре, але чому ваш colorcontrastметод приймає hexаргумент, але цей аргумент не використовується?
ctlockey
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.