Recreate HSV color using blend modes

此生再无相见时 提交于 2019-12-05 00:25:12

问题


I’m working on an app that creates images whose hue, saturation, and value change according to different parameters. For performance reasons, it would make sense to render the hue, saturation, and value components separately, and then composite them together using Photoshop-style blending modes (multiply, overlay, screen, hue, etc).

I already know how to do this for RGB images: split each channel into its own red, green, or blue image with values ranging from transparent to that channel’s color. Layer them together on top of black and set their blend mode to Screen, and hey presto, you have your color image:

How would I do this with an image defined by HSV values? My app often changes one of these channels without changing the other two, and it would speed up my rendering if I could composite existing images on the GPU instead of rendering a completely new image every time something changes.

Here’s an example:

In this example, the hue varies from 0º to 360º around the circumference, the saturation varies from 0% to 100% from the center to the edge, and the brightness (V) varies from 0% to 100% around the circumference. This is typical of the kind of image my app creates. Is there a combination of common blending modes I could use to create these channels separately and composite them in a mathematically perfect way?


回答1:


My app often changes one of these channels without changing the other two, and it would speed up my rendering if I could composite existing images on the GPU instead of rendering a completely new image every time something changes. [OP, @ZevEisenberg]

With regard to fastness and the GPU, I'd just throw a conversion function into a fragment shader (e.g.). This would read HSV stored in a texture or three different textures, do the conversion per-pixel and output RGB. Nice and easy. I can't see any benefit to not changing other layers since either H, S or V will affect all RGB channels. Perhaps storing intermediate RGB results such as hue=hsv2rgb(H,1,1), and updating with final=(hue*S+1-S)*V, caching hue-to-rgb but I don't think it's worth it.

Anyway, each blend mode has a simple formula, and you could string them together for HSV involving an overly complex set of intermediate textures, but it will be much slower primarily because of the unnecessary temporary storage and memory bandwidth. Not to mention, trying to rewrite the formula into blend functions sounds pretty challenging, what with branching, divisions, fract, clamping, absolutes etc...

I am very interested in an a solution for splitting an Image into its HSV components and recreate the original Image using blend modes in Photoshop. [Bounty, @phisch]

With regard to photoshop... I'm not made of money. So in gimp, there's Colours -> Components -> Compose/Decompose which does this for you. I'd be kinda surprised if this doesn't exist in photoshop but then also kinda not. Perhaps there are photoshop scripts/plugins that could do it if not? But you did specifically say blending. Your question might get better attention at https://graphicdesign.stackexchange.com/. Below, I've given an idea of the complexity involved and I doubt photoshop can actually do it. There may be ways around pixel values outside 0 to 1, but then you might run into precision issues, it just shouldn't be done.


Anyway, a challenge is a challenge despite how impractical. The following is just for fun.

I'll start with the following function (from here) and three HSV textures...

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);
}

I only know OpenGL and I'm not sure how I'd do this without floating point textures or some of the extended blending functions, so I'm using them. But I'm only allowed to use blending (no shaders whatsoever). For the constants I'll make textures with (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) and (0,0,0) because I couldn't get GL_ZERO to scale with GL_DIFFERENCE_NV.

  1. start with hue texture
  2. use additive blending to add (1,2/3,1/3)
  3. find the fractional part

    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. scale down by 1/255. this could be done with regular alpha blending, but I've scaled with a colour texture instead.
    3. pass through a non-floating point texture to round to the nearest 1/255
    4. scale back up by 255 (back into a floating point texture)

    5. now we have the integer component. subtract this from what we started with

  4. scale by 6

  5. with subtractive blending, take 3
  6. take the absolute of the value

    I'm going to simply use GL_DIFFERENCE_NV for this, but without it there might be a way using two separate clamps for the next step. since negatives will be clamped anyway, something along the lines of clamp(p-K.xxx,0,1) + clamp(-p-K.xxx,0,1).

  7. subtract 1

    well, that's hue done
  8. could clamp by passing through a non-floating point texture, but just going to use GL_MIN

  9. now I could use alpha blending for mix(), but saturation is loaded as a B/W image without an alpha channel. since it's mixing white, doing it by hand is actually easier...

    scale by the saturation

  10. add 1
  11. subtract the saturation

    and saturation has been applied
  12. scale by value

    and there's the image
  13. coffee break

All done using

  • glBlendEquation with GL_FUNC_REVERSE_SUBTRACT, GL_MIN and GL_DIFFERENCE_NV
  • glBlendFunc

Here's my code...

//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


来源:https://stackoverflow.com/questions/12700733/recreate-hsv-color-using-blend-modes

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!