Subhasish Dash
Subhasish Dash

Reputation: 219

How to achieve brightness + contrast in WebGL?

I am trying to apply window level similar to this example ,drag your mouse over the image to see the brightness + contrast effect.

But I want to achieve the same using WebGL ,as GPU will process them more faster than CPU.

Here's what I have done:

Vertex Shader:

attribute vec3 attrVertexPos;
attribute vec2 attrTextureCoord;

varying highp vec2 vTextureCoord;

void main(void) {
    gl_Position = vec4(attrVertexPos, 0.81);
    vTextureCoord = attrTextureCoord;
}

Fragment Shader:

#ifdef GL_ES
    precision highp float;
#endif

varying highp vec2 vTextureCoord;

uniform sampler2D uImage;
uniform float brightnessFactor;
uniform float contrastFactor;

void main(void) {
    gl_FragColor = texture2D(uImage, vTextureCoord) *(brightnessFactor/contrastFactor);
}

But it doesn't work as expected according the link given above.

Here's the Javascript code:

var isMouseDown = false;

document.getElementById('canvas').onmousedown = function() { isMouseDown = true  };
document.getElementById('canvas').onmouseup   = function() { isMouseDown = false };
document.getElementById('canvas').onmousemove = function(e) {
    if (isMouseDown) {
        var intWidth = $('canvas').innerWidth();
        var intHeight = $('canvas').innerHeight();
        var x = (e.clientX/intWidth)*100;
        var y = (e.clientY/intHeight)*100;

        console.log(x/10 + ' :: ' + y/10);

        brightnessVal = x/10;
        gl.uniform1f(gl.getUniformLocation(program, "contrastFactor"), brightnessVal);
        contrastVal = y/10;
        gl.uniform1f(gl.getUniformLocation(program, "brightnessFactor"), contrastVal);
    }
};

Upvotes: 1

Views: 2122

Answers (1)

Kirill Dmitrenko
Kirill Dmitrenko

Reputation: 3629

The difference is in pixel manipulation. You're just multiplying by a coefficient (also, the names of the coefficient you use are incorrect), where as in the linked exampled something a bit more complicated is happening. Some colour range (described by its center and width) from the source image gets expanded to full [0,1] range:

newColor = (oldColor - rangeCenter) / rangeWidth + 0.5

Why is it doing this is beyond my knowledge (the page is an example for medical imaging library and I don't know anything about it). Nevertheless, I've managed to port the formula to your code. First, fragment shader changes:

#ifdef GL_FRAGMENT_PRECISION_HIGH
    precision highp float;
#else
    precision mediump float;
#endif

varying highp vec2 vTextureCoord;
uniform sampler2D uImage;

// New uniforms
uniform float rangeCenter;
uniform float rangeWidth;

void main(void) {
    vec3 c = texture2D(uImage, vTextureCoord).rgb;
    gl_FragColor = vec4(
        // The above formula, clamped to [0, 1]
        clamp((c - windowCenter) / windowWidth + 0.5, 0.0, 1.0),
        // Also, let's not screw alpha
        1
    );
}

As for JavaScript, I've taken the liberty to make it a bit closer to linked example:

var isMouseDown = false,
    // Initially we set "equality" colour mapping
    rangeCenter = 0.5,
    ragneWidth = 1,
    lastX, lastY;

document.getElementById('canvas').onmousedown = function(e) {
    lastX = e.clientX;
    lastY = e.clientY;
    isMouseDown = true
};
document.getElementById('canvas').onmouseup = function() {
    isMouseDown = false
};
document.getElementById('canvas').onmousemove = function(e) {
    if (isMouseDown) {
        var intWidth = $('canvas').innerWidth();
        var intHeight = $('canvas').innerHeight();

        // Change params according to cursor coords delta
        rangeWidth += (e.clientX - lastX) / intWidth;
        rangeCenter += (e.clientY - lastY) / intHeight;

        gl.uniform1f(
            gl.getUniformLocation(program, "rangeWidth"),
            rangeWidth
        );

        gl.uniform1f(
            gl.getUniformLocation(program, "rangeCenter"),
            rangeCenter
        );

        lastX = e.clientX;
        lastY = e.clientY;
    }
};

Old answer:

The problem is that, according to your code, you don't redraw the picture after mousemove event. Just insert a draw call (or several draw calls) with appropriate parameters after setting uniforms. For example, it may look like this:

document.getElementById('canvas').onmousemove = function(e) {
    if (isMouseDown) {
        // your code

        gl.drawArrays(/* params, e.g gl.TRIANGLES, 0 and vertex count */); 
    }
};

A better way would be using requestAnimationFrame callback to redraw. To do that, define a draw function, which will make the draw call for you and use it a requestAnimationFrame callback:

function draw () {
    /* your draw calls here */
}

document.getElementById('canvas').onmousemove = function(e) {
    if (isMouseDown) {
        // your code

        requestAnimationFrame(draw);
    }
};

P.S. Also, I'm intersted, where does this #ifdef GL_ES thing come from? It's incorrect to set highp precision based on GL_ES macro. Hardware doesn't have to support it according to the standard. The right way would be:

#ifdef GL_FRAGMENT_PRECISION_HIGH
    precision highp float;
#else
    precision mediump float;
#endif

Upvotes: 3

Related Questions