6

Please see this example: http://codepen.io/anon/pen/QNGzBP

const canvas = document.getElementById('canvas')
const ctx = canvas.getContext('2d')

// set pixel at 0,0 to rgba(2, 0, 255, 0.2)
const imageData = ctx.getImageData(0, 0, 1, 1)
imageData.data[0] = 2
imageData.data[1] = 0
imageData.data[2] = 255
imageData.data[3] = 0.2 * 255 // 0.2 opacity
ctx.putImageData(imageData, 0, 0, 0, 0, 1, 1)
console.log('Setting pixel 0,0 to', {
  r: imageData.data[0],
  g: imageData.data[1],
  b: imageData.data[2],
  a: imageData.data[3] / 255
})

// retrieve pixel at 0,0
const newImageData = ctx.getImageData(0, 0, 1, 1)
console.log('Fetching pixel at 0,0', {
  r: newImageData.data[0],
  g: newImageData.data[1],
  b: newImageData.data[2],
  a: newImageData.data[3] / 255
})

The above code modifies a single pixel on the canvas, and then retrieves it, while console logging the process. The canvas seems to mutate the RGB data when the alpha is lower than 1.

It seems to occur both in Chrome and Firefox. Is this simply a browser bug? I'm producing a PNG out of the canvas, and require the colors to be 100% accurate. Is there any workaround?

Edit:

http://codepen.io/anon/pen/ZWLbKx

More tests to show how the alpha mutates the rgb values.

J Doe
  • 57
  • 1
  • 5
  • 2
    The alpha element of canvas imageData is an integer in the range of 0-255. Non-integers are not valid (they might get boxed or ignored). – markE Mar 16 '16 at 14:47
  • Yes, but it still mutates values, even when you set `a` to `51`, at least for me.`ctx.fillStyle = "rgba("+r+","+g+","+b+","+(a/255)+")"; ctx.fillRect( x, y, 1, 1 );` worked as expected, and was able to get the value back later successfully – Wish Mar 16 '16 at 14:55
  • I made a second test, that better shows that the rgb is mutated by the alpha. – J Doe Mar 16 '16 at 18:06
  • @Wish fillRect doesn't seem to work either: http://codepen.io/anon/pen/mPRVRw Care to show how you got it working? – J Doe Mar 16 '16 at 19:09
  • Yeah, you are right, it is still mutating.. – Wish Mar 17 '16 at 07:00

1 Answers1

4

Very sorry to disappoint, but the HTML5 canvas has been ratified as "lossy": it uses alpha-channel premultiplication.

From https://www.w3.org/TR/2dcontext/#dom-context-2d-getimagedata (see 2nd "Note:" box):

Due to the lossy nature of converting to and from premultiplied alpha color values, pixels that have just been set using putImageData() might be returned to an equivalent getImageData() as different values.

...There are lots of JavaScript PNG encoders/decoders. Unfortunately that's where it's at, as of 2017.

FWIW, I've had a go at bumping the r/g/b/a values up and down to try and make the canvas spit out the values I actually want. 5 minutes of experimenting shows that it "hops" over the specific pixel value you want depending on what the alpha is set to.

Community
  • 1
  • 1
i336_
  • 1,813
  • 1
  • 20
  • 41