#algorithm #colors #rgb #color-space #color-conversion
Вопрос:
Я пытаюсь реализовать преобразование цвета HSI <=> RGB<=>
В вики есть формулы https://en.wikipedia.org/wiki/HSL_and_HSV#HSI_to_RGB
RGB для HSI, похоже, работает нормально.
Однако у меня возникли трудности с подключением HSI к RGB.
Я буду писать на Ruby, примеры будут на Ruby, однако, если вы напишете на JS/Python/и т.д., Я думаю, что это тоже будет понятно, так как это просто математика.
Онлайн-переводчик ruby.
def hsi_to_rgb(hsi_arr)
# to float
hue, saturation, intensity = hsi_arr.map(amp;:to_f)
hue /= 60
z = 1 - (hue % 2 - 1).abs
chroma = (3 * intensity * saturation) / (1 z)
x = chroma * z
point = case hue
when 0..1 then [chroma, x, 0]
when 1..2 then [x, chroma, 0]
when 2..3 then [0, chroma, x]
when 3..4 then [0, x, chroma]
when 4..5 then [x, 0, chroma]
when 5..6 then [chroma, 0, x]
else [0, 0, 0]
end
# calculation rgb amp; scaling into range 0..255
m = intensity * (1 - saturation)
point.map { |channel| ((channel m) * 255).round }
end
Итак, с простыми цветами html, казалось, все работало.
Пока я не попробовал такие ценности, как это:
p hsi_to_rgb([0, 1, 1]) # => [765, 0, 0]
p hsi_to_rgb([360, 1, 1]) # => [765, 0, 0]
p hsi_to_rgb([357, 1, 1]) # => [729, 0, 36]
p hsi_to_rgb([357, 1, 0.5]) # => [364, 0, 18]
Полученные значения явно неверны, находятся за пределами диапазона 0..255.
Я также видел реализации, использующие тригонометрические функции:
https://hypjudy.github.io/images/dip/hsi2rgb.jpg
Однако я тоже не получил правильных результатов.
Единственный онлайн-конвертер RGB в HSI, который я нашел: https://www.picturetopeople.org/color_converter.html
Просто чтобы было с чем сравнить.
Ответ №1:
Ваша реализация выглядит правильной (при условии, что Википедия верна).
Единственная недостающая часть-ограничение вывода RGB до [0, 255].
- Как прокомментировал Джакомо Катенацци, вместо того, чтобы обрезать до [0, 255], лучше разделить R,G, B на max(R, G, B), если максимум превышает 255.
В большинстве формул преобразования цветового пространства есть значения, которые находятся в допустимом диапазоне исходного цветового пространства, но выходят за допустимый диапазон целевого цветового пространства.
Распространенным решением является обрезка результата до допустимого диапазона.
В некоторых случаях существуют неопределенные значения.
Взгляните на первые 3 строки таблицы примеров.
Оттенок обозначен N/A для белого, черного и серого цветов.
Все примеры значений HSI по вашему выбору:
[0, 1, 1]
[360, 1, 1]
[357, 1, 1]
[357, 1, 0.5]
Выпадает из допустимого диапазона цветового пространства RGB (после преобразования HSI в RGB).
Я предлагаю вам проверить допустимые кортежи из таблицы примеров:
H S I R G B
--- ---- ---- ---- ---- ----
0 100% 33.3% 100% 0% 0%
60 100% 50% 75% 75% 0%
120 100% 16.7% 0% 50% 0%
180 40% 83.3% 50% 100% 100%
240 25% 66.7% 50% 50% 100%
300 57.1% 58.3% 75% 25% 75%
61.8 69.9% 47.1% 62.8% 64.3% 14.2%
251.1 75.6% 42.6% 25.5% 10.4% 91.8%
134.9 66.7% 34.9% 11.6% 67.5% 25.5%
49.5 91.1% 59.3% 94.1% 78.5% 5.3%
283.7 68.6% 59.6% 70.4% 18.7% 89.7%
14.3 44.6% 57% 93.1% 46.3% 31.6%
56.9 36.3% 83.5% 99.8% 97.4% 53.2%
162.4 80% 49.5% 9.9% 79.5% 59.1%
248.3 53.3% 31.9% 21.1% 14.9% 59.7%
240.5 13.5% 57% 49.5% 49.3% 72.1%
Я не знаю синтаксиса языка программирования Rubi, но ваша реализация выглядит правильной.
Вот реализация Python, которая соответствует формуле преобразования из Википедии:
def hsi_to_rgb(hsi):
"""
Convert HSI tuple to RGB tuple (without scaling the result by 255)
Formula: https://en.wikipedia.org/wiki/HSL_and_HSV#HSI_to_RGB
H - Range [0, 360] (degrees)
S - Range [0, 1]
V - Range [0, 1]
The R,G,B output range is [0, 1]
"""
H, S, I = float(hsi[0]), float(hsi[1]), float(hsi[2])
Htag = H / 60
Z = 1 - abs(Htag % 2 - 1)
C = (3 * I * S) / (1 Z)
X = C * Z
if 0 <= Htag <= 1:
R1, G1, B1 = C, X, 0
elif 1 <= Htag <= 2:
R1, G1, B1 = X, C, 0
elif 2 <= Htag <= 3:
R1, G1, B1 = 0, C, X
elif 3 <= Htag <= 4:
R1, G1, B1 = 0, X, C
elif 4 <= Htag <= 5:
R1, G1, B1 = X, 0, C
elif 5 <= Htag <= 6:
R1, G1, B1 = C, 0, X
else:
R1, G1, B1 = 0, 0, 0 # Undefined
# Calculation rgb
m = I * (1 - S)
R, G, B = R1 m, G1 m, B1 m
# Limit R, G, B to valid range:
#R = max(min(R, 1), 0)
#G = max(min(G, 1), 0)
#B = max(min(B, 1), 0)
# Handling RGB values above 1:
# -----------------------------
# Avoiding weird colours - see the comment of Giacomo Catenazzi.
# Find the maximum between R, G, B, and if the value is above 1, divide the 3 channels with such numbers.
max_rgb = max((R, G, B))
if max_rgb > 1:
R /= max_rgb
G /= max_rgb
B /= max_rgb
return (R, G, B)
def rgb2percent(rgb):
""" Convert rgb tuple to percentage with one decimal digit accuracy """
rgb_per = (round(rgb[0]*1000.0)/10, round(rgb[1]*1000.0)/10, round(rgb[2]*1000.0)/10)
return rgb_per
print(rgb2percent(hsi_to_rgb([ 0, 100/100, 33.3/100]))) # => (99.9, 0.0, 0.0) Wiki: 100% 0% 0%
print(rgb2percent(hsi_to_rgb([ 60, 100/100, 50/100]))) # => (75.0, 75.0, 0.0) Wiki: 75% 75% 0%
print(rgb2percent(hsi_to_rgb([ 120, 100/100, 16.7/100]))) # => ( 0.0, 50.1, 0.0) Wiki: 0% 50% 0%
print(rgb2percent(hsi_to_rgb([ 180, 40/100, 83.3/100]))) # => (50.0, 100.0, 100.0) Wiki: 50% 100% 100%
print(rgb2percent(hsi_to_rgb([ 240, 25/100, 66.7/100]))) # => (50.0, 50.0, 100.0) Wiki: 50% 50% 100%
print(rgb2percent(hsi_to_rgb([ 300, 57.1/100, 58.3/100]))) # => (74.9, 25.0, 74.9) Wiki: 75% 25% 75%
print(rgb2percent(hsi_to_rgb([ 61.8, 69.9/100, 47.1/100]))) # => (62.8, 64.3, 14.2) Wiki: 62.8% 64.3% 14.2%
print(rgb2percent(hsi_to_rgb([251.1, 75.6/100, 42.6/100]))) # => (25.5, 10.4, 91.9) Wiki: 25.5% 10.4% 91.8%
print(rgb2percent(hsi_to_rgb([134.9, 66.7/100, 34.9/100]))) # => (11.6, 67.6, 25.5) Wiki: 11.6% 67.5% 25.5%
print(rgb2percent(hsi_to_rgb([ 49.5, 91.1/100, 59.3/100]))) # => (94.1, 78.5, 5.3) Wiki: 94.1% 78.5% 5.3%
print(rgb2percent(hsi_to_rgb([283.7, 68.6/100, 59.6/100]))) # => (70.4, 18.7, 89.7) Wiki: 70.4% 18.7% 89.7%
print(rgb2percent(hsi_to_rgb([ 14.3, 44.6/100, 57/100]))) # => (93.2, 46.3, 31.6) Wiki: 93.1% 46.3% 31.6%
print(rgb2percent(hsi_to_rgb([ 56.9, 36.3/100, 83.5/100]))) # => (99.9, 97.4, 53.2) Wiki: 99.8% 97.4% 53.2%
print(rgb2percent(hsi_to_rgb([162.4, 80/100, 49.5/100]))) # => ( 9.9, 79.5, 59.1) Wiki: 9.9% 79.5% 59.1%
print(rgb2percent(hsi_to_rgb([248.3, 53.3/100, 31.9/100]))) # => (21.1, 14.9, 59.7) Wiki: 21.1% 14.9% 59.7%
print(rgb2percent(hsi_to_rgb([240.5, 13.5/100, 57/100]))) # => (49.5, 49.3, 72.2) Wiki: 49.5% 49.3% 72.1%
Как вы можете видеть, результаты соответствуют таблице примеров из Википедии.
Комментарии:
1. Действительно, сегодня я решил провести полное перечисление диапазонов RGB: RGB => HSI =>> RGB, сравнивая исходный RGB и полученный RGB, и все значения совпадают. Что подтверждает ваши слова о том, что эти значения HSI находятся за пределами диапазона RGB. И если вы возьмете значения из приведенной выше таблицы, все будет в порядке. > Единственная недостающая часть-это обрезка вывода RGB до [0, 255]. Правильно, нужно это добавить, спасибо за вашу помощь!
2. Примечание: Я не рекомендую такой метод обрезки (который вызывает странные цвета). Просто найдите максимальное значение между R, G, B, и если значение выше 1, разделите 3 канала такими числами. Таким образом, вы сохраните оттенок (но он будет темным). Просто обрезка изменит оттенок
3. @GiacomoCatenazzi Если подумать, то деление на максимум лучше, чем отсечение. Я обновил ответ.
Ответ №2:
Сравнение с таблицей цветов ВИКИ:
def print_rgb(rgb)
puts "[%s]" % rgb.map {|val| "%5.1f" % ((val / 255.0) * 100) }.join(", ")
end
print_rgb hsi_to_rgb([ 0, 100/100.0, 33.3/100.0]) # => [100.0, 0.0, 0.0] Wiki: 100% 0% 0%
print_rgb hsi_to_rgb([ 60, 100/100.0, 50/100.0]) # => [ 74.9, 74.9, 0.0] Wiki: 75% 75% 0%
print_rgb hsi_to_rgb([ 120, 100/100.0, 16.7/100.0]) # => [ 0.0, 50.2, 0.0] Wiki: 0% 50% 0%
print_rgb hsi_to_rgb([ 180, 40/100.0, 83.3/100.0]) # => [ 49.8, 100.0, 100.0] Wiki: 50% 100% 100%
print_rgb hsi_to_rgb([ 240, 25/100.0, 66.7/100.0]) # => [ 50.2, 50.2, 100.0] Wiki: 50% 50% 100%
print_rgb hsi_to_rgb([ 300, 57.1/100.0, 58.3/100.0]) # => [ 74.9, 25.1, 74.9] Wiki: 75% 25% 75%
print_rgb hsi_to_rgb([ 61.8, 69.9/100.0, 47.1/100.0]) # => [ 62.7, 64.3, 14.1] Wiki: 62.8% 64.3% 14.2%
print_rgb hsi_to_rgb([251.1, 75.6/100.0, 42.6/100.0]) # => [ 25.5, 10.6, 91.8] Wiki: 25.5% 10.4% 91.8%
print_rgb hsi_to_rgb([134.9, 66.7/100.0, 34.9/100.0]) # => [ 11.8, 67.5, 25.5] Wiki: 11.6% 67.5% 25.5%
print_rgb hsi_to_rgb([ 49.5, 91.1/100.0, 59.3/100.0]) # => [ 94.1, 78.4, 5.1] Wiki: 94.1% 78.5% 5.3%
print_rgb hsi_to_rgb([283.7, 68.6/100.0, 59.6/100.0]) # => [ 70.6, 18.8, 89.8] Wiki: 70.4% 18.7% 89.7%
print_rgb hsi_to_rgb([ 14.3, 44.6/100.0, 57/100.0]) # => [ 93.3, 46.3, 31.8] Wiki: 93.1% 46.3% 31.6%
print_rgb hsi_to_rgb([ 56.9, 36.3/100.0, 83.5/100.0]) # => [100.0, 97.3, 53.3] Wiki: 99.8% 97.4% 53.2%
print_rgb hsi_to_rgb([162.4, 80/100.0, 49.5/100.0]) # => [ 9.8, 79.6, 59.2] Wiki: 9.9% 79.5% 59.1%
print_rgb hsi_to_rgb([248.3, 53.3/100.0, 31.9/100.0]) # => [ 21.2, 14.9, 59.6] Wiki: 21.1% 14.9% 59.7%
print_rgb hsi_to_rgb([240.5, 13.5/100.0, 57/100.0]) # => [ 49.4, 49.4, 72.2] Wiki: 49.5% 49.3% 72.1%
Значения немного отличаются, так как метод возвращает целочисленные значения RGB из диапазона 0..255
Как сказал Ротем, значения HSI, которые я пытался преобразовать в RGB, находятся вне диапазона RGB.
Все остальные значения RGB 16,7 Млн цветов преобразованы правильно.