Воссоздайте цвет HSV, используя режимы наложения
Я работаю над приложением, которое создает изображения, чей оттенок, насыщенность и значение изменяются в соответствии с различными параметрами. Из соображений производительности имеет смысл визуализировать компоненты оттенка, насыщенности и значения по отдельности, а затем объединить их вместе, используя режимы наложения в стиле Photoshop (умножение, наложение, экран, оттенок и т. Д.).
Я уже знаю, как сделать это для изображений RGB: разделить каждый канал на свое собственное красное, зеленое или синее изображение со значениями от прозрачного до цвета этого канала. Сложите их вместе поверх черного и установите их режим смешивания на Screen, и, эй, presto, у вас есть цветное изображение:
Как бы я сделал это с изображением, определенным значениями HSV? Мое приложение часто меняет один из этих каналов без изменения двух других, и это ускорило бы мой рендеринг, если бы я мог комбинировать существующие изображения на графическом процессоре вместо того, чтобы воспроизводить совершенно новое изображение каждый раз, когда что-то меняется.
Вот пример:
В этом примере оттенок варьируется от 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 / 3,1 / 3)
найти дробную часть
- с вычитающим смешением, вычтите 0,5 (это для
floor()
как я предполагаю, GL округляет цвета при преобразовании в 8 бит. если нет, пропустите это) - уменьшить на 1/255. это можно сделать с помощью обычного альфа-смешивания, но вместо этого я изменил цветовой текстуры.
- пройти через текстуру без плавающей точки, чтобы округлить до ближайшей 1/255
уменьшить масштаб на 255 (обратно в текстуру с плавающей точкой)
Теперь у нас есть целочисленный компонент. вычесть это из того, что мы начали с
- с вычитающим смешением, вычтите 0,5 (это для
масштаб 6
- с вычитающим смешиванием, возьмите 3
принять абсолютное значение
Я собираюсь просто использовать
GL_DIFFERENCE_NV
для этого, но без него может быть способ использовать два отдельных зажима для следующего шага. так как негативы будут в любом случае зажаты, что-то вродеclamp(p-K.xxx,0,1) + clamp(-p-K.xxx,0,1)
,вычесть 1
хорошо, это сделано
может зажимать, проходя через текстуру без плавающей точки, но просто собираюсь использовать
GL_MIN
Теперь я мог бы использовать альфа-смешивание для
mix()
, но насыщенность загружается как черно-белое изображение без альфа-канала. так как он смешивает белый цвет, делать это на самом деле проще...масштаб по насыщенности
- добавить 1
вычесть насыщенность
и насыщение было применено
масштабировать по значению
и есть изображение
перерыв на кофе
Все сделано с помощью
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