Tuesday, September 17, 2019

Save and Load Binary Files in HTML5

In this example, we firstly draw something (shapes & lines) on the canvas, convert it into a bitmap (pixel data) and save the pixel values as a binary file.

//Draw something
ctx.fillStyle = "red";
ctx.fillRect(0, 0, 60, 60);
ctx.beginPath();
ctx.rect(60, 60, 160, 80);
ctx.fillStyle = "blue";
ctx.fill();
ctx.beginPath();
ctx.lineWidth = "5";
ctx.strokeStyle = "green";
ctx.moveTo(6, 160);
ctx.lineTo(240, 360);
ctx.lineTo(60, 800);
ctx.lineTo(800, 60);
ctx.stroke();//draw
//Convert canvas draws into pixels
var buffer = ctx.getImageData(0, 0, w, h);

//https://stackoverflow.com/questions/23451726/saving-binary-data-as-file-using-javascript-from-a-browser
var saveByteArray = (function () {
    var a = document.createElement("a");
    document.body.appendChild(a);
    a.style = "display: none";
    return function (data, name) {
        var blob = new Blob(data, {type: "octet/stream"}),
            url = window.URL.createObjectURL(blob);
        a.href = url;
        a.download = name;
        a.click();
        window.URL.revokeObjectURL(url);
    };
}());
//Save pixel data (ArrayBuffer) as binary file
saveByteArray([buffer.data], 'example.bf');

Then we load the binary file and display the pixel values it stores (as a bitmap) using a new canvas.
//The requestAnimFrame fallback for better and smoother animation
var buffer = ctx.createImageData(w, h);

//http://qnimate.com/an-introduction-to-javascript-blobs-and-file-interface/
var xhr = new XMLHttpRequest(); 
xhr.open("GET", "./example.bf"); 
//although we can get the remote data directly into an arraybuffer using the string "arraybuffer" assigned to responseType property. For the sake of example we are putting it into a blob and then copying the blob data into an arraybuffer.
xhr.responseType = "blob";

function analyze_data(blob)
{
    var myReader = new FileReader();
    myReader.readAsArrayBuffer(blob);
    
    myReader.addEventListener("loadend", function(e)
    {
        var buf = e.srcElement.result;//arraybuffer object
        var buf8 = new Uint8ClampedArray(buf);//first view for copy pixel data to ImageData
        var data = new Uint32Array(buf);//second view for setting pixel values
        buffer.data.set(buf8);
        //we use putImageData() to copy the image data back to the canvas.
        ctx.putImageData(buffer, 0, 0);
    });
}

xhr.onload = function() 
{
    analyze_data(xhr.response);
}
xhr.send();

Demo & Full Source Code: Create & Save, Load & Show

Monday, September 9, 2019

Fast per pixel bitmap animation in HTML5

According to this tutorial (Faster Canvas Pixel Manipulation with Typed Arrays), we can make the code in my last post (per pixel bitmap animation in HTML5/JavaScript) faster using Typed Arrays:

//The requestAnimFrame fallback for better and smoother animation
window.requestAnimFrame = (function () {
    return window.requestAnimationFrame || window.webkitRequestAnimationFrame || 
 window.mozRequestAnimationFrame || window.oRequestAnimationFrame || 
 window.msRequestAnimationFrame || function (callback) {
        window.setTimeout(callback, 1000 / 60);
    };
})();

//Prepare our canvas
var canvas = document.querySelector('#render');
var w = window.innerWidth;
var h = window.innerHeight;
canvas.width = w;
canvas.height = h;
var ctx = canvas.getContext('2d');

var time = Date.now();//record initial time
var buffer = ctx.createImageData(w, h);//The back buffer we used to paint the result into the canvas
//The main render function
//Calculate a color value from elapsed time and [x,y] coordinates (scaled to [0,1])
function render(time, fragcoord) {
    /* put the GLSL fragment shader's JavaScript equivalent here. */
    //begin of per pixel bitmap manipulation
    var x = fragcoord[0]; var y = fragcoord[1];
    var red = x;
    var green = y;
    var blue = 1/(1+time);
    var alpha = 1;
    //end of per pixel bitmap manipulation
    return [red,green,blue,alpha]; //the final color value (scaled to [0,1])
};
var buf;
function animate() {
    var delta = (Date.now() - time) / 1000;
    buffer = ctx.createImageData(w, h);
    //
    /*
    Next we create two ArrayBuffer views. 
    One that allows us to view buf as a one-dimensional array of unsigned 8-bit values 
    and another that allows us to view buf as a one-dimensional array of unsigned 32-bit values.
    */
    buf = new ArrayBuffer(buffer.data.length);
    var buf8 = new Uint8ClampedArray(buf);//first view for copy pixel data to ImageData
    var data = new Uint32Array(buf);//second view for setting pixel values
    //
    ctx.clearRect(0, 0, w, h);
    for (var x = 0; x < w; x++) {
        for (var y = 0; y < h; y++) {
            var ret = render(delta, [x/w, y/h]);
            //var i = (y * buffer.width + x) * 4;
            //buffer.data[i] = ret[0] * 255;//red
            //buffer.data[i + 1] = ret[1] * 255;//green
            //buffer.data[i + 2] = ret[2] * 255;//blue
            //buffer.data[i + 3] = ret[3] * 255;//alpha  
            data[y * w + x] =
            (ret[3]*255 << 24) | // alpha
            (ret[2]*255 << 16) | // blue
            (ret[1]*255 <<  8) | // green
             ret[0]*255;  // red
        }
    }
    /*
    now assign the contents of the ArrayBuffer buf to imageData.data. 
    We use the Uint8ClampedArray.set() method to set the data property 
    to the Uint8ClampedArray view of our ArrayBuffer by specifying buf8 as the parameter.
    */
    buffer.data.set(buf8);
    //Finally, we use putImageData() to copy the image data back to the canvas.
    ctx.putImageData(buffer, 0, 0);
    requestAnimFrame(animate);
};

window.onresize = function () {
    w = window.innerWidth;
    h = window.innerHeight;
    canvas.width = w;
    canvas.height = h;
};

animate();

Demo & Full Source Code: http://vvv.flaswf.tk/demo/?url=HTML5Pixelsfast

The difference: Here we use an ArrayBuffer "buf" to hold the ImageData "buffer", and create two ArrayBuffer views of "buf"; one as an array of unsigned 8-bit values for using "putImageData()" function to copy the image data back to the canvas, and the other one as unsigned 32-bit values for setting pixel values (just like in AS3, allowing you to use only one, instead of four, array assignment to set a pixel's value).

Sponsors