Compose an image with floating point layers in webgl

189 Views Asked by At

I have trying to render an image in the browser which is built like this:

  • A bunch of rectangles are each filled with a radial gradient (ideally Gaussian, but can be approximated with a few stopping points
  • Each rectangle is rotated and translated before being deposited on a drawing area

  • The image is flattened by summing all the intensities of the rectangles (and cropping to the drawing area's dimensions )

  • The intensity is rescaled so that the highest intensity is 255 and the lowest 0 (ideally I can apply some sort of gamma correction too)

  • Finally an image is drawn where the color of each pixel is taken from a palette of 256 colors.

The reason I cannot do this easily with a canvas object is that I need to be working in floating points or I'll lose precision. I do not know in advance what the maximum intensity and minimum intensity will be, so I cannot merely draw transparent rectangles and hope for the best.

Is there a way to do this in webgl? If so, how would I go about it?

1

There are 1 best solutions below

7
On

You can use the regular canvas to perform this task :

1) check min/max of your rects, so you can build a mapping function double -> [0-255] out of that range.

2) draw the rects in 'lighter' mode == add the component values.

3) you might have a saturation when several rects overlaps : if so, double the mapping range and go to 2).
Now if you don't have saturation just adjust the range to use the full [0-255] range of the canvas, and you're done.

Since this algorithm makes use of getImageData, it might not reach 60 fps on all browsers/devices. But more than 10fps on desktop/Chrome seems perfectly possible.

Hopefully the code below will clarify my description :

//noprotect
// boilerplate
var cv = document.getElementById('cv');
var ctx = cv.getContext('2d');

// rectangle collection
var rectCount = 30;
var rects = buildRandRects(rectCount);


iterateToMax();


// --------------------------------------------

function iterateToMax() {
    var limit = 10; // loop protection
    // initialize min/max mapping based on rects min/max
    updateMapping(rects);
    //
    while (true) {
        // draw the scene using current mapping
        drawScene();
        // get the max int value from the canvas
        var max = getMax();
        if (max == 255) {
            // saturation ?? double the min-max interval
            globalMax = globalMin + 2 * (globalMax - globalMin);
        } else {
            // no sauration ? Just adjust the min-max interval
            globalMax = globalMin + (max / 255) * (globalMax - globalMin);
            drawScene();
            return;
        }
        limit--;
        if (limit <= 0) return;
    }
}

// --------------------------------------------
// --------------------------------------------

// Oriented rectangle Class.
function Rect(x, y, w, h, rotation, min, max) {
    this.min = min;
    this.max = max;
    this.draw = function () {
        ctx.save();
        ctx.fillStyle = createRadialGradient(min, max);
        ctx.translate(x, y);
        ctx.rotate(rotation);
        ctx.scale(w, h);
        ctx.fillRect(-1, -1, 2, 2);
        ctx.restore();
    };
    var that = this;

    function createRadialGradient(min, max) {
        var gd = ctx.createRadialGradient(0, 0, 0, 0, 0, 1);
        var start = map(that.min);
        var end = map(that.max);
        gd.addColorStop(0, 'rgb(' + start + ',' + start + ',' + start + ')');
        gd.addColorStop(1, 'rgb(' + end + ',' + end + ',' + end + ')');
        return gd;
    }
}

// Mapping : float value -> 0-255 value
var globalMin = 0;
var globalMax = 0;

function map(value) {
    return 0 | (255 * (value - globalMin) / (globalMax - globalMin));
}

// create initial mapping 
function updateMapping(rects) {
    globalMin = rects[0].min;
    globalMax = rects[0].max;
    for (var i = 1; i < rects.length; i++) {
        var thisRect = rects[i];
        if (thisRect.min < globalMin) globalMin = thisRect.min;
        if (thisRect.max > globalMax) globalMax = thisRect.max;
    }
}

// Random rect collection
function buildRandRects(rectCount) {
    var rects = [];
    for (var i = 0; i < rectCount; i++) {
        var thisMin = Math.random() * 1000;
        var newRect = new Rect(Math.random() * 400, Math.random() * 400, 10 + Math.random() * 50, 10 + Math.random() * 50, Math.random() * 2 * Math.PI, thisMin, thisMin + Math.random() * 1000);
        rects.push(newRect);
    }
    return rects;
}

// draw all rects in 'lighter' mode (=sum values)
function drawScene() {
    ctx.save();
    ctx.globalCompositeOperation = 'source-over';
    ctx.clearRect(0, 0, cv.width, cv.height);
    ctx.globalCompositeOperation = 'lighter';
    for (var i = 0; i < rectCount; i++) {
        var thisRect = rects[i];
        thisRect.draw();
    }
    ctx.restore();
}


// get maximum value for r for this canvas 
//   ( == max r, g, b value for a gray-only drawing. )
function getMax() {
    var data = ctx.getImageData(0, 0, cv.width, cv.height).data;
    var max = 0;
    for (var i = 0; i < data.length; i += 4) {
        if (data[i] > max) max = data[i];
        if (max == 255) return 255;
    }
    return max;
}
<canvas id='cv' width = 400 height = 400></canvas>