Оптимизация HSV для светодиодной ленты под управлением AVR

от автора

В качестве хобби я занимаюсь светодиодным реквизитом и столкнулся с интересной задачей — показать что-то «красивое» на управляемой светодиодной ленте вместо традиционной радуги.

Пиксели светодиодной ленты отличаются от пикселей экрана отсутствием фоновой подсветки. Чёрный пиксель не будет выглядеть «чёрным» — он сольётся с фоном, а в движении фактически будет «прозрачным», но если добавить хотя бы единицу к любому цветовому каналу — этот пиксель будет светиться. В свою очередь, «серый» пиксель от белого будет отличаться только яркостью и будет казаться более тусклым, но всё же именно «белым».

Хранится и передаётся цвет пикселя в 24-bit RGB, но значительная часть этого цветового диапазона (ненасыщенные и яркие цвета) не слишком репрезентабельна в отдельных светодиодах. Кроме того, строить симпатичные градиенты в модели RGB не получится — смешивание RGB-цветов даёт не интуитивно-очевидный результат (жёлтый + синий = серый, а хочется — зелёный). Модели HSL и HSV подойдут лучше, но стандартные реализации используют нецелочисленную арифметику. Удобно будет использовать модель, которая сможет компактно хранить параметры цвета и быстро считать их RGB-значения, не используя числа с плавающей запятой и деление на произвольное число — речь идёт о микроконтроллере и сложные алгоритмы нам ни к чему, а деление (кроме небольших степеней двойки) и вовсе противопоказано.

Решение

Для своих нужд я использую модель HSV (HSB) с определёнными диапазонами для каждой из координат (немного magic numbers).

  • Hue — тон, цикличная угловая координата.
  • Value, Brightness — яркость, воспринимается как альфа-канал, при V = 0 пиксель не светится, при Vmax = 17 — светится максимально ярко, в зависимости от H и S.
  • Saturation. С отсутствием фона, значения S = 0 дадут не серый цвет, а белый разной яркости, поэтому параметр W = Smax - S можно называть Whiteness — он отражает степень «белизны» цвета. При W = 0, S = Smax = 15 цвет полностью определяется Hue, при S=0, W = Wmax = 15 цвет пикселя будет белым.

Математика модели строится на целочисленном делении на одну шестую максимального значения тона (размер одного сектора), поэтому в качестве Hmax удобно взять максимальное значение равное 6 * 2^x, например 48 или 96. Это позволит удобно вычислять RGB-цвет, а значение меньше 128 позволит строить градиент, который несколько раз содержит полный цветовой круг. В моделях HSV/HSL Hmax = 360, в MS Paint — 240, в некоторых библиотеках — 255.

При выборе максимальных значений Bmax = 17 и Wmax = 15 перемножение B*W даёт результат, лежащий в диапазоне 0..255.

Минимальная конфигурация HSV, простая в расчёте и ограниченная 8-битными значениями, при диапазонах H = 0..11, B = 0..17 и W = 0..3 даёт нам 12*18*4 = 864 цвета, часть из которых практически повторяется, а часть отстоит довольно далеко друг от друга (справедливости ради замечу, что этим грешат все цветовые модели, оперирующие H — натянуть три стороны куба на конус не исказив длины не смог бы и Меркатор). Цифра кажется скудной в сравнении с 24-битным цветом в типичном мониторе (16,7 млн уникальных цветов), но её достаточно, чтобы разнообразить светодиодный реквизит, в котором раньше и семь цветов вместо одного зачастую были приятным бонусом. Координаты цвета в такой модели можно хранить в двух байтах.

Разумеется, разрешение HSV можно и нужно повышать до удобного. Я использую W = 0..15 и 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/


Комментарии

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

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