2

Problem:

I am learning p5.js and I am following a tutorial from Coding Train YouTube channel. Everything was fine until I had to call a library function on an Image object. The problem is that I have instantiated the library in an object p and I'm using it's variables through p object. I don't know why it isn't recognizing the loadPixels() function. In the tutorial, the function works fine.

Error Message:

p5.js says: There's an error as "loadPixels" could not be called as a function

(on line 17 in help.html [file:///G:/Android/help.html:17:11]).
Verify whether "img" has "loadPixels" in it and check the spelling, letter-casing (Javacript is case-sensitive) and its type.
For more: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Errors/Not_a_function#What_went_wrong

Code:

<!DOCTYPE html>
<head>
  <script src='p5/p5.js'></script>
</head>
<body>
  <div id='container'></div>
  <script>
  let sketch = function(p) {
    p.setup = function(){
      p.createCanvas(56, 56);
      img = new Image();
      img.src = "scott.jpg";
    }
    p.draw = function() {
        // p.drawingContext.drawImage(img, 0, 0);
      p.loadPixels();
      img.loadPixels();
      for (var x=0; x<p.width; x++) {
        for (var y=0; y<p.height; y++) {
          // var d = p.dist(x, y, p.width/2, p.height/2);
          var loc = x+y+p.width;
          // p.pixels[loc] = p.color(d);
          p.pixels[loc] = img.pixels[loc];
        }
      }
    }
  p.updatePixels();
  };
  new p5(sketch, 'container');
  </script>
</body>
</html>

Edit: As someone pointed out that the problem is that I'm using Image() which is the default Image class for javascript. I did some changes to my code but now it gives me this error.

Error :-

Uncaught DOMException: The operation is insecure. help.html:18
    openWindow file:///G:/Android/help.html:18
    onclick file:///G:/Android/help.html:1

Code :-

<!DOCTYPE html>
<head>
  <script src='p5/p5.js'></script>
</head>
<body>
  <button onclick="openWindow()">click me to open a new window.</button>
  <div id='container'></div>
  <script>
    function openWindow() {
        var newWindow = window.open("", "Import Image", "height=56,width=56,status=yes,toolbar=no,menubar=no,location=no");  

        newWindow.document.write("<canvas id='imagePlaceholder'>Canvas not supported!</canvas>");
        var canvas = newWindow.document.getElementById("imagePlaceholder");
        var ctx = canvas.getContext("2d");

        ctx.drawImage(img, 0, 0);
        // console.log(ctx.getImageData(0, 0, 56, 56).data);
        dest = ctx.getImageData(0, 0, 56, 56).data;

    }


  let sketch = function(p) {
    p.setup = function(){
      p.createCanvas(56, 56);
      img = new Image();
      img.src = "scott.jpg";
      let dest = p.createImage(56, 56);
      console.log(img);
    }
    p.draw = function() {
        // p.drawingContext.drawImage(img, 0, 0);
      // p.loadPixels();
      img.loadPixels();
      for (var x=0; x<p.width; x++) {
        for (var y=0; y<p.height; y++) {
          // var d = p.dist(x, y, p.width/2, p.height/2);
          var loc = x+y+p.width;
          // p.pixels[loc] = p.color(d);
          p.pixels[loc] = img.pixels[loc];
        }
      }
    }
  p.updatePixels();
  };
  new p5(sketch, 'container');
  </script>
</body>
</html>
Paul Wheeler
  • 18,988
  • 3
  • 28
  • 41
  • Html Image element does not have a loadPixels method. – Alice Oualouest Apr 25 '21 at 07:19
  • To get the pixels, you need to load the image, draw it in a canvas, then use getImageData on this canvas. – Alice Oualouest Apr 25 '21 at 07:21
  • Alice, I'm trying to do that now, but the problem is that I don't want to show the user the canvas I'm using to draw the image and get pixels from. When I'm not showing the canvas, the data I get from getImageData is an Array of Array of 0s. I am trying to solve that now. Will edit the question later after getting clarity on my little experiment. – Sukhchain Singh Apr 25 '21 at 07:59
  • look here : https://stackoverflow.com/questions/8751020/how-to-get-a-pixels-x-y-coordinate-color-from-an-image – Alice Oualouest Apr 25 '21 at 09:20
  • Thanks Alice for the reference, but the problem is now CORS. I don't have a server and I don't want to implement a server, this is supposed to be a static application which anyone can download and double click to open in a browser. To avoid CORS, I used context, but if the problem remains then I don't know what can I do. Maybe trying to make a local application in web was a mistake. – Sukhchain Singh Apr 25 '21 at 09:28
  • That's a different problem, and different question. Still, you can embed the image in the js or html by giving image.src a base64 url containing the whole image. – Alice Oualouest Apr 25 '21 at 09:51

3 Answers3

1

One obvious problem here is that you are using the builtin Image() constructor, which creates an HTMLImageTag (see the MDN Article) instead of creating a p5js p5.Image object (see the p5js Reference). However there are several other issues. In p5js you need to load images in the preload function to ensure they are available when you start drawing (this is an asynchronous operation). You'd have a much easier time drawing images in p5js using the built in image function. If you are going to use pixel arrays, you need to understand the structure of these arrays. They don't store Color objects, they store 4 separate numbers for each color channel (red, green, blue, and alpha). So the indices in the array are not (x + y * width), but ((x + y * width) * 4 + channel) where channel is a number from 0 to 3. Also you need to account for the fact that the canvas may have a pixel density > 1, whereas the image will have a pixel density of 1. I strongly suggest you read all of the documentation for the Image related p5js functions.

let sketch = function(p) {
  let img;
  
  p.preload = function() {
    img = p.loadImage("https://s3-ap-southeast-1.amazonaws.com/investingnote-production-webbucket/attachments/41645da792aef1c5054c33de240a52e2c32d205e.png");
  };
  
  p.setup = function() {
    p.createCanvas(200, 200);
  };
  
  p.draw = function() {
    // this would be a lot simpler way to draw the image:
    // p.image(img, 0, 0);
    
    p.loadPixels();
    img.loadPixels();
    // Handle high pixel density displays. This code effectively scale the image up so that each 1 pixel in the source image is density * density pixels in the display, thus preserving the size of the image but leading to visible pixelation.
    let density = p.pixelDensity();
    for (var x = 0; x < p.width && x < img.width; x++) {
      for (var y = 0; y < p.height && y < img.height; y++) {
        // There are 4 values per pixel in the pixels array:
        var srcLoc = (x + y * img.width) * 4;
        for (var xd = 0; xd < density; xd++) {
          for (var yd = 0; yd < density; yd++) {
            var destLoc =
              (x  * density + xd + (y * density + yd) * p.width * density) * 4;
            for (var i = 0; i < 4; i++) {
              p.pixels[destLoc + i] = img.pixels[srcLoc + i];
            }
          }
        }
      }
    }
  
    p.updatePixels();
  };
};

new p5(sketch, 'container');
<!DOCTYPE html>
<html lang="en">

<head>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.3.1/p5.min.js"></script>
</head>

<body>
  <div id='container'></div>
</body>

</html>

Here is an alternate version of the snippet that handles pixelDensity differently:

let sketch = function(p) {
  let img;
  
  p.preload = function() {
    img = p.loadImage("https://s3-ap-southeast-1.amazonaws.com/investingnote-production-webbucket/attachments/41645da792aef1c5054c33de240a52e2c32d205e.png");
  };
  
  p.setup = function() {
    p.createCanvas(200, 200);
  };
  
  p.draw = function() {
    // this would be a lot simpler way to draw the image:
    // p.image(img, 0, 0);
    
    p.loadPixels();
    img.loadPixels();
    // Handle high pixel density displays. This code shrinks the image down by mapping one pixel in the source image to 1 / (density ^ 2) actual pixels in the canvas.
    let density = p.pixelDensity();
    for (var x = 0; x < p.width * density && x < img.width; x++) {
      for (var y = 0; y < p.height * density && y < img.height; y++) {
        // There are 4 values per pixel in the pixels array:
        var srcLoc = (x + y * img.width) * 4;
        var destLoc = (x + y * p.width * density) * 4;
        for (var i = 0; i < 4; i++) {
          p.pixels[destLoc + i] = img.pixels[srcLoc + i];
        }
      }
    }
  
    p.updatePixels();
  };
};

new p5(sketch, 'container');
<!DOCTYPE html>
<html lang="en">

<head>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.3.1/p5.min.js"></script>
</head>

<body>
  <div id='container'></div>
</body>

</html>
Paul Wheeler
  • 18,988
  • 3
  • 28
  • 41
  • The image URL used in this code snippet does not have permissive CORS headers, so if there were any issue with CORS I would expect it to surface here. If you have an image src that breaks this code sample please let me know so I can test it out. If it is a private image that requires authentication can you describe the authentication mechanism and share the response headers? – Paul Wheeler Apr 28 '21 at 01:51
  • Hi Paul, maybe I forgot to mention before, I used JavaScript Image because I couldn't fetch local image in p5.loadImage() so that's why I'm using ctx.drawImage(img, 0, 0);. – Sukhchain Singh Apr 28 '21 at 10:17
  • If you can tell me a way to load loacal image in p5.Image datatype then it would be a big help and thanks for telling me that I need to do this ((x + y * width) * 4 + channel) to got correct mapping. I was wondering why all my pixels were drawing on a single line. – Sukhchain Singh Apr 28 '21 at 10:19
  • Ok, yeah, it wasn't clear that you specifically wanted to load images from localhost. I'm going to leave this answer here as-is because it addresses several issues with the original code. – Paul Wheeler Apr 28 '21 at 19:51
1

Because you are specifically trying to load an image from a local computer as opposed to a publicly accessible URL, a file input with user interaction is going to be the only way to do this. This is a deliberate constraint put in place by web browsers to prevent a malicious webpage from illicitly reading data from your local files. However there is a much simpler way to get the image data from the file input onto your p5js canvas. In fact this exact use case can be seen in the documentation for the createFileInput function.

let input;
let img;

function setup() {
  input = createFileInput(handleFile);
  input.position(0, 0);
}

function draw() {
  background(255);
  if (img) {
    image(img, 0, 0, width, height);
  }
}

function handleFile(file) {
  if (file.type === 'image') {
    img = createImg(file.data, '');
    img.hide();
  } else {
    img = null;
  }
}
<!DOCTYPE html>
<html lang="en">

<head>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.3.1/p5.min.js"></script>
</head>

<body>
</body>

</html>
Paul Wheeler
  • 18,988
  • 3
  • 28
  • 41
  • gee, thanks. I really appriciate your help, didn't know this function existed as all the tutorials I followed only showed loadImage(). – Sukhchain Singh Apr 29 '21 at 06:49
0

I tried a lot of things and almost giveup, but at the end I had to change the code a bit and this worked for me. Although what I got was base64 url as Alice in the comment suggested and I converted it into Uint8ClampedArray. Now if anyone wants a full image or all the pixels of an image then they can follow this link :- https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API/Tutorial/Pixel_manipulation_with_canvas . I hope it will be helpful for anyone who wants to make an offline webcanvas based application and don't want to dabble with CORS.

var fileSelector = document.createElement('input');
                fileSelector.setAttribute('type', 'file');
                fileSelector.setAttribute('accept', 'image/gif, image/jpeg, image/png');

                fileSelector.click();
                fileSelector.onchange = function(e) {
                    img = new Image();
                    var file = e.target.files[0];

                    var reader = new FileReader();
                    reader.onloadend = function() {
                         img.src = reader.result;
                    }
                    reader.readAsDataURL(file);


                    var newWindow = window.open("", "_blank", "height=56,width=56,status=yes,toolbar=no,menubar=no,location=no");  
                    
                    newWindow.document.write("<canvas id='imagePlaceholder'>Canvas not supported!</canvas>");
                    var canvas = newWindow.document.getElementById("imagePlaceholder");
                    var ctx = canvas.getContext("2d");

                    ctx.drawImage(img, 0, 0);
                    // console.log(ctx.getImageData(0, 0, 56, 56).data);
                    var dest = ctx.getImageData(0, 0, img.width, img.height).data;
                    console.log(dest);
                    newWindow.close();
                }
  • I'm not convinced that all this complexity is needed. You should be able to do all your image handling using p5js functionality (see my answer). If there release is an issue with that please provide a minimal, reproducible example – Paul Wheeler Apr 28 '21 at 01:55
  • I tried a bunch of solutions including this https://stackoverflow.com/questions/51157895/load-image-generated-from-a-function-in-p5-js-canvas/51162033#51162033 and it's possible variants but none of them worked, I tried your solution too, but couldn't get it to work with local files. If you know a way to achieve this in a less ambiguous way then let me know. – Sukhchain Singh Apr 28 '21 at 16:36