使用混合模式重新创建HSV颜色

时间:2012-10-03 01:22:59

标签: graphics colors alphablending compositing color-blending

我正在开发一款应用程序,可根据不同的参数创建色调,饱和度和值发生变化的图像。出于性能原因,分别渲染色调,饱和度和值组件,然后使用Photoshop风格的混合模式(乘法,叠加,屏幕,色调等)将它们组合在一起是有意义的。

我已经知道如何为RGB图像执行此操作:将每个通道分成自己的红色,绿色或蓝色图像,其值从透明到该通道的颜色。将它们一起层叠在黑色上并将它们的混合模式设置为屏幕,嘿,你有你的彩色图像:

RGB image made from components

如何使用HSV值定义的图像执行此操作?我的应用程序经常更改其中一个通道而不更改其他两个通道,如果我可以在GPU上合成现有图像而不是每次发生变化时渲染全新图像,它都会加快渲染速度。

这是一个例子: an image generated with separate H, S, and V channels

在此示例中,色调在圆周周围从0º到360º变化,饱和度从中心到边缘从0%到100%变化,亮度(V)在圆周周围从0%变化到100% 。这是我的应用程序创建的典型图像。是否有一些常见的混合模式可以用来分别创建这些通道并以数学方式完美地合成它们?

1 个答案:

答案 0 :(得分:7)

  

我的应用程序经常更改其中一个通道而不更改其他两个通道,如果我可以在GPU上合成现有图像而不是每次更改时都呈现全新图像,它会加快我的渲染速度。 [OP,@ ZevEisenberg]

关于牢度和GPU,我只是将a conversion function扔进片段着色器(e.g.)。这将读取存储在纹理或三种不同纹理中的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]

关于photoshop ......我不是钱。 所以在gimp中,Colours -> Components -> Compose/Decompose 会为你做这件事。如果在Photoshop中不存在这种情况,我会感到有点惊讶,但有点也没有。也许有photoshop脚本/插件可以做到,如果没有?但你确实说过混合。您可以在https://graphicdesign.stackexchange.com/处更好地关注您的问题。下面,我已经了解了所涉及的复杂性,我怀疑Photoshop实际上可以做到这一点。可能存在0到1之外的像素值的方法,但是你可能会遇到精度问题,不应该这样做。


无论如何,尽管不切实际,挑战仍然是一项挑战。以下只是为了好玩。

我将从以下函数开始(来自here)和三个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);
}

h h 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_DIFFERENCE_NV来缩放GL_ZERO。< / p>

  1. 以色调纹理开始
  2. 使用添加剂混合添加(1,2 / 3,1 / 3)
  3. 找到小数部分

    1. 使用减法混合,减去0.5(这是floor(),因为我在转换为8位时假设GL 舍入颜色。如果没有,请跳过此处)
    2. 缩小1/255。这可以通过常规的alpha混合来完成,但我用颜色纹理缩放了。
    3. 通过非浮点纹理以舍入到最接近的1/255
    4. 缩小255(返回浮点纹理)

      integer

    5. 现在我们有整数组件。从我们开始的那个中减去这个

      fractional

  4. 按比例缩放

  5. 减法混合,取3
  6. 取绝对值

    我将简单地使用GL_DIFFERENCE_NV,但如果没有它,可能会有一种方法使用两个单独的夹子进行下一步。因为负面影响无论如何都会受到限制,类似于clamp(p-K.xxx,0,1) + clamp(-p-K.xxx,0,1)

  7. 减去1

    enter image description here嗯,这已经完成了

  8. 可以通过非浮点纹理进行钳制,但只是使用GL_MIN

  9. 现在我可以对mix()使用Alpha混合,但饱和度会加载为没有Alpha通道的黑白图像。因为它是混合白色,所以手工操作实际上更容易......

    按饱和度缩放

  10. 添加1
  11. 减去饱和度

    enter image description here并已应用饱和度

  12. 按值缩放

    enter image description here并且有图像

  13. 喝咖啡休息时间

  14. 全部使用

    完成
    • glBlendEquationGL_FUNC_REVERSE_SUBTRACTGL_MINGL_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