2

I'm trying to create an image editing tool using HTML5 canvas. The input image is a png with alpha channel. Is there an efficient way to set alpha values to 255 and restore RGB colors?

Something like the piece of code below but without the huge pixel loop. I tried to use blending but it seems that once alpha value is 0, there's no way to recover RGB.

var imageData = context.getImageData(0, 0, 512, 512);
var data = imageData.data;
for(var i = 0, n = data.length; i < n; i += 4) {
    data[i + 3] = 255;
}
...
Kaiido
  • 123,334
  • 13
  • 219
  • 285
user3469029
  • 117
  • 9

2 Answers2

0

While png format does indeed store the "non-premutlipied" alpha, CanvasRenderingContext2D on the other hand always do pre-multiply alpha.

So what you are after can not be done from a CanvasRenderingContext2D.

Indeed, since the alpha gets pre-multiplied at writing, if you do write an #AABBCC00 (transparent) pixel over your context, what will be drawn will actually be #00000000.
Also, because of how binary floating points work, you can not be sure to have the same output in different browsers/devices, even with non-zero alpha values:

// demonstrates alpha premultiplying at writing on a canvas 2d context
//
// 4 pixels are authored with the same RGB channels values but with different alpha values
// If no pre-multiplying was done, all these would output the same RGB channels values at getting


var ctx = document.createElement('canvas').getContext('2d'),
  img = ctx.createImageData(4,1),
// we use an Uint32Array to write every pixel in a single op
  arr = new Uint32Array(img.data.buffer);
// AARRGGBB in little endian
arr[0] = 0x00AABBCC; // transparent (alpha 0)
arr[1] = 0x01AABBCC; // almost-transparent (alpha 1/255)
arr[2] = 0x7FAABBCC; // semi-transparent (alpha 0.5/1)
arr[3] = 0xFFAABBCC; // opaque
// write it to our context
ctx.putImageData(img, 0,0);
// grab it directly
var result = ctx.getImageData(0,0,4,1).data;

console.log('transparent', result.slice(0,4));
// outputs [0, 0, 0, 0]
console.log('almost-transparent', result.slice(4,8));
// outputs something like [255, 255, 255, 1]
console.log('semi-transparent', result.slice(8,12));
// outputs something around [204, 187, 170, 127]
// on my FF it is           [204, 188, 170, 127]
// on my chrome it is       [205, 187, 171, 127]
console.log('opaque', result.slice(12,16));
// outputs [204, 187, 171, 125]

As you can see, only full opaque pixels keep their initial RGB channels' values.


So to do what you want, you would have to actually parse manually the png file, and extract the data contained in the IDAT chunks manually.
To do it one the front-side, you'd load your file as an ArrayBuffer and parse it following these specs.

But before you start writing your parser, you'd have to consider how likely it is that 100% of your users actually used a lossless software that would preserve the RGB channels' values (unlike the canvas).

Kaiido
  • 123,334
  • 13
  • 219
  • 285
  • Thanks for the answer :-) What I was trying to do was to recover the background as original rgb colors stored in the png instead of blending with a new background. Is there a way to do that? I'm now using 2 separate canvases, one for only rgb and the other only for alpha, which works but is a little messy to maintain... – user3469029 Aug 02 '18 at 18:23
  • @user3469029 you mean you really want #aabbcc00 to #aabbccff? While png format **can** indeed store non premultiplied alpha colors, camvas will always premultiply. This means you can't use a canvas to get back the color channels that could be stored in your png (because in the case above, it will produce #00000000 pixel). If you really want to retrieve it, you'd have to parse the pixel data from the png file yourself. But are tou sure this data has actually been saved prenultiplied? Where do the images come from? – Kaiido Aug 02 '18 at 22:32
  • Ah. Yeah. Thanks That was what I meant. :-) I was trying to do something like photoshop where you can erase alpha and unerase later. I think the work around I used was actually ok. I used 2 canvases for rgb and alpha separately. The code could have been cleaner if they had a blend mode to unerase, but the work around wasn't too bad. :-) – user3469029 Aug 04 '18 at 00:26
0

My solution for this is to use 2 canvases, one for rgb and the other for alpha. I set alpha of the rgb canvas to 255. So when we erase/unerase, it only happens in the alpha canvas. I blended the rgb canvas with alpha canvas using 'destination-in' for display.

user3469029
  • 117
  • 9