GLSL: How to calculate fragments output RGB value based on Photoshops curve value?

865 Views Asked by At

I am working on image editing using OPENGL in Android and I have applied filter to an image using photoshop curve now I want to reproduce the same in Android using glsl. Is there any formula to calculate single fragment color using photoshops curve output value?

EDIT

The math behind the photoshop curve has already been answered in this question How to recreate the math behind photoshop curves but I am not very clear about how to reproduce the same in glsl fragment shader.

Screen Shot of my photoshop curve enter image description here

1

There are 1 best solutions below

13
On BEST ANSWER

You're after a function fragColour = curves(inColour, constants...). If you have just the one curve for red, green and blue you apply the same curve to each individually. This answer has a link (below) to code which plots points along the function. The key line is:

double y = ...

Which you'd return from curves. The variable x in the loop is your inColour. All you need now is the constants which come from the points and the second derivative sd arrays. These you'll have to pass in as uniforms. The function first has to figure out which point each colour x is between (finding cur, next, sd[i] and sd[i+1]), then evaluate and return y.

EDIT:
If you just want to apply some curve you've created in photoshop then the problem is much simpler. The easiest way is to create a simple function that gives a similar shape. I use these as a starting point. A gamma correction curve is also quite common.

This is overkill, but if you do need a more exact result, you could create an image with a linear ramp (e.g. 255 pixels from black to white), apply your filter to it in photoshop and the result becomes a lookup table. Passing in all 255 values to a shader is expensive so if it's a smooth curve you could try some curve fitting tools (for example).

Once you have a function, simply apply it to your colour in GLSL. Applying a gamma curve for example is done like this:

fragColour = vec4(pow(inColour.rgb, 1.0 / gamma.rgb), inColour.a);

EDIT2:

The curve you have looks very similar to this:

fragColour = vec4(pow(inColour.rgb, 1.0 / vec3(0.6)), inColour.a);

Or even simpler:

fragColour = vec4(inColour.rgb * inColour.rgb, inColour.a);

Just in case the link dies, I'll copy the code here (not that I've tested it):

Point[] points = /* liste de points, triés par "x" croissants */

double[] sd = secondDerivative(points);

for(int i=0;i<points.length-1;i++) {
    Point cur   = points[i];
        Point next  = points[i+1];

        for(int x=cur.x;x<next.x;x++) {
        double t = (double)(x-cur.x)/(next.x-cur.x);

        double a = 1-t;
        double b = t;
        double h = next.x-cur.x;

        double y= a*cur.y + b*next.y + (h*h/6)*( (a*a*a-a)*sd[i]+ (b*b*b-b)*sd[i+1] );

        draw(x,y); /* ou tout autre utilisation */
    }
}

And the second derivative:

public static double[] secondDerivative(Point... P) {
    int n = P.length;
    double yp1=0.0; 
    double ypn=0.0;

    // build the tridiagonal system 
    // (assume 0 boundary conditions: y2[0]=y2[-1]=0) 
    double[][] matrix = new double[n][3];
    double[] result = new double[n];
    matrix[0][1]=1;
    for(int i=1;i<n-1;i++) {
        matrix[i][0]=(double)(P[i].x-P[i-1].x)/6;
        matrix[i][1]=(double)(P[i+1].x-P[i-1].x)/3;
        matrix[i][2]=(double)(P[i+1].x-P[i].x)/6;
        result[i]=(double)(P[i+1].y-P[i].y)/(P[i+1].x-P[i].x) - (double)(P[i].y-P[i-1].y)/(P[i].x-P[i-1].x);
    }
    matrix[n-1][1]=1;

    // solving pass1 (up->down)
    for(int i=1;i<n;i++) {
        double k = matrix[i][0]/matrix[i-1][1];
        matrix[i][1] -= k*matrix[i-1][2];
        matrix[i][0] = 0;
        result[i] -= k*result[i-1];
    }
    // solving pass2 (down->up)
    for(int i=n-2;i>=0;i--) {
        double k = matrix[i][2]/matrix[i+1][1];
        matrix[i][1] -= k*matrix[i+1][0];
        matrix[i][2] = 0;
        result[i] -= k*result[i+1];
    }

    // return second derivative value for each point P
    double[] y2 = new double[n];
    for(int i=0;i<n;i++) y2[i]=result[i]/matrix[i][1];
    return y2;
}