Воссоздайте цвет HSV, используя режимы наложения

Я работаю над приложением, которое создает изображения, чей оттенок, насыщенность и значение изменяются в соответствии с различными параметрами. Из соображений производительности имеет смысл визуализировать компоненты оттенка, насыщенности и значения по отдельности, а затем объединить их вместе, используя режимы наложения в стиле Photoshop (умножение, наложение, экран, оттенок и т. Д.).

Я уже знаю, как сделать это для изображений RGB: разделить каждый канал на свое собственное красное, зеленое или синее изображение со значениями от прозрачного до цвета этого канала. Сложите их вместе поверх черного и установите их режим смешивания на Screen, и, эй, presto, у вас есть цветное изображение:

RGB изображение из компонентов

Как бы я сделал это с изображением, определенным значениями HSV? Мое приложение часто меняет один из этих каналов без изменения двух других, и это ускорило бы мой рендеринг, если бы я мог комбинировать существующие изображения на графическом процессоре вместо того, чтобы воспроизводить совершенно новое изображение каждый раз, когда что-то меняется.

Вот пример:H, S и V

В этом примере оттенок варьируется от 0 ° до 360 ° по окружности, насыщенность варьируется от 0% до 100% от центра к краю, а яркость (V) изменяется от 0% до 100% по окружности. Это типично для изображения, которое создает мое приложение. Есть ли комбинация общих режимов наложения, которые я мог бы использовать, чтобы создать эти каналы отдельно и скомпоновать их математически совершенным способом?

1 ответ

Мое приложение часто меняет один из этих каналов без изменения двух других, и это ускорило бы мой рендеринг, если бы я мог комбинировать существующие изображения на графическом процессоре вместо того, чтобы воспроизводить совершенно новое изображение каждый раз, когда что-то меняется. [OP, @ZevEisenberg]

Что касается быстродействия и графического процессора, я бы просто добавил функцию преобразования в фрагментный шейдер ( например). Это будет считывать HSV, сохраненный в текстуре или трех разных текстурах, делать преобразование на пиксель и выводить RGB. Легко и приятно. Я не вижу никакой выгоды в том, чтобы не менять другие слои, так как H, S или V будут влиять на все каналы RGB. Возможно сохранение промежуточных результатов RGB, таких как hue=hsv2rgb(H,1,1)и обновление с final=(hue*S+1-S)*V, кэширование hue-to-rgb, но я не думаю, что оно того стоит.

В любом случае, каждый режим наложения имеет простую формулу, и вы можете связать их вместе для HSV, включающего слишком сложный набор промежуточных текстур, но он будет намного медленнее, прежде всего, из-за ненужного временного хранения и пропускной способности памяти. Не говоря уже о том, что попытка переписать формулу в функции смешивания звучит довольно сложно, чем с ветвлением, делением, fract, зажимы, абсолюты и т.д...

Мне очень интересно решение для разделения изображения на его компоненты HSV и воссоздания исходного изображения с использованием режимов наложения в Photoshop. [Баунти, @phisch]

Что касается фотошопа... Я не из денег. Так что в GIMP, естьColours -> Components -> Compose/Decompose который делает это для вас. Я был бы немного удивлен, если бы этого не было в фотошопе, но тогда вроде бы тоже нет. Возможно, есть скрипты / плагины для фотошопа, которые могут это сделать, если нет? Но ты специально сказал смешивание. Ваш вопрос может получить более пристальное внимание на https://graphicdesign.stackexchange.com/. Ниже я дал представление о сложности, и я сомневаюсь, что фотошоп сможет это сделать. Могут быть способы обойти значения пикселей за пределами от 0 до 1, но тогда вы можете столкнуться с проблемами точности, но это просто не должно быть сделано.


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

Я начну со следующей функции ( отсюда) и трех текстур HSV...

vec3 hsv2rgb(vec3 c)
{
    vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0);
    vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www);
    return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y);
}

часчасчас

Я знаю только OpenGL, и я не уверен, как бы я делал это без текстур с плавающей запятой или некоторых расширенных функций смешивания, поэтому я использую их. Но мне разрешено использовать только смешивание (без шейдеров). Для констант я сделаю текстуры с (1,1,1), (1,2/3,1/3), (3,3,3), (6,6,6) (1/255,1/255,1/255), (255,255,255), (1/2,1/2,1/2) и (0,0,0), потому что я не мог заставить GL_ZERO масштабироваться с GL_DIFFERENCE_NV,

  1. начать с оттенка текстуры
  2. используйте добавку, чтобы добавить (1,2 / 3,1 / 3)
  3. найти дробную часть

    1. с вычитающим смешением, вычтите 0,5 (это для floor() как я предполагаю, GL округляет цвета при преобразовании в 8 бит. если нет, пропустите это)
    2. уменьшить на 1/255. это можно сделать с помощью обычного альфа-смешивания, но вместо этого я изменил цветовой текстуры.
    3. пройти через текстуру без плавающей точки, чтобы округлить до ближайшей 1/255
    4. уменьшить масштаб на 255 (обратно в текстуру с плавающей точкой)

      целое число

    5. Теперь у нас есть целочисленный компонент. вычесть это из того, что мы начали с

      дробный

  4. масштаб 6

  5. с вычитающим смешиванием, возьмите 3
  6. принять абсолютное значение

    Я собираюсь просто использовать GL_DIFFERENCE_NV для этого, но без него может быть способ использовать два отдельных зажима для следующего шага. так как негативы будут в любом случае зажаты, что-то вроде clamp(p-K.xxx,0,1) + clamp(-p-K.xxx,0,1),

  7. вычесть 1

    хорошо, это сделано

  8. может зажимать, проходя через текстуру без плавающей точки, но просто собираюсь использовать GL_MIN

  9. Теперь я мог бы использовать альфа-смешивание для mix(), но насыщенность загружается как черно-белое изображение без альфа-канала. так как он смешивает белый цвет, делать это на самом деле проще...

    масштаб по насыщенности

  10. добавить 1
  11. вычесть насыщенность

    и насыщение было применено

  12. масштабировать по значению

    и есть изображение

  13. перерыв на кофе

Все сделано с помощью

  • glBlendEquation с GL_FUNC_REVERSE_SUBTRACT, GL_MIN а также GL_DIFFERENCE_NV
  • glBlendFunc

Вот мой код...

//const tex init
constTex[0] = makeTex() with 1, 1, 1...
constTex[1] = makeTex() with 1, 2/3, 1/3...
constTex[2] = makeTex() with 3, 3, 3...
constTex[3] = makeTex() with 6, 6, 6...
constTex[4] = makeTex() with 1/255, 1/255, 1/255...
constTex[5] = makeTex() with 255, 255, 255...
constTex[6] = makeTex() with 1/2, 1/2, 1/2...
constTex[7] = makeTex() with 0, 0, 0...

...

fbo[0] = makeFBO() with GL_RGB
fbo[1] = makeFBO() with GL_RGB32F
fbo[2] = makeFBO() with GL_RGB32F

...


hsv[0] = loadTex() hue
hsv[1] = loadTex() value
hsv[2] = loadTex() saturation

...

fbo[1].bind();
glDisable(GL_BLEND);
draw(hsv[0]); //start with hue
glEnable(GL_BLEND);
glBlendEquation(GL_FUNC_ADD);
glBlendFunc(GL_ONE, GL_ONE); //add
draw(constTex[1]); //(1, 2/3, 1/3)
glBlendFunc(GL_ONE, GL_ONE);
fbo[1].unbind();

//compute integer part
fbo[2].bind();
glDisable(GL_BLEND);
draw(*fbo[1].colour[0]); //copy the last bit
glEnable(GL_BLEND);
glBlendEquation(GL_FUNC_REVERSE_SUBTRACT);
glBlendFunc(GL_ONE, GL_ONE); //subtract
draw(constTex[6]); //0.5
glBlendEquation(GL_FUNC_ADD);
glBlendFunc(GL_ZERO, GL_SRC_COLOR); //scale down
draw(constTex[4]); //1/255
fbo[2].unbind();

fbo[0].bind(); //floor to integer
glDisable(GL_BLEND);
draw(*fbo[2].colour[0]);
fbo[0].unbind();

fbo[2].bind(); //scale back up
glDisable(GL_BLEND);
draw(*fbo[0].colour[0]);
glEnable(GL_BLEND);
glBlendEquation(GL_FUNC_ADD);
glBlendFunc(GL_ZERO, GL_SRC_COLOR); //scale up
draw(constTex[5]); //255
fbo[2].unbind();

//take integer part for fractional
fbo[1].bind();
glEnable(GL_BLEND);
glBlendEquation(GL_FUNC_REVERSE_SUBTRACT);
glBlendFunc(GL_ONE, GL_ONE); //subtract
draw(*fbo[2].colour[0]); //integer part
glBlendEquation(GL_FUNC_ADD);
glBlendFunc(GL_ZERO, GL_SRC_COLOR); //scale
draw(constTex[3]); //6
glBlendEquation(GL_FUNC_REVERSE_SUBTRACT);
glBlendFunc(GL_ONE, GL_ONE); //subtract
draw(constTex[2]); //3
glBlendEquation(GL_DIFFERENCE_NV);
glBlendFunc(GL_ZERO, GL_ONE); //take the absolute
draw(constTex[7]); //0
glBlendEquation(GL_FUNC_REVERSE_SUBTRACT);
glBlendFunc(GL_ONE, GL_ONE); //subtract
draw(constTex[0]); //1
glBlendEquation(GL_MIN);
glBlendFunc(GL_ONE, GL_ONE); //clamp (<0 doesn't matter, >1 use min)
draw(constTex[0]); //1
glBlendEquation(GL_FUNC_ADD);
glBlendFunc(GL_ZERO, GL_SRC_COLOR); //scale
draw(hsv[1]); //saturation
glBlendEquation(GL_FUNC_ADD);
glBlendFunc(GL_ONE, GL_ONE); //add
draw(constTex[0]); //1
glBlendEquation(GL_FUNC_REVERSE_SUBTRACT);
glBlendFunc(GL_ONE, GL_ONE); //subtract
draw(hsv[1]); //saturation
glBlendEquation(GL_FUNC_ADD);
glBlendFunc(GL_ZERO, GL_SRC_COLOR); //scale
draw(hsv[2]); //saturation
fbo[1].unbind();

fbo[1].blit(); //check result
Другие вопросы по тегам