Each postcard is unique and will never appear again in exactly the same way. But of course all of these images are variations on a theme that I created when I wrote the program.
To draw a new card, just click your mouse in the graphics window (it may take a moment for the new card to be created and displayed).
You can see from the program below that drawing these cards takes a lot of steps. We have to draw every little element, from the windows to the fuzzy reflections in the water. There are lots of subtleties, from the colors of the background to how the stars appear. Once you've finished the course, everything in this program will make sense to you, and you'll be able to make your own postcards of your own imaginary scenes.
int NumLayers; // how many layers of buildings to draw
int Waterline; // height of waterline
float MaxHeight; // maximmum building height
color WindowColor = color(220, 220, 20); // average color of windows
boolean Redraw = true; // do we need to draw a new postcard?
void setup() {
size(600, 400); // create graphics window 600 wide by 400 high
smooth(); // draw everything pretty
noStroke(); // don't draw outlines
}
void draw() {
if (Redraw) {
makeNewCard(); // make a new cared
Redraw = false; // don't repeat until requested
}
}
void makeNewCard() { // create a new card
Waterline = int(height * 0.8); // height of waterline
MaxHeight = random(.2*height, .6*height); // building max height
NumLayers = int(random(6, 10)); // building density
drawSkyline(); // and draw the result
}
void drawSkyline() {
drawBackground(); // draw the sky and water
for (int layer=0; layer<NumLayers; layer++) { // draw each layer of the building
drawLayer(layer);
}
drawLights(); // draw the lights and reflections
}
void drawLayer(int layer) { // draw one layer of buildings
float a = norm(layer, 0, NumLayers-1); // how far back we are
float avgWidth = lerp(width/50.0, width/20.0, a); // average building width
float avgHeight = lerp(MaxHeight, height/20.0, a); // and height
float layerDensity = lerp(.1, 1, a); // guides how many buildings get drawn
float left = -avgWidth; // draw left to right, starting here
while (left < width) {
float buildingWidth = vary(avgWidth, .1); // pick a width
float buildingHeight = vary(avgHeight, .2); // and height
boolean drawMe = random(0, 1) < layerDensity; // draw this one?
if (drawMe) {
drawBuilding(left, Waterline, buildingWidth, buildingHeight); // draw it
}
left += buildingWidth; // and move right
}
}
void drawBuilding(float bLeft, float bBottom, float bWid, float bHgt) {
float buildingGrayColor = random(30, 90); // basic building color
fill(buildingGrayColor); // use that color
rect(bLeft, bBottom, bWid, -bHgt); // draw the building
// now get a window color based on varying the generic window color
color windowColor = color(vary(red(WindowColor), .1),
vary(green(WindowColor), .1),
vary(blue(WindowColor), .1));
fill(windowColor);
// figure out how many windows to draw, then draw each one
int numAcross = int(random(10.0, 20.0));
int numHigh = int(random(10.0, 20));
float wWid = bWid / (numAcross*2.0);
float wHgt = bHgt / (numHigh*2.0);
float windowDensity = random(0.1, 0.7); // density of drawn windows
for (int wx=0; wx<numAcross; wx++) {
for (int wy=0; wy<numHigh; wy++) {
float wLeft = (1.0/(numAcross*2.0)) + (wx*2*wWid);
float wBottom = (1.0/(numHigh*2.0)) + (wy*2*wHgt);
if (random(0, 1) < windowDensity) { // draw this one?
rect(bLeft+wLeft, bBottom-wBottom, wWid, -wHgt);
}
}
}
}
void drawBackground() {
// draw the sky: a radial gradient blend from the moon at (cx, cy)
float cx = width * random(.6, .8); // the moon is on the right-ish
float cy = vary(Waterline, .1); // and near the waterline
float distToUL = dist(cx, cy, 0, 0); // distance to upper-left (0,0)
color lighter = color(5, 60, 130); // light color for gradient
color darker = color(0, 15, 45); // dark color for gradient
for (int y=0; y<height; y++) {
for (int x=0; x<width; x++) {
float a = dist(x, y, cx, cy)/distToUL; // distance from (x,y) to moon
a = constrain(a, 0, 1);
color clr = lerpColor(lighter, darker, a); // get the color here
float ya = 1 - norm(y, 0, Waterline); // height above waterline
float threshold = .001 * ya; // draw a star here?
if (random(0, 1) < threshold) {
// stars are dim at the waterline, and brighter as we go up.
// The square root function creates a nice-looking blend of intensity
a = sqrt(a);
clr = lerpColor(clr, color(255), a); // make this pixel bright
}
set(x, y, clr); // set this pixel's colors
}
}
// draw the (fake) building reflections
color waterColor = color(10,10,30); // color of the water
for (int y=Waterline; y<height; y++) {
for (int x=0; x<width; x++) {
// ya is distance from Waterline
float ya = 1-norm(y, Waterline, height-1);
// ya2 creates a short fade right at the Waterline
float ya2 = constrain((y-Waterline)/3.0, 0, 1);
float wnoise = noise(x*.04, y*.01);
wnoise = ya2 * ya * sq(wnoise);
color clr = lerpColor(waterColor, WindowColor, wnoise);
set(x, y, clr);
}
}
}
void drawLights() {
int numLights = 20;
noStroke();
int lradius = 8;
for (int l=0; l<numLights; l++) {
int lx = int(random(0, width));
int ly = int(Waterline-lradius+vary(lradius, .1));
color lightColor = color(
random(210, 255), random(210, 255), random(210, 255));
// draw the light as two circles to fake a glow
fill(red(lightColor), green(lightColor), blue(lightColor), 128);
ellipse(lx, ly, lradius, lradius);
fill(red(lightColor), green(lightColor), blue(lightColor), 255);
ellipse(lx, ly, lradius/2, lradius/2);
// draw fake reflections and add to water color
for (int y=Waterline; y<height; y++) {
for (int x=lx-2; x<lx+2; x++) {
float ya = 1-norm(y, Waterline, height-1);
float wnoise = noise(x*.04, y*.01);
wnoise = sq(ya) * sq(wnoise); // fade out noise
color oldclr = get(x, y);
color clr = lerpColor(oldclr, lightColor, wnoise);
set(x, y, clr);
}
}
}
}
// wiggle a number by a given percent and return the result
float vary(float value, float percent) {
float range = value * percent;
value += random(-range, range);
return(value);
}
void mousePressed() { Redraw = true; }