Home Technical Talk

The Photoshop color curve in HLSL?

Hi,
Does anyone know how to write a function in hlsl that does what the curve in the attached image does?
I want to make that math on a texture in a shader.
For example, I'd like to call the curve function like this
float4 modifiedTexture = curveFunction(texture);
Thanks! :)
curvefunction.jpg

Replies

  • JoakimMellergard
    So, if anyone's interested, I found a solution.

    This is glsl but can easily be translated to hlsl.
    Basically, open a random photo in PS, use the color curve to fit your preferences and save the curve out as a .acv file. Open the .acv file in PS, apply it to an empty image (256x1px or 256x256)and save it out as a format your engine accepts.

    Sample the original texture (inColor in example).
    Sample the curve texture (outColor in example). Prefer to use texture1D if your engine supports it!
    Use the (original) texture.channel as uv input for the curve texture.

    This effect requieres to sample 3 textures so again, if anyone knows how to do the same effect but with math I am eager to know! :)
    void main(void)
    {
        vec2 uv = gl_FragCoord.xy / Resolution.xy;
        
        vec3 InColor = texture2D(texture,uv ).xyz;
        vec4 OutColor;
        OutColor.r = texture2D(curveTexture, InColor.rr).r;
        OutColor.g = texture2D(curveTexture, InColor.gg).g;
        OutColor.b = texture2D(curveTexture, InColor.bb).b;
        
        gl_FragColor = vec4(InColor * OutColor.rgb, 1.0);
    }
    
  • RN
    Offline / Send Message
    RN sublime tool
    Hello Joakim.
    That Curves tool in Photoshop is performing a mapping of the colour values. You plug in a value A and it outputs a value B.
    In mathematics, the name of the device that does this is a "function."

    In order to transfer that Curves tool to a pixel shader, you need to devise a way to input a value A and get a value B with shader code.
    You found out that you can do this by using a "look-up texture." You go to Photoshop, create a 256 x 1 texture, draw a black to white gradient from the leftmost pixel to rightmost pixel, apply the Curves tool to this gradient and export it as a texture. In your shader code you then sample that altered gradient using the colour values from the sampled diffuse texture.

    The other way to do this is to evaluate a function directly with shader code.
    There are many ways to write functions, but to represent those curves that you can make with Photoshop it's best to use quadratic or cubic polynomial functions. The process involved in regressing a polynomial from specific points on a graph is complicated, but we can use tools to do the work for us.

    Looking at that curve screenshot that you posted, I went to this website ( Polynomial Regression Tools ) and proceeded as such:
    Based on the control points of your curve and that 4 x 4 grid I scrolled to the bottom of the page, typed 3 in the "number of data points" field and clicked "OK."
    Then I typed in the X and Y fields the coordinates of what those control points in that curve that you posted would look like, but I made sure to write the coordinates normalised to the range [ 0.0, 1.0 ].
    So that curve has 3 control points and I imagined that they were:

    [ 0, 0.37 ]
    [ 0.37, 0.125 ]
    [ 1, 1 ]

    When you insert those values in the X and Y table on that page and click "Calculate," it shows you the simplest function that goes through those points. The polynomial degree was set to 2, so the result is a quadratic function:

    y = 2.051051051x² - 1.421051051x + 0.37

    If you request more data points you can set higher degrees, but quadratic or cubic are the simplest and fastest to compute.
    To confirm that this is the function that we want, I went to another website ( FooPlot ) and wrote that function (all the part after the "y =") in the function field so that the website can plot it on the graph.
    We can see that the function looks very similar to the one you posted:

    function_Plot.png

    This confirms that that polynomial function is an adequate approximation of your custom curve from Photoshop.
    Now it's easy to use that in a shader. I'm not sure if the following is valid HLSL, but it should look like this:
    // Polynomial function equivalent to preset "My Preset" in Photoshop Curves tool.
    
    float curveFunction( float x )
    {   
        float y = 2.051051051 * pow( x, 2 ) - 1.421051051 * x + 0.37;
        return y;
    }
    
    // Usage:
    
    OutColor.r = curveFunction( InColor.r );
    ...
    
  • kodde
    Offline / Send Message
    kodde polycounter lvl 18
    Thank you for that explanation. I learnt something new.
  • kodde
    Offline / Send Message
    kodde polycounter lvl 18
    I played around a bit with this and my colleague advised me to try WolframAlpha. Was really easy to use. Just type in: polynomial (0, 0.37),(0.37,0.125),(1, 1). Worked like a charm.
  • JoakimMellergard
    Kryzon wrote: »
    Hello Joakim.
    That Curves tool in Photoshop is performing a mapping of the colour values. You plug in a value A and it outputs a value B.
    In mathematics, the name of the device that does this is a "function."

    In order to transfer that Curves tool to a pixel shader, you need to devise a way to input a value A and get a value B with shader code.
    You found out that you can do this by using a "look-up texture." You go to Photoshop, create a 256 x 1 texture, draw a black to white gradient from the leftmost pixel to rightmost pixel, apply the Curves tool to this gradient and export it as a texture. In your shader code you then sample that altered gradient using the colour values from the sampled diffuse texture.

    The other way to do this is to evaluate a function directly with shader code.
    There are many ways to write functions, but to represent those curves that you can make with Photoshop it's best to use quadratic or cubic polynomial functions. The process involved in regressing a polynomial from specific points on a graph is complicated, but we can use tools to do the work for us.

    Looking at that curve screenshot that you posted, I went to this website ( Polynomial Regression Tools ) and proceeded as such:
    Based on the control points of your curve and that 4 x 4 grid I scrolled to the bottom of the page, typed 3 in the "number of data points" field and clicked "OK."
    Then I typed in the X and Y fields the coordinates of what those control points in that curve that you posted would look like, but I made sure to write the coordinates normalised to the range [ 0.0, 1.0 ].
    So that curve has 3 control points and I imagined that they were:

    [ 0, 0.37 ]
    [ 0.37, 0.125 ]
    [ 1, 1 ]

    When you insert those values in the X and Y table on that page and click "Calculate," it shows you the simplest function that goes through those points. The polynomial degree was set to 2, so the result is a quadratic function:

    y = 2.051051051x² - 1.421051051x + 0.37

    If you request more data points you can set higher degrees, but quadratic or cubic are the simplest and fastest to compute.
    To confirm that this is the function that we want, I went to another website ( FooPlot ) and wrote that function (all the part after the "y =") in the function field so that the website can plot it on the graph.
    We can see that the function looks very similar to the one you posted:

    function_Plot.png

    This confirms that that polynomial function is an adequate approximation of your custom curve from Photoshop.
    Now it's easy to use that in a shader. I'm not sure if the following is valid HLSL, but it should look like this:
    // Polynomial function equivalent to preset "My Preset" in Photoshop Curves tool.
    
    float curveFunction( float x )
    {   
        float y = 2.051051051 * pow( x, 2 ) - 1.421051051 * x + 0.37;
        return y;
    }
    
    // Usage:
    
    OutColor.r = curveFunction( InColor.r );
    ...
    

    Hi Kryzon,
    Thank you so much! :D
Sign In or Register to comment.