View Categories

Работа с цветом

RGB #

Красный (Red, R), зелёный (Green, G) и синий (Blue, B) являются основными цветами: смешивая эти три цвета в разных пропорциях можно получить плюс-минус все остальные цвета:

Этот наглядный "двухмерный" случай с кругами вы тоже скорее всего видели. Если раскручивать тему дальше, то можно задаться интенсивностью каждого цвета и получить итоговый цвет как функцию от трёх переменных, или же трёхмерное цветовое пространство RGB. Если интенсивности всех трёх цветов равны нулю - получится чёрный цвет, если все три максимальны - белый, а всё что между - оттенки:

На картинке выше интенсивность каждого цвета представлена диапазоном 0-255. Знакомое число, не правда ли? Всё верно, в большинстве применений диапазон каждого цвета кодируется одним байтом, потому что это удобно с точки зрения программирования и достаточно с точки зрения глаза: три цвета - три байта - 256*256*256 == 16.8 миллионов оттенков. Да, именно эта цифра часто фигурирует в рекламах смартфонов и телевизоров и именно столько оттенков мы можем получить из 8-бит ШИМ:

analogWrite(PIN_R, 10);
analogWrite(PIN_G, 130);
analogWrite(PIN_B, 210);

RGB24 (RGB888) #

Для хранения и передачи RGB цвета как единого числа часто используется формат RGB24 - три байта цвета объединяются в один тип данных (в данном случае ближайший uint32_t) в порядке RRGGBB. Результирующее число из трёх значений каналов красного, зелёного и синего можно получить так:

uint32_t RGB24(uint8_t r, uint8_t g, uint8_t b) {
    return ((uint32_t)r << 16) | ((uint32_t)g) << 8 | b;
}

Либо более быстрая реализация без сдвигов:

uint32_t RGB24(uint8_t r, uint8_t g, uint8_t b) {
    return (union { uint8_t bytes[4]; uint32_t hex; }){b, g, r, 0 }.hex;
}

Для "распаковки" цвета обратно в три переменные можно так же использовать сдвиги:

// col - rgb24 (uint32_t)
uint8_t r = col >> 16;
uint8_t g = col >> 8;
uint8_t b = col;

Либо более быстрый вариант:

// col - rgb24 (uint32_t)
uint8_t r = ((uint8_t*)&col)[2];
uint8_t g = ((uint8_t*)&col)[1];
uint8_t b = ((uint8_t*)&col)[0];

Либо опять же через union:

union RGB {
    uint32_t hex;
    struct {
        uint8_t b, g, r, a;
    } ch;
};

// col - rgb24 (uint32_t)
RGB rgb{col};

//Serial.println(rgb.ch.r);
//Serial.println(rgb.ch.g);
//Serial.println(rgb.ch.b);

RGB16 (RGB565) #

В embedded часто используется формат RGB565 - цвет кодируется двумя байтами (16 бит), из которых первые 5 бит - красный, далее 6 зелёных и 5 синих. Преобразовать из RGB24 можно так:

uint16_t rgb888to565(uint32_t col) {
    return ((col >> 8) & 0b1111100000000000) | ((col >> 5) & 0b0000011111100000) | ((col >> 3) & 0b0000000000011111);
}

Из трёх каналов цвета - так:

uint16_t rgb888to565(uint8_t r, uint8_t g, uint8_t b) {
    return ((r & 0b11111000) << 8) | ((g & 0b11111100) << 3) | ((b & 0b11111000) >> 3);
}

Расширить обратно до RGB888 можно так, с потерей младших битов яркости:

uint32_t rgb565to888(uint16_t col) {
    uint8_t r = (col >> 8) & 0b11111000;
    uint8_t g = (col >> 3) & 0b11111100;
    uint8_t b = (col << 3);
    return (union { uint8_t bytes[4]; uint32_t hex; }){b, g, r, 0xff}.hex;
}

Либо, согласно мануалу от Apple - так, этот вариант выполняется дольше, но масштабирует до полной 8-бит яркости канала (например красный 11111 превратится в 11111111):

uint32_t rgb565to888full(uint16_t col) {
    uint8_t r = ((col >> 11) * 255 + 15) / 31;
    uint8_t g = (((col >> 5) & 0b00111111) * 255 + 31) / 63;
    uint8_t b = ((col & 0b00011111) * 255 + 15) / 31;
    return (union { uint8_t bytes[4]; uint32_t hex; }){b, g, r, 0xff}.hex;
}

Распаковать обратно в каналы можно так:

// col - rgb565 (uint16_t)
uint8_t r = (col & 0b1111100000000000) >> 8;
uint8_t g = (col & 0b0000011111100000) >> 3;
uint8_t b = (col & 0b0000000000011111) << 3;

RGB wheel #

В цветовом пространстве RGB можно получить "радугу" - плавный переход между цветами в порядке цветов радуги, для этого нужно подавать сигналы согласно следующей закономерности:

Вариант реализации для 8 бит радуги (значение 0-255):

uint32_t raibow8(uint8_t value) {
  uint8_t shift, r, g, b;

  if (value > 170) {
    shift = (value - 170) * 3;
    r = shift;
    g = 0;
    b = 255 - shift;
  } else if (value > 85) {
    shift = (value - 85) * 3;
    r = 0;
    g = 255 - shift;
    b = shift;
  } else {
    shift = value * 3;
    r = 255 - shift;
    g = shift;
    b = 0;
  }

  // либо забрать как каналы
  return ((uint32_t)r << 16) | ((uint32_t)g) << 8 | b;
}

Либо вариант с 0-1530 значениями "радуги":

uint32_t wheel1530(uint16_t value) {
  uint8_t r, g, b;

  switch (value) {
    case 0 ... 255:
      r = 255;
      g = value;
      b = 0;
      break;
    case 256 ... 510:
      r = 510 - value;
      g = 255;
      b = 0;
      break;
    case 511 ... 765:
      r = 0;
      g = 255;
      b = value - 510;
      break;
    case 766 ... 1020:
      r = 0;
      g = 1020 - value;
      b = 255;
      break;
    case 1021 ... 1275:
      r = value - 1020;
      g = 0;
      b = 255;
      break;
    case 1276 ... 1530:
      r = 255;
      g = 0;
      b = 1530 - value;
      break;
  }

  // либо забрать как каналы
  return ((uint32_t)r << 16) | ((uint32_t)g) << 8 | b;
}

HSV #

Ещё одно полезное цветовое пространство - HSV (Hue Saturation Value) - цвет, насыщенность, яркость:

Быстрый вариант преобразования, не очень точный:

uint32_t HSVfast(uint8_t h, uint8_t s, uint8_t v) {
  uint8_t r, g, b;
  uint8_t value = ((24 * h / 17) / 60) % 6;
  uint8_t vmin = (long)v - v * s / 255;
  uint8_t a = (long)v * s / 255 * (h * 24 / 17 % 60) / 60;
  uint8_t vinc = vmin + a;
  uint8_t vdec = v - a;

  switch (value) {
    case 0: r = v; g = vinc; b = vmin; break;
    case 1: r = vdec; g = v; b = vmin; break;
    case 2: r = vmin; g = v; b = vinc; break;
    case 3: r = vmin; g = vdec; b = v; break;
    case 4: r = vinc; g = vmin; b = v; break;
    case 5: r = v; g = vmin; b = vdec; break;
  }

  // либо забрать как каналы
  return ((uint32_t)r << 16) | ((uint32_t)g) << 8 | b;
}

И медленный, но более красивый:

uint32_t HSV(uint8_t h, uint8_t s, uint8_t v) {
  float R, G, B;
  float H = h / 255.0;
  float S = s / 255.0;
  float V = v / 255.0;

  int i = int(H * 6);
  float f = H * 6 - i;
  float p = V * (1 - S);
  float q = V * (1 - f * S);
  float t = V * (1 - (1 - f) * S);

  switch (i % 6) {
    case 0: R = V, G = t, B = p; break;
    case 1: R = q, G = V, B = p; break;
    case 2: R = p, G = V, B = t; break;
    case 3: R = p, G = q, B = V; break;
    case 4: R = t, G = p, B = V; break;
    case 5: R = V, G = p, B = q; break;
  }

  uint8_t r = R * 255.0;
  uint8_t g = G * 255.0;
  uint8_t b = B * 255.0;

  // либо забрать как каналы
  return ((uint32_t)r << 16) | ((uint32_t)g) << 8 | b;
}
0 0 голоса
Рейтинг статьи
Подписаться
Уведомить о
guest

0 комментариев
Старые
Новые Популярные
Межтекстовые Отзывы
Посмотреть все комментарии
Прокрутить вверх