Pixelating Sketch


#1

Hello,

I would really appreciate any advice on how to approach this project I want to start. The basic notion is that I want to create a pixelation algorithm.

The slightly odd thing I would like to do is pixelate the image into a brick hatch pattern.

I would like to:

  • split an original image into blocks of 10 x 3 pixels or multiples there of (30 x 9 , 60 x 18 …)
  • sample the central pixel for a colour value (dominant colour would also be a good option…possible?)
  • fill a block with the sampled colour and draw that block to the processed, new image in the same position as it came from in the original image

Perhaps this illustration will help:

Step 1. in the image is possibly a bit misleading. I only intend that it communicates the idea that I would be sampling an original image. Not that I intend to arbitrarilly place a grid over an image and sample the colour.

Any suggestions as to how I should organise my sketch, sample colours from an image in a grid-like way etc. would be greatly appreciated.

Cheers :slight_smile:


#2

A way to approach such a task is to use get() function onto the original image to get a color of a specific pixel, remember it, and draw a rectangle over it with that color. Then, repeat that action with 2 for loops one inside another - one responsible for iterating over the brick pattern horizontally, another vertically.

In any case, here’s a complete code doing the thing you want, with stuff commented to help learning.

PImage img;

int brickWidth = 75;
int brickHeight = 25;

void settings() {
  img = loadImage(sketchPath("image.jpg"));
  size(img.width,img.height, P2D);
}

void setup() {
  noStroke(); //Remove this line to have black outlines around each brick
}

void draw() {
  image(img,0,0);
  
  for(int x=0;x<width;x+=brickWidth){
    for(int y=0;y<height;y+=brickHeight){
      int offset = 0;
      if(y%2==1){ //If Y is odd
        offset = brickWidth/2;
      }
      sampleRect(x-offset, y, brickWidth, brickHeight);
    }
  }
}


void sampleRect(int x,int y,int w, int h){ //X Y correspond to the upper left corner, W and H are sizes
  color thisColor = get( max(min(x+w/2,width),0) , max(min(y+h/2,height),0)); //Get color from the middle pixel of our future block
  //The max(min( functions are to limit the center coordinate so that it stays in the picture so that it doesn't try to grab a pixel from outside the screen. 
  fill(thisColor); //Use that color for drawing
  rect(x,y,w,h); //Draw it with that color
}

It obviously isn’t the most efficient approach in terms of computer speed and performance - if I would have a need to do this faster I’d write myself a GLSL shader that does this or something. But I tried to keep the code least complicated to aid learning it.

If you have questions about some function or something, you can always right-click it and press “Find in reference”, which will lead you to an offline web page that explains what it does!


#3

Hey Architector_4,

Thank you so much for your reply and solution. I got great results in no time, just had to disable P2D - for some reason it was causing the sketch to crash.

I will have a good play around with the code to make sure I fully understand and learn from the sketch.

Thanks again!


#4

Oh. To be honest, I slapped “P2D” into size without even thinking, and then assumed that long processing time was normal for such operations. Thanks for letting me know!


#5

Do you want the average color of each brick? You could try making a buffer image and using a blur operation first. Or you could scale the image down and sample the scaled down grid (similar). Or you could take multiple samples from each block and average them. The best strategy depends in part on the nature of your input images – will they be photographs? Will they be noisy? Will they ever be black and white (vs grayscale)?


#6

Hey Jeremy, blurring first is a great idea, that would initially average out the colours for sure. Taking multiple samples and averaging sounds technically more complex. The images - for this project at least - will be colourful photographs.


#7

One approach is to create a buffer image that lists your block values (one per pixel), then scale those results.

  1. Create the buffer with PImage.resize().
  2. Scale the output with image(src, x, y, w, h) – while noSmooth() is turned on.
/**
 * ScaledImageBlockSize
 * 2019-01-12 Jeremy Douglass - Processing 3.4
 * https://discourse.processing.org/t/pixelating-sketch/7791
 */
PImage img;
PImage valuesImg;
int BLOCKWIDTH = 10;
int BLOCKHEIGHT = 3;

void setup() {
  size(264, 480);
  noSmooth();

  img = loadImage("https://upload.wikimedia.org/wikipedia/commons/thumb/2/2e/Processing_3_logo.png/240px-Processing_3_logo.png");
  // trim the img to an exact multiple of our block size, cropping the right and bottom edge
  int cropWidth = img.width - img.width%BLOCKWIDTH;
  int cropHeight = img.height - img.height%BLOCKHEIGHT;
  img = img.get(0, 0, cropWidth, cropHeight);

  // scale img down to one pixel per block, using the resize algorithm. No need to reinvent the wheel
  int scaleWidth = cropWidth / BLOCKWIDTH;
  int scaleHeight = cropHeight / BLOCKHEIGHT;
  valuesImg = img.copy();
  valuesImg.resize(scaleWidth, scaleHeight);
}

void draw() {
  background(255);
  // original image
  image(img, 0, 0);
  // scaled image -- one pixel per block
  image(valuesImg, img.width, 0);
  // upscaled output, with one 10x3 pixel region per block.
  // this depends on noSmooth() being set, or upscale will blur.
  // the 5-arg image command is also really inefficient to do every frame!
  // instead, consider drawing only once into a buffer.
  image(valuesImg, 0, img.height, img.width, img.height);
}

In general, this makes the process very clear in code, but isn’t the most efficient approach. If you want real performance, my guess would be that you should some version of the pixelate shader method described in the PShader tutorial – although this requires shaders, which can be advanced:

https://processing.org/tutorials/pshader/


#8

Optionally, for performance, save the scaled output in a new image so you don’t have to redraw it every frame. Here is a revised sketch demonstrating that last step.

/**
 * ScaledImageBlockSize2
 * 2019-01-12 Jeremy Douglass - Processing 3.4
 * https://discourse.processing.org/t/pixelating-sketch/7791
 */

PImage img;
PImage valuesImg;
PGraphics blocksImg;
int BLOCKWIDTH = 10;
int BLOCKHEIGHT = 3;

void setup() {
  size(480, 480);
  noSmooth();

  img = loadImage("https://upload.wikimedia.org/wikipedia/commons/thumb/2/2e/Processing_3_logo.png/240px-Processing_3_logo.png");
  // trim the img to an exact multiple of our block size, cropping the right and bottom edge
  int cropWidth = img.width - img.width%BLOCKWIDTH;
  int cropHeight = img.height - img.height%BLOCKHEIGHT;
  img = img.get(0, 0, cropWidth, cropHeight);

  // scale img down to one pixel per block, using the resize algorithm. No need to reinvent the wheel
  int scaleWidth = cropWidth / BLOCKWIDTH;
  int scaleHeight = cropHeight / BLOCKHEIGHT;
  valuesImg = img.copy();
  valuesImg.resize(scaleWidth, scaleHeight);

  // save the upscaled output to a new PGraphics
  blocksImg = createGraphics(240, 240, JAVA2D);
  blocksImg.beginDraw();
  blocksImg.image(valuesImg, 0, 0, img.width, img.height);
  blocksImg.endDraw();
}

void draw() {
  background(255);
  // original image
  image(img, 0, 0);
  // scaled image -- one pixel per block
  image(valuesImg, img.width, 0);
  // upscaled output, with one 10x3 pixel region per block.
  // this depends on noSmooth() being set, or it will be blurry.
  // this is also really inefficient to do every frame!
  image(valuesImg, 0, img.height, img.width, img.height);

  // the pre-calculated upscaled image. this is fast --
  // although not as flexible as a PShader
  image(blocksImg, img.width, img.height);
}