Skip to content Skip to sidebar Skip to footer

I'm Having Troubles With Copying Transparent Pixels With Canvas Putimagedata From Another Canvas/image(png)

I'm trying to copy method discribed here on stackoverflow. But I'm having some problems which I don't know how to solve. I set up jsfiddle to demonstrate everything. Here is the se

Solution 1:

Hunting the black pixels


@Loktar's great answer was made for a particular image, only composed of black and transparent pixels.

In the imageData, these two type of pixels are very similar, since only their alpha value differ. So his code was only doing a should-draw check over the alpha value (the fourth in each loop).

cData[pData] = imagePixData[iData];
cData[pData + 1] = imagePixData[iData + 1];
cData[pData + 2] = imagePixData[iData + 2];
// only checking for the alpha value...if(cData[pData + 3] < 100){
  cData[pData + 3] = imagePixData[iData + 3];
}

You, in the other hand, are dealing with colored images. So when this part is executed against a transparent pixel, and that you already have a colored pixel at this position, the three first lines will convert the existing pixel to the transparent one's rgb values (0,0,0) but leave the alpha value of the existing pixel (in your case 255).

You then have a black pixel instead of the colored one that were here previously.

To solve it, you can wrap the full block in a condition that checks the opacity of the current imagePixData, instead of checking the one already drawn.

if (imagePixelData[iData+3]>150){
    cData[pData] = imagePixelData[iData];
    cData[pData + 1] = imagePixelData[iData + 1];
    cData[pData + 2] = imagePixelData[iData + 2];
    cData[pData + 3] = imagePixelData[iData + 3];
}

Fighting the white ones


Those white pixels are here because of the anti-aliasing. It was already there in @Loktar's original example, simply less visible because of the size of his images.

These artifacts are crap when you do deal with imageData, since we can just modify each pixel, and that we can't set values on sub-pixels. In other words, we can't make use of anti-aliasing.

That's the purpose of the <100 in original checking, or the >150 in my solution above.

The smallest range you will take in this check against the alpha value, the less artifacts you'll get. But in the other hand, the rougher your borders will be.

You ave to find the right value by yourself, but circles are the worst since almost every border pixels will be anti-aliased.

Improving the awesome


Your actual implementation made me think about some improvements that could be made on @Loktar's solution.

Instead of keeping the original image's data, we could do a first loop over every pixels, and store a new imageData array, composed of six slots : [x, y, r, g, b ,a].

This way, we can avoid the storing of all the transparent pixels we don't want, which makes less iterations at each call, and we can also avoid any alpha checking in each loop. Finally, we don't even need to "get the position pixel from the image canvas" since we stored it for each pixel.

Here is an annotated code example as a proof of concept.

var parseImageData = function(ctx) {
  var pixelArr = ctx.getImageData(0, 0, ctx.canvas.width, ctx.canvas.height).data;
  // the width of our imagevar w = ctx.canvas.width;
  // first store our image's dimensionvar filtered = [];
  // loop through all our image's pixelsfor (var i = 0; i < pixelArr.length; i += 4) {
    // we don't want traparent or almost transparent pixelsif (pixelArr[i + 3] < 250) {
      continue;
    }
    // get the actual x y position of our pixelvar f = (i / 4) / w;
    var y = Math.floor(f);
    var x = Math.round((f - y) * w);
    // add the pixel to our array, with its x y positions
    filtered.push(x, y, pixelArr[i], pixelArr[i + 1], pixelArr[i + 2], pixelArr[i + 3]);
  }

  return filtered;
};
// here we will store all our pixel arraysvar images = [];
// here we will store our entitiesvar objects = [];

var draw = function() {
  // create a new empty imageData of our main canvasvar imageData = mainCtx.createImageData(mainCanvas.width, mainCanvas.height);
  // get the array we'll write ontovar pixels = imageData.data;

  var width = mainCanvas.width;

  var pixelArray,
    deg = Math.PI / 180; // micro-optimizaion doesn't hurtfor (var n = 0; n < objects.length; n++) {
    var entity = objects[n],
      // HERE update your objects// some fancy things by OP
      velY = Math.cos(entity.angle * deg) * entity.speed,
      velX = Math.sin(entity.angle * deg) * entity.speed;

    entity.x += velX;
    entity.y -= velY;

    entity.angle++;
    // END update// retrieve	the pixel array we created before
    pixelArray = images[entity.image];

    // loop through our pixel Arrayfor (var p = 0; p < pixelArray.length; p += 6) {
      // retrieve the x and positions of our pixel, relative to its original imagevar x = pixelArray[p];
      var y = pixelArray[p + 1];
      // get the position of our ( pixel + object ) relative to the canvas sizevar pData = (~~(entity.x + x) + ~~(entity.y + y) * width) * 4// draw our pixel
      pixels[pData] = pixelArray[p + 2];
      pixels[pData + 1] = pixelArray[p + 3];
      pixels[pData + 2] = pixelArray[p + 4];
      pixels[pData + 3] = pixelArray[p + 5];
    }
  }
  // everything is here, put the image data
  mainCtx.putImageData(imageData, 0, 0);
};



var mainCanvas = document.createElement('canvas');
var mainCtx = mainCanvas.getContext('2d');

mainCanvas.width = 800;
mainCanvas.height = 600;

document.body.appendChild(mainCanvas);


// just for the demovar colors = ['lightblue', 'orange', 'lightgreen', 'pink'];
// the canvas that will be used to draw all our images and get their dataImagevar imageCtx = document.createElement('canvas').getContext('2d');

// draw a random imagevar randomEgg = function() {
  if (Math.random() < .8) {
    var radius = Math.random() * 25 + 1;
    var c = Math.floor(Math.random() * colors.length);
    var c1 = (c + Math.ceil(Math.random() * (colors.length - 1))) % (colors.length);
    imageCtx.canvas.width = imageCtx.canvas.height = radius * 2 + 3;
    imageCtx.beginPath();
    imageCtx.fillStyle = colors[c];
    imageCtx.arc(radius, radius, radius, 0, Math.PI * 2);
    imageCtx.fill();
    imageCtx.beginPath();
    imageCtx.fillStyle = colors[c1];
    imageCtx.arc(radius, radius, radius / 2, 0, Math.PI * 2);
    imageCtx.fill();
  } else {
    var img = Math.floor(Math.random() * loadedImage.length);
    imageCtx.canvas.width = loadedImage[img].width;
    imageCtx.canvas.height = loadedImage[img].height;
    imageCtx.drawImage(loadedImage[img], 0, 0);
  }
  returnparseImageData(imageCtx);
};

// init our objects and shapesvar init = function() {
  var i;
  for (i = 0; i < 30; i++) {
    images.push(randomEgg());
  }
  for (i = 0; i < 10000; i++) {
    objects.push({
      angle: Math.random() * 360,
      x: 100 + (Math.random() * mainCanvas.width / 2),
      y: 100 + (Math.random() * mainCanvas.height / 2),
      speed: 1 + Math.random() * 20,
      image: Math.floor(Math.random() * (images.length))
    });
  }
  loop();
};

var loop = function() {
  draw();
  requestAnimationFrame(loop);
};

// were our offsite images will be storedvar loadedImage = [];
(functionpreloadImages() {
  var toLoad = ['https://dl.dropboxusercontent.com/s/4e90e48s5vtmfbd/aaa.png',
    'https://dl.dropboxusercontent.com/s/rumlhyme6s5f8pt/ABC.png'
  ];

  for (var i = 0; i < toLoad.length; i++) {
    var img = newImage();
    img.crossOrigin = 'anonymous';
    img.onload = function() {
      loadedImage.push(this);
      if (loadedImage.length === toLoad.length) {
        init();
      }
    };
    img.src = toLoad[i];
  }
})();

Note that the bigger your images to draw will be, the slowest the drawings will be too.

Post a Comment for "I'm Having Troubles With Copying Transparent Pixels With Canvas Putimagedata From Another Canvas/image(png)"