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).

Sunday, August 25, 2019

Per pixel bitmap animation in HTML5/JavaScript

LICSON showed how to simulate GLSL shader effects on HTML5 Canvas using pure JavaScript:

//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])
};

function animate() {
    var delta = (Date.now() - time) / 1000;
    buffer = ctx.createImageData(w, h);
    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
        }
    }
    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=HTML5Pixels

The difference: In AS3/Haxe, a color value is represented by a single Unsigned Int value (0xAARRGGBB). In HTML5, the color data (BitmapData) array stores a whole color value as four neighboring elements (integer between 0 and 255), representing the red, green, blue and alpha values, respectively. To get/set the color value at coordinate (x,y), you should go to index "i=(y*buffer.width+x)*4", then the red, green, blue and alpha values are respectively "buffer.data[i], buffer.data[i+1], buffer.data[i+2], buffer.data[i+3]".

Links:
https://licson.net/post/glsl-fragment-shaders-in-javascript/
https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API/Tutorial/Pixel_manipulation_with_canvas
https://bruce-lab.blogspot.com/2019/02/four-ways-for-per-pixel-bitmap.html
Faster Canvas Pixel Manipulation with Typed Arrays
References:
HTML5 Canvas: Native Interactivity and Animation for the Web, see Animation Loop (P.27), Pixel Manipulation (P.170).
Foundation HTML5 Animation with JavaScript, see Animation loops (P.16), Pixel manipulation (P.94).
HTML5 Game Development Insights, see High-Performance Update Loops (P.106), A Bitmap API Example (P.246).
https://developer.mozilla.org/en-US/docs/Web/API/window/requestAnimationFrame
https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/putImageData
https://www.w3schools.com/js/js_htmldom_animate.asp
https://www.w3schools.com/tags/canvas_putimagedata.asp

Friday, February 8, 2019

Four ways for per pixel bitmap manipulation in OpenFL

A BitmapData can be seen as an array of length width*height, holding unsigned int colors values. There are at least four ways to manipulate pixels of a bitmap image(Bitmap/BitmapData) in OpenFL. The first way is using setPixel() or setPixel32() function:

var myBitmapData:BitmapData = new BitmapData(800, 600, true, 0);
myBitmapData.lock();
for (j in 0...600)
    {
    for (i in 0...200)
        {
         myBitmapData.setPixel32(i, j, (i % 255) << 24 | 0x0000ff);
        }
    }
myBitmapData.unlock();
You can use lock() and unlock() function before and after multiple calls of setPixel() function, so the BitmapData will only be updated on Screen after unlock(). The second way is using setVector() function, and a Vector of UInt to hold pixel values:
var myVector:Vector = new Vector(200 * 600, true);
for (j in 0...600)
    {
    for (i in 0...200)
        {
        myVector[j * 200 + i] = (i % 255) << 24 | 0x0000ff;
        }
    }
var myRect2:Rectangle = new Rectangle(200, 0, 200, 600);
myBitmapData.setVector(myRect2,myVector);
The third way is using setPixels() function, and a ByteArray to hold pixel values:
var myByteArray:ByteArray = new ByteArray(200 * 600 * 4);
for (j in 0...600)
   {
   for (i in 0...200)
       {
       //myByteArray.position = (j * 200 + i) * 4;
       myByteArray.writeUnsignedInt((i % 255) << 24 | 0x0000ff);
       }
   }
var myRect4:Rectangle = new Rectangle(400, 0, 200, 600);
myByteArray.position = 0;
myBitmapData.setPixels(myRect4,myByteArray);
Remember to set the position of the ByteArray to 0 before calling setPixels(). The position "(j * width + i) * 4" of the ByteArray is associated with the pixel at (x=i,y=j) in the BitmapData. There is no need to set the position of the ByteArray to "(j * 200 + i) * 4" above since in the double for loops the "writeUnsignedInt()" function will update the position automatically. The fourth way is using the Memoery API, which is similar to the third way. See https://bruce-lab.blogspot.com/2013/03/fast-way-for-per-pixel-bitmap.html for more details.
var myMem:ByteArray = new ByteArray(200 * 600 * 4);
Memory.select(myMem);
for (j in 0...600)
    {
    for (i in 0...200)
        {
        Memory.setI32((j * 200 + i) * 4, (i % 255) << 24 | 0x0000ff);
        }
    }
var myRect4:Rectangle = new Rectangle(600, 0, 200, 600);
myMem.position = 0;
myBitmapData.setPixels(myRect4,myMem);
The full source code: See the result here (HTML5): http://vvv.flaswf.tk/demo/?url=BitmapDataPixel

Sponsors