Воссоздать цвет HSV с помощью режимов наложения

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

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

Изображение RGB, созданное из компонентов

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

Вот пример: изображение, созданное с отдельными каналами H, S и V

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


person Zev Eisenberg    schedule 03.10.2012    source источник
comment
Думаю, это очень интересный вопрос. Я тоже пытаюсь найти ответ. Вы что-то нашли?   -  person Alejandro    schedule 09.08.2013
comment
Я действительно играл с этим совсем недавно. Точного решения не нашел, но Screen подходит близко. Изображение, которое я создал в Photoshop, на самом деле выглядело лучше (более яркие, более насыщенные цвета), чем версия, созданная с помощью кода, но, конечно, если вы стремитесь к точности, это не поможет. Возможно, вы могли бы использовать комбинацию режимов наложения Hue и Lightness с дублированными слоями?   -  person Zev Eisenberg    schedule 09.08.2013


Ответы (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. [Bounty, @phisch]

Что касается фотошопа ... Я не на деньгах. Итак, в gimp есть Colours -> Components -> Compose/Decompose, который делает это за вас. Я был бы немного удивлен, если бы этого не было в фотошопе, но это тоже не так. Возможно, есть скрипты / плагины для фотошопа, которые могли бы это сделать, если нет? Но вы конкретно сказали смешивание. Вашему вопросу можно уделить больше внимания на https://graphicdesign.stackexchange.com/. Ниже я дал представление о сложности, и я сомневаюсь, что Photoshop действительно может это сделать. Могут быть способы обойти значения пикселей за пределами от 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);
}

hh h

Я знаю только 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. with subtractive blending, subtract 0.5 (this is for a floor() as I'm assuming GL rounds colours when converting to 8 bits. if not, skip this)
    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
person jozxyqk    schedule 06.03.2015