Как проверить, не «слишком ли черный» шестнадцатеричный цвет?

111

Я пытаюсь оценить темноту цвета, выбранного палитрой цветов, чтобы увидеть, "слишком ли он черный", и если да, установите его на белый. Я подумал, что могу использовать первые символы шестнадцатеричного значения, чтобы осуществить это. Он работает, но меняет и некоторые вполне законные «светлые» цвета.

У меня есть код для этого:

        if (lightcolor.substring(0,3) == "#00"|| lightcolor.substring(0,3) == "#010"){
            lightcolor="#FFFFFF";
            color=lightcolor;
        }

Должен быть более эффективный способ с помощью шестнадцатеричной математики узнать, что цвет вышел за пределы определенного уровня темноты? Например, если lightcolor + "какое-то шестнадцатеричное значение" <= "какое-то шестнадцатеричное значение", тогда установите его на белый.

Я добавил tinyColor, который может пригодиться для этого, но я точно не знаю.

Дшиз
источник
1
Вы пробовали взять палитру цветов и проверить значения? Я заметил, что когда R, G и B все ниже ~ 70, становится темно. Возможно, это неправильный способ, но он один.
Рик Кейперс,
1
Поскольку вы уже используете tinyColor, преобразуйте цвет в HSL и посмотрите на компонент L. 1 = белый, 0 = черный
Андреас
4
@Andreas HSL легкость не учитывает человеческое восприятие. Значение L 0,5 будет иметь различную воспринимаемую яркость для разных оттенков.
Alnitak
1
@Alnitak Вы правы, но описание ТО не совсем точное. Таким образом, любое значение ниже 3/8 могло быть достаточно темным для его целей.
Андреас
1
@Andreas, это зависит - если вы посмотрите на значения яркости ITU в моем ответе, вы увидите, что синий цвет воспринимается только на 1/10 яркости зеленого.
Alnitak

Ответы:

226

Вы должны извлечь три компонента RGB по отдельности, а затем использовать стандартную формулу для преобразования полученных значений RGB в их воспринимаемую яркость.

Предполагая шестизначный цвет:

var c = c.substring(1);      // strip #
var rgb = parseInt(c, 16);   // convert rrggbb to decimal
var r = (rgb >> 16) & 0xff;  // extract red
var g = (rgb >>  8) & 0xff;  // extract green
var b = (rgb >>  0) & 0xff;  // extract blue

var luma = 0.2126 * r + 0.7152 * g + 0.0722 * b; // per ITU-R BT.709

if (luma < 40) {
    // pick a different colour
}

РЕДАКТИРОВАТЬ

С мая 2014 года tinycolorтеперь есть getBrightness()функция, хотя и с использованием весовых коэффициентов CCIR601 вместо вышеупомянутых ITU-R.

РЕДАКТИРОВАТЬ

Результирующий диапазон значений яркости составляет 0..255, где 0 - самый темный, а 255 - самый светлый. Значения больше 128 считаются незначительными tinycolor. (бессовестно скопировано из комментариев @ pau.moreno и @Alnitak)

Альнитак
источник
12
давно не видел хороших манипуляций с битами в javascript. крутая вещь. en.wikipedia.org/wiki/Rec._709#Luma_coefficients
jbabey
2
Хороший код, но после тестирования предлагаю var luma = (r + g + b) / 3; if (luma <128) {// будет полезнее. }
Терри Лин
2
@TerryLin Почему? Приведенные коэффициенты являются стандартными значениями ITU, которые учитывают тот факт, что зеленый цвет воспринимается более ярко, чем красный (а затем синий).
Alnitak
1
Результирующий lumaдиапазон значений: 0..255где 0самый темный и 255самый светлый (сумма трех коэффициентов равна одному).
pau.moreno
1
@gabssnake только с мая 2014 года, а isDark()порог жестко запрограммирован на 128
Alnitak
21

Библиотека TinyColor (вы уже упомянули о ней) предоставляет несколько функций для проверки и управления цветами, среди них:

скали
источник
15

Я нашел эту PHP-функцию WooCommerce Wordpress ( wc_hex_is_light ) и преобразовал ее в JavaScript. Работает отлично!

function wc_hex_is_light(color) {
    const hex = color.replace('#', '');
    const c_r = parseInt(hex.substr(0, 2), 16);
    const c_g = parseInt(hex.substr(2, 2), 16);
    const c_b = parseInt(hex.substr(4, 2), 16);
    const brightness = ((c_r * 299) + (c_g * 587) + (c_b * 114)) / 1000;
    return brightness > 155;
}
Серджио Кабрал
источник
1
Супер круто, спасибо! Я тестировал его с несколькими цветами, обнаружение было правильным со всеми из них :)
Дэвид Дал Буско
colorIsDarkOrLight (цвет) {var hex = color.replace ("#", ""); var c_r, c_g, c_b, яркость = ""; если (hex.length == 3) {c_r = parseInt (hex.substr (0, 2), 16); c_g = parseInt (hex.substr (1, 2), 16); c_b = parseInt (hex.substr (2, 2), 16); яркость = (c_r * 299 + c_g * 587 + c_b * 114) / 1000; } еще {c_r = parseInt (hex.substr (0, 2), 16); c_g = parseInt (hex.substr (2, 2), 16); c_b = parseInt (hex.substr (4, 2), 16); } вернуть яркость> 155; },
Педро Энрике
Используйте с шестнадцатеричными символами 3 и 6
Педро Энрике
6

Вы можете вычислить яркость :

Таким образом, яркость является показателем того, насколько яркой будет поверхность.

Так что здорово выбрать, будет ли текст белым или черным.

var getRGB = function(b){
    var a;
    if(b&&b.constructor==Array&&b.length==3)return b;
    if(a=/rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/.exec(b))return[parseInt(a[1]),parseInt(a[2]),parseInt(a[3])];
    if(a=/rgb\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*\)/.exec(b))return[parseFloat(a[1])*2.55,parseFloat(a[2])*2.55,parseFloat(a[3])*2.55];
    if(a=/#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec(b))return[parseInt(a[1],16),parseInt(a[2],16),parseInt(a[3],
16)];
    if(a=/#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/.exec(b))return[parseInt(a[1]+a[1],16),parseInt(a[2]+a[2],16),parseInt(a[3]+a[3],16)];
    return (typeof (colors) != "undefined")?colors[jQuery.trim(b).toLowerCase()]:null
};

var luminance_get = function(color) {
    var rgb = getRGB(color);
    if (!rgb) return null;
        return 0.2126 * rgb[0] + 0.7152 * rgb[1] + 0.0722 * rgb[2];
}

Приведенный выше метод позволяет передавать цвет в разных форматах, но алгоритм в основном только в luminance_get.

Когда я его использовал, я устанавливал черный цвет, если яркость была больше 180, в противном случае - белый.

Робин
источник
5

Здесь есть важное различие между яркостью и яркостью. Яркость, в конце концов, является мерой того, сколько энергии проходит через определенную область, и полностью игнорирует то, как наши системы восприятия воспринимают эту энергию. Яркость, с другой стороны, является мерой того, как мы воспринимаем эту энергию, и учитывает взаимосвязь между яркостью и нашей системой восприятия. (В качестве источника путаницы есть термин, называемый относительной яркостью, который, кажется, используется как синоним терминов яркости. Это меня здорово сбило с толку).

Чтобы быть точным, вы ищете «яркость», «значение» или «относительную яркость», как предлагали другие. Вы можете рассчитать это по-разному (например, быть человеком!) Http://en.wikipedia.org/wiki/HSL_and_HSV#Lightness

  1. Возьмите максимум R, G и B.
  2. Возьмите среднее значение максимума и минимума из R, G и B.
  3. Возьмите среднее значение всех трех.
  4. Используйте некоторые средневзвешенные значения, как предлагали здесь другие.
Дэвид Нгуен
источник
AFAIK только расчет яркости, описанный на странице Википедии, является моделью, основанной на восприятии.
Alnitak
2
Приятно отметить различие между физической световой энергией и воспринимаемой яркостью, но я думаю, что у вас все довольно перепутано. Раздел статьи в Википедии, на которую вы ссылаетесь, имеет четвертый пункт, в котором говорится: «Более релевантной для восприятия альтернативой является использование яркости Y 'в качестве измерения яркости» (выделено мной), а затем приводится формула, представленная в книге Альнитака. и ответы Робина. Другими словами, метод, который вы упустили и не рекомендовали, лучше всего соответствует человеческому восприятию.
John Y
@JohnY да, это то, что я пытался сказать - он упустил единственный, который действительно соответствует остальной части его ответа.
Alnitak
Да, оказывается, я был единственным, кого здесь смутил. Я согласен с этим :) Я просто хотел подчеркнуть главное, что есть разница между энергией и восприятием. Я обновлю свой ответ соответственно.
Дэвид Нгуен
4

Это работает с шестнадцатеричным кодом, например #fefefe

function isTooDark(hexcolor){
    var r = parseInt(hexcolor.substr(1,2),16);
    var g = parseInt(hexcolor.substr(3,2),16);
    var b = parseInt(hexcolor.substr(4,2),16);
    var yiq = ((r*299)+(g*587)+(b*114))/1000;
    // Return new color if to dark, else return the original
    return (yiq < 40) ? '#2980b9' : hexcolor;
}

Вы можете изменить его на возврат trueили falseна изменение

return (yiq < 40) ? '#2980b9' : hexcolor;

к

return (yiq < 40);
TheCrazyProfessor
источник
@Vivek, потому что вам нужно использовать полные шестнадцатеричные значения (# 000000 и #ffffff)
TheCrazyProfessor
2

Возможное решение - преобразовать цвет из RGB в HSB . HSB обозначает оттенок, насыщенность и яркость (также известный как HSV, где V означает значение). Тогда вам нужно проверить только один параметр: яркость.

Охад
источник
1

Я понимаю, что этому разговору несколько лет, но он все еще актуален. Я хотел добавить, что у моей команды была такая же проблема в Java (SWT), и я обнаружил, что это немного точнее:

private Color getFontColor(RGB bgColor) {
    Color COLOR_BLACK = new Color(Display.getDefault(), 0, 0, 0);
    Color COLOR_WHITE = new Color(Display.getDefault(), 255, 255, 255);

    double luminance = Math.sqrt(0.241 
       * Math.pow(bgColor.red, 2) + 0.691 * Math.pow(bgColor.green, 2) +  0.068 
       * Math.pow(bgColor.blue, 2));
    if (luminance >= 130) {
        return COLOR_BLACK;
    } else {
        return COLOR_WHITE;
    }
}
Крис Кларк
источник