skip to Main Content

I’m trying to take advantage of web workers in order to merge an array of pngs into a single image with transparency, same like photoshop does when you merge multiple layers.

The reason I’m doing this is because I’m saving olny the result of the combination into indexeddb for offline usage, and after that I’m creating a texture with that image in my three.js app.

The scenario is that there are about 400 products and the texture – image on the 3d plane(product) is a mixture of 3 images (product title, price and product image).

I’ ve implemented this so far, but image processing is not that good and I was wondering how could I do that with web workers if i pass them the imageData.data

Here is what I have so far but I can’t make it work. (this is done without webworkers for now just trying to make the merge to work)

var images = [];
var imageData = [];

images.push( function ( callback ) {

    var mobile = new Image();

    mobile.onload = function () {
        console.log( "mobile loaded" );
        callback( null, mobile );
    };

    mobile.onerror = function ( e ) {
        console.log( "mobile error" );
        callback( e, null );
    };

    mobile.src = "mobile.png";

} );

images.push( function ( callback ) {

    var text = new Image();

    text.onload = function () {
        console.log( "text loaded" );
        callback( null, text );
    };

    text.onerror = function ( e ) {
        console.log( "text error" );
        callback( e, null );
    };

    text.src = "text.png";

} );

async.series( images, function ( err, results ) {

    if ( err ) {
        console.error( err );
    } else {
        console.log( results );
        getImageData( results );
    }

} );


var getImageData = function ( images ) {


    for ( var i = 0; i < images.length; i++ ) {

        var canvas = document.createElement( 'canvas' );

        canvas.width = 512;
        canvas.height = 512;

        var ctx = canvas.getContext( '2d' );

        ctx.drawImage( images[ i ], 0, 0 );

        imageData.push( ctx.getImageData( 0, 0, canvas.width, canvas.height ).data );

    }

    merge( imageData );

};

var merge = function ( data ) {

    var merged = [];
    var mixFactor = 0.5;

    //var newPixel = imageMainPixel * mixFactor + imageSecPixel * ( 1 - mixFactor )

    //for ( var i = 0, len = data.length; i < len; i++ ) {

    for ( var j = 0, byteLen = data[ 0 ].length; j < byteLen; j += 4 ) {

        var r = data[ 0 ][ j ] * mixFactor + data[ 1 ][ j ] * ( 1 - mixFactor );
        var g = data[ 0 ][ j + 1 ] * mixFactor + data[ 1 ][ j + 1 ] * ( 1 - mixFactor );
        var b = data[ 0 ][ j + 2 ] * mixFactor + data[ 1 ][ j + 2 ] * ( 1 - mixFactor );
        var a = data[ 0 ][ j + 3 ] * mixFactor + data[ 1 ][ j + 3 ] * ( 1 - mixFactor );

        merged[ j ] = r;
        merged[ j + 1 ] = g;
        merged[ j + 2 ] = b;
        merged[ j + 4 ] = a;

    }

    //}

    var canvas = document.getElementById( 'canvas' );
    canvas.width = 512;
    canvas.height = 512;
    var ctx = canvas.getContext( '2d' );
    var imageData = ctx.createImageData( 512, 512 );
    imageData.data.set( merged );
    ctx.putImageData( imageData, 0, 0 );

};

The error I’m currently getting is this:

Uncaught RangeError: Source is too large

UPDATE

I managed to merge the images by changing my merge function into this:

var merge = function ( data ) {

    var canvas = document.getElementById( 'canvas' );
    canvas.width = 512;
    canvas.height = 512;
    var ctx = canvas.getContext( '2d' );
    var imageData = ctx.createImageData( 512, 512 );
    var mixFactor = 0.5;


    for ( var j = 0, byteLen = data[ 0 ].length; j < byteLen; j += 4 ) {

        imageData.data[ j ] = data[ 0 ][ j ] * mixFactor + data[ 1 ][ j ] * ( 1 - mixFactor );
        imageData.data[ j + 1 ] = data[ 0 ][ j + 1 ] * mixFactor + data[ 1 ][ j + 1 ] * ( 1 - mixFactor );
        imageData.data[ j + 2 ] = data[ 0 ][ j + 2 ] * mixFactor + data[ 1 ][ j + 2 ] * ( 1 - mixFactor );
        imageData.data[ j + 3 ] = data[ 0 ][ j + 3 ] * mixFactor + data[ 1 ][ j + 3 ] * ( 1 - mixFactor );

    }


    console.log( imageData.data.length );

    ctx.putImageData( imageData, 0, 0 );

};

The result is like 2 images both with 50% opacity. That is not the desired result though. I would like just the images to act as layers like having multiple pngs one on top of the other… IF the one on top has transparent places you should be able to see the image under it.

Perhaps I should draw based on the alpha value ?

3

Answers


  1. Chosen as BEST ANSWER

    Well after some thinking I ended up checking the alpha value of every pixel, and managed to accomplish what I wanted. I rewrote the merge function like this:

    var merge = function ( data ) {
    
        var canvas = document.getElementById( 'canvas' );
        canvas.width = 512;
        canvas.height = 512;
        var ctx = canvas.getContext( '2d' );
        var imageData = ctx.createImageData( 512, 512 );
        var mixFactor = 0.5;
    
    
        for ( var j = 0, byteLen = data[ 0 ].length; j < byteLen; j += 4 ) {
    
            // imageData.data[ j ] = data[ 0 ][ j ] * mixFactor + data[ 1 ][ j ] * ( 1 - mixFactor );
            // imageData.data[ j + 1 ] = data[ 0 ][ j + 1 ] * mixFactor + data[ 1 ][ j + 1 ] * ( 1 - mixFactor );
            // imageData.data[ j + 2 ] = data[ 0 ][ j + 2 ] * mixFactor + data[ 1 ][ j + 2 ] * ( 1 - mixFactor );
            // imageData.data[ j + 3 ] = data[ 0 ][ j + 3 ] * mixFactor + data[ 1 ][ j + 3 ] * ( 1 - mixFactor );
    
            imageData.data[ j ] = ( data[ 1 ][ j + 3 ] === 0 ? data[ 0 ][ j ] : data[ 1 ][ j ] );
            imageData.data[ j + 1 ] = ( data[ 1 ][ j + 3 ] === 0 ? data[ 0 ][ j + 1 ] : data[ 1 ][ j + 1 ] );
            imageData.data[ j + 2 ] = ( data[ 1 ][ j + 3 ] === 0 ? data[ 0 ][ j + 2 ] : data[ 1 ][ j + 2 ] );
            imageData.data[ j + 3 ] = ( data[ 1 ][ j + 3 ] === 0 ? data[ 0 ][ j + 3 ] : data[ 1 ][ j + 3 ] );
    
        }
    
    
        console.log( imageData.data.length );
    
        ctx.putImageData( imageData, 0, 0 );
    
    };
    

    Some improvements are still needed but I suppose now I'll be able to merge images inside a web worker :)


  2. I wrote a lib that handles this. Not only that, but it handles shading and tiles.

    Login or Signup to reply.
  3. The modified code is:

    for ( var j = 0, byteLen = data[0].length; j < byteLen; j += 4 )
    {
      var rA = data[0][j],   rB = data[1][j];
      var gA = data[0][j+1], gB = data[1][j+1];
      var bA = data[0][j+2], bB = data[1][j+2];
      var aA = data[0][j+3], aB = data[1][j+3];
    
      var rOut = (rA * aA / 255) + (rB * aB * (255 - aA) / (255*255));
      var gOut = (gA * aA / 255) + (gB * aB * (255 - aA) / (255*255));
      var bOut = (bA * aA / 255) + (bB * aB * (255 - aA) / (255*255));
      var aOut = aA + (aB * (255 - aA) / 255);
    
      imageData.data[j] = rOut;
      imageData.data[j+1] = gOut;
      imageData.data[j+2] = bOut;
      imageData.data[j+3] = aOut;
    
    } 
    

    Here is the solution(Alpha blending) How to mix two ARGB pixels?

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search