Недавно я столкнулся с проблемой программного осветления цветов, и это оказалось необычайной кроличьей норой. Проблема в том, что наивное решение, заключающееся в простом добавлении некоторого значения дельты к значениям RGB по отдельности, выглядит ужасно и меняет оттенок цвета после клипа самого яркого компонента. Это очевидно, если у вас есть красный светодиод и, например, R = 240, G = 60, B = 60, и вы хотите добавить 20, тогда вам нужно обрезать R = 255 максимум, но G = 80 и B = 80продолжают увеличиваться. Чем больше вы добавите, тем больше свет изменит свой оттенок, и тогда вы получите белый свет, который снова выглядит реалистично. Конечно, вы можете добавить столько цвета, сколько у вас есть. Но результат некрасивый.
Один из способов получить желаемый эффект — преобразовать цвет из RGB в HSL, цветовое пространство, в котором цвета кодируются оттенком, насыщенностью и яркостью. Затем вы можете увеличить яркость, что выглядит довольно аккуратно. Цветовое пространство Lab еще лучше подходит для этого. Но для этого требуются некоторые библиотеки, и, как я, к своему ужасу, понял, самостоятельно работать с цветовыми пространствами ужасно сложно. К сожалению, я программировал на Go, и все библиотеки цветов, которые я нашел, очень затрудняли преобразование в стандартный color.Color, который мне нужен в качестве вывода.
К счастью, я нашел хороший stackoverflow post, в котором Марк Рэнсом объясняет проблему и то, как ее исправить в RGB путем перераспределения цветов. Ключ в том, чтобы перераспределить равномерно, как только цвет начинает обрезаться. Код был на Python, поэтому я преобразовал его в Go:
func minMax(array ...int) (int, int) { var max int = array[0] var min int = array[0] for _, value := range array { if max < value { max = value } if min > value { min = value } } return min, max } func redistributeRGB(r, g, b float64) (int, int, int) { threshold := 255.999 _, m := minMax(int(r), int(g), int(b)) if float64(m) <= threshold { return int(r), int(g), int(b) } total := r + g + b if total >= 3*threshold { return int(r), int(g), int(b) } x := (3*threshold - total) / (3*float64(m) - total) gray := threshold - x*float64(m) return int(gray + x*r), int(gray + x*g), int(gray + x*b) }
Затем в моем виджете я могу вычислить цвет, умножив цвет на 1 + напряжение моего светодиода из [0, 1] и перераспределив значения RGB:
func (s *SignalLightWidget) computeColor(volt float64, alpha uint8) color.Color { r, g, b, _ := s.offColor.RGBA() fr := float64(r) * (1 + volt) fg := float64(g) * (1 + volt) fb := float64(b) * (1 + volt) nr, ng, nb := redistributeRGB(fr, fg, fb) return color.RGBA{uint8(nr), uint8(ng), uint8(nb), alpha} }
Я добавил немного дополнительного альфа-смешивания вокруг света и увеличил его, когда напряжение выше 0,9. Результат выглядит нормально: