0

I need to read the transparency value for each pixel of a user submitted PNG, on the front-end.

Right now I'm blocking the form submission and I convert the PNG in the file input to a FileReader object. Now I was thinking that I could simply read the transparency values out of that data. For example, I converted to ArrayBuffer and then to Uint8Array, and that's a large array of ints whose values range from 0-255, but it doesn't look like the right colors.

Then I read that you must actually display the PNG on an HTML5 canvas in order to do this.

Ryan
  • 5,883
  • 13
  • 56
  • 93

2 Answers2

1

the png buffer don't hold the pixel data in a readable way, it's compressed so you can't read it like you would like to. you have to get the pixels somehow, the easiest straight forward way is to use createImageBitmap(file) to turn it into a bitmap and then use the OffscreenCanvas to paint the bitmap and read it using getImageData This way you can read more than just pngs... and also using it in worker threads

// simulate getting a file you would get from eg file input (1x1 pixel)
fetch('data:;base64,R0lGODlhAQABAIAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==')
  .then(r => r.blob())
  .then(createImageBitmap)
  .then(readData)

/** @param {ImageBitmap} bitmap */
function readData (bitmap) {
  const { width: w, height: h } = bitmap
  const canvas = new OffscreenCanvas(w, h)
  const ctx = canvas.getContext('2d')

  ctx.drawImage(bitmap, 0, 0)
  const pixels = ctx.getImageData(0, 0, w, h).data

  // read alpha channel
  for (let i = 0; i < pixels.length; i += 4) {
    console.log(pixels[i + 3])
  }
}
Endless
  • 34,080
  • 13
  • 108
  • 131
0

PNG does not contain the raw image data, but is compressed. In order to decode it, you need to do some complex calculations. Load it into a <canvas> and let the browser do the decoding:

<canvas id="myCanvas"></canvas>
<input type="file" accept="image/png, image/gif, image/jpeg" id="myFileInput" />
<script>
    const canvas = document.getElementById('myCanvas');
    const context = canvas.getContext("2d"); 
    const fileInput = document.getElementById('myFileInput');

    fileInput.onchange = function(changeEvent) {
        const files = changeEvent.target.files;
        const file = files[0];
        const reader = new FileReader();
        reader.readAsDataURL(file);
        reader.onload = function(loadEvent){
            const image = new Image();
            image.src = loadEvent.target.result;
            canvas.width = image.width;
            canvas.height = image.height;
            context.drawImage(image, 0, 0);

            const imageData = context.getImageData(0, 0, image.width, image.height);
            console.log(imageData)
            // see https://stackoverflow.com/a/667074/17870699
        }  
    };
</script>
  • a faster approach would be to use URL.createObjectURL instead of using the FileReader – Endless Jan 09 '22 at 16:29
  • ...and also using [img.decode()](https://developer.mozilla.org/en-US/docs/Web/API/HTMLImageElement/decode) instead of onload... – Endless Jan 09 '22 at 21:35