Search code examples
javascripthtmlhtml5-canvas

How can I get RGB array from img?


I am trying to get the RGB array of img using canvas like this:

Teplate

<!DOCTYPE html>
<html>
  <head>
    <title>Parcel Sandbox</title>
    <meta charset="UTF-8" />
  </head>

  <body>
    <div id="display"></div>
    <img id="face" src="./220px-Leonhard_Euler.jpg" />
    <canvas
      id="face2"
      width="112"
      height="112"
      style="border: 1px solid #d3d3d3;"
    ></canvas>

    <script src="src/index.js"></script>
  </body>
</html>

Script

const img = document.getElementById("face");
img.crossOrigin = "anonymous";

const canvas = document.getElementById("face2");
const context = canvas.getContext("2d");
context.drawImage(img, 0, 0);

let pix = context.getImageData(0, 0, 112, 112).data;
pix = Array.from(pix);

const pix2 = [];

for (let i = 0; i < pix.length; i += 4) {
  pix2.push(pix[i + 0]);
  pix2.push(pix[i + 1]);
  pix2.push(pix[i + 2]);
}

const imgData = context.createImageData(112, 112);

for (let i = 0; i < imgData.data.length; i += 4) {
  imgData.data[i + 0] = pix2[i + 0];
  imgData.data[i + 1] = pix2[i + 1];
  imgData.data[i + 2] = pix2[i + 2];
  imgData.data[i + 3] = 255;
}

context.putImageData(imgData, 10, 10);

As you can see in the first part of the code I try to extract the 4d array of the image RGBA, and try to remove the alpha channel.

After that, I tried to set again the image with a default alpha channel like 255 only to check that I got the RGB data only.

But I got the following image:

enter image description here

The input image is 112x112 and the canvas is 112x112, any idea about why I could get only the RGB of the img?

EDIT2

The @tracktor response worked but I still get error with another image like this:

enter image description here

Same code the only difference is that I load this image using http://localhost:8080/face.jpg instead of crossorigin.

Thanks


Solution

  • HTMLImageElement crossorigin attribute

    Put a crossorigin attribute on the img tag in HTML to ensure the browser requests the image with headers for a CORS operation, such as

       Origin: http://localhost
       Sec-Fetch-Mode: cors
       Sec-Fetch-Site: cross-site
    

    In return the site responding with the image data must include an "Access-Control-Allow-Origin" header set from the "origin" request header, such as

    Access-Control-Allow-Origin: http://localhost
    

    or the wildcard version:

    Access-Control-Allow-Origin: *
    

    to enable javascript access to the data returned.

    Note https://i.stack.imgur.com does not return access control headers, so images returned from that domain will taint canvas objects. I highly recommend reading http://expressjs.com/resources/middleware/cors.html if you are are writing or modifying a node/express server to support CORS.

    window load event

    Wait until after the window load event fires to process image data. If processed earlier image loading may not be available.


    Corrupt image results

    The incorrect image data shown in the updated post is caused by not alligning the 3-tuples of rgb data in pix2 with the 4-tuples of rgba data required in imageData.data. To transpose the pixel data successfully try something equivalent to:

    for (let i = 0, j = 0; i < imgData.data.length; i += 4) {
      imgData.data[i + 0] = pix2[j++];
      imgData.data[i + 1] = pix2[j++];
      imgData.data[i + 2] = pix2[j++];
      imgData.data[i + 3] = 128;  // test value
    }
    

    The following snippet creates two canvas elements: one to show the image loaded and another to show the result of canvas data manipulation. The code uses localhost servers on different ports and won't run successfully without them:

    <!DOCTYPE html>
    <html><head><meta charset="utf-8"><title>cross origin</title></head>
    
    <body>
    <h2>Image</h2>
    <img id="face" crossorigin src="http://localhost:8080/share/imperial.jpg" />
    <h2>Canvas</h2>
    <canvas
        id="face2"
        width="112"
        height="112"
        style="border: 1px solid blue"
    ></canvas>
    
    <canvas
        id="modified"
        width="112"
        height="112"
        style="border: 1px solid red"
    ></canvas>
    
    <script type="text/javascript">"use strict";
    window.addEventListener("load", ()=> {
      const img = document.getElementById("face");
      const canvas = document.getElementById("face2");
      const context = canvas.getContext("2d");
      context.drawImage(img, 0, 0);
    
      let imgData1 = context.getImageData(0, 0, 112, 112)
      let pix;
      try {
        pix = imgData1.data;
      }
      catch( err) {
        console.warn( err.message);
      }
    
      pix = Array.from(pix);
    
      const pix2 = [];
    
      for (let i = 0; i < pix.length; i += 4) {
        pix2.push(pix[i + 0]);
        pix2.push(pix[i + 1]);
        pix2.push(pix[i + 2]);
      }
    
      const imgData = context.createImageData(112, 112);
      for (let i = 0, j = 0; i < imgData.data.length; i += 4) {
        imgData.data[i + 0] = pix2[j++];
        imgData.data[i + 1] = pix2[j++];
        imgData.data[i + 2] = pix2[j++];
        imgData.data[i + 3] = 128; // test value
      }
     
      const canvas2 = document.getElementById("modified");
      const context2 = canvas2.getContext("2d");
      context2.putImageData(imgData, 10, 10);
    
    });
    </script></body>
    </html>