#ffffff

Monochromatic Glyphs

This project began purely experimentally. I'd heard a few things about a mysterious IDE/ programming language combo called Processing, and I decided to try it out. I've been interested in the procedural writing of an artist called Atticus Bones for a long time, and an attempt at emulating his work was my first port of call.

Initially, I wrote a simple function which draws grids of 3x3 and 4x4 'tiles', and randomises which ones it colours in. I added another function which re-draws all the tiles each time the mouse is clicked.

I added a function which takes those grids of tiles, and connects some of them at random to form more complex lines. I was surprised by how quickly 'letters' became recognisable within the randomness. I also rewrote all the code from scratch, because it got bloated.

To see what would happen, I disabled drawing the tiles altogether and drew only the connections between them, as lines. This ended up being quite interesting, and lent itself quite well to pareidolia.

I cable tied a pen to a 3D printer and got it to draw some procedural glyphs in analogue form, too. For a few reasons, the printer didn't lift the pen from the paper while moving between glyphs, so a series of accidental connections was drawn as well. I'm not distraught about that.

At this point, I'd posted all of the above images on Twitter and was revelling in the positive feedback they'd been getting. One person who expressed an interest was my friend Ian Battaglia, a writer and photographer whose work I've admired for some time. After a short conversation, I offered to repurpose the procedural glyphs into a custom icon font for use on his website.

At first I was just going to redraw the generative symbols in my font editor, but I got slightly carried away. I added some which looked similar and followed the same grid, but I hid the initials 'IJB' in them. I drew Ian's Monochromatic logo in the same style, although I changed the proportions slightly so it'd look better when viewed at smaller sizes (narrow lines drawn close together tend to look awkward or blurry; I gave the circle some extra breathing room because of this).

The last three weren't procedural, but were an attempt to fit in a little more with Monochromatic's visual style. The body copy there is set in Anonymous Pro, which is rigidly monospace but still quite rounded, so I went for some less blocky, more circular symbols.

Ian was kind enough to interview me about my work. You can read that interview, and see live examples of the final glyphs, on his website.


Source code for the glyph generator: paste this into a blank Processing 3 document and hit cmd+R for some cool shapes. They'll refresh every time you click. Feel free to play with the variables at the top, and the numbers in the "size(800, 800);" line and see how the result changes. Set LineMode to "false" for a different drawing method, or ExportMode to "true" to export the result as a vector PDF.

import processing.pdf.*;

// GLYPHS are grids of TILES
// WORDS are grids of GLYPHS

// Editable Variables
int background = 220;
int margin = 100;
int tiles = 4;               // How many 'tiles' are in each glyph
int tilesize = 10;           // How big those tiles should be
int tilespacing = 20;        // How far apart they are
int darkness = 9;            // Affects the number of tiles which draw black (10 = all of them)
float connectedness = 0.6;   // Affects how many connections get made between tiles in a glyph
boolean lineMode = true;     // Set true to draw only tile connections
int lineThickness = 5;       // Thickness of tile connections (Line Mode only)
boolean exportMode = false;  // If true, programme will export output to a PDF.

// Programme Variables
int glyphspacing = 30;
int working_x;
int working_y;
int glyphsize;
int tilesize_inclusive;
int[][] glyphs;
boolean done = false;

void setup() {
  size(800, 800);
  background(background);

  glyphsize = (tilesize * tiles) + (tilespacing * (tiles - 1)) + glyphspacing;
  tilesize_inclusive = tilesize + tilespacing;
  darkness --;
  if(lineMode) {
    tilesize = 0;
  }

  if(exportMode) {
    noLoop();
    beginRecord(PDF, "export.pdf");
  }
}

void draw() {
  if(!done) {
    addWord();
    done = true;
  }

  if(exportMode) {
    endRecord();
  }
}

void mouseClicked() {
  background(background);
  addWord();
}

void addWord() {
  int maxglyphs = (width - margin * 2) / glyphsize;

  // Set up the Glyphs[] array

  // glyphs[][] is a 2D array, where glyphs[n][0] is the x, and glyphs[n][1] is the y value for the glyph at position [n] (counted left to right, top to bottom)

  // Initialise the array
  glyphs = new int[maxglyphs * maxglyphs][2];

  // Reset the 'toolhead'
  working_x = margin;
  working_y = margin;

  // For every value of n in glyphs[][]
  for(int i = 0, j = 0; i < maxglyphs * maxglyphs; i ++) {
    // Add coordinates for the current toolhead
    glyphs[i][0] = working_x;
    glyphs[i][1] = working_y;
    // Move the toolhead right, if it's not already at the end of the line, or if it is, put it back to the far left and move it down
    if(j <= maxglyphs - 2) {
      working_x += glyphsize;
      j ++;
    } else {
      working_x = margin;
      working_y += glyphsize;
      j = 0;
    }
    // The toolhead will stop when it runs out of array data to populate, no need to cap it manually
  }

  // Iterate through the glyphs[] array, and add a glyph for each one
  for(int i = 0, l = glyphs.length; i < l; i ++) {
    addGlyph(glyphs[i][0], glyphs[i][1]);
  }

}

void addGlyph(int x, int y) {

  int[][] glyphtiles = new int[tiles * tiles][3];
  int local_x = x;
  int local_y = y;

  // For every value of n in glyphs[][]
  for(int i = 0, j = 0, l = glyphtiles.length; i < l; i ++) {
    // Add coordinates for the current toolhead
    glyphtiles[i][0] = local_x;
    glyphtiles[i][1] = local_y;
    glyphtiles[i][2] = int(random(10));
    // Move the toolhead right, if it's not already at the end of the line, or if it is, put it back to the far left and move it down
    if(j <= tiles - 2) {
      local_x += tilesize + tilespacing;
      j ++;
    } else {
      local_x = x;
      local_y += tilesize + tilespacing;
      j = 0;
    }
    // The toolhead will stop when it runs out of array data to populate, no need to cap it manually
  }

  for(int i = 0, l = glyphtiles.length; i < l; i ++) {
    if(glyphtiles[i][2] <= darkness) {
      addTile(glyphtiles[i][0], glyphtiles[i][1]);
    }

    // Make horizontal connections (drawing vertical bars)
    if(i != l - 1) {
      if(glyphtiles[i][2] <= darkness && glyphtiles[i + 1][2] <= darkness && glyphtiles[i + 1][1] == glyphtiles[i][1]) { // Called if glyph immediately to the right is drawn
        float connection = random(1);
        if(connection <= connectedness) {
          connectTile(glyphtiles[i][0] + tilesize, glyphtiles[i][1], true);
        }
      }
    }

    // Make vertical connections (drawing horizontal bars)
    if(i + tiles <= l - tiles + 2) {
      if(glyphtiles[i][2] <= darkness && glyphtiles[i + tiles][2] <= darkness && glyphtiles[i + tiles][0] == glyphtiles[i][0]) { // Called if glyph immediately below is drawn
        float connection = random(1);
        if(connection <= connectedness) {
          connectTile(glyphtiles[i][0], glyphtiles[i][1] + tilesize, false);
        }
      }
    }

  }

}

void legacyAddGlyph(float x, float y) {

  float local_x = x;
  float local_y = y;

  for(int i = 0, j = 0; i < tiles * tiles; i ++) {

    if(random(1) <= darkness) {
      addTile(local_x, local_y);
    }

    j++;
    if(j >= tiles) {
      local_x = x;
      local_y += tilesize_inclusive;
      j = 0;
    } else {
      local_x += tilesize_inclusive;
    }

  }

}

void addTile(float x, float y) {
  fill(0);
  stroke(0);
  strokeWeight(0);
  rect(x, y, tilesize, tilesize);
}

void connectTile(float x, float y, boolean isVertical) {
  fill(0);
  stroke(0);
  if(lineMode) {
    strokeWeight(lineThickness);
  } else {
    strokeWeight(0);
  }
  if(isVertical) {
    rect(x, y, tilespacing, tilesize);
  } else {
    rect(x, y, tilesize, tilespacing);
  }
}