A Mondrian image generator in Go takes a canvas of a certain size and divides it into partitions, thus creating a Piet Mondrian inspired image with primary colored rectangles. The partition complexity can be varied and can greatly change how your resulting image looks like. To learn more about the partition complexity and the algorithmic approach, check out section “Algorithm description”.
The Mondrian generator has the possibility to generate two styles of Mondrian-inspired images:
- the classical Mondrian image with primary colored rectangle partitions which are outlined with black lines
- a more abstract Mondrian inspired image with only primary colored rectangles which are not outlined.
Feel free to check out the mondrian playground or github repo.
The Mondrian Process
The core of the algorithm is the 2D Mondrian Process, as defined in this paper.
We describe the 2D Mondrian Process M ~ MP(lambda, x0, x1, y0, y1)
on the rectangle (x0, x1) x (y0, y1)
with initial budget lambda = x1 - x0 + y1 - y0
.
A horizontal y-axis-aligned cut is made uniformly at random along the interval (y0, y1)
. The new point y_new
splits (y0, y1)
into
two intervals (y_new, y1)
and (y0, y_new)
and the initial budget lambda
is regenerated as lambda_new = lambda - Expcost
, where
Expcost ~ Exp(x1 - x0 + y1 - y0)
is drawn from the exponential distribution with rate the sum of intervals.
The process then recurses, generating independent Mondrian Processes MP(lambda_new, x0, x1, y_new, y1)
and MP(lambda_new, x0, x1, y0, y_new)
with diminished budget parameter lambda_new
.
The parameter lambda
controls the number of cuts, with the process more likely to cut rectangles with large perimeters.
The recursive process stops when lambda_new < 0
, thus the budget is exhausted.
Interestingly enough, setting lambda_new = lambda - Expcost
, where Expcost ~ Exp(x1 - x0 + y1 - y0)
did not yield expected
aesthetic results in our case because the inital budget was used up too slowly, which generated too many partitions. Trying out
a Poisson distribution for the cost reduction also did not yield expected results becuse now the budget was used up to quickly,
generating too few partitions. Finally, after trying the Gamma distribution (as part of the exponential distribution family we had a gut feeling that it might be a good fit),
satisfying aesthetic results could be generated. Letting the alpha
parameter fixed at alpha = x1 - x0 + y1 - y0
while we
varied the beta
parameter with a complexity factor greather than 0 (beta = (y1 - y0) * complexity
), allowed us to generate
partitions with slightly varied partition depth.
The algorithm
The partitioning algorithm takes a rectangle and splits it in two rectangles of randomly chosen sizes. This process continues
for the new two rectangles, which are again split in two and so on. In order to prevent an infinite splitting of rectangles and
ensure that the partitioning will eventually stop, we assign a starting budget lambda
, which is reduced after every new partitioning.
If the rectangle to be partitioned is large, a lower amount is deducted from the cost, in order to make the algorithm rather split
the larger rectangles than the small ones.
The algorithm takes the following input parameters:
- a fixed 2D empty canvas size (in pixels), for example 500x500. This will be the base rectangle for the first partitioning.
- an initial Mondrian Process with parameters
x1
andy1
set to the initial canvas size and initial budget parameterlambda
set tox1 - x0 + y1 - y0
. - boolean parameter
horizontal
, which tells the algorithm if the first cut should be horizontal (whenhorizontal=true
) or vertical (whenhorizontal=false
). - partitioning complexity parameter, which is directly embedded into the calculation of the partitioning cost deduction and thus controls how deep the algorithm will split the rectangles.
Without loss of generality let us assume that horizontal=true
, meaning that the first split will be horizontal.
The algorithm starts with the initial rectangle represented by the Mondrian Process
MP(lambda, x0, x1, y0, y1)
of the initial canvas size and chooses a pointy_new
within the(y0, y1)
interval uniformly at random.It calculates the cost of the split and the new reduced budget
lambda_new = lambda - Gammacost
, whereGammacost
is a random sample taken from the Gamma distribution with parametersalpha = x1 - x0 + y1 - y0
andbeta = (y1 - y0) * complexity
.The result are two new Mondrian Processes
MP(lambda_new, x0, x1, y0, y_new)
andMP(lambda_new, x0, x1, y_new, y1)
.The algorithm then runs recursively for the two new Mondrian Processes with
horizontal=false
, meaning that the following partition will be made vertically.For
lambda_new
the recursion stops and all previously made partitions (rectangles) are returned in a single data type. To prevent large data sets being passed around between recursions, the algorithm works with pointers, reading data directly from memory and altering it there.