hizlik-book

Update: As of 2020, an updated documentation for this project is now on my website at hiz.al/yearone.

Year One is a book of cityscapes. Each city represents the daily volume of text messages that my girlfriend and I sent each other during our first year of dating. There are 366 total cities (I included both our first day, and first anniversary), and each building represents an hour in the day (24 total buildings, at most), in the manner of a bar chart.

cover_front

Each city is rendered with a watercolor/painted effect. The book was mainly generated using Processing, but other programming languages were used as well, which served various purposes in the creation of the imagery within the pages.

365

As described in the process video (below), I wanted to create a project based on the texts that my girlfriend sent to each other during our first year of dating, which was long-distance for most of the time. The other thing I wanted to do was create generative cityscapes (I have a fascination with cities). After being unable to pick between the two concepts, I put them together. Essentially, the total significance of all the texts per hour (so not the amount of texts, but the total content length) dictates the height of a building, while the number of images sent are represented by the various attachments to the buildings (antennas, water towers, balconies, “blocks/vents” on the roof, etc. I used multiple languages to create this project, detailed below. One of the significant issues for this was creating the painterly effect, which in the end I think turned out exactly as I had hoped it would. I took inspiration from artist Michael Tompsett for the artistic style of my generated imagery.

screen-shot-2016-10-28-at-1-48-53-am screen-shot-2016-10-28-at-1-49-04-am

The most time-consuming process was the creation of the variety of buildings and building parameters. Each building consists of differently modified versions of vertex-based shapes. I believe I succeeded in everything I intended to do, which surprises me because normally I either run out of time or am unable to achieve my vision. This was one of the more multi-step and complicated projects I’ve undertaken, so I was surprised I was able to not only achieve the exact image I had in my head, but also within record time (I started Wednesday night, finished by Saturday afternoon). I love how the buildings look, am pleased with their variety (although it could always use more) and love the way the watercolor effect looks. If I had to change anything, I would change the % possibility for some types of more unique buildings (to a lower chance), and also add more brush types and more “floors” the buildings rest on, for more variety. (You can see some repetition if you pay attention).

The numbers at the bottom of each page represent the total number of text messages, and total volume of characters that we exchanged. Golan expressed surprise that we sent each other (in some cases) hundreds of text messages per day. It’s true.

Here’s a video of Golan flipping through the book:

FILES

Short excerpt of the book (8MB PDF): hizlik_book_short.pdf

You can download this PDF and all the source code from my GitHub repo.

CODE

cover_back


Shell/Bash Script: Export text messages from Apple Messages SQLite database

if [ $# -lt 1 ]; then
    echo "Enter a iMessage account (email of phone number i.e +33616.....) "
fi
login=$1
    
sqlite3 ~/Library/Messages/chat.db "
select '[str][' as 'ph1',datetime(date + strftime('%s', '2001-01-01 00:00:00'), 'unixepoch', 'localtime') as date,']' as 'ph2','[' as 'ph2',is_from_me,']' as 'ph2',NULL as 'filename',text from message where handle_id=(
select handle_id from chat_handle_join where chat_id=(
select ROWID from chat where guid='iMessage;-;$1')
)
union all
select '[att][' as 'ph1',datetime(date + strftime('%s', '2001-01-01 00:00:00'), 'unixepoch', 'localtime') as date,']' as 'ph2','[' as 'ph3',is_from_me,']' as 'ph2',NULL as 'text',filename from attachment,message_attachment_join,message where attachment.rowid = message_attachment_join.attachment_id and message_attachment_join.message_id = message.rowid and message.cache_has_attachments=1 and message.handle_id=(
select handle_id from chat_handle_join where chat_id=(
select ROWID from chat where guid='iMessage;-;$1')
) order by date" | sed 's/\|1\|/H/g;s/\|0\|/N/g;s/\|//g' > iMessages$1.txt

Python: Analyze texts for number, total characters, and amount of attachments sent (per hour)

# -*- coding: utf-8 -*-
import glob
import os
import re
import string

from datetime import datetime,timedelta

filenames = []
texts = []
data = {}

metadata = {
	'total_N_str': 0,
	'total_N_att': 0,
	'total_N_str_len': 0,
	'max_N_len': 0,
	'total_H_str': 0,
	'total_H_att': 0,
	'total_H_str_len': 0,
	'max_H_len': 0
}

def getLogs():
	cwd = os.getcwd()
	os.system("cd '" + cwd + """'
./imbfull.sh PHONE
./imbfull.sh EMAIL""")

def getFileNames():
	for filename in glob.glob('*.txt'):
		if "iMessage" in filename: filenames.append(filename)

def displayFileList():
	print len(filenames),"files found:"
	print '\n'.join(filenames)

def openFile(s):
	print "Openning file...",
	with open(s, 'r') as f: 
		readList = f.readlines()
	print "			File successfully open."
	return readList

def parseText(text):
	# search for [type][YYYY-MM-DD HH:MM:SS][FROM]
	pattern = re.compile('^\[[a-z]{3}\]\[\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\]\[[A-Z]\]')

	meta = re.search(pattern, text).group()
	t_type = meta[1:4]
	t_from = meta[-2:-1]
	t_date = datetime.strptime(meta[6:25], '%Y-%m-%d %H:%M:%S')
	t_str = text.replace(meta,"").rstrip()

	return t_type, t_from, t_date, t_str

def listTexts(file, filename):
	print "Listing messages...",

	# search for [type][YYYY-MM-DD HH:MM:SS][FROM]
	pattern = re.compile('^\[[a-z]{3}\]\[\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\]\[[A-Z]\]')

	for line in file:
		line = line.rstrip()
		if re.match(pattern, line):
			texts.append(line)
		else:
			texts[-1] += line

	print "		Messages successfully listed."

def runMessageExtractor(s):
	print "\nExtracting text IDs on %s:"%(s)
	listTexts(openFile(s), s)
	# H_att,N_att,H_txt,N_txt = countTexts(readList,s)
	# texts.append((H_att,N_att,H_txt,N_txt))

def analyze():
	global total_N_str, total_N_att, total_H_str, total_H_att

	print "Analyzing messages...",
	#initialize time range
	start_date = datetime.strptime("2014-05-02", '%Y-%m-%d')
	while start_date < datetime.strptime("2015-05-03", '%Y-%m-%d'): 
		data[start_date.strftime('%Y-%m-%d %H')] = { 
			'H': { 'txt_count':0, 'len':0, 'att_count':0 },
			'N': { 'txt_count':0, 'len':0, 'att_count':0 }
		}
		start_date += timedelta(hours=1)
	#populate data
	for text in texts:
		t_type, t_from, t_date, t_str = parseText(text)
		if t_type == "str":
			if t_from == "H":
				metadata['total_H_str'] += 1
				metadata['total_H_str_len'] += len(t_str)
				metadata['max_H_len'] = max(metadata['max_H_len'],len(t_str))
			else:
				metadata['total_N_str'] += 1
				metadata['total_N_str_len'] += len(t_str)
				metadata['max_N_len'] = max(metadata['max_N_len'],len(t_str))
		else:
			if t_from == "H":
				metadata['total_H_att'] += 1
			else:
				metadata['total_N_att'] += 1
		if t_date.strftime('%Y-%m-%d %H') in data:
			data[t_date.strftime('%Y-%m-%d %H')][t_from]['txt_count'] += 1
			if t_type == "att":
				data[t_date.strftime('%Y-%m-%d %H')][t_from]['att_count'] += 1
			else:
				data[t_date.strftime('%Y-%m-%d %H')][t_from]['len'] += len(t_str)
	print "		Analysis complete."
def writeData():
	print "Writing data...",
	with open("h_data.txt", 'w') as f:
		start_date = datetime.strptime("2014-05-02", '%Y-%m-%d')
		prev_day = -1
		while start_date < datetime.strptime("2015-05-03", '%Y-%m-%d'): if prev_day != start_date.day: if prev_day >= 0:
					f.write("\n")
				f.write(start_date.strftime("%B %d").lstrip("0").replace(" 0", " "))
				prev_day = start_date.day
			txt_count = data[start_date.strftime('%Y-%m-%d %H')]['H']['txt_count']
			t_len = data[start_date.strftime('%Y-%m-%d %H')]['H']['len']
			att_count = data[start_date.strftime('%Y-%m-%d %H')]['H']['att_count']
			f.write("-%d&%d&%d"%(txt_count, t_len, att_count))
			start_date += timedelta(hours=1)
		f.close()
	with open("n_data.txt", 'w') as f:
		start_date = datetime.strptime("2014-05-02", '%Y-%m-%d')
		prev_day = -1
		while start_date < datetime.strptime("2015-05-03", '%Y-%m-%d'): if prev_day != start_date.day: if prev_day >= 0:
					f.write("\n")
				f.write(start_date.strftime("%B %d").lstrip("0").replace(" 0", " "))
				prev_day = start_date.day
			txt_count = data[start_date.strftime('%Y-%m-%d %H')]['N']['txt_count']
			t_len = data[start_date.strftime('%Y-%m-%d %H')]['N']['len']
			att_count = data[start_date.strftime('%Y-%m-%d %H')]['N']['att_count']
			f.write("-%d&%d&%d"%(txt_count, t_len, att_count))
			start_date += timedelta(hours=1)
		f.close()
	print "			Write complete."

def writeDataTogether():
	print "Writing data...",
	with open("data.txt", 'w') as f:
		start_date = datetime.strptime("2014-05-02", '%Y-%m-%d')
		prev_day = -1
		while start_date < datetime.strptime("2015-05-03", '%Y-%m-%d'): if prev_day != start_date.day: if prev_day >= 0:
					f.write("\n")
				f.write(start_date.strftime("%B %d, %Y").lstrip("0").replace(" 0", " "))
				prev_day = start_date.day
			txt_count = data[start_date.strftime('%Y-%m-%d %H')]['H']['txt_count'] + data[start_date.strftime('%Y-%m-%d %H')]['N']['txt_count']
			t_len = data[start_date.strftime('%Y-%m-%d %H')]['H']['len'] + data[start_date.strftime('%Y-%m-%d %H')]['N']['len']
			att_count = data[start_date.strftime('%Y-%m-%d %H')]['H']['att_count'] + data[start_date.strftime('%Y-%m-%d %H')]['N']['att_count']
			f.write("-%d&%d&%d"%(txt_count, t_len, att_count))
			start_date += timedelta(hours=1)
		f.close()
	print "			Write complete."

def runTextCounter():
	getLogs()
	getFileNames()
	displayFileList()
	print "\nBeginning chatlog indexing..."
	for chatlog in filenames:
		runMessageExtractor(chatlog)
	print ""

	analyze()
	writeDataTogether()

	# only within time range
	non0len_H = 0
	non0count_H = 0
	maxlen_H = 0
	non0len_N = 0
	non0count_N = 0
	maxlen_N = 0

	start_date = datetime.strptime("2014-05-02", '%Y-%m-%d')
	while start_date < datetime.strptime("2015-05-03", '%Y-%m-%d'): t_len = data[start_date.strftime('%Y-%m-%d %H')]['H']['len'] if t_len > 0:
			non0len_H += t_len
			non0count_H += 1
			maxlen_H = max(maxlen_H, t_len)
		t_len = data[start_date.strftime('%Y-%m-%d %H')]['N']['len']
		if t_len > 0:
			non0len_N += t_len
			non0count_N += 1
			maxlen_N = max(maxlen_N, t_len)
		start_date += timedelta(hours=1)

	print "\nChatlog indexing is complete.\n"
	print "%d total messages: %d by H and %d by N."%(metadata['total_H_str']+metadata['total_N_str'], metadata['total_H_str'], metadata['total_N_str'])
	print "%d total attachments: %d by H and %d by N."%(metadata['total_H_att']+metadata['total_N_att'], metadata['total_H_att'], metadata['total_N_att'])
	print "%d ave characters: %d for H (max %d) and %d for N (max %d)"%((metadata['total_H_str_len']+metadata['total_N_str_len'])*1.0/(metadata['total_H_str']+metadata['total_N_str']), metadata['total_H_str_len']*1.0/metadata['total_H_str'], metadata['max_H_len'], metadata['total_N_str_len']*1.0/metadata['total_N_str'], metadata['max_N_len'])
	print "%d ave characters per hour: %d for H (max %d) and %d for N (max %d)"%((non0len_H + non0len_N)*1.0/(non0count_H + non0count_N), non0len_H*1.0/non0count_H, maxlen_H, non0len_N*1.0/non0count_N, maxlen_N)
	

runTextCounter()


Processing: Generating cityscape masks using brush strokes and random buildings based on text analysis

import java.util.*; 
boolean debug = false;

ArrayList<String[][]> texts = new ArrayList<String[][]>();
int index = 0;
int hour = 0;
boolean var3used = false;
boolean var4used = false;
boolean thinused = false;

// VARIATIONS
float overlap = 10;
float ignore = 10;
float xscap = 25;
float smallcap = 50;
float medcap = 200;
float tallcap = 500;

// CITY VARIABLES
float citystartx;
float citystarty;
float building_width;
ArrayList trees = new ArrayList();

// PAINT
ArrayList floors = new ArrayList();
ArrayList lefts = new ArrayList();
ArrayList rights = new ArrayList();
ArrayList mids = new ArrayList();

void setup() {
  size(1700,1700); //start 350 in from 1700
  String[] fileLines = loadStrings("../txtcounter/data.txt");
  for(int i=0; i<fileLines.length; i++)
    texts.add(parseData(fileLines[i]));
  
  // trees
  for(int i=0; i<12; i++)
    trees.add(loadImage("greenery/s_treeTop"+i+".png"));
  
  // floors
  for(int i=0; i<10; i++)
    floors.add(loadImage("brushes/floors/floor"+i+".png"));
  
  // lefts
  for(int i=0; i<12; i++)
    lefts.add(loadImage("brushes/lefts/left"+i+".png"));
  
  // rights
  for(int i=0; i<12; i++)
    rights.add(loadImage("brushes/rights/right"+i+".png"));
  
  // mids
  for(int i=0; i<46; i++) mids.add(loadImage("brushes/mids/mid"+i+".png")); citystartx = 350; citystarty = (height-350)-(height-700)/3.0; building_width = (width-700)/24.0; background(255); //buildCity(texts.get(index)); println(getMetadata(texts.get(index))); } void draw() { // draw background if(hour == 0) { int floor = int(random(floors.size())); image(floors.get(floor),0,-2); int left = int(random(lefts.size())); if(round(random(2))>0)
      image(lefts.get(left),0,0);
    int right = int(random(rights.size()));
    if(round(random(2))>0)
      image(rights.get(right),0,0);
    for(int i=0; i<round(random(4,6)); i++) { int mid = int(random(mids.size())); image(mids.get(mid),random(-100,100),0); } hour ++; } // draw template String[][] data = texts.get(index); if(hour>0 && hour<data.length) {
    buildCity(data, hour);
    hour++;
  }
}

void reset() {
  hour = 0;
  background(255);
  var3used = false;
  var4used = false;
  thinused = false;
}

void keyPressed() {
  if (key == 'b' || key == 'B') {
    index--;
    if(index<0) index = texts.size()-1; println(getMetadata(texts.get(index))); reset(); } if (key == 'n' || key == 'N') { index++; if(index >= texts.size()) index = 0;
    println(getMetadata(texts.get(index)));
    reset();
  }
  if (key == 's' || key == 'S') {
    String filename = "drawings/"+index+" "+texts.get(index)[0][0]+".png";
    saveFrame(filename);
  }
  if (key == 'r' || key == 'R') {
    reset();
  }
}

String getMetadata(String[][] data){
  String info = data[0][0]+" ";
  int texts = 0;
  int att = 0;
  for(int i=1; i<data.length; i++) {
    texts += parseInt(data[i][0]);
    att += parseInt(data[i][2]);
  }
  info += "("+texts+" texts, "+att+" attachments)";
  return info;
}

void buildCity(String[][] data, int i) {
  overlap = random(10);
  float limitter = 8000; //ave 755
  float hval = map(parseInt(data[i][1]), 0, limitter, 0, height/2);
  if(hval < smallcap) { //is small fill(255,0,0,128); } else if(hval >= smallcap && hval < medcap) { //is med fill(0,255,0,128); } else if(hval >= medcap && hval < tallcap) { //is tall
    fill(0,0,255,128);
  } 
  else { //is super tall
    fill(128,128,128,128);
  }
  if(debug) {
    noStroke();
    rect(citystartx+building_width*(i-1)-overlap, citystarty, building_width+overlap, random(-100,-200));
  }
  if(hval != 0) {
    if(hval <= ignore && parseInt(data[i][2])>0)
      attGroup0(citystartx+building_width*(i-1)-overlap, citystarty, building_width+overlap, parseInt(data[i][2]));
    else
      randomBuilding(citystartx+building_width*(i-1)-overlap, citystarty, building_width+overlap, hval, parseInt(data[i][2]));
  }
}

void randomBuilding(float startx, float starty, float w, float h, int numAtt) {
  // is too small
  if(h <= ignore) {
    var11(startx, starty, w, h, numAtt);
  }
  
  // is xs
  else if(h < xscap) {
    if(round(random(1)) == 0)
      var11(startx, starty, w, h, numAtt);
    else
      var13(startx, starty, w, h, numAtt);
  }
  
  // is small
  else if(h < smallcap) {
    int choice = int(random(100));
    if(choice < 30)
      var9(startx, starty, w, h, numAtt);
    else if(choice < 60)
      oldRoof(startx, starty, w, h, numAtt);
    else if(choice < 90)
      slantRoof(startx, starty, w, h, numAtt);
    else
      basic(startx, starty, w, h, numAtt);
  }
  
  // is medium
  else if(h < medcap) { 
    int choice = int(random(8));
    if(choice == 0)
      oldRoof(startx, starty, w, h, numAtt);
    else if(choice == 1) {
      if(thinused)
        randomBuilding(startx, starty, w, h, numAtt);
      else
        thin(startx, starty, w, h, numAtt);
    }
    else if(choice == 2)
      slantRoof(startx, starty, w, h, numAtt);
    else if(choice == 3)
      basic(startx, starty, w, h, numAtt);
    else if(choice == 4)
      stairstep(startx, starty, w, h, numAtt);
    else if(choice == 5)
      angled(startx, starty, w, h, numAtt);
    else if(choice == 6)
      bevel(startx, starty, w, h, numAtt);
    else
      roundRoof(startx, starty, w, h, numAtt);
  }
  
  // is tall
  else if(h < tallcap) { 
    int choice = int(random(14));
    if(choice == 0)
      var1(startx, starty, w, h, numAtt);
    else if(choice == 1) {
      if(var3used)
        randomBuilding(startx, starty, w, h, numAtt);
      else
        var3(startx, starty, w, h, numAtt);
    }
    else if(choice == 2) {
      if(var4used)
        randomBuilding(startx, starty, w, h, numAtt);
      else
        var4(startx, starty, w, h, numAtt);
    }
    else if(choice == 3)
      var8(startx, starty, w, h, numAtt);
    else if(choice == 4)
      slant(startx, starty, w, h, numAtt);
    else if(choice == 5)
      stairstep(startx, starty, w, h, numAtt);
    else if(choice == 6)
      bevel(startx, starty, w, h, numAtt);
    else if(choice == 7)
      slice(startx, starty, w, h, numAtt);
    else if(choice == 8)
      angled(startx, starty, w, h, numAtt);
    else if(choice == 9)
      blockRoof(startx, starty, w, h, numAtt);
    else if(choice == 10)
      triangleRoof(startx, starty, w, h, numAtt);
    else if(choice == 11)
      basic(startx, starty, w, h, numAtt);
    else if(choice == 12) {
      if(thinused)
        randomBuilding(startx, starty, w, h, numAtt);
      else
        thin(startx, starty, w, h, numAtt);
    }
    else
      var2(startx, starty, w, h, numAtt);
  }
  
  // is super tall
  else { 
    int choice = int(random(100));
    if(choice < 20)
      var1(startx, starty, w, h, numAtt);
    else if(choice < 40)
      var5(startx, starty, w, h, numAtt);
    else if(choice < 60)
      var7(startx, starty, w, h, numAtt);
    else if(choice < 80)
      stairstep(startx, starty, w, h, numAtt);
    else
      var8(startx, starty, w, h, numAtt);
  }
}

String[][] parseData(String texts) { 
  String[][] hourlyData = new String[25][3];
  String[] hours = texts.split("-");
  hourlyData[0] = new String[1];
  hourlyData[0][0] = hours[0];
  for(int i=1; i<hours.length; i++) { hourlyData[i] = hours[i].split("&"); } return hourlyData; } // ******************** VARIATION DESIGNS ********************* // void basic(float startx, float starty, float w, float h, int numAtt) { noStroke(); fill(0); rect(startx, starty, w, -1*h); if(numAtt>0)
    attGroup1(startx, starty, w, h, numAtt);
}

void slant(float startx, float starty, float w, float h, int numAtt) {
  int both = round(random(2));
  int left = round(random(1));
  float xr = random(w/6, w/3);
  float xl = random(w/6, w/3);
  noStroke();  
  fill(0);
  beginShape();
  vertex(startx, starty);
  if(both==1) {
    vertex(startx+xl, starty-h);
    vertex(startx+w-xr, starty-h);
  }
  else {
    if(left==1) {
      vertex(startx+xl, starty-h);
      vertex(startx+w, starty-h);
    }
    else {
      vertex(startx, starty-h);
      vertex(startx+w-xr, starty-h);
    }
  }
  vertex(startx+w, starty);
  endShape(CLOSE);
  
  if(numAtt > 0) {
    if(both == 1)
      attGroup1(startx+xl, starty, w-(xr+xl), h, numAtt);
    else if(left == 1)
      attGroup1(startx+xl, starty, w-xl, h, numAtt);
    else
      attGroup1(startx, starty, w-xr, h, numAtt);
  }
}

void bevel(float startx, float starty, float w, float h, int numAtt) {
  int both = round(random(2));
  int left = round(random(1));
  float x1 = random(w/6, w/3); // width of slant
  float y1 = random(h/5); // height of slant
  noStroke();  
  fill(0);
  beginShape();
  vertex(startx, starty);
  if(both==1) {
    vertex(startx, starty-h+y1);
    vertex(startx+x1, starty-h);
    vertex(startx+w-x1, starty-h);
    vertex(startx+w, starty-h+y1);
  }
  else {
    if(left==1) {
      vertex(startx, starty-h+y1);
      vertex(startx+x1, starty-h);
      vertex(startx+w, starty-h);
    }
    else {
      vertex(startx, starty-h);
      vertex(startx+w-x1, starty-h);
      vertex(startx+w, starty-h+y1);
    }
  }
  vertex(startx+w, starty);
  endShape(CLOSE);
  
  if(numAtt > 0) {
    if(both == 1)
      attGroup1(startx+x1, starty, w-x1*2, h, numAtt);
    else if(left == 1)
      attGroup1(startx+x1, starty, w-x1, h, numAtt);
    else
      attGroup1(startx, starty, w-x1, h, numAtt);
  }
}

void slice(float startx, float starty, float w, float h, int numAtt) {
  int left = round(random(1));
  float y1 = random(h/8, h/3); // height of slant
  noStroke();  
  fill(0);
  beginShape();
  vertex(startx, starty);
  if(left==1) {
    vertex(startx, starty-h);
    vertex(startx+w, starty-h+y1);
  }
  else {
    vertex(startx, starty-h+y1);
    vertex(startx+w, starty-h);
  }
  vertex(startx+w, starty);
  endShape(CLOSE);
  
  if(numAtt>0)
    attGroup2(startx+10+random(w-20), starty-h+y1, w, random(h/6, h/4), startx, starty, numAtt);
}

void angled(float startx, float starty, float w, float h, int numAtt) {
  float x1 = random(w/4, w-w/4); // corner point
  float yl = constrain(random(h/3),0,10); // height of left
  float yr = constrain(random(h/3),0,10); // height of right
  noStroke();  
  fill(0);
  beginShape();
  vertex(startx, starty);
  vertex(startx, starty-h+yl);
  vertex(startx+x1, starty-h);
  vertex(startx+w, starty-h+yr);
  vertex(startx+w, starty);
  endShape(CLOSE);
  
  if(numAtt>0)
    attGroup2(startx+10+random(w-20), starty-h+max(yl,yr), w, random(h/8, h/5), startx, starty, numAtt);
}

void stairstep(float startx, float starty, float w, float h, int numAtt) {
  int single = round(random(3));
  int left = round(random(1));
  float x1; //width of left
  float y1; //height of left
  if(single == 0) { // is both
    x1 = random(w/5, w/2);
    y1 = random(h/8, h/3);
  }
  else {
    x1 = random(w/4, w-w/4);
    y1 = random(h/8, h/3);
  }
  
  noStroke();  
  fill(0);
  beginShape();
  vertex(startx, starty);
  if(single==0) {
    stairLeft(startx, starty, w, h, x1, y1);
    stairRight(startx, starty, w, h, x1, y1);
  }
  else if(left == 1) {
    stairLeft(startx, starty, w, h, x1, y1);
    vertex(startx+w, starty-h);
  }
  else {
    vertex(startx, starty-h);
    stairRight(startx, starty, w, h, x1, y1);
  }
  vertex(startx+w, starty);
  endShape(CLOSE);
  
  numAtt = attGroup3(startx, starty, w, h-y1, numAtt);
  if(numAtt > 0) {
    if(single==0)
      attGroup0(startx, starty, w, numAtt);
    else if(left == 1)
      attGroup1(startx+x1, starty, w-x1, h, numAtt);
    else
      attGroup1(startx, starty, w-x1, h, numAtt);
  }
}

void stairLeft(float startx, float starty, float w, float h, float x1, float y1) {
  int steps = round(random(4,6));
  for(int i=0; i<=steps; i++) {
    float x = startx + ((x1/steps)*i);
    float y = (starty-h+y1) - ((y1/steps)*i);
    vertex(x,y);
    vertex(x+(x1/steps),y);
  }
}

void stairRight(float startx, float starty, float w, float h, float x2, float y2) {
  int steps = round(random(4,6));
  for(int i=0; i<steps; i++) {
    float x = startx + (w-x2) + ((x2/steps)*i);
    float y = (starty - h) + ((y2/steps)*i);
    vertex(x,y);
    vertex(x+(x2/steps),y);
  }
}

void blockRoof(float startx, float starty, float w, float h, int numAtt) {
  float y = random(h/2); // height of block
  if(y<20) y = 20; float space = random(5,13); //spacing between steps noStroke(); fill(0); beginShape(); vertex(startx, starty); vertex(startx, starty-h+y); vertex(startx+(w/space), starty-h+y); vertex(startx+(w/space), starty-h+y/2); vertex(startx+((w/space)*2), starty-h+y/2); vertex(startx+((w/space)*2), starty-h); //mid vertex(startx+w-((w/space)*2), starty-h); vertex(startx+w-((w/space)*2), starty-h+y/2); vertex(startx+w-(w/space), starty-h+y/2); vertex(startx+w-(w/space), starty-h+y); vertex(startx+w, starty-h+y); vertex(startx+w, starty); endShape(CLOSE); numAtt = attGroup3(startx, starty, w, h-y, numAtt); if(numAtt>0)
    attGroup2(startx+((w/space)*2)+5+random((startx+w-((w/space)*2))-(startx+((w/space)*2))-10), starty-h, w, random(h/6, h/4), startx, starty, numAtt);
}

void triangleRoof(float startx, float starty, float w, float h, int numAtt) {
  float y = random(h/15,h/5);
  noStroke();  
  fill(0);
  beginShape();
  vertex(startx, starty);
  vertex(startx, starty-h+y);
  vertex(startx+w/2, starty-h);
  vertex(startx+w, starty-h+y);
  vertex(startx+w, starty);
  endShape(CLOSE);
  
  if(numAtt>0)
    attGroup2(startx+w/2, starty, w, h+random(h/6, h/4), startx, starty, numAtt);
}

void roundRoof(float startx, float starty, float w, float h, int numAtt) {
  float y = random(h/5);
  noStroke();  
  fill(0);
  beginShape();
  vertex(startx, starty);
  vertex(startx, starty-h+y);
  bezierVertex(startx, starty-h, startx+w/2, starty-h, startx+w/2, starty-h);
  bezierVertex(startx+w, starty-h, startx+w, starty-h+y, startx+w, starty-h+y);
  vertex(startx+w, starty);
  endShape(CLOSE);
  
  if(numAtt>0)
    attGroup2(startx+5+random(w-10), starty, w, h+random(h/8, h/5), startx, starty, numAtt);
}

void oldRoof(float startx, float starty, float w, float h, int numAtt) {
  float x1 = overlap*0.75; //distance from edge
  float y1 = smallcap/4; //height
  float y2 = 3; //edge height
  if(h<=smallcap) { h = map(h, 0, smallcap, smallcap-smallcap/3, smallcap); float div = random(0.8, 1.8); startx += overlap*div; w -= (overlap*div)*2; x1 /= 3; y1 /= 3; y2 /= 3; } noStroke(); fill(0); beginShape(); vertex(startx, starty); vertex(startx, starty-h+y1-y2); vertex(startx+y2, starty-h+y1-y2); vertex(startx+y2, starty-h+y1); vertex(startx+x1, starty-h+y1); vertex(startx+x1, starty-h+y2); vertex(startx+x1-y2, starty-h); //mid vertex(startx+w-x1+y2, starty-h); vertex(startx+w-x1, starty-h+y2); vertex(startx+w-x1, starty-h+y1); vertex(startx+w-y2, starty-h+y1); vertex(startx+w-y2, starty-h+y1-y2); vertex(startx+w, starty-h+y1-y2); vertex(startx+w, starty); endShape(CLOSE); numAtt = attGroup3(startx, starty, w, h-y1, numAtt); if(numAtt > 0)
    attGroup4(startx, starty, w, h, numAtt);
}

void slantRoof(float startx, float starty, float w, float h, int numAtt) {
  float x1 = random(3,8); //horizontal size
  float y1 = random(3,15); //vertical size
  if(h<=smallcap) { h = map(h, 0, smallcap, smallcap-smallcap/3, smallcap); float div = random(0.8, 1.8); startx += overlap*div; w -= (overlap*div)*2; x1 /= 3; y1 /= 3; } noStroke(); fill(0); beginShape(); vertex(startx, starty); vertex(startx, starty-h+y1); vertex(startx-x1, starty-h); vertex(startx+w+x1, starty-h); vertex(startx+w, starty-h+y1); vertex(startx+w, starty); endShape(CLOSE); numAtt = attGroup3(startx, starty, w, h-y1, numAtt); } void thin(float startx, float starty, float w, float h, int numAtt) { thinused = true; float x1 = random(w/10, w/3); // start point of thin float x2 = random(w/2, w*.75); // width of thin float y1 = random(h/5, h/3); // height of first float y2 = random(h/4, h/2); // height of second noStroke(); fill(0); randomBuilding(startx+x1, starty, x2, h, numAtt); randomBuilding(startx-overlap/2, starty, overlap/2+x1+x2/2, y1, 0); randomBuilding(startx+x1+x2/2, starty, w-(x1+x2/2)+overlap/2, y2, 0); } // ******************** FULL DESIGNS ********************* // void var1(float startx, float starty, float w, float h, int numAtt) { startx -= overlap/3; w += (overlap/3)*2; float x1 = random(w/6, w/4); //slant width float y1 = random(h/4, h-h/4); //left slant start float y2 = random(20); //slant height float y1r = random(h/4,h-h/4); //right slant start int uneven = round(random(5)); noStroke(); fill(0); beginShape(); vertex(startx, starty); vertex(startx, starty-y1); vertex(startx+x1, starty-y1-y2); vertex(startx+x1, starty-h); vertex(startx+w-x1, starty-h); if(uneven > 0) {
    vertex(startx+w-x1, starty-y1r-y2);
    vertex(startx+w, starty-y1r);
  }
  else {
    vertex(startx+w-x1, starty-y1-y2);
    vertex(startx+w, starty-y1);
  }
  vertex(startx+w, starty);
  endShape(CLOSE);
  
  attGroup1(startx+x1, starty, w-x1*2, h, numAtt);
}

void var2(float startx, float starty, float w, float h, int numAtt) {
  float x1 = random(w/6, w/4); //slant width
  float y1 = random(h-h/3); //bottom left slant start
  float y2 = random(20); //slant height
  float y1r = random(h-h/4); //bottom right slant start
  float y3 = random(h/3); //top height
  y1=constrain(y1,h/5,h-y3);
  y1r=constrain(y1,h/5,h-y3);
  int uneven = round(random(1));
  noStroke();
  fill(0);
  beginShape();
  vertex(startx, starty);
  vertex(startx, starty-y1);
  vertex(startx+x1, starty-y1-y2);
  vertex(startx+x1, starty-(h-y3-y2*2));
  vertex(startx+w/4, starty-(h-y3-y2));
  vertex(startx+w/4, starty-(h-y2));
    //mid
  vertex(startx+w/2, starty-h);
  vertex(startx+w/2+w/4, starty-(h-y2));
  vertex(startx+w/2+w/4, starty-(h-y3-y2));
  vertex(startx+w-x1, starty-(h-y3-y2*2));
  if(uneven == 1) {
    vertex(startx+w-x1, starty-y1r-y2);
    vertex(startx+w, starty-y1r);
  }
  else {
    vertex(startx+w-x1, starty-y1-y2);
    vertex(startx+w, starty-y1);
  }
  vertex(startx+w, starty);
  endShape(CLOSE);
  
  if(numAtt>0)
    attGroup2(startx+w/2, starty-h, w, random(h/7, h/4), startx, starty, numAtt);
}

void var3(float startx, float starty, float w, float h, int numAtt) {
  var3used = true;
  startx -= overlap/2;
  w += overlap;
  float y1 = random(h/5, h/2); // start of curve
  float x1 = random(5,w/4); // gap
  int bridges = round(random(1,5));
  noStroke();  
  fill(0);
  beginShape();
  vertex(startx, starty);
  vertex(startx, starty-h+y1);
  bezierVertex(startx, starty-h, startx+(w-x1)/2, starty-h, startx+(w-x1)/2, starty-h);
  vertex(startx+(w-x1)/2, starty);
  endShape(CLOSE);
  beginShape();
  vertex(startx+w, starty);
  vertex(startx+w, starty-h+y1);
  bezierVertex(startx+w, starty-h, (startx+w)-(w-x1)/2, starty-h, (startx+w)-(w-x1)/2, starty-h);
  vertex((startx+w)-(w-x1)/2, starty);
  endShape(CLOSE);
  boolean top = true;
  for(int i=0; i<bridges; i++) { float y2 = random(h/2); //height on building float x3 = random(3,8); //thickness if(top) { y2 = h-y2; top = false; } else top = true; rect(startx+(w-x1)/2,starty-y2,x1,x3); } if(numAtt>0)
    attGroup0(startx, starty, w, numAtt);
}

void var4(float startx, float starty, float w, float h, int numAtt) {
  var4used = true;
  float left = round(random(1));
  float xl = random(w/4, w/2); // left "waist" amount
  float xr = random(w/4, w/2); // right "waist" amount
  noStroke();  
  fill(0);
  beginShape();
  vertex(startx, starty);
  if(left == 1)
    vertex(startx+xl, starty-h/2);
  vertex(startx, starty-h);
  vertex(startx+w, starty-h);
  if(left == 0)
    vertex((startx+w)-xr, starty-h/2);
  vertex(startx+w, starty);   
  endShape(CLOSE);
  float weight = random(1.5,5);
  strokeWeight(weight);
  stroke(0);
  if(left==1)
    line(startx+weight, starty-weight, startx+weight, starty-h+weight);
  else
    line(startx+w-weight, starty-weight, startx+w-weight, starty-h+weight);
  
  if(numAtt>0)
    attGroup1(startx, starty, w, h, numAtt);
}

void var5(float startx, float starty, float w, float h, int numAtt) {
  float x1 = random(w/3, w*.75); //width of curve
  int left = round(random(1));
  w += x1/2; // *2?
  noStroke();
  fill(0);
  beginShape();
  if(left == 1) {
    startx -= x1/2;
    vertex(startx, starty);
    bezierVertex(startx+x1, starty, startx+x1, starty-h, startx+x1, starty-h);
    vertex(startx+w, starty-h);
    vertex(startx+w, starty);
  }
  else {
    vertex(startx+w, starty);
    bezierVertex(startx+w-x1, starty, startx+w-x1, starty-h, startx+w-x1, starty-h);
    vertex(startx, starty-h);
    vertex(startx, starty);
  }
  endShape(CLOSE);
  
  if(numAtt>0) {
    if(left == 1)
      attGroup1(startx+x1, starty, w-x1, h, numAtt);
    else
      attGroup1(startx, starty, w-x1, h, numAtt);
  }
}

void var7(float startx, float starty, float w, float h, int numAtt) {
  float x1 = random(w/10, w/3); // width of side
  float x2 = random(w/10, w/3);
  float y1 = random(h); // height of side
  float y2 = random(h);
  noStroke();
  fill(0);
  beginShape();
  vertex(startx, starty);
  vertex(startx, starty-h+y1);
  vertex(startx+x1, starty-h);
  vertex(startx+w-x2, starty-h);
  vertex(startx+w, starty-h+y2);
  vertex(startx+w, starty);
  endShape(CLOSE);
  
  if(numAtt>0)
    attGroup1(startx+x1, starty, w-x1-x2, h, numAtt);
}

void var8(float startx, float starty, float w, float h, int numAtt) {
  float y1 = random(h-h/4, h); // height of angle corner
  float y2 = random(h/3, h/2); // height of start angle
  int left = round(random(1));
  noStroke();
  fill(0);
  beginShape();
  vertex(startx, starty);
  if(left == 1) {
    vertex(startx, starty-y2);
    vertex(startx+w/3, starty-y1);
    vertex(startx+w, starty-h);
  }
  else {
    vertex(startx, starty-h);
    vertex(startx+w-w/3, starty-y1);
    vertex(startx+w, starty-y2);
  }
  vertex(startx+w, starty);
  endShape(CLOSE);
  
  if(numAtt>0) {
    if(left == 1)
      attGroup2(startx+5+w/3+random(w-w/3-5), starty-y1, w, random(h/5, h/3), startx, starty, numAtt);
    else
      attGroup2(startx+random(w-w/3-5), starty-y1, w, random(h/5, h/3), startx, starty, numAtt);
  }
}

void var9(float startx, float starty, float w, float h, int numAtt) {
  float origW = w;
  w = random(w-w/4, w);
  float y1 = random(8); // side slant height
  float y2 = 8; // top slant height
  float y3 = 10; // tower base height
  float x1 = w/3; // inward amount
  constrain(h, y1+y2+y3+5, smallcap);
  noStroke();
  fill(0);
  beginShape();
  vertex(startx, starty);
  vertex(startx, starty-h+y2+y3+y1);
  vertex(startx+x1, starty-h+y2+y3);
  vertex(startx+x1, starty-h+y2);
  vertex(startx+w/2, starty-h);
    //mid
  vertex(startx+w-w/2, starty-h);
  vertex(startx+w-x1, starty-h+y2);
  vertex(startx+w-x1, starty-h+y2+y3);
  vertex(startx+w, starty-h+y2+y3+y1);
  vertex(startx+w, starty);
  endShape(CLOSE);
  fill(255);
  if(round(random(2)) > 0)
    ellipse(startx+w/2, starty-h+y2+y3/2, x1/3, x1/3);
    
  if(numAtt>0)
    attGroup0(startx, starty, origW, numAtt);
}

void var11(float startx, float starty, float w, float h, int numAtt) {
  float x1 = w/4; // width
  float x2 = random(3); // "wing"
  noStroke();
  fill(0);
  beginShape();
  vertex(startx+w/2-x1/2, starty);
  vertex(startx+w/2-x1/2, starty-h);
  vertex(startx+w/2-x1/2-x2, starty-h);
  vertex(startx+w/2, starty-h-random(4,10));
  vertex(startx+w/2+x1/2+x2, starty-h);
  vertex(startx+w/2+x1/2, starty-h);
  vertex(startx+w/2+x1/2, starty);
  endShape(CLOSE);
  
  if(numAtt>0)
    attGroup0(startx, starty, w, numAtt);
}

void var13(float startx, float starty, float w, float h, int numAtt) {
  float x1 = random(w/4,w); // width
  float x2 = random(3); // "wing"
  float y1 = random(4, 10);
  float start = startx + w/2;
  noStroke();
  fill(0);
  beginShape();
  vertex(start - x1/2, starty);
  vertex(start - x1/2, starty-h);
  vertex(start - x1/2 - x2, starty-h);
  vertex(start - x1/2 + x2*2, starty-h-y1);
  vertex(start + x1/2 - x2*2, starty-h-y1);
  vertex(start + x1/2 + x2, starty-h);
  vertex(start + x1/2, starty-h);
  vertex(start + x1/2, starty);
  endShape(CLOSE);
  
  if(round(random(1)) == 1) {
    beginShape();
    vertex(start - x2, starty-h-y1);
    vertex(start, starty-h-y1-y1/2);
    vertex(start + x2, starty-h-y1);
    endShape(CLOSE);
  }
  
  if(numAtt>0)
    attGroup0(startx, starty, w, numAtt);
}

// ******************** ATTACHMENT DESIGNS ********************* //

// limits to greenery
void attGroup0(float startx, float starty, float w, int numAtt) {
  while(numAtt>0) {
    tree(startx+random(w), starty);
    numAtt -= 10;
  }
}

// limits to blocks, antennas, greenery
void attGroup1(float startx, float starty, float w, float h, int numAtt) { //h = total height
  boolean antennaDone = false;
  while(numAtt > 0) {
    int doAntenna = round(random(1));
    int doSimple = round(random(1));
    if(numAtt>0 && doAntenna==1 && doSimple==0 && !antennaDone) { //two levels
      complexAntenna(constrain(startx+random(w),startx+10, (startx+w)-10), starty-h, random(h/7, h/4));
      numAtt -= 10;
      antennaDone = true;
    }
    if(numAtt>0 && doAntenna==1 && !antennaDone) {
      antenna(startx+5+random(w-10), starty-h, random(h/9, h/5));
      numAtt -= 10;
      antennaDone = true;
    }
    if(numAtt>0) {
      block(startx, startx+w, starty-h);
      numAtt -= 10;
    }
    //draw greenery, last resort
  }
}

// limits to single antenna starting at various heights (calculated beforehand), then greenery
void attGroup2(float startx, float starty, float w, float h, float treeStartx, float treeStarty, int numAtt) {
  antenna(startx, starty, h);
  numAtt -= 10;
  if(numAtt>0)
    attGroup0(treeStartx, treeStarty, w, numAtt);
}

// draws balconies alongside the two vertical edges, returns adjusted numAtt value
int attGroup3(float startx, float starty, float w, float h, int numAtt) {
  if(numAtt > 10) {
    balcony(startx, starty, h);
    balcony(startx+w, starty, h);
    return numAtt - 20;
  }
  return numAtt;
}

// limits to blocks, dishes, watertowers, and greenery
void attGroup4(float startx, float starty, float w, float h, int numAtt) { //h = total height
  boolean towerDone = false;
  boolean dishDone = false;
  while(numAtt > 0) {
    int doTower = round(random(5));
    int doDish = round(random(5));
    if(!towerDone && doTower == 1) {
      watertower(startx+5+random(w-25), starty-h, constrain(map(h,0,medcap,.1,.9), .1, .9));
      numAtt -= 10;
      towerDone = true;
    }
    if(!dishDone && doDish == 1 && numAtt > 0) {
      dish(startx+15+random(w-30), starty-h, constrain(map(h,0,medcap,.5,1), .5, 1));
      numAtt -= 10;
      dishDone = true;
    }
    if(numAtt>0) {
      block(startx+5, startx+w-10, starty-h);
      numAtt -= 10;
    }
    //draw greenery, last resort
  }
}

void antenna(float startx, float starty, float h) {
  strokeWeight(random(1.5,3));
  stroke(0);
  line(startx, starty, startx, starty-h);
}

void complexAntenna(float startx, float starty, float h) {
  float x1 = random(3,8); // distance between beams
  float y1 = random(h/7, h-h/4); // height of secondary beam
  float y2 = random(y1); //connector beam heights
  float y3 = random(y1);
  if(round(random(1))==1) x1 *= -1;
  strokeWeight(random(1.5,3));
  stroke(0);
  line(startx, starty, startx, starty-h);
  strokeWeight(random(1.5,3));
  line(startx+x1, starty, startx+x1, starty-y1);
  line(startx, starty-y2, startx+x1, starty-y2);
  line(startx, starty-y3, startx+x1, starty-y3);
}

void block(float startx, float endx, float starty) {
  float w = random(8, (endx-startx)-(endx-startx)/4); //width
  float h = -1*random(4, w/2); //height
  float x1 = random(startx, endx-w); //start position
  noStroke();
  fill(0);
  rect(x1, starty, w, h);
}

void balcony(float startx, float starty, float hval) {
  int amount = int(map(hval, 0, tallcap, 0, 40));
  float w = 8;
  float h = w/2;
  float x1 = random(w/3,w/2);
  noStroke();
  fill(0);
  for(int i=0; i<amount; i++) {
    rect(startx-x1, starty-(hval/amount)*i-h, w, h);
  }
}

void watertower(float startx, float starty, float scale) {
  float h = 22*scale;
  float w = 15*scale;
  float y1 = h/5.5; // barrel start
  float y2 = h/10; // barrel roof height
  float x1 = w*.8; // barrel width
  noStroke();
  fill(0);
  beginShape();
  vertex(startx, starty-y1);
  vertex(startx, starty-h+y2);
  bezierVertex(startx, starty-h,startx+x1/2, starty-h,startx+x1/2, starty-h);
  bezierVertex(startx+x1, starty-h,startx+x1, starty-h+y2,startx+x1, starty-h+y2);
  vertex(startx+x1, starty-y1);
  endShape(CLOSE);
  strokeWeight(2*scale);
  stroke(0);
  line(startx, starty, startx+w*.1, starty-y1);
  line(startx+x1, starty, (startx+x1)-w*.1, starty-y1);
  line(startx+x1/2, starty, startx+x1/2, starty-y1);
  strokeWeight(1.5*scale);
  line(startx+w, starty, startx+w, starty-h+y2);
  line(startx+w, starty-h+y2*2, startx+w-w*.3, starty-h+y2*2);
}

void dish(float startx, float starty, float scale) {
  float side = 25*scale;
  noFill();
  strokeWeight(2*scale);
  stroke(0);
  if(round(random(1)) == 1) {
    arc(startx, starty-side/2-5, side, side, PI/2, PI);
    line(startx-side*.35, starty, startx-side*.35, starty-side*.35);
    line(startx-side*.35, starty-side*.35, startx-side*.35+side*.2, starty-side*.35-side*.2);
  }
  else {
    arc(startx, starty-side/2-5, side, side, 0, PI-PI/2);
    line(startx+side*.35, starty, startx+side*.35, starty-side*.35);
    strokeWeight(1.5*scale);
    line(startx+side*.35, starty-side*.35, startx+side*.35-side*.2, starty-side*.35-side*.2);
  }
}

void tree(float startx, float starty) {
  int treeIndex = int(random(trees.size()));
  //int y = round(random(25, 50));
  PImage tree = trees.get(treeIndex);
  //tree.resize(0,y);
  image(tree, startx-tree.width/2, starty-tree.height+1);
}

Processing: Modified version of Justin Livi’s code for generating watercolor paintings

/*
 * WatercolorSediment
 * May, 2011
 *
 * Copyright 2011  Justin Livi
 * justinlivi.net
 * Written in Processing
 *
 */

int seedcount = 10; // default = 50
float h, maxh, start; // overall distance from center
float vspeed, rspeed;
float theta = 0;
float speed = 1;
float rot;
boolean up = false;
float[] hm; // hue array
float[] sm; // saturation array
float[] lm; // lightness array
float[] dhm; // delta hue array
float[] dsm; // delta saturation array
float[] dlm; // delta lightness array
float[] dm; // distance array
float[] stm; // streakiness array
HSL hsl = new HSL();

int count = 0;
boolean a = true;
boolean pause;

void setup() {
  size(1700, 1700);
  smooth();
  noStroke();
  background(255);
  if(width > height)
    maxh = height;
  else
    maxh = width;
  hm = new float[seedcount];
  sm = new float[seedcount];
  lm = new float[seedcount];
  dhm = new float[seedcount];
  dsm = new float[seedcount];
  dlm = new float[seedcount];
  dm = new float[seedcount];
  stm = new float[seedcount];
  reset();
}

void draw() {
  if(pause) { return; }
  if (theta < width) {
    pushMatrix();
      translate(theta, 0);
      rotate(PI/2);
      generate();
    popMatrix();
    theta++;
  }
  else if(count<400) {
    if(a) {
      saveFrame("paintings/painting-"+count+"_a.png");
      a = false;
    }
    else {
      saveFrame("paintings/painting-"+count+"_b.png");
      a = true;
      count++;
    }
    reset();
  }
}

void mousePressed() {
  pause = !pause;
  //background(255);
  //reset();
}

void reset() {
  seedcount = (int)random(8, seedcount); 
  float centerhue = random(0, 360);
  float variance = random(10, 40);
  for(int count = 0; count < seedcount; count++) {
    hm[count] = random(centerhue-variance, centerhue+variance);
    sm[count] = random(45, 70);
    lm[count] = random(20, 90);
    dhm[count] = hm[count];
    dsm[count] = sm[count];
    dlm[count] = lm[count];
    dm[count] = random(maxh);
    stm[count] = random(.1, 1);
  }
  dm[0] = 0;
  dm[seedcount-1] = height;
  dm = sort(dm, seedcount);
  h = 0;
  start = h;
  theta = -20;
  vspeed = random(1, 9);
  rspeed = random(1, 4);
}

void generate() {
  for(int count = 0; count < seedcount; count++) { changeDm(count); fill(hsl.toRGB(dhm[count],dsm[count],dlm[count],100)); blend(count, dm[count]); } dm = sort(dm, seedcount); } // ---------------------- POSITION! ------------------------------------ // void changeDm(int count) { int prev = 0; int next = seedcount-1; if(count > 0)
    prev = count-1;
  if(count < seedcount-1)
    next = count+1;
  if(count == seedcount-1)
    prev = seedcount-1;
  if(count == 0)
    next = 0;
  dm[count] = constrain(dm[count]+random(-1, 1), 0, height);
  if (count == 0)
    dm[count] = 0;
  else if (count == seedcount-1)
    dm[count] = height;
}


// ---------------------- BLEND! ------------------------------------ // 

void blend(int count, float distance) {
  int prev = seedcount-1;
  if(count < seedcount-1)
    prev = count+1;
  float formax = abs(dm[prev]-distance);
  changeColor(count);
  for(int count2 = 0; count2 < formax; count2++) {
    for(int count3 = 0; count3 < (10/3.0*stm[count]+5/3.0); count3++) {
      float hi = (dhm[prev]-dhm[count])/formax;
      float si = (dsm[prev]-dsm[count])/formax;
      float li = (dlm[prev]-dlm[count])/formax;
      fill(hsl.toRGB(dhm[count]+random(-1,1)+(count2*hi),
                    dsm[count]+random(-.5,.5)+(count2*si),
                    dlm[count]+random(-.5,.5)+(count2*li), random(2, 10)));
      pushMatrix();
        translate(distance+count2+random(-1,1), random(-stm[count]*10,stm[count]*10));
        rotate(random(PI*2));
        ellipse(0, 0, random(2, 10+5*stm[count]), random(2, 10+5*stm[count]));
      popMatrix();
    }
  }
}

void changeColor(int count) {
  stm[count] += random(-.01, .01);
  stm[count] = constrain(stm[count], .1, 1);
  dhm[count] += random(-stm[count], stm[count]);
  dhm[count] = constrain(dhm[count], hm[count]-20, hm[count]+20);
  dsm[count] += random(-stm[count], stm[count]);
  dsm[count] = constrain(dsm[count], sm[count]-20, sm[count]+20);
  dlm[count] += random(-stm[count], stm[count]);
  dlm[count] = constrain(dlm[count], lm[count]-20, lm[count]+20);
}

class HSL {
  color toRGB(float H, float S, float L) {
    float R = 0, G = 0, B = 0;
    H /= 360;
    S /= 100;
    L /= 100;
    float temp1 = 0, temp2 = 0, Rtemp3 = 0, Gtemp3 = 0, Btemp3 = 0;
    if (S == 0) {
      R = L;
      G = L;
      B = L;
    }
    else {
      if (L < 0.5) temp2 = L*(1.0+S); else if (L >= 0.5)
        temp2 = L+S-L*S;
      temp1 = 2.0*L-temp2;
      Rtemp3 = H+1.0/3.0;
      
      if (Rtemp3 < 0) Rtemp3 = Rtemp3 + 1.0; if (Rtemp3 > 1)
        Rtemp3 = Rtemp3 - 1.0;
        
      Gtemp3 = H;
      if (Gtemp3 < 0) Gtemp3 = Gtemp3 + 1.0; if (Gtemp3 > 1)
        Gtemp3 = Gtemp3 - 1.0;
        
      Btemp3 = H-1.0/3.0;
      if (Btemp3 < 0) Btemp3 = Btemp3 + 1.0; if (Btemp3 > 1)
        Btemp3 = Btemp3 - 1.0;
        
      if (6.0*Rtemp3 < 1)
        R = temp1+(temp2-temp1)*6.0*Rtemp3;
      else if (2.0*Rtemp3 < 1)
        R = temp2;
      else if (3.0*Rtemp3 < 2)
        R = temp1+(temp2-temp1)*((2.0/3.0)-Rtemp3)*6.0;
      else
        R = temp1;
        
      if (6.0*Gtemp3 < 1)
        G = temp1+(temp2-temp1)*6.0*Gtemp3;
      else if (2.0*Gtemp3 < 1)
        G = temp2;
      else if (3.0*Gtemp3 < 2)
        G = temp1+(temp2-temp1)*((2.0/3.0)-Gtemp3)*6.0;
      else
        G = temp1;
        
      if (6.0*Btemp3 < 1)
        B = temp1+(temp2-temp1)*6.0*Btemp3;
      else if (2.0*Btemp3 < 1)
        B = temp2;
      else if (3.0*Btemp3 < 2)
        B = temp1+(temp2-temp1)*((2.0/3.0)-Btemp3)*6.0;
      else
        B = temp1;
    }
      
    R *= 255;
    B *= 255;
    G *= 255;
    
    return color((int)R, (int)G, (int)B);
  }
  
  color toRGB(float H, float S, float L, float A) {
    float R = 0, G = 0, B = 0;
    H /= 360;
    S /= 100;
    L /= 100;
    float temp1 = 0, temp2 = 0, Rtemp3 = 0, Gtemp3 = 0, Btemp3 = 0;
    if (S == 0) {
      R = L;
      G = L;
      B = L;
    }
    else {
      if (L < 0.5) temp2 = L*(1.0+S); else if (L >= 0.5)
        temp2 = L+S-L*S;
      temp1 = 2.0*L-temp2;
      Rtemp3 = H+1.0/3.0;
      
      if (Rtemp3 < 0) Rtemp3 = Rtemp3 + 1.0; if (Rtemp3 > 1)
        Rtemp3 = Rtemp3 - 1.0;
        
      Gtemp3 = H;
      if (Gtemp3 < 0) Gtemp3 = Gtemp3 + 1.0; if (Gtemp3 > 1)
        Gtemp3 = Gtemp3 - 1.0;
        
      Btemp3 = H-1.0/3.0;
      if (Btemp3 < 0) Btemp3 = Btemp3 + 1.0; if (Btemp3 > 1)
        Btemp3 = Btemp3 - 1.0;
        
      if (6.0*Rtemp3 < 1)
        R = temp1+(temp2-temp1)*6.0*Rtemp3;
      else if (2.0*Rtemp3 < 1)
        R = temp2;
      else if (3.0*Rtemp3 < 2)
        R = temp1+(temp2-temp1)*((2.0/3.0)-Rtemp3)*6.0;
      else
        R = temp1;
        
      if (6.0*Gtemp3 < 1)
        G = temp1+(temp2-temp1)*6.0*Gtemp3;
      else if (2.0*Gtemp3 < 1)
        G = temp2;
      else if (3.0*Gtemp3 < 2)
        G = temp1+(temp2-temp1)*((2.0/3.0)-Gtemp3)*6.0;
      else
        G = temp1;
        
      if (6.0*Btemp3 < 1)
        B = temp1+(temp2-temp1)*6.0*Btemp3;
      else if (2.0*Btemp3 < 1)
        B = temp2;
      else if (3.0*Btemp3 < 2)
        B = temp1+(temp2-temp1)*((2.0/3.0)-Btemp3)*6.0;
      else
        B = temp1;
    }
      
    R *= 255;
    B *= 255;
    G *= 255;
    
    return color((int)R, (int)G, (int)B, (int)A);
  }
}

Processing: Combining city masks and paintings to create final images

PImage painting;
PImage drawing;
int index = 17;
int savedIndex = -1;
String file = "../Watercolor_JustinLivi/paintings/painting-"+int(random(400))+"_a.png";

ArrayList used = new ArrayList();

void setup() {
  size(1700,1700);
  reload();
}

void draw() {
}

void keyPressed() {
  if (key == 'b' || key == 'B') {
    index--;
    if(index<0) index = 365; reload(); } if (key == 'n' || key == 'N') { index++; if(index >= 366) index = 0;
    reload();
  }
  if (key == 's' || key == 'S') {
    String filename = "renders/render"+index+".png";
    saveFrame(filename);
    if(savedIndex == index)
      used.remove(index);
    used.add(index, file);
    savedIndex = index;
  }
  if (key == 'r' || key == 'R') {
    reload();
  }
}

void reload() {
  background(255);
  String prevFile = file;
  while(used.contains(file) || prevFile.equals(file)) {
    if(round(random(1)) == 1)
      file = "../Watercolor_JustinLivi/paintings/painting-"+int(random(400))+"_a.png";
    else 
      file = "../Watercolor_JustinLivi/paintings/painting-"+int(random(400))+"_b.png";
  }
  println("drawing"+index+".png + "+ file);
  painting = loadImage(file);
  drawing = loadImage("../city_generator/drawings/drawing"+index+".png");
  drawing.filter(INVERT);
  painting.mask(drawing);
  image(painting, 0, 0);
}

Javascript/Basil.js: Create final Indesign/PDF file

#includepath "~/Documents/;%USERPROFILE%Documents";
#include "basiljs/bundle/basil.js";

var StringData = b.loadString("data.txt");
var data = b.loadString('data.txt').split("\n");

var side = 7.5*72;
var img;

function parseData(texts) { 
  var hourlyData = [];
  var hours = texts.split("-");
  hourlyData.push([hours[0]]);
  for(var i=1; i<hours.length; i++)
    hourlyData.push(hours[i].split("&"));
  return hourlyData;
}

function getMetaData(hourlyData){
  var info = [];
  info.push(hourlyData[0][0]);
  var texts = 0;
  var chars = 0;
  var atts = 0;
  for(var i=1; i<hourlyData.length; i++) {
    texts += parseInt(hourlyData[i][0]);
    chars += parseInt(hourlyData[i][1]);
    atts += parseInt(hourlyData[i][2]);
  }
  info.push(texts);
  info.push(chars);
  info.push(atts);
  return info;
}

function setup() {

  b.clear (b.doc());
  
  b.noStroke(); 
  img = b.image("dedication_s.png", 0, 0, side, side);
  b.fill(255,255,255);
  b.textSize(12);
  b.textFont("Avenir","Light"); 
  b.textAlign(Justification.CENTER_ALIGN); 
  b.text("for nicole", 0, side/2-8, side, 16);

  // b.fill(175,175,175);
  // b.textSize(13);
  // b.text("special thanks to", 0, side/2-58, side, 16);
  // b.textSize(10);
  // b.text("golan levin", 0, side/2-8, side, 16);
  // b.text("justin livi", 0, side/2+12, side, 16);
  // b.text("adam knuckey", 0, side/2+32, side, 16);

  return

  for(var i=0; i<data.length; i++) {
    b.addPage();

    var date = getMetaData(parseData(data[i]))[0].split(",")[0].split(" ");
    var date_space = 10;
    
    b.fill(175,175,175);
    var tbh = 30;
    b.textSize(tbh-5);
    b.textFont("Avenir","Light"); 
    b.textAlign(Justification.RIGHT_ALIGN);

    b.text(date[0].toUpperCase().substring(0,3), 0, side/2-tbh/2, side/2-date_space/2, tbh);
    b.textFont("Avenir","Black"); 
    b.textAlign(Justification.LEFT_ALIGN);
    b.text(date[1], side/2+date_space/2, side/2-tbh/2, side/2, tbh);

    var y = side-72;
    var iconHeight = 15;
    var chatWidth = iconHeight*1.6;
    var leftSpace = 5;
    var typeX = chatWidth+leftSpace+chatWidth*1.5;
    var clipX = typeX+chatWidth+leftSpace+chatWidth*2;
    var totalWidth = clipX + chatWidth*0.55 + leftSpace + chatWidth*2;
    var x = side/2-totalWidth/2;

    b.textSize(10);
    b.fill(220,220,220);
    b.textFont("Avenir","Black"); 

    img = b.image("chat.png", x, y, chatWidth, iconHeight);
    b.text(getMetaData(parseData(data[i]))[1], x+chatWidth+leftSpace, y+1.5, chatWidth*1.5, 15);
    img = b.image("type.png", x+ typeX, y+1.5, chatWidth, iconHeight*.8);
    b.text(getMetaData(parseData(data[i]))[2], x+typeX+chatWidth+leftSpace, y+1.5, chatWidth*2, 15);
    img = b.image("clip.png", x+ clipX, y+1.5, chatWidth*0.55, iconHeight*.8);
    b.text(getMetaData(parseData(data[i]))[3], x+clipX+chatWidth*0.55+leftSpace, y+1.5, chatWidth*2, 15);


    b.addPage();

    img = b.image("renders/render"+i+".png", 0, 0, side, side);
  }
}

b.go();