Search code examples
javascriptcanvasweb-worker

Canvas image flattern - combine imageData


Hello I've implemented a function in which I pass an array of imageData of png images (rgba) with transparent parts. I flatten the images with the code bellow but I have an issue regarding the alpha and I could use some help. As you may see in the screenshot bellow the red text has a semi transparent black shadow but instead it is rendered white and not semi transparent.

enter image description here

var mergeImageData = function ( imageDataArray ) {

    var canvas = document.getElementById( 'canvas' );
    canvas.width = 512;
    canvas.height = 512;
    var ctx = canvas.getContext( '2d' );
    var newImageData = imageDataArray[ 0 ];


    for ( var j = 1, len = imageDataArray.length; j < len; j++ ) { // iterate through the imageDataArray

        console.log( imageDataArray[ j ].data.length );

        for ( var i = 0, bytes = imageDataArray[ j ].data.length; i < bytes; i += 4 ) { // iterate through image bytes

            var index = ( imageDataArray[ j ].data[ i + 3 ] === 0 ? 0 : j );

            newImageData.data[ i ] = imageDataArray[ index ].data[ i ];
            newImageData.data[ i + 1 ] = imageDataArray[ index ].data[ i + 1 ];
            newImageData.data[ i + 2 ] = imageDataArray[ index ].data[ i + 2 ];
            newImageData.data[ i + 3 ] = imageDataArray[ index ].data[ i + 3 ];
        }

    }

    ctx.putImageData( newImageData, 0, 0 );
    console.log( "all done" );

};

IMPORTANT This will be done in a webworker in the future, that's why I'm interested in this method since web workers have no canvas access.


Solution

  • Your first issue is with putImageData() function : According to this answer by @ellisbben,

    the putImageData method does not pay any attention to compositing; it merely copies pixel values. In order to get compositing, we need to use drawing operations.

    So you have to make use of a second canvas, in memory (as well described in this answer by @markE).

    Then, your second issue is that you want to do it in a webworker.
    You're right, webworkers don't seem able to process such drawing operations : according to this answer by @AshleysBrain

    Web workers can only calculate, not modify the DOM or make any calls to draw to a canvas.

    So your only way is to split your operations by processing datas by the worker, then draw it into your main thread.

    Here is an example, assuming that you've got 2 png files called respectively test0.png & test1.png in your root folder.

    index.html

    <html>
      <body>
      <canvas id="canvas"></canvas>
      </body>
       <script src="main.js"></script>
    </html>
    

    main.js

    if (!!window.Worker) {
        var myWorker = new Worker("worker.js");
    
        myWorker.onmessage = function(e) {
            var tmp=document.createElement("canvas");
            tmp.width=512;
            tmp.height=512;
            var ctx2=tmp.getContext("2d");
            ctx2.putImageData(e.data[0],0,0);
    
            var canvas = document.getElementById( 'canvas' );
            canvas.width = 512;
            canvas.height = 512;
            var ctx = canvas.getContext( '2d' );
            ctx.fillStyle = "rgb(0,255, 0)";
            ctx.fillRect(0,0,250,50);
            ctx.drawImage(tmp,0,0);
        }
    }
    
      var images = [];
      function getImagesData(){
        for(i=0; i<2; i++){
            var img = new Image();
            img.src = "test"+i+".png";
            //Should add an onload method
            var canvas = document.createElement("canvas");
            canvas.width = img.width;
            canvas.height = img.height;
            var ctx = canvas.getContext("2d");
            ctx.drawImage(img, 0, 0);
    
            images[i] = ctx.getImageData(0,0,img.width,img.height);
            }
        myWorker.postMessage(images);
        }
    
    getImagesData();
    

    Worker.js

    var mergeImageData = function( imageDataArray ) {
    
        var newImageData = imageDataArray[ 0 ];
    
        for ( var j = 0; j < imageDataArray.length; j++ ) { // iterate through the imageDataArray
    
    
            for ( var i = 0, bytes = imageDataArray[ j ].data.length; i < bytes; i += 4 ) { // iterate through image bytes
    
                var index = ( imageDataArray[ j ].data[ i + 3 ] === 0 ? 0 : j );
    
                newImageData.data[ i ] = imageDataArray[ index ].data[ i ];
                newImageData.data[ i + 1 ] = imageDataArray[ index ].data[ i + 1 ];
                newImageData.data[ i + 2 ] = imageDataArray[ index ].data[ i + 2 ];
                newImageData.data[ i + 3 ] = imageDataArray[ index ].data[ i + 3 ];
            }
        }
        return newImageData;
    };
    
    
    onmessage = function(e) {
    var res = mergeImageData(e.data);
    postMessage([res]);
    }
    

    Original png files
    test0.png enter image description here

    Result
    Result