Primer

to generate a new and unique variation

Source Code

Every variart piece is a small program. This is the one that drew what you're looking at.

var background = '#E8DFD3';
var colors = ['#D9483B', '#EDB92E', '#1E68A0', '#1A1718'];
var margin = 80;
var grid = 5;

var blocks = intVariable('Blocks', 5, 16);
var curves = intVariable('Curves', 0, 60);
var zoom = [100, 120, 250][intVariable('Zoom', 1, 3) - 1];

s.rect(0, 0, width, height).attr({fill: background});

// Three steps rather than a continuum: in between, the cluster ends just short
// of the edge, in the wrap, which reads as a mistake rather than a crop.
var size = (width - margin * 2) / grid * zoom / 100;
var has = function(r, c){ return !!filled[r + ',' + c]; };

// Grow a connected cluster from the middle of the grid.
var mid = Math.floor(grid / 2);
var cells = [{r: mid, c: mid}];
var filled = {};
filled[mid + ',' + mid] = true;
while (cells.length < blocks){
  var from = cells[getRandomInt(0, cells.length - 1)];
  var d = [[0, 1], [0, -1], [1, 0], [-1, 0]][getRandomInt(0, 3)];
  var r = from.r + d[0], c = from.c + d[1];
  if (r >= 0 && r < grid && c >= 0 && c < grid && !filled[r + ',' + c]){
    filled[r + ',' + c] = true;
    cells.push({r: r, c: c});
  }
}

// Centre the cluster, whatever shape it grew into.
var minR = grid, maxR = 0, minC = grid, maxC = 0;
cells.forEach(function(cell){
  minR = Math.min(minR, cell.r); maxR = Math.max(maxR, cell.r);
  minC = Math.min(minC, cell.c); maxC = Math.max(maxC, cell.c);
});
var offsetX = (width - (maxC - minC + 1) * size) / 2 - minC * size;
var offsetY = (height - (maxR - minR + 1) * size) / 2 - minR * size;

// The square corner of a curve tucks in against its neighbours, so the arc
// faces open space the way an arch does.
function cornerFor(r, c){
  var right = has(r, c + 1) && !has(r, c - 1) ? 1 : (has(r, c - 1) && !has(r, c + 1) ? 0 : getRandomInt(0, 1));
  var bottom = has(r + 1, c) && !has(r - 1, c) ? 1 : (has(r - 1, c) && !has(r + 1, c) ? 0 : getRandomInt(0, 1));
  return bottom * 2 + right;
}

// Neighbours never share a colour, so every block reads on its own.
var colorAt = {};
function pickColor(r, c){
  var taken = [colorAt[(r - 1) + ',' + c], colorAt[(r + 1) + ',' + c],
               colorAt[r + ',' + (c - 1)], colorAt[r + ',' + (c + 1)]];
  var options = colors.filter(function(k){ return taken.indexOf(k) == -1; });
  return options[getRandomInt(0, options.length - 1)];
}

// A quarter circle filling the cell, its right angle in one corner.
function quarter(x, y, corner, fill){
  var cx = x + (corner % 2) * size, cy = y + Math.floor(corner / 2) * size;
  var sx = corner % 2 ? -1 : 1, sy = corner < 2 ? 1 : -1;
  s.path('M' + cx + ',' + cy + ' L' + (cx + sx * size) + ',' + cy +
         ' A' + size + ',' + size + ' 0 0,' + (sx * sy > 0 ? 1 : 0) + ' ' + cx + ',' + (cy + sy * size) + ' Z')
    .attr({fill: fill, stroke: fill, strokeWidth: 1});
}

cells.forEach(function(cell){
  var fill = pickColor(cell.r, cell.c);
  colorAt[cell.r + ',' + cell.c] = fill;
  var x = offsetX + cell.c * size, y = offsetY + cell.r * size;
  if (getRandomInt(1, 100) <= curves){
    quarter(x, y, cornerFor(cell.r, cell.c), fill);
  } else {
    // The hairline stroke closes the anti-aliasing seams between tiles.
    s.rect(x, y, size, size).attr({fill: fill, stroke: fill, strokeWidth: 1});
  }
});