Skip to content

Instantly share code, notes, and snippets.

@kchapelier
Created September 2, 2016 16:46
Show Gist options
  • Save kchapelier/f88199b73e2ea165e0d301b72eda2854 to your computer and use it in GitHub Desktop.
Save kchapelier/f88199b73e2ea165e0d301b72eda2854 to your computer and use it in GitHub Desktop.
// all code under the DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE Version 2
// all code written by Carl Olsson (surt)
// simple moore neighbourhod
var neighbourOffsets = [[-1, -1], [0, -1], [+1, -1], [+1, 0], [+1, +1], [0, +1], [-1, +1], [-1, 0]];
// retrieve the colours of all the pixels in moore neighbourhood of the given coordinates
function getNeighbours(data, x, y) {
var neighbours = new Array();
for(var i = 0; i < 8; i++) {
try {
neighbours[i] = getPixel(data, x + neighbourOffsets[i][0], y + neighbourOffsets[i][1]);
}
catch (e) {}
}
return neighbours;
}
function lerp(start, end, pos) {
return start + (end - start) * pos;
}
function bias(bias, value) {
// y(x) = x^(log(B)/log(0.5))
return Math.pow(value, Math.log(bias) / Math.log(0.5));
}
function gain(gain, value) {
// y(x) = Bias(2*x, 1-G)/2 if x<0.5
// 1-Bias(2-2*x, 1-G)/2 if x>0.5
return value <= 0.5 ? bias(2 * value, 1 - gain) / 2 : 1 - bias(2 - 2 * value, 1 - gain) / 2;
}
// this method is the meat of the generation, the rest of the code is mostly UI initialization, utility functions, color palettes and scaling functions
SpriteGenerator.prototype.generateTile = function() {
// create data holder a single tile
var tile = this.context.createImageData(values["size"], values["size"]);
// define whether we have to apply a mirror Horizontally and/or vertically
var doMirrorH = (random() <= values["mirrorh"]);
var doMirrorV = (random() <= values["mirrorv"]);
var xRange = tile.width / 2;
var yRange = tile.height / 2;
// limit the actual tile generation to only half or a quarter of a tile if mirrors are to be applied (generation range)
var xLimit = ( doMirrorH ? Math.ceil(xRange) : tile.width);
var yLimit = ( doMirrorV ? Math.ceil(yRange) : tile.height);
// generate a color palette by randomly picks colors from the user selected palette
var spritePal = [];
for (var i = 0; i < values["colours"]; i++) {
spritePal[i] = pickRandom(palettes[values["pal"]].colours);
}
for(var y = 0; y < yLimit; y++) {
for(var x = 0; x < xLimit; x++) {
// for each single pixel in the generation range
// calculate some kind of distorted distance (seems to vary wildly when mirrors are applied, that's probably not intended ?)
var falloffX = falloffs[values["falloff"]].func(1.0 - Math.abs((xRange - (x + 0.5)) / xRange));
var falloffY = falloffs[values["falloff"]].func(1.0 - Math.abs((yRange - (y + 0.5)) / yRange));
// calculate the probability of having a sprite pixel instead of a background here here
// do a linear interpolation between the user provided minimum probabily and maximum probality with a ratio calculated from the previous distance
var prob = lerp(values["probmin"], values["probmax"], bias(values["bias"], gain(values["gain"], falloffX * falloffY)));
// decide whether the current pixel is a background pixel or if it is a sprite pixel
// if it is a sprite pixel pick a random colour from the generated palette
var rand = random();
if(rand <= prob)
putPixel(tile, x, y, rgbToRgba(pickRandom(spritePal)));
else
putPixel(tile, x, y, backgroundColour);
}
}
// the generation range is now filled with sprite pixels and background pixels
// post processing
// some kind of stochastic cellular automation to smoothen the sprite and limit the number of 'rogue' single pixels
for(var y = 0; y < yLimit; y++) {
for(var x = 0; x < xLimit; x++) {
// for each single pixel in the generation range
// count how many of their neighbours (moore neighbourhood) is filled (not a background pixel)
var neighbours = getNeighbours(tile, x, y);
var count = 0;
for(var i = 0; i < neighbours.length; i++) {
if(neighbours[i] != null && !coloursEqual(backgroundColour, neighbours[i]))
count++;
}
// if there is only one filled/alive neighboor, randomly check if it has to be replaced by a background pixel, using a user provided probabilty (values["despur"])
if(count == 1) {
// despur
if(random() <= values["despur"]) {
putPixel(tile, x, y, backgroundColour);
}
// if there is no filled/alive neighboor, randomly check if it has to be replaced by a background pixel, using another user provided probabilty (values["despeckle"])
} else if(count == 0) {
// despeckle
if(random() <= values["despeckle"]) {
putPixel(tile, x, y, backgroundColour);
}
}
}
}
// that's about it
// copy mirrors, if mirrors are to be applied => mirror the generated content to the empty quarters of the tile
xLimit = ( doMirrorH ? Math.floor(xRange + 0.5) : tile.width);
yLimit = ( doMirrorV ? Math.floor(yRange + 0.5) : tile.height);
for(var y = 0; y < yLimit; y++) {
for(var x = 0; x < xLimit; x++) {
var colour = getPixel(tile, x, y);
if(doMirrorH)
putPixel(tile, tile.width - 1 - x, y, colour);
if(doMirrorV)
putPixel(tile, x, tile.height - 1 - y, colour);
if(doMirrorH && doMirrorV)
putPixel(tile, tile.width - 1 - x, tile.height - 1 - y, colour);
}
}
var out = tile;
// some scaling with user selected function, not part of the actual tile generation
/*
if (values["scaler0"] != "none") {
var scaler = scalers[values["scaler0"]];
var scaled = this.context.createImageData(out.width * scaler.factor, out.height * scaler.factor);
scale(out, scaled, scaler);
out = scaled;
}
if (values["scaler1"] != "none") {
var scaler = scalers[values["scaler1"]];
var scaled = this.context.createImageData(out.width * scaler.factor, out.height * scaler.factor);
scale(out, scaled, scaler);
out = scaled;
}
*/
return out;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment