Masked and Weighted Truchet Tiles in Processing

I first saw this effect used with an image of Alan Turing and the word CODE superimposed. It was many months before several disparate ideas came together and I finally rediscovered how to generate these kinds of pictures. Here now I’ll show you how I built up the Processing code to do the same.

I used Processing because it is my favorite for making quick visual prototypes. I’m going to skip the basics of Processing and get right to it. In the first draft all I did is make a tiny sketch to draw one half of one Truchet Tile of diagonal lines:

int tileSize = 60;
int lineSpacing = 10;

// style=/
void tileA(int x0,int y0) {
  int x1=x0+tileSize;
  int y1=y0+tileSize;
  
  for(int x=0;x<tileSize;x+=lineSpacing) {
    line(x0+x,y0,x0,y0+x);
  }
}

void setup() {
  size(960,960);
  
  for(int y=0;y<height;y+=tileSize) {
    for(int x=0;x<width;x+=tileSize) {
      tileA();
    }
  }
}

void draw() {}

Which makes this image:

Next, I added the code to fill in the second half of the square and a second method to draw the second tile – a repeat of tile 1 turned 90 degrees. To make it a bit fun I filled the screen with randomly chosen tiles.

// style=/
void tileA(int x0,int y0) {
  int x1=x0+tileSize;
  int y1=y0+tileSize;
  
  for(int x=0;x<tileSize;x+=lineSpacing) {
    line(x0+x,y0,x0,y0+x);
    line(x0+x,y1,x1,y0+x);
  }
}

// style=\
void tileB(int x0,int y0) {
  int x1=x0+tileSize;
  int y1=y0+tileSize;
  
  for(int x=0;x<tileSize;x+=lineSpacing) {
    line(x0+x,y0,x1,y1-x);
    line(x0+x,y1,x0,y1-x);
  }
}

void setup() {
  size(960,960);
  
  for(int y=0;y<height;y+=tileSize) {
    for(int x=0;x<width;x+=tileSize) {
      int t = floor(random(2));
      if(0==t) tileA(x,y);
      else     tileB(x,y);
    }
  }
}

Nice. Next, in setup, I loaded an image that was exactly the size of my window.

PImage img;

void setup() {
  size(960,960);
  
  img = loadImage("tunein-turnon-dropout-karililt.jpg");
  img.filter(GRAY);
  img.loadPixels();

  for(int y=0;y<height;y+=tileSize) {
    ...

I knew I wanted to play with the Processing method strokeWeight() to make the line thick wherever the picture is dark. strokeWeight() can only be set once per line, so where I used to have one long line I would now need lots of little lines. I swapped out every call to line() with a new method, inter()

int iterSize = 1;
float maxWeight = lineSpacing*2/3;

void inter(int x0,int y0,int x1,int y1) {
  // get the line length
  float dx = x1-x0;
  float dy = y1-y0;
  float len = sqrt(dx*dx+dy*dy);

  // step many times over the whole line in tiny segments.
  for(float i=0;i<len;i+=iterSize) {
    // find the x/y at the start of the line segment
    float ox = x0+dx*(i/len);
    float oy = y0+dy*(i/len);
    // don't go past the end of the line.
    float i2 = min(i+iterSize,len);
    // find the x/y at the end of the line segment
    float px = x0+dx*(i2/len);
    float py = y0+dy*(i2/len);
    // the stroke weight is the image darkness.
    float c=0;
    // as long as we're still inside the image!
    if( px>=0 && px<img.width &&
        py>=0 && py<img.height ) {
      float darkness = red(img.get((int)px,(int)py));
      // I have a number where 0 is black and 255 is white.
      // I want 0 for white and maxWeight for black.
      c = maxWeight * (255.0-darkness) / 255.0;
    }
    // keep the weight between 1 and maxWeight.
    c = max(c,1);
    // now draw the line segment.
    line(ox,oy,px,py,c));
  }
}

And boom!

By the way: go give a like to @karililt

My last experiment was to make the tileSize equal to the lineSpacing – as small as they can get – and use a second image of white letters on a black background to choose the tile. In this way I was able to recreate the image that first inspired me.

If you liked that, like/share/subscribe/patreon. You can find all the code on my github.