7

Программное осветление или затемнение HEX-цвета (или RGB) и смешивание цветов

1

Я работаю над функцией для программного осветления или затемнения цвета в формате HEX на определённое значение. Достаточно передать строку, например, "3F6D2A" для цвета (col) и целое число в десятичной системе (amt) для величины осветления или затемнения. Чтобы затемнить цвет, нужно передать отрицательное число (например, -20).

Причина, по которой я решил это реализовать, заключается в том, что все найденные решения казались излишне сложными, и у меня было ощущение, что это можно сделать всего в несколько строк кода. Буду признателен за любые замечания или предложения по улучшению производительности.

Вот код функции:

function LightenDarkenColor(col, amt) {
  col = parseInt(col, 16);
  return (((col & 0x0000FF) + amt) | ((((col >> 8) & 0x00FF) + amt) << 8) | (((col >> 16) + amt) << 16))).toString(16);
}

// ТЕСТ
console.log(LightenDarkenColor("3F6D2A", 40));

Для разработки я подготовил более читаемую версию кода:

function LightenDarkenColor(col, amt) {
  var num = parseInt(col, 16);
  var r = (num >> 16) + amt;
  var b = ((num >> 8) & 0x00FF) + amt;
  var g = (num & 0x0000FF) + amt;
  var newColor = g | (b << 8) | (r << 16);
  return newColor.toString(16);
}

// ТЕСТ
console.log(LightenDarkenColor("3F6D2A", -40));

И вот версия, которая обрабатывает цветовые значения с или без символа # в начале, а также проверяет на корректность значений:

function LightenDarkenColor(col, amt) {
    var usePound = false;
    if (col[0] === "#") {
        col = col.slice(1);
        usePound = true;
    }

    var num = parseInt(col, 16);
    var r = (num >> 16) + amt;

    // Ограничение значений цветовых каналов
    r = Math.min(Math.max(r, 0), 255);
    
    var b = ((num >> 8) & 0x00FF) + amt;
    b = Math.min(Math.max(b, 0), 255);
    
    var g = (num & 0x0000FF) + amt;
    g = Math.min(Math.max(g, 0), 255);

    return (usePound ? "#" : "") + (g | (b << 8) | (r << 16)).toString(16);
}

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

Вопрос заключается в следующем: прав ли я в своих предположениях? Охватывает ли это большинство (обычных) ситуаций? И если да, то какой самый быстрый и минимистичный способ решения этой задачи? Я планирую использовать это в анимациях и в небольшом окружении, поэтому скорость является самым важным фактором, размер - на втором месте, точность - на третьем, а читаемость... не входит в список требований (извините, знаю, что некоторые из вас сейчас вырывают волосы!).

5 ответ(ов)

2

Если вы ищете способ изменения оттенков цвета в JavaScript, вот решение, которое отлично работает для меня:

function shadeColor(color, percent) {
    var R = parseInt(color.substring(1, 3), 16);
    var G = parseInt(color.substring(3, 5), 16);
    var B = parseInt(color.substring(5, 7), 16);

    R = parseInt(R * (100 + percent) / 100);
    G = parseInt(G * (100 + percent) / 100);
    B = parseInt(B * (100 + percent) / 100);

    R = (R < 255) ? R : 255;  
    G = (G < 255) ? G : 255;  
    B = (B < 255) ? B : 255;  

    R = Math.round(R);
    G = Math.round(G);
    B = Math.round(B);

    var RR = ((R.toString(16).length == 1) ? "0" + R.toString(16) : R.toString(16));
    var GG = ((G.toString(16).length == 1) ? "0" + G.toString(16) : G.toString(16));
    var BB = ((B.toString(16).length == 1) ? "0" + B.toString(16) : B.toString(16));

    return "#" + RR + GG + BB;
}

Примеры использования:

Чтобы осветлить цвет, вы можете вызвать функцию следующим образом:

shadeColor("#63C6FF", 40);

А чтобы потемнить цвет, просто передайте отрицательное значение:

shadeColor("#63C6FF", -40);

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

1

Вот простой однострочник на основе ответа Эрика:

function adjust(color, amount) {
    return '#' + color.replace(/^#/, '').replace(/../g, color => ('0'+Math.min(255, Math.max(0, parseInt(color, 16) + amount)).toString(16)).substr(-2));
}

Примеры использования:

adjust('#ffffff', -20) => "#ebebeb"
adjust('000000', 20) => "#141414"

Эта функция adjust принимает цвет в формате HEX и число amount, на которое нужно изменить яркость цвета. Она удаляет символ #, разбивает цвет на составляющие компоненты, корректирует каждую компоненту, добавляя amount, и затем собирает их обратно в строку формата HEX.

0

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

Например, 20% от 200 синих значений значительно отличается от 20% от 40 синих значений.

В любом случае, вот моя модификация. Спасибо за исходный код!

function adjustBrightness(col, amt) {
    var usePound = false;

    if (col[0] == "#") {
        col = col.slice(1);
        usePound = true;
    }

    var R = parseInt(col.substring(0, 2), 16);
    var G = parseInt(col.substring(2, 4), 16);
    var B = parseInt(col.substring(4, 6), 16);

    // чтобы сделать цвет менее ярким, чем исходный
    // измените следующие три символа "+" на "-"
    R = R + amt;
    G = G + amt;
    B = B + amt;

    if (R > 255) R = 255;
    else if (R < 0) R = 0;

    if (G > 255) G = 255;
    else if (G < 0) G = 0;

    if (B > 255) B = 255;
    else if (B < 0) B = 0;

    var RR = ((R.toString(16).length == 1) ? "0" + R.toString(16) : R.toString(16));
    var GG = ((G.toString(16).length == 1) ? "0" + G.toString(16) : G.toString(16));
    var BB = ((B.toString(16).length == 1) ? "0" + B.toString(16) : B.toString(16));

    return (usePound ? "#" : "") + RR + GG + BB;
}

Если у вас возникнут вопросы по функционалу или если нужны доработки, не стесняйтесь спрашивать!

0

Ответ на этот вопрос основан на ответах Давида Шеррета и Пабло. Мы переписали решение в более безопасном формате для TypeScript:

/**
 * @param color Hex value format: #ffffff или ffffff
 * @param decimal Значение для осветления или затемнения, например, 0.5 для осветления на 50% или 1.5 для затемнения на 50%.
 * @returns Измененный цвет в формате hex.
 */
static shadeColor(color: string, decimal: number): string {
    // Проверяем, начинается ли цвет с '#', и устанавливаем base в соответствующее значение
    const base = color.startsWith('#') ? 1 : 0;

    // Извлекаем красный, зеленый и синий компоненты цвета из hex
    let r = parseInt(color.substring(base, 2 + base), 16);
    let g = parseInt(color.substring(2 + base, 4 + base), 16);
    let b = parseInt(color.substring(4 + base, 6 + base), 16);

    // Применяем осветление или затемнение к каждому компоненту
    r = Math.round(r / decimal);
    g = Math.round(g / decimal);
    b = Math.round(b / decimal);

    // Ограничиваем значения каждого компонента не выше 255
    r = Math.min(r, 255);
    g = Math.min(g, 255);
    b = Math.min(b, 255);

    // Приводим компоненты к строковому виду, добавляя ведущий ноль при необходимости
    const rr = r.toString(16).padStart(2, '0');
    const gg = g.toString(16).padStart(2, '0');
    const bb = b.toString(16).padStart(2, '0');

    // Возвращаем цвет в формате hex
    return `#${rr}${gg}${bb}`;
}

В этом обновленном коде учтены несколько важных особенностей:

  1. Использование Math.min() вместо тернарного оператора для ограничения значений компонентов цвета.
  2. Применение метода padStart(2, '0') для правильного форматирования значений RGB в hex-строку, что делает код более простым и читаемым.

Теперь эта функция безопаснее для использования в TypeScript и лучше обрабатывает входные данные.

0

Похоже, что в вашей функции есть небольшая ошибка: если финальное значение 'r' состоит из одной цифры, результат будет выглядеть как 'a0a0a', тогда как правильный результат должен быть '0a0a0a', например.

Я быстро исправил это, добавив следующий код вместо вашего возвращаемого значения:

var rStr = (r.toString(16).length < 2) ? '0' + r.toString(16) : r.toString(16);
var gStr = (g.toString(16).length < 2) ? '0' + g.toString(16) : g.toString(16);
var bStr = (b.toString(16).length < 2) ? '0' + b.toString(16) : b.toString(16);

return (usePound ? "#" : "") + rStr + gStr + bStr;

Возможно, это не самый элегантный способ, но он работает. Кстати, отличная функция! Именно то, что мне было нужно. 😃

Чтобы ответить на вопрос, пожалуйста, войдите или зарегистрируйтесь