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






Фотография
* * * * * 3 голосов

Палитры OpenComputers

Написано Fingercomp , в OpenComputers, Programming in Lua, Tutorials 19 Апрель 2017 · 1 090 просмотров

палитра OpenComputers palette цвет color gpu gui OC

Немногие знают, как работают палитры в 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. Теперь нужно сгенерировать остальные цвета - они не изменяются. Здесь будет посложнее.
Посмотрим на картинку с палитрой:
Изображение
В 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






Фотография
electronic_steve
19 апр 2017 18:32

также палитра позволяет делать так
 

    • Totoro, Fingercomp, ECS и еще 1 это нравится

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

23 оттенка зеленого:
6w26ire.png
    • Totoro и Fingercomp это нравится

Обратные ссылки на эту запись [ URL обратной ссылки ]

Обратных ссылок на эту запись нет

0 посетителей

0 пользователей, 0 гостей, 0 анонимных

Лицензия

Новые комментарии