Перейти к содержимому

Fingercomp's Playground

  • записи
    92
  • комментарий
    371
  • просмотров
    221 627
Fingercomp

Обновлено 2019-06-16. Опять (как же мне форумный редактор этот надоел, если честно) пофиксил форматирование. Если опять слетит — рекомендую прочесть статью на кукбуке, где я с чуть более красивым кодом рассказываю о том же.

Сообщение добавил Fingercomp

Палитры OpenComputers

Fingercomp

1 590 просмотров

Немногие знают, как работают палитры в OpenComputers. Расскажу здесь, как избавиться от необходимости прописывать гектары цветов в палитре, покажу, как упаковываются цвета в OpenComputers и дам пару алгоритмов для работы с индексами.

 

Сразу условимся, что индексы палитр у нас будут начинаться с нуля.

 

На каждой из трёх уровней видеокарт и мониторов своя поддерживаемая палитра цветов. Будем двигаться снизу вверх.

 

Первый уровень
Палитра состоит из двух цветов: чёрного и заданного в конфиге (по умолчанию белого). При конвертации цвета в индекс палитры вернётся ближайший к данному цвет из палитры.

 

Для определения разницы между цветами здесь и далее используется функция delta. Вот как она выглядит (вместе с функций extract, выделяющей из числа вида 0xABCDEF числа 0xAB, 0xCD, 0xEF):

local function extract(color)
  color = color % 0x1000000
  local r = math.floor(color / 0x10000)
  local g = math.floor((color - r * 0x10000) / 0x100)
  local b = color - r * 0x10000 - g * 0x100
  return r, g, b
end

local function delta(color1, color2)
  local r1, g1, b1 = extract(color1)
  local r2, g2, b2 = extract(color2)
  local dr = r1 - r2
  local dg = g1 - g2
  local db = b1 - b2
  return (0.2126 * dr^2 +
          0.7152 * dg^2 +
          0.0722 * db^2)
end
 

Теперь можно конвертировать цвет в индекс палитры. Суть такова: выбираем из двух цветов ближайший и возвращаем его.

local palette = {
  0x000000,  CONFIG.monochromeColor
}

local function t1deflate(color)
  -- Сначала проверяем, совпадает ли данный цвет
  -- с каким-либо из палитры
  for idx, v in pairs(palette) do
    if v == color then
      return idx
    end
  end
  
  -- Составляем таблицу разниц между цветами
  local deltas = {}
  
  for idx, v in pairs(palette) do
    table.append(deltas, {idx, delta(v, color)})
  end
  
  -- Сортируем по увеличению разницы
  table.sort(deltas, function(a, b)
    return a[2] < b[2]
  end)
  
  -- Первый элемент будет с наименьшей разницей,
  -- то есть искомый. Возвращаем индекс.
  
  return deltas[1][1] - 1
end


В случае с конвертацией из из индекса в цвет всё просто.

local function t1inflate(index)
  return palette[index + 1]
end

Как и говорил.


Второй уровень
В палитре второго уровня имеется 16 закреплённых цветов:

local palette = {0xFFFFFF, 0xFFCC33, 0xCC66CC, 0x6699FF,
                 0xFFFF33, 0x33CC33, 0xFF6699, 0x333333,
                 0xCCCCCC, 0x336699, 0x9933CC, 0x333399,
                 0x663300, 0x336600, 0xFF3333, 0x000000}


В остальном конвертация из иднекса в цвет и из цвета в иднекс аналогична алгоритмам для первого уровня. Так как они были даны в общем виде, код для них тот же самый.

local t2deflate = t1deflate
local t2inflate = t1inflate


Третий уровень
Палитра третьего уровня содержит уже 256 цветов: первые 16 цветов изменяются, а остальные соответствуют цветам палитры RGB-6-8-5. Это означает, что можно смешивать 6 оттенков красного, 8 оттенков зелёного и 5 оттенков синего. В общем-то, довольно очевидна причина такого выбора: человеческий глаз лучше всего различает оттенки зелёного и хуже всего — оттенки синего.


В любом случае, здесь алгоритмец будет посложнее. Сначала нужно сгенерировать палитру.
Начнём с первых 16 цветов. Они не включаются в палитру RGB-6-8-5, поэтому их заполнять нужно отдельно. В OpenComputers по умолчанию они содержат оттенки серого. Так как чёрный и белый уже включены в основную, зафиксированную палитру, то заново их дублировать смысла нет.

local palette = {}

-- grayscale
for i = 1, 16, 1 do
  palette[i] = 0xFF * i / (16 + 1) * 0x10101
end


Таким образом в таблице получаются следующие оттенки серого:

0x0F, 0x1E, 0x2D, 0x3C, 0x4B, 0x5A, 0x69, 0x78,0x87, 0x96, 0xA5, 0xB4, 0xC3, 0xD2, 0xE1, 0xF0

Эти цвета мы записываем в индексы от 0 до 15. Теперь нужно сгенерировать остальные цвета — они не изменяются. Здесь будет посложнее.
Посмотрим на картинку с палитрой:
RGB_6-8-5levels_palette.png
В OpenComputers левая верхняя ячейка палитры (0x000000) имеет индекс 16, а правая нижняя (0xFFFFFF) имеет индекс 255. Индексы распределяются слева направо, сверху вниз. То есть правая верхняя ячейка (0x00FFFF) имеет индекс 55, а вторая сверху и левая (0x330000) — это номер 56. Отсюда вытекает следующий алгоритм нахождения цвета: сначала найти индексы отдельно по R, G, B, затем для каждого из этих трёх индексов найти соответствующий ему оттенок цвета, а затем всё сложить.

for idx = 16, 255, 1 do
  local i = idx - 16
  local iB = i % 5
  local iG = (i / 5) % 8
  local iR = (i / 5 / 8) % 6
  local r = math.floor(iR * 0xFF / (6 - 1) + 0.5)
  local g = math.floor(iG * 0xFF / (8 - 1) + 0.5)
  local b = math.floor(iB * 0xFF / (5 - 1) + 0.5)
  palette[idx + 1] = r * 0x10000 + g * 0x100 + b
end


К слову сказать, math.floor(x + 0.5) - это округление до ближайшего целого.

 

Всё. Палитра есть, теперь можно, наконец-то, конвертировать индексы между цветами.
Из индексов получить цвет довольно просто. Достаточно использовать ту же функцию, что и для предыдущих уровней:

t3inflate = t2inflate


С обратной же конвертацией всё в разы сложнее. Функция, используемая в OC, подбирает ближайший цвет очень хитрым алгоритмом, который я привожу ниже.

local function t3deflate(color)
  local paletteIndex = t2deflate(color)

  -- Если цвет из палитры, то используем значение выше
  for k, v in pairs(palette) do
    if v == color then
      return paletteIndex
    end
  end
  
  -- Иначе используем хитромудрый код
  local r, g, b = extract(color)
  local idxR = math.floor(r * (6 - 1) / 0xFF + 0.5)
  local idxG = math.floor(g * (8 - 1) / 0xFF + 0.5)
  local idxB = math.floor(b * (5 - 1) / 0xFF + 0.5)
  local deflated = 16 + idxR * 8 * 5 + idxG * 5 + idxB
  
  if (delta(t3inflate(deflated % 0x100), color) < 
      delta(t3inflate(paletteIndex & 0x100), color)) then
    return deflated
  else
    return paletteIndex
  end
end
 

В общем-то, это всё. Показал портированный со Scala на Lua код, который используется в OpenComputers. С помощью этого можно оптимизировать операции с экраном, выбирая поддерживаемые монитором цвета. И заодно избавиться от таблиц цветов, которые некоторые буквально берут и копипастят в файл, даже не задумываясь об изменяемых цветах палитры.
Особенно это важно, когда берётся значение цвета через gpu.get, потому что следующий код всегда вернёт false:

local gpu = require("component").gpu

gpu.setForeground(0x20AFFF)
gpu.setBackground(0x20AFFF)
gpu.set(1, 1, "HI")
return select(2, gpu.get(1, 1)) == 0x20AFFF

И всё потому, что gpu.get возвращает уже приведённый к индексу из палитры цвет. А 0x20AFFF в палитре, если не менять первые 16 цветов, не имеется.


Enjoy :P

  • Нравится 11


3 комментария


Рекомендуемые комментарии

Отлично! Давно хотел написать статью по работе с цветом. А теперь можно просто оставить ссылку, т. к. все основы хорошо разобраны.

Поделиться комментарием


Ссылка на комментарий

Присоединяйтесь к обсуждению

Вы можете написать сейчас и зарегистрироваться позже. Если у вас есть аккаунт, авторизуйтесь, чтобы опубликовать от имени своего аккаунта.

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

×   Вы вставили отформатированное содержимое.   Удалить форматирование

  Разрешено использовать не более 75 эмодзи.

×   Ваша ссылка была автоматически встроена.   Отобразить как ссылку

×   Ваш предыдущий контент был восстановлен.   Очистить редактор

×   Вы не можете вставлять изображения напрямую. Загружайте или вставляйте изображения по ссылке.

×
×
  • Создать...