Convolution example doesn't work in p5 editor

Hi,

I want to customize the image convolution example from the archive web site (https://archive.p5js.org/examples/image-convolution.html) but it doesn’t work outside the example page. When I paste it in the p5 editor with a personnal image, it gets very slow and displays a buggy image. I removed the mouseX and mouseY to just have a fixed convolution rectangle, it doesn’t work and displays alternated lines like it seems to struggle to reconstruct the image. I tried with light images and same thing.

So I did want to know if anybody has an idea how to solve this problem. What’s the difference between these two environments that make the code to not work.

Thanks.

Hi @Chloros and thanks for your question!

The p5.js examples are available in the editor by going to File > Examples.

The convolution example is here: Image: Convolution by p5 -p5.js Web Editor

If you share your version, we could have a look at the differences to figure out why it is slower.

1 Like

Thanks for your answer @sableRaph,

I didn’t notice that the examples was accessible from there, that’s great. The example works fine in the editor, but still very slowy and especially not working when pasted with a personnal image. I’m on Chromium for p5, I don’t do anything else on, beside I have Firefox devoted to researches.

What do you mean about ‘version’ ? If you mean version of the code, I first tried to run the original code and after I just removed the mouseX and mouse Y in order to have a fixed rectangle. Not a significative change that deserve to be pasted here I think.

I also tried to run it in Atom on a local server but it doesn’t work else.

Assuming you’re using the same code, and the same browser for both tests, my first thought is to check the size of the image you’re using. Larger images can slow down performance, especially during pixel-based operations like convolution. How large is your image?

1 Like

The smallest image I tried is 290x280 pixels, it’s very small so I doubt that’s the problem. The other are around 2000x2000 pixels. About this subject noticed the example image seems to be of a rather high quality. I don’t know how to verify it (I can’t download it) but just looking at it make me think to that.

The moonwalk.jpg image is 720 by 406 pixels. A good first step would be to try your sketch with the original image and see if you still experience performance issues:

https://archive.p5js.org/assets/examples/assets/moonwalk.jpg

1 Like

Well, the quality was not so high, I didn’t zoom to look at it.
So I tried with the ‘moonwalk’ picture and that works, that’s a good start. I tried with another image which I resized to pretty much the same size and that’s ok to.

It seems that there are a gradation in the efficiency of the algorithm ( that’s not a surprise), I will now make different tests to identify where the problems start, increasing the resolution slowly, try with other formats. I’ll also make test with what I would do initially. I’m back as soon as I’m finished with that.

Hi, I’m back after few tests. So I made the code work as I wanted and the problem is, without any surprise, the resolution. The image I want to work with is 5184x3456 pixels. What I want to do is to have a filtered square that move randomly on the image at a reasonnable frame rate (2 frame/s would be ok). For now, until the code work fluidly, I just made the square move on a line. This basically the same code but I removed the mouseX and mouseY and replaced them by a variable I incremente every loop:


let img;
let w = 150;
let a = 50; //position variable that will be incremented to make the filtered square move
let v = 10;
let r = 200;
// It's possible to convolve the image with many different
// matrices to produce different effects. This is a high-pass
// filter; it accentuates the edges.
const matrix = [
  [-1, -1, -1],
  [-1,  9, -1],
  [-1, -1, -1]
];


function preload() {

  img = loadImage('moonwalk.jpg');

}

function setup() {
  createCanvas(900,600);
  img.loadPixels();
  
  // pixelDensity(1) for not scaling pixel density to display density
  // for more information, check the reference of pixelDensity()
  pixelDensity(1);
}

function draw() {
  // We're only going to process a portion of the image
  // so let's set the whole image as the background first
  background(img);

  
   // Calculate the small rectangle we will process
  // const xstart = constrain(a - w / 2, 0, img.width);
  // const ystart = constrain(a - w / 2, 0, img.height);
  // const xend = constrain(a + w / 2, 0, img.width);
  // const yend = constrain(a + w / 2, 0, img.height);
  const matrixsize = 3;

  loadPixels();
  // Begin our loop for every pixel in the smaller image
  for (let x = xstart; x < xend; x++) {
    for (let y = ystart; y < yend; y++) {
      let c = convolution(x, y, matrix, matrixsize, img);

      // retrieve the RGBA values from c and update pixels()
      let loc = (x + y * img.width) * 4;
      pixels[loc] = red(c);
      pixels[loc + 1] = green(c);
      pixels[loc + 2] = blue(c);
      pixels[loc + 3] = alpha(c);
    }
  }
  updatePixels();
 
// I increase the square position here
  a = a + 5;
}

function convolution(x, y, matrix, matrixsize, img) {
  let rtotal = 0.0;
  let gtotal = 0.0;
  let btotal = 0.0;
  const offset = Math.floor(matrixsize / 2);
  for (let i = 0; i < matrixsize; i++) {
    for (let j = 0; j < matrixsize; j++) {
      // What pixel are we testing
      const xloc = x + i - offset;
      const yloc = y + j - offset;
      let loc = (xloc + img.width * yloc) * 4;

      // Make sure we haven't walked off our image, we could do better here
      loc = constrain(loc, 0, img.pixels.length - 1);

      // Calculate the convolution
      // retrieve RGB values
      rtotal += img.pixels[loc] * matrix[i][j];
      gtotal += img.pixels[loc + 1] * matrix[i][j];
      btotal += img.pixels[loc + 2] * matrix[i][j];
    }
  }
  // Make sure RGB is within range
  rtotal = constrain(rtotal, 0, 255);
  gtotal = constrain(gtotal, 0, 255);
  btotal = constrain(btotal, 0, 255);

  // Return the resulting color
  return color(rtotal, gtotal, btotal);
}

What was happening when my first tries with this image didn’t work was that I defined a smaller canvas than the image size. So, if someone could tell if it’s right, it seems that when we do that the image is constrained and pixels or stripes of pixels are not well reconstructed. By instance it appears regular stripes with a color that seems to come from the image, probably artifacts. I don’t even have the filter square on this resolution.

I’ll try on a local server to see if it’s the same. By the way I wanted to know if the preview window of the p5 editor is movable because I can’t display large images and see if something is working on.

I think I have to go deeper on the question of the convolution algorithm (maybe there exists a sort of fast convolution as for the Fourier transform ?). The code is pretty straigth forward, compute all the pixels in the square and let’s go, I know image processing requires a lot of ressources but it should be possible to optimize it. If someone has advices on these issues I would appreciate.

If you’re looking to speed up convolution on larger images, using a shader would be a great solution. Shaders run on the GPU, allowing for more efficient pixel-based operations compared to CPU-based operations in JavaScript. You can start with the p5.js shader tutorial to understand how to create custom fragment shaders in p5.js.

Once you have a grasp of the basics, check out the sketch below by @aferriss for an example of a convolution shader in p5.js:

That’s nice, I will dig that. Thanks @sableRaph !

1 Like