Circle Pictures

Take an image.

Pick a random pixel. Over a plain background of the same size as the image, draw a circle over that spot: bigger if it’s lighter, smaller if it’s darker. (Or reverse it: big for dark, small for light.) Repeat, but don’t overlap the circles. After a while, you’ll run out of empty places for new circles, so stop.

I screwed up the math for checking circle overlaps. At first, I was thinking: pack the circles as tight as you can, so only call it an overlap when the distance between the circle centers is less than the sum of their radii.

overlapping circles

But then, from another part of my brain, I drew the circles at half-size, forgetting that Processing’s ellipse method takes width and height parameters, not a radius. So the circles were packed less densely, and I wasn’t clever enough to see why. I hacked it, and decided to count it as an overlap when the distance is bigger than the radius of the bigger circle. (Shrug. Hack.) It worked. And it looked cool.

Eventually I figured out my bug, and fixed it, but then the circles were too dense, so I went back to the happy accident.

I also tried color images. I think there’s potential here, but I like these less.

The source images are images of Paul Erdős, Jorge Luis Borges (twice), Henri de Toulouse-Lautrec, two sunflowers, and a cow.

Here’s the source. (I tried putting it on openprocessing.org, which I normally really like, but I had troubles with the images in JavaScript mode, and they no longer support Java mode.)

  1 PImage image;
  2 boolean dark = true;
  3 
  4 ArrayList circles;
  5 
  6 // Trailing average. When the average number of tries
  7 // to place a circle is too high,
  8 // stop trying.
  9 Averager averager;
 10 
 11 String paul = "erdos.jpg";
 12 String jorge1 = "borges1.jpg";
 13 String jorge2 = "borges2.jpg";
 14 String henri = "lautrec.jpg";
 15 String sunflower = "sunflower.jpg";
 16 String sunflower2 = "sunflower2.jpg";
 17 String cow = "cow.png";
 18 
 19 void setup() {
 20   image = loadImage(paul);
 21   image.resize(600, 0);
 22   size(image.width, image.height);
 23 
 24   ellipseMode(CENTER);
 25   noStroke();
 26   smooth();
 27 
 28   reset();
 29 }
 30 
 31 void reset() {
 32   circles = new ArrayList();
 33   averager = new Averager(20);
 34   background(dark ? 0 : 255);
 35 }
 36 
 37 void draw() {
 38   for (int i = 0; i < 10; i++) {
 39     drawRandomCircle();
 40     if (averager.average() > 100) {
 41       //save("7.dark.png");
 42       reset();
 43       break;
 44     }
 45   }
 46 }
 47 
 48 void drawRandomCircle() {
 49   //println(averager.average());
 50   Circle circ;
 51   int tries = 0;
 52   do {
 53     int x = floor(random(width));
 54     int y = floor(random(height));
 55 
 56     color c = image.get(x, y);
 57     float val = brightness(c);
 58 
 59     tries++;
 60     float circleSize = dark ?
 61          map(val, 255, 0, 1, 60) :
 62          map(val, 0, 255, 1, 60);
 63     circ = new Circle(x, y, c, circleSize);
 64   }
 65   while (overlaps (circ));
 66 
 67   averager.record(tries);
 68 
 69   circles.add(circ);
 70   circ.draw();
 71 }
 72 
 73 boolean overlaps(Circle c) {
 74   for (Circle other : circles) {
 75     if (c.overlaps(other)) {
 76       return true;
 77     }
 78   }
 79   return false;
 80 }
 81 
 82 class Circle {
 83   int x;
 84   int y;
 85   color c;
 86   float diameter;
 87 
 88   Circle(int x, int y, color c, float diameter) {
 89     this.x = x;
 90     this.y = y;
 91     this.c = c;
 92     this.diameter = diameter;
 93   }
 94 
 95   boolean overlaps(Circle other) {
 96     return dist(x, y, other.x, other.y) < max(diameter, other.diameter);
 97   }
 98 
 99   void draw() {
100     fill(c);
101     ellipse(x, y, diameter, diameter);
102   }
103 }
104 
105 class Averager {
106   float[] values;
107   int index = 0;
108   Averager(int length) {
109     values = new float[length];
110   }
111 
112   void record(float value) {
113     values[index] = value;
114     index = (index + 1) % values.length;
115   }
116 
117   float average() {
118     float sum = 0;
119     for (float val : values) {
120       sum += val;
121     }
122     return sum / values.length;
123   }
124 }