Interesting Problems: Mondrian
Published 2018-06-05 @ 12:00
Tagged graphics
I had an interesting problem presented to me the other day that I found fascinating to work through and allowed me to use my graphics gem. The problem was easy enough to describe:
How would you make this?:
![]() |
This is just one of many pieces by Piet Mondrian, who is really worth checking out if you like this style of art! |
To which my answer was “fascinating! I haven’t the foggiest idea. Let’s figure out!”. I don’t do visual stuff very much but I knew that this was a good opportunity to use my graphics gem.
Normally, my graphics gem is meant for doing visualizations of simulations, or other mathy things, or even games… but art? I hadn’t really thought about that, but why not?
But I hadn’t touched my graphics gem in a while so I was put on the
spot to quickly get up to speed. The graphics gem provides a couple
different options for a canvas, one is called Graphics::Drawing
which is just a single canvas that is never cleared. This seemed
ideal. The first thing I needed to do was to get up and running with
anything, so I poked at one of the examples and extracted this:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
require "graphics" class Mondrian0 < Graphics::Drawing CLEAR_COLOR = :white def initialize super 850, 850, 16, self.class.name fast_rect 10, 10, 100, 100, :black end end Mondrian0.new.run if $0 == __FILE__ |
which gets me:
OK. So now I remember how to start a simulation, size a blank white canvas to 850x850, and draw a rectangle at 10,10 (quadrant 1 maths. The origin is in the bottom left) with dimensions of 100x100. Not a bad start. From there it is easy to draw some lines that span the whole width or height:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
require "graphics" class Mondrian1 < Graphics::Drawing CLEAR_COLOR = :white def initialize super 850, 850, 16, self.class.name xs = rand(10).times.map { rand w }.sort ys = rand(10).times.map { rand h }.sort xs.each do |x| fast_rect x, 0, 20, h, :black end ys.each do |y| fast_rect 0, y, w, 20, :black end end end Mondrian1.new.run if $0 == __FILE__ |
Not bad! This still has some problems. Purely random numbers can be duplicate or overlap. It can be unaesthetic. Adding and switching to the following will help clean those problems up:
This makes things more “griddy”, but I think that’s fine for now.
From there, I’d like to get colored regions. That’s not too hard:
1 2 3 4 5 6 7 8 9 |
rand(2..5).times.map { xi = rand(xs.size-1) yi = rand(ys.size-1) x0, x1 = xs[xi..xi+1] y0, y1 = ys[yi..yi+1] c = COLOR.sample fast_rect x0+W, y0+W, x1-x0-W, y1-y0-W, c # where W=20 } |
and now it looks like:
That’s damn similar to the original! I’m pretty happy with it at this point, but I do want to take it further. I want to animate it and I want to add partial-length colored lines akin to some of Mondrian’s other pieces.
So, switch from Graphics::Drawing
to Graphics::Simulation
(which
has an update
+ draw
architecture similar to Gosu or Processing),
and move the actual drawing to a draw
method and add an update
method like the following:
1 2 3 4 5 6 7 8 9 10 11 12 |
DIRS = [-1, 0, 1].repeated_permutation(2).to_a.shuffle.cycle # ... def update n self.direction = DIRS.next if n % CYCLE == 0 dx, dy = direction xs.map! { |x| (x+dx) % w } ys.map! { |y| (y+dy) % h } partials.map! { |x, y, ww, hh, c| [(x+dx)%w, (y+dy)%h, ww, hh, c] } regions.map! { |x, y, ww, hh, c| [(x+dx)%w, (y+dy)%h, ww, hh, c] } end |
There’s some other little things that still need doing, but really, at this point it really doesn’t take much at all to wind up with an animated version: