Пиксели светодиодной ленты отличаются от пикселей экрана отсутствием фоновой подсветки. Чёрный пиксель не будет выглядеть «чёрным» — он сольётся с фоном, а в движении фактически будет «прозрачным», но если добавить хотя бы единицу к любому цветовому каналу — этот пиксель будет светиться. В свою очередь, «серый» пиксель от белого будет отличаться только яркостью и будет казаться более тусклым, но всё же именно «белым».
Хранится и передаётся цвет пикселя в 24-bit RGB, но значительная часть этого цветового диапазона (ненасыщенные и яркие цвета) не слишком репрезентабельна в отдельных светодиодах. Кроме того, строить симпатичные градиенты в модели RGB не получится — смешивание RGB-цветов даёт не интуитивно-очевидный результат (жёлтый + синий = серый, а хочется — зелёный). Модели HSL и HSV подойдут лучше, но стандартные реализации используют нецелочисленную арифметику. Удобно будет использовать модель, которая сможет компактно хранить параметры цвета и быстро считать их RGB-значения, не используя числа с плавающей запятой и деление на произвольное число — речь идёт о микроконтроллере и сложные алгоритмы нам ни к чему, а деление (кроме небольших степеней двойки) и вовсе противопоказано.
Решение
Для своих нужд я использую модель HSV (HSB) с определёнными диапазонами для каждой из координат (немного magic numbers).
- Hue — тон, цикличная угловая координата.
- Value, Brightness — яркость, воспринимается как альфа-канал, при пиксель не светится, при — светится максимально ярко, в зависимости от и .
- Saturation. С отсутствием фона, значения дадут не серый цвет, а белый разной яркости, поэтому параметр можно называть Whiteness — он отражает степень «белизны» цвета. При цвет полностью определяется Hue, при цвет пикселя будет белым.
Математика модели строится на целочисленном делении на одну шестую максимального значения тона (размер одного сектора), поэтому в качестве удобно взять максимальное значение равное , например 48 или 96. Это позволит удобно вычислять RGB-цвет, а значение меньше 128 позволит строить градиент, который несколько раз содержит полный цветовой круг. В моделях HSV/HSL , в MS Paint — 240, в некоторых библиотеках — 255.
При выборе максимальных значений и перемножение даёт результат, лежащий в диапазоне .
Минимальная конфигурация HSV, простая в расчёте и ограниченная 8-битными значениями, при диапазонах , и даёт нам цвета, часть из которых практически повторяется, а часть отстоит довольно далеко друг от друга (справедливости ради замечу, что этим грешат все цветовые модели, оперирующие H — натянуть три стороны куба на конус не исказив длины не смог бы и Меркатор). Цифра кажется скудной в сравнении с 24-битным цветом в типичном мониторе (16,7 млн уникальных цветов), но её достаточно, чтобы разнообразить светодиодный реквизит, в котором раньше и семь цветов вместо одного зачастую были приятным бонусом. Координаты цвета в такой модели можно хранить в двух байтах.
Разумеется, разрешение HSV можно и нужно повышать до удобного. Я использую и 96 тонов, что даёт уже 27,6 тысяч оттенков. Пример кода с такими параметрами (конфигурация модели — max_value, max_whiteness, sixth_hue):
Код
typedef struct __attribute__ ((__packed__)) { uint8_t r; uint8_t g; uint8_t b; } RGB_t; typedef struct __attribute__ ((__packed__)) { uint8_t h; uint8_t s; uint8_t v; } HSV_t; const uint8_t max_whiteness = 15; const uint8_t max_value = 17; const uint8_t sixth_hue = 16; const uint8_t third_hue = sixth_hue * 2; const uint8_t half_hue = sixth_hue * 3; const uint8_t two_thirds_hue = sixth_hue * 4; const uint8_t five_sixths_hue = sixth_hue * 5; const uint8_t full_hue = sixth_hue * 6; inline RGB_t rgb(uint8_t r, uint8_t g, uint8_t b) { return (RGB_t) {r, g, b}; } inline HSV_t hsv(uint8_t h, uint8_t s, uint8_t v) { return (HSV_t) {h, s, v}; } const RGB_t black = {0, 0, 0}; RGB_t hsv2rgb(HSV_t hsv) { if (hsv.v == 0) return black; uint8_t high = hsv.v * max_whiteness;//channel with max value if (hsv.s == 0) return rgb(high, high, high); uint8_t W = max_whiteness - hsv.s; uint8_t low = hsv.v * W;//channel with min value uint8_t rising = low; uint8_t falling = high; uint8_t h_after_sixth = hsv.h % sixth_hue; if (h_after_sixth > 0) {//not at primary color? ok, h_after_sixth = 1..sixth_hue - 1 uint8_t z = hsv.s * uint8_t(hsv.v * h_after_sixth) / sixth_hue; rising += z; falling -= z + 1;//it's never 255, so ok } uint8_t H = hsv.h; while (H >= full_hue) H -= full_hue; if (H < sixth_hue) return rgb(high, rising, low); if (H < third_hue) return rgb(falling, high, low); if (H < half_hue) return rgb(low, high, rising); if (H < two_thirds_hue) return rgb(low, falling, high); if (H < five_sixths_hue) return rgb(rising, low, high); return rgb(high, low, falling); }
P.S.
TeX-коды в топик включил в порядке эксперимента. Если есть способ делать это удобнее или правильнее — намекните в ПМ.
Если будет интересно — могу отдельно пояснить особенности обсчёта HSV, в частности механику функций rising/falling и функцию обратного расчёта в «такой» HSV.
ссылка на оригинал статьи http://habrahabr.ru/post/166317/
Добавить комментарий