cambu-Plot

dsc01883

Table of Contents:
  1. Process
  2. Technical Details
  3. Photo & Video Gallery
  4. Full Code Embed

Process

I kicked off my thought process for this project thinking about this font specimen and how I liked the seemingly arbitrary selection of numbers used. I wanted to create a system that would allow the continual generation of such values and also the spatial placement of such (typographic) numbers.

In contrast to my clock project, where I did a lot of drawing in Sketch App to define my design before beginning to code, with this project, I jumped into Processing almost right away. I did this to ’embrace the symptom’  and let the limitations of the tool guide my way through the design problem.

To be completely honest, this is a foreign way of working for me. I’m very used to asking the traditional question of “What should be designed?”, not the engineering question of “what is designable?” Playing with this question was an ongoing game of discovery during the project and one I’m learning to better understand. Furthermore, the way I setup my code using the provided template made my program consistently export PDFs throughout my working process, so I have a good amount of iteration history. [see above and below]

Technical Details

From an engineering point of view, this project involved more complexity than I’ve dealt with in the past. To start, I used a dynamic-grid-column drawing system to define the regions of my canvas. Then I use those predefined rectangular shapes to write numbers inside of them using the createFont() method, but importantly, instead of drawing the fonts to the canvas, I drew them to a JAVA2D PGraphics ‘offscreen’ canvas. I remixed some of the code for this from this github project. This means all of the numbers are being drawn in my custom font, GT Walsheim, directly onto an image object instead onto the primary canvas. The reason I do this is to allow for easy distortion and warping of the pixels and elements without having to convert text to outlines and deal with bezier curves.

The followup question was how do I get my design back out of raster format into ‘vector/object’ format, so I can use an exported PDF with the AkiDraw device? I used a method for scanning the pixels of the raster with the get() method, then I’m able to ‘etch the drawing’ back out of pixels and place objects that will export in the PDF where the colour values register in certain ranges. [see the video below for an example]

Etching Method Test
Etching Method Test

Photo & Video Gallery

gallery

Full Code Embed

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
// see https://processing.org/reference/libraries/pdf/index.html //<>// //<>//
import processing.pdf.*;
boolean bRecordingPDF;
int pdfOutputCount = 0; 
PFont myFont;
Gridmaker newGrid;
PGraphics pg;
int textSize = 40;
float magicYimpactor;
float amount; 
float currentChaos;
 
void setup() {
  size(612, 792);
  bRecordingPDF = true;
  myFont = createFont("GT-Walsheim-Thin-Trial.otf", textSize);
  textFont(myFont);
 
  newGrid = new Gridmaker();
  pg = createGraphics(width, height, JAVA2D); // create a PGraphics the same size as the main sketch display window
}
 
void draw() {
  if (bRecordingPDF) {
    background(255); // this should come BEFORE beginRecord()
 
    //START -- -- -- CAMBU FUNCTIONS
    pg.beginDraw(); // start drawing to the PGraphics
    drawGrid();
    chaosRepresentation();
 
    pg.endDraw(); // finish drawing to the PGraphics
    //END -- -- -- CAMBU FUNCTIONS
    image(pg, 0, 0);
    // -- -- -- function that reads all of pg and places points/ellipses at certain values of a certain brightness
    beginRecord(PDF, "cambu_" + pdfOutputCount + ".pdf");
    rasterToNotVector();
    endRecord();
    bRecordingPDF = false;
    pdfOutputCount++;
  }
}
 
void keyPressed() {
  //rasterToNotVector();
  //magicYimpactor = mouseX*0.0005;
  magicYimpactor = mouseX*0.05;
  //magicXXX = mouseX;
  //magicXimpactor = mouseY*0.0005;
  //amount = mouseX*0.0005;
  bRecordingPDF = true;
}
 
void chaosRepresentation() {
  float chaosStart = 1;
  int startX = 0;
  int startY = 0;
 
  int chaosIndex = 0;
  for (int y = 0; y < newGrid.numberOfRows; y++) { //verticalDivisor, x amount
    startX = 0;
    for (int x = 0; x < newGrid.numberOfCols; x++) { // horizontalDivisor, y amount
      fill((255/newGrid.numberOfCols)*(y/2), (255/newGrid.numberOfRows)*x, 200);
      //rect(startX,startY,newGrid.horizontalDivisor,newGrid.verticalDivisor); //within the domain & range of this rectangle, transform the pixels on pg 
      chaosIndex = chaosIndex + 1;
      currentChaos = chaosStart * chaosIndex;
      charsHere(startX, startY, currentChaos);
      startX = startX + newGrid.horizontalDivisor;
    }
    startY = startY + newGrid.verticalDivisor;
  }
}
 
void charsHere(int x, int y, float currentChaos) {
  int a = round((x + y)*.5);
 
  pg.textFont(myFont);
  pg.textSize(textSize);
  pg.fill(0, 0, 0);
 
  int xDes = x+(newGrid.horizontalDivisor/16);
  int yDes = y-(newGrid.verticalDivisor/4);
 
  pg.text(a, xDes, yDes);
  quadrantDestoryer(xDes, yDes, currentChaos); // operates between (startX, startY, newGrid.horizontalDivisor, newGrid.verticalDivisor)
}
 
void quadrantDestoryer(int xToDes, int yToDes, float currentChaos) {
  float xA = xToDes + 0.6*newGrid.horizontalDivisor - noise(currentChaos, yToDes, xToDes);
  float yA = yToDes - 0.2*newGrid.verticalDivisor;
 
  pg.fill(255, 235, 250);
  //pg.noStroke();
  //pg.ellipse(xToDes + 0.5*newGrid.horizontalDivisor * noise(currentChaos, yToDes), yToDes - 0.2*newGrid.verticalDivisor, noise(currentChaos, yToDes)*0.5*currentChaos, 0.05*currentChaos);
  //pg.ellipse(xA, yA, random(0, newGrid.horizontalDivisor)*0.8, noise(50, newGrid.horizontalDivisor)*2);
  //pg.rect(xA-8, yA, xA+ 30, yA + newGrid.verticalDivisor * 0.5);
  //pg.ellipse(xToDes, yToDes, currentChaos*noise(xToDes, yToDes), noise(currentChaos+currentChaos));
}
 
void rasterToNotVector() {//y down
  for (int y = 0; y < height; y ++) {
    for (int x = 0; x < width; x++) { //x across              
      color cp = get(x, y);
      int b = (int)blue(cp);
      int g = (int)green(cp); 
      int r = (int)red(cp);
      int tolerance = 150;
 
      float noised = 30;
 
      if (r < tolerance && g < tolerance && b < tolerance) { 
 
        float amount = 30;
 
        float nx = noise(x/noised, y/noised); 
        float ny = noise(magicYimpactor + x/noised, magicYimpactor + y/noised); 
 
        nx = map(nx, 0, 1, -amount, amount); //cc to Golan for explaining distortion fields.
 
        ny = map(ny, 0, 1, -amount, amount*magicYimpactor); 
 
        //line(x, y, x+nx, y+ny);
        strokeWeight(2);
        fill(34, 78, 240);
        ellipse(x + nx*0.5, y + ny/2, 4, 3);
      }
    }
  }
}
void drawGrid() {
  noStroke();
  int i = 0;
  for (int y = 0; y < newGrid.totalHeight; y = y + newGrid.verticalDivisor) { //squares down
    if (i % 2 == 0) {
      fill(140, 140, 140, 80);
    } else {
      fill(240, 240, 240, 80);
    } //if even, else odd
    i++;
  }
 
  int j = 0;
  for (int x = 0; x < newGrid.totalWidth; x = x + newGrid.horizontalDivisor) { ////squares across
    if (j % 2 == 0) {
      fill(140, 140, 140, 80);
    } else {
      fill(240, 240, 240, 80);
    } //if even, else odd
    j++;
  }
}
 
class Gridmaker {
  int totalHeight = height;
  int totalWidth = width;
  int numberOfRows = 12;
  int numberOfCols = 54;
  int verticalDivisor = round(totalHeight/numberOfRows);
  int horizontalDivisor = totalWidth/numberOfCols;
}