🕷 zenspider.com

by ryan davis



sitemap
Looking for the Ruby Quickref?

Interesting Problems: Mondrian

Published 2018-06-05 @ 12:00

Tagged graphics

This is part of the Interesting Problems series.

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?:

/img/mondrian_orig.jpg     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:

/img/mondrian0.png

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__

/img/mondrian1.png

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:

1
2
3
  def semirand max
    (0..max).step(20).to_a.sample
  end

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:

/img/mondrian2.png

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: