0

I have some code which generates collages from sets of photos, im doing this by averaging the pixels' colors of the images themselves and eventually, after a certain manipulation i just point(x,y) with the averaged color. The only problem is that when I zoom on retina screens (above certain resolution) it is very visible that this is indeed a bunch of points on the screen and not 1 complete image. I guess it has something to do with pixelDensity() but after a lot of experimentations with that, it didn't help as well.

attached here is an example of a zoomed crop - enter image description here

The main loop which combines the pixels is very basic and looks like this -

for (let y = 0; y <= imgOne.height; y++) {
    for (let x = 0; x <= imgOne.width; x++) {
      // Get the colors.
      const colorOne = imgOne.get(x, y);
      const colorTwo = imgTwo.get(x, y);

      let avgRed = (red(colorOne) + red(colorTwo)  ) / 2;
      let avgGreen = (green(colorOne) + green(colorTwo)) / 2;
      let avgBlue = (blue(colorOne) + blue(colorTwo)  ) / 2;
      stroke(avgRed,avgGreen,avgBlue);
      point(x, y);
      
    }
  }
Gonras Karols
  • 1,150
  • 10
  • 30
  • I think you need to scale the canvas or have higher resolution to begin with. This discussion may help: [Html Canvas at varying pixel densities](https://stackoverflow.com/a/34310873/15273968) – the Hutt Jan 30 '22 at 03:04

1 Answers1

1

The point function is specifically going to draw a tiny round shape with a diameter equal to the pixelDensity. When you scale up the canvas either by CSS transform or by using your browsers zoom function you are going to start to see the sub-pixel artifacts of this. There are two ways to make sure your "points" of color are square and completely fill the plane even when zoomed in: 1) use the set() function to explicitly draw pixels, 2) use the square() or rect() functions to deliberately draw a square. (Theoretically you could also directly manipulate the pixels array, but this would be significantly more complicated).

Here is an example that demonstrates the original issue, as well as the different solutions.

// only show a portion of the image.
const W = 120;
const H = 120;

let imgOne;
let imgTwo;

function preload() {
  // "Recursive raytrace of a sphere" by Tim Babb is licensed under CC BY-SA 4.0
  // https://creativecommons.org/licenses/by-sa/4.0/
  imgOne = loadImage("https://upload.wikimedia.org/wikipedia/commons/thumb/3/32/Recursive_raytrace_of_a_sphere.png/240px-Recursive_raytrace_of_a_sphere.png");
  // 
  imgTwo = loadImage("https://upload.wikimedia.org/wikipedia/commons/thumb/3/31/Rainbow-gradient-fully-saturated.svg/240px-Rainbow-gradient-fully-saturated.svg.png");
}

function setup() {
  createCanvas(W * 3, H);
  noLoop();
}

function draw() {
  background(220);
  for (let y = 0; y < H; y++) {
    for (let x = 0; x < W; x++) {
      // Get the colors.
      const colorOne = imgOne.get(x, y);
      const colorTwo = imgTwo.get(x, y);

      let avgRed = (red(colorOne) + red(colorTwo)) / 2;
      let avgGreen = (green(colorOne) + green(colorTwo)) / 2;
      let avgBlue = (blue(colorOne) + blue(colorTwo)) / 2;
      stroke(avgRed, avgGreen, avgBlue);
      point(x, y);
    }
  }
  for (let y = 0; y < H; y++) {
    for (let x = 0; x < W; x++) {
      const colorOne = imgOne.get(x, y);
      const colorTwo = imgTwo.get(x, y);

      let avgRed = (red(colorOne) + red(colorTwo)) / 2;
      let avgGreen = (green(colorOne) + green(colorTwo)) / 2;
      let avgBlue = (blue(colorOne) + blue(colorTwo)) / 2;
      set(x + W, y, color(avgRed, avgGreen, avgBlue));
    }
  }
  updatePixels();

  for (let y = 0; y < H; y++) {
    for (let x = 0; x < W; x++) {
      const colorOne = imgOne.get(x, y);
      const colorTwo = imgTwo.get(x, y);

      let avgRed = (red(colorOne) + red(colorTwo)) / 2;
      let avgGreen = (green(colorOne) + green(colorTwo)) / 2;
      let avgBlue = (blue(colorOne) + blue(colorTwo)) / 2;
      fill(avgRed, avgGreen, avgBlue);
      noStroke();
      square(x + W * 2, y, 1);
    }
  }
}

// This work is licensed under a CC BY-SA 4.0 License.
// https://creativecommons.org/licenses/by-sa/4.0/
// Author: Paul Wheeler
html,
body {
  margin: 0;
  padding: 0;
}

canvas {
  display: block;
  transform-origin: top left;
  transform: scale(4);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.4.0/p5.js"></script>

In order for the problem to be reproduced you need to use your browsers zoom capability first and then run the code. I'm not sure exactly why the behavior differs when you run the code and then zoom.

Paul Wheeler
  • 18,988
  • 3
  • 28
  • 41