Как мне избежать поиска для вычисления «нормализованного цвета»?

Прежде чем я продолжу, как я пытался это сделать, немного предыстории.

WCAG предоставляет метод, который вычисляет яркость как часть вычисление коэффициента контрастности.

То, что я пытаюсь сделать, — это дать цвет в пространстве RGB, преобразовать его в пространство HSL и настроить значение L так, чтобы оно соответствовало яркости другого цвета (в моем случае я выбрал #808080).

Идея состоит в том, чтобы сделать переходы между затемнением / осветлением более равномерными.

цветовая палитра

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

// https://www.w3.org/TR/WCAG20-TECHS/G17.html

/**
 * RGB Values.  Each number is a number between 0.0 to 1.0
 */
type Rgb = { r: number; g: number; b: number };
/**
 * HSL Values.
 * @property h hue 0 to 360
 * @property s saturation 0.0 to 1.0
 * @property l luminiance 0.0 to 1.0
 */
type Hsl = { h: number; s: number; l: number };
/**
 * Given a sRGB hex code convert to RGB
 * @param hex
 * @returns
 */
const hexToRgb = (hex: string): Rgb => {
  const match = hex
    .toLowerCase()
    .match(/^#([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})$/);
  return {
    r: parseInt(match![1], 16) / 255.0,
    g: parseInt(match![2], 16) / 255.0,
    b: parseInt(match![3], 16) / 255.0,
  };
};

/**
 *
 * @param rgb
 * @returns
 */
const rgbToHex = (rgb: Rgb): string => {
  return (
    "#" +
    Math.round(rgb.r * 255.0).toString(16).padStart(2, "0") +
    Math.round(rgb.g * 255.0).toString(16).padStart(2, "0") +
    Math.round(rgb.b * 255.0).toString(16).padStart(2, "0")
  );
};

const luminance = (rgb: Rgb): number => {
  const r =
    rgb.r <= 0.03928 ? rgb.r / 12.92 : Math.pow((rgb.r + 0.055) / 1.055, 2.4);
  const g =
    rgb.g <= 0.03928 ? rgb.g / 12.92 : Math.pow((rgb.g + 0.055) / 1.055, 2.4);
  const b =
    rgb.b <= 0.03928 ? rgb.b / 12.92 : Math.pow((rgb.b + 0.055) / 1.055, 2.4);
  return 0.2126 * r + 0.7152 * g + 0.0722 * b;
};

const contrastRatio = (c1: Rgb, c2: Rgb): number => {
  const l1 = luminance(c1);
  const l2 = luminance(c2);
  if (l1 > l2) {
    return (l1 + 0.05) / (l2 + 0.05);
  } else {
    return (l2 + 0.05) / (l1 + 0.05);
  }
};

/**
 * https://en.wikipedia.org/wiki/HSL_and_HSV#HSL_to_RGB_alternative
 * @param hsl
 * @returns
 */
const hslToRgb = (hsl: Hsl): Rgb => {
  const f = (n: number): number => {
    const k = (n + hsl.h / 30) % 12;
    const a = hsl.s * Math.min(hsl.l, 1 - hsl.l);
    return hsl.l - a * Math.max(-1, Math.min(k - 3, 9 - k, 1));
  };
  return {
    r: f(0),
    g: f(8),
    b: f(4),
  };
};

const rgbToHsl = (rgb: Rgb): Hsl => {
  // Minimum and Maximum RGB values are used in the HSL calculations

  const min = Math.min(rgb.r, Math.min(rgb.g, rgb.b));
  const max = Math.max(rgb.r, Math.max(rgb.g, rgb.b));

  // Calculate the Hue

  let h;

  if (max === min) {
    h = 0;
  } else if (max === rgb.r) {
    h = ((60 * (rgb.g - rgb.b)) / (max - min) + 360) % 360;
  } else if (max === rgb.g) {
    h = (60 * (rgb.b - rgb.r)) / (max - min) + 120;
  } else if (max === rgb.b) {
    h = (60 * (rgb.r - rgb.g)) / (max - min) + 240;
  } else {
    throw "unexpected";
  }
  // Calculate the Luminance
  const l = (max + min) / 2;

  // Calculate the Saturation

  let s: number;
  if (max == min) {
    s = 0;
  } else if (l <= 0.5) {
    s = (max - min) / (max + min);
  } else {
    s = (max - min) / (2 - max - min);
  }
  return { h, s, l };
};

/**
 * Given an RGB value, find the luminance value for HSL where
 * the luminance matches that of gray.
 */
const hslWithIdealLuminance = (rgb: Rgb): Hsl => {
  const grayRgb = { r: 0.5, g: 0.5, b: 0.5 };
  const grayLuminance = luminance(grayRgb);
  let delta = 0.1;

  let currentLuminance = luminance(rgb);

  const hsl = rgbToHsl(rgb);
  while (delta > 0.0000001) {
    console.log(rgbToHex(rgb), currentLuminance, grayLuminance, delta);
    if (currentLuminance > grayLuminance) {
        while (currentLuminance > grayLuminance) {
            hsl.l -= delta;
            currentLuminance = luminance(hslToRgb(hsl));
        }
        
    }
    else {
        while (currentLuminance < grayLuminance) {
            hsl.l += delta;
            currentLuminance = luminance(hslToRgb(hsl));
        }
    }
    delta /= 10;
  }
  return hsl;
};

export const luminanceHex = (hex: string): number => {
  return luminance(hexToRgb(hex));
};
export const normalizeLuminance = (hex: string): string => {
  return rgbToHex(hslToRgb(hslWithIdealLuminance(hexToRgb(hex))));
};

export const contrastRatioHex = (c1:string, c2:string): number => {
  return contrastRatio(hexToRgb(c1), hexToRgb(c2));
}

Проблемная функция hslWithIdealLuminance где я ищу оптимальное значение, я думаю, что может быть какой-то способ сделать это менее грубой силой. Я, вероятно, смогу еще немного оптимизировать цикл, но я бы предпочел избавиться от него. Если я смогу свести это к математической функции, я, вероятно, смогу объединить всю эту логику в SASS.

0

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *