Year One
A beautiful generative data-vis book of text conversations.
technologies used
Processing 2, Python, basil.js, bash script, Photoshop, InDesign, Lulu publishing
course
Year One is a generative data-vis book of texting and 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.
You can view the original project documentation on the class website. This was part of a class-wide generative book assignment.
Beautiful generative watercolor cities
Each day's cityscape is rendered with a unique generative watercolor/painted effect, which is from a modified code by Justin Livi. All generative media was created with Processing.
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.
Variations
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).
Final Book
The final pdf is generated programatically, then printed using the service Lulu. The numbers at the bottom of each page represent the total number of text messages, and total volume of characters that we exchanged. Professor Golan Levin expressed surprise that we sent each other (in some cases) hundreds of text messages per day. It’s true.
The final book is 734 pages long and cost $150 to print.
Professor Golan Levin flipping through the abridged version of the final printed book.
View a sample of the final book.
View the source code
This project was created using iMessage history accessed with bash scripting, analysis using Python, generative artworks using Processing, and pdf-generation using basil.js. The following are a few key files, and all files can be found on GitHub
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
imbfull.sh (source)Shell/Bash Script
Export text messages from Apple Messages SQLite database
# -*- 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()
counter.py (source)Python
Analyze texts for number, total characters, and amount of attachments sent (per hour)
import java.util.*; boolean debug = false; ArrayList
texts = new ArrayList (); 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); } city_generator.pde (source)Processing
Generate cityscape masks using brush strokes and random buildings based on text analysis
/* * 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); } }
Watercolor_JustinLivi.pde (source)Processing
Modified version of Justin Livi’s code for generating watercolor paintings
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); } masker.pde (source)Processing
Combining city masks and paintings to create final images
#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();
bookGenerator.jsx (source)Javascript / basil.js
Create final Indesign/PDF file