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 core of the algorithm is the 2D Mondrian Process, as defined in [1].
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 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:
x1
and y1
set to the initial canvas size and initial budget parameter lambda
set to x1 - x0 + y1 - y0
.horizontal
, which tells the algorithm if the first cut should be horizontal (when horizontal=true
) or vertical (when horizontal=false
).Without loss of generality let us assume that horizontal=true
, meaning that the first split will be horizontal.
MP(lambda, x0, x1, y0, y1)
of the initial canvas size and chooses a point y_new
within the (y0, y1)
interval uniformly at random.
lambda_new = lambda - Gammacost
, where Gammacost
is a random sample taken
from the Gamma distribution with parameters alpha = x1 - x0 + y1 - y0
and beta = (y1 - y0) * complexity
.
MP(lambda_new, x0, x1, y0, y_new)
and MP(lambda_new, x0, x1, y_new, y1)
.horizontal=false
, meaning that the following
partition will be made vertically.
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.
[1] Mondrian Process