p5.Image is fuzzy

As soon as a p5.Graphics element gets stored in a p5.Image element, it gets a bit fuzzier. Some smoother anti alias I think.
The effect is subtle, but visible to me.

Is there a way to prevent this?

I’m trying to make a small drawing app which I can use to try out all the brushes I might make with p5. I wrote an undo function which uses p5.Images to store previous states of the image. I wish those states could be stored in a lossless format, is that possible?
Or is there a way to do what I did by copying p5.Graphics objects? I haven’t yet worked out how…
My current version: https://editor.p5js.org/hapiel/sketches/rROK3rm_F

I originally used get() instead of myImg.copy, it is even still in the file (see line 118). So line 112 to 116 can be replaced with line 120. But then I copy everything that is on screen, and I want to only copy a single layer, which is layerDraw or stored in my object as layer.

I tried layer.get() but that doesn’t seem to work?

EDIT: Of course that didn’t work, because as usual, I forgot the keyword ‘this’. But now that it does work (locally, haven’t updated the editor file), it still produces fuzzy images…

EDIT2: also using get() I can’t reuse my old ‘add background’ system (112 - 115), so I would have to rewrite a bit if I want to use get I guess?

  • I confess I’ve never used method get() on p5.js.
  • On Processing PGraphics is a subclass of class PImage and everything works as expected.
  • However for unknown reasons p5js devs took a wrong turn for not doing the same for p5.Graphics & p5.Image.
  • Regardless, get() creates a clone, which is wasteful for most cases.
  • Best technique is to transfer the pixels[] content to another 1, which avoids unnecessary cloning:
    reference | p5.js

I just tried to replace my copy function with pixels. It makes the code a bit longer, but it kinda works

    this.layer.loadPixels();
    this.img[this.img.length - 1].loadPixels();

    for (let i = 0; i < this.layer.pixels.length; i++) {
      this.img[this.img.length - 1].pixels[i] = this.layer.pixels[i];
      
    }
    // this.img[this.img.length - 1].pixels = this.layer.pixels;

    this.img[this.img.length - 1].updatePixels();

Why does the for loop work, but the simple line that I commented out does not? Don’t they do the same thing?

EDIT: I just discovered that I have to clone an array by saying array1 = [...array2]; or array1 = array2.splice();, but neither work here too…
this.img[this.img.length - 1].pixels = [...this.layer.pixels];

Anyway, I can’t add a background to this, because the way I add the background is by adding it to a picture, and then copying the drawing on top. But the whole copying function is what gives the fuzzyness… :confused:
I’m still confused as to how to solve this. Perhaps I can add the background in another place (and then double check if this pixel swapping actually solves the fuzzyness )

EDIT: found a place to enter the background. All works now, except that I have this rather wasteful for loop in my code…

No! The loop is dealing w/ the indexed contents of the arrays.

While the single assignment you’re making 2 properties pointing to the same array.

1 Like

I think all is sorted now, this is the final(ish) version:
https://editor.p5js.org/hapiel/sketches/D8U0fLVc8

Thank you @GoToLoop for pointing me towards pixels!

I still wonder if the for loop at 115 can be replaced with something like at 119 (wouldn’t that be faster?), other than that I’m super happy with it, :smiley:

I like to cache values in local variables: :wink:

const { layer, img } = this, lastImg = img[img.length - 1];
layer.loadPixels(), lastImg.loadPixels();

const { pixels: src } = layer, { pixels: dst } = lastImg, { length: len } = src;
for (let i = 0; i < len; dst[i] = src[i++]);
lastImg.updatePixels();

The statement above creates a new Array w/ the contents of layer.pixels[] and replaces img[-1].pixels[] w/ that.

However that is a big mistake, b/c pixels[] is expected to be of datatype Uint8ClampedArray, not some vanilla Array as you’re doing there:

Description: Uint8ClampedArray containing the values for all the pixels in the display window.

Moreover, typed arrays don’t have the method splice(); so definitely array1 = array2.splice(); won’t work!

So you should just access the indexed content and refrain from reassigning pixels[] w/ your own array.

Most you can do is replace the loop w/ the typed array’s method set():

const { layer, img } = this, lastImg = img[img.length - 1];
layer.loadPixels(), lastImg.loadPixels();

lastImg.pixels.set(layer.pixels);
lastImg.updatePixels();

Thanks for all the info, you’ve been very helpful.

In the end, I just discovered that after all the modifications I made, and introducing a clear() before displaying the new content on screen, I got rid of the fuzzy lines too. Turns out I could use get() after all, and never needed to use anything as complex as copying the pixel data.

Silly me for looking in the wrong place, but at least it is now al working smooth! And with the get() replacing the for loop it can handle waaaay larger canvasses!

Take notice that each get() creates a new HTMLCanvasElement, which is not a light object btW:

Depending on how many of them are created each draw() iteration, the sketch can even crash some mobile devices:

Mutating already existing pixels[] saves memory and can be more performant.

1 Like