Skip to content

Instantly share code, notes, and snippets.

@blixt
Last active December 29, 2020 14:32
Show Gist options
  • Select an option

  • Save blixt/b914062d855df8f3d556 to your computer and use it in GitHub Desktop.

Select an option

Save blixt/b914062d855df8f3d556 to your computer and use it in GitHub Desktop.
requirebin sketch
var procedural = require('procedural');
var avatar = procedural('avatar')
// The block size is just visual, so it shouldn't affect randomization.
.doNotHash('blockSize')
// The username is needed to create a unique avatar for every user.
.takes('username')
// Size, in blocks. Different sizes will create different avatars.
.takes('size', function validate(avatar, blocks) {
// Ensure that size is a positive integer divisible by 2.
return typeof blocks == 'number' && blocks > 0 && !(blocks % 2);
})
// The pixel size of a single (square) block.
.takes('blockSize', function validate(avatar, px) {
return typeof px == 'number' && px > 0;
})
// Calculate the colors that make up the avatar.
.provides('hueAngle', function (avatar) {
// Use a named number generator to get an independent sequence.
return avatar.getRandGen('color').nextInt(360);
})
.provides('background', function (avatar) {
return 'hsl(' + avatar.hueAngle + ', 100%, 50%)';
})
.provides('foreground', function (avatar) {
var hueAngle = (avatar.hueAngle + 180) % 360;
return 'hsl(' + hueAngle + ', 100%, 50%)';
})
// 25% of avatars mirror vertically instead.
.provides('mirrorVertically', function (avatar) {
return avatar.getRandGen('mirror').nextFloat() <= .25;
})
// A particular avatar has a unique set of blocks.
.generates('block')
// The validator will run independently for both parameters.
.takes('x', 'y', function validate(block, xy) {
// We can refer to the parent instance (the avatar).
return typeof xy == 'number' && xy >= 0 && xy < block.avatar.size;
})
// The color of this block.
.provides('color', function (block) {
// You don't have to use named random generators.
if (block.getRandGen().nextFloat() > .5) {
return block.avatar.foreground;
} else {
return block.avatar.background;
}
})
// Go back to defining the parent (avatar).
.done()
// Renders to a canvas and returns a URL for <img>.
.provides('url', function (avatar) {
var canvas = document.createElement('canvas'),
context = canvas.getContext('2d');
canvas.width = avatar.size * avatar.blockSize;
canvas.height = avatar.size * avatar.blockSize;
context.fillStyle = avatar.background;
context.fillRect(0, 0, avatar.size, avatar.size);
var finalX = avatar.mirrorVertically ? avatar.size : avatar.size / 2,
finalY = avatar.mirrorVertically ? avatar.size / 2 : avatar.size,
blockSize = avatar.blockSize;
for (var y = 0; y < finalY; y++) {
for (var x = 0; x < finalX; x++) {
var realX = x * blockSize, realY = y * blockSize;
var block = avatar.block(x, y);
context.fillStyle = block.color;
context.fillRect(realX, realY, blockSize, blockSize);
if (avatar.mirrorVertically) {
var mirroredY = avatar.size * blockSize - realY - blockSize;
context.fillRect(realX, mirroredY, blockSize, blockSize);
} else {
var mirroredX = avatar.size * blockSize - realX - blockSize;
context.fillRect(mirroredX, realY, blockSize, blockSize);
}
}
}
return canvas.toDataURL();
});
// Set up document.
document.body.innerHTML = '<p><code>img.src = avatar(<input id="username" size="8" value="bob">, <input id="size" size="2" value="16">, <input id="blockSize" size="2" value="4">).url;</code></p><p><img></p><pre></pre>';
var img = document.querySelector('img'),
pre = document.querySelector('pre'),
username = document.querySelector('#username'),
size = document.querySelector('#size'),
blockSize = document.querySelector('#blockSize');
// Create a new "avatar" instance every time a key is pressed.
username.onkeyup = size.onkeyup = blockSize.onkeyup = function () {
var avatarInstance = avatar(username.value,
parseInt(size.value, 10),
parseInt(blockSize.value, 10));
img.src = avatarInstance.url;
pre.textContent = JSON.stringify(avatarInstance, null, ' ');
};
username.onkeyup();
require=function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);throw new Error("Cannot find module '"+o+"'")}var f=n[o]={exports:{}};t[o][0].call(f.exports,function(e){var n=t[o][1][e];return s(n?n:e)},f,f.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s}({BmBy9Z:[function(require,module,exports){var murmur3=require("./lib/murmur3");var random=require("./lib/random");var PARAMETER={},COMPUTED={};module.exports=function procedural(name,parent){var values=[],valuesMap={hash:null};var doNotHash=[];var parameterCount=0;function getName(){return name}function getValues(opt_type){var names=[];for(var i=0;i<values.length;i++){if(opt_type&&values[i].type!=opt_type)continue;names.push(values[i].name)}return names}function getParameters(){return getValues(PARAMETER)}function getProvides(){return getValues(COMPUTED)}function ProceduralInstance(parentInstance){if(parentInstance){this[parentInstance.getName()]=parentInstance}else if(parent){console.warn("Creating detached "+name+" instance (expected parent "+parent+")")}}ProceduralInstance.prototype={getName:getName,getParameters:getParameters,getProvides:getProvides,getValues:getValues,get:function(keyPath){var keys=keyPath.split("."),value=this;for(var i=0;i<keys.length;i++){value=value[keys[i]]}return value},getParent:function(){if(!parent)return null;return this[parent.getName()]},getRandGen:function(opt_id){var seed=opt_id?murmur3.hash32(opt_id,this.hash):this.hash;return new random.ParkMiller(seed)},toString:function(){var args,proc=this,pieces=[];while(proc){args=proc.getParameters().map(function(paramName){return paramName+": "+JSON.stringify(proc[paramName])});pieces.unshift(proc.getName()+"("+args.join(", ")+")");proc=proc.getParent()}return pieces.join(".")}};function create(){if(arguments.length!=parameterCount){throw new Error("Wrong number of parameters for "+create+": "+arguments.length)}var parentInstance=parent&&parent.isInstance(this)?this:null;var instance=new ProceduralInstance(parentInstance);var hashParts=[name],hashSeed=parentInstance?parentInstance.hash:0;function createHashOnce(){if("hash"in instance)return;instance.hash=murmur3.hash32(hashParts.join("\x00"),hashSeed)}var argumentIndex=0;for(var i=0;i<values.length;i++){var value=values[i],shouldHash=doNotHash.indexOf(value.name)==-1;if(value.type==PARAMETER){if("hash"in instance&&shouldHash){throw new Error("Cannot define hashed parameters after hash is generated")}var argument=arguments[argumentIndex++];if(value.validator&&!value.validator(instance,argument)){throw new Error("Invalid value for "+name+"."+value.name+": "+argument)}instance[value.name]=argument;if(shouldHash){hashParts.push(JSON.stringify(instance[value.name]))}}else if(value.type==COMPUTED){if(value.fn){createHashOnce();instance[value.name]=value.fn(instance)}else{instance[value.name]=value.constant}}}createHashOnce();Object.freeze(instance);return instance}create.getName=getName;create.getParameters=getParameters;create.getProvides=getProvides;create.getValues=getValues;create.done=function(){return parent};create.doNotHash=function(var_args){Array.prototype.push.apply(doNotHash,arguments);return this};create.generates=function(nameValue){var proc=procedural(nameValue,this);ProceduralInstance.prototype[nameValue]=proc;this[nameValue]=proc;return proc};create.getParent=function(){return parent};create.isInstance=function(obj){return obj instanceof ProceduralInstance};create.provides=function(name,fnOrConstant){if(name in valuesMap){throw new Error("A value named "+name+" is already defined")}var value={name:name,type:COMPUTED};if(typeof fnOrConstant=="function"){value.fn=fnOrConstant}else{value.constant=fnOrConstant}values.push(value);valuesMap[name]=value;return this};create.takes=function(var_args){var numParams=arguments.length,validator;if(typeof arguments[numParams-1]=="function"){validator=arguments[numParams--]}if(!numParams){throw new Error("At least one parameter must be specified")}for(var i=0;i<numParams;i++){var name=arguments[i];if(typeof name!="string"){throw new Error("Invalid parameter name "+name)}if(name in valuesMap){throw new Error("A value named "+name+" is already defined")}var param={name:name,type:PARAMETER};if(typeof validator=="function"){param.validator=validator}values.push(param);valuesMap[name]=param}parameterCount+=numParams;return this};create.toString=function(){var pieces=[],proc=this;while(proc){pieces.unshift(proc.getName());proc=proc.getParent()}var names=this.getParameters().join(", ");return pieces.join(".")+"("+names+")"};return create}},{"./lib/murmur3":3,"./lib/random":4}],procedural:[function(require,module,exports){module.exports=require("BmBy9Z")},{}],3:[function(require,module,exports){exports.hash32=function hash32(key,seed){var remainder,bytes,h1,h1b,c1,c1b,c2,c2b,k1,i;remainder=key.length&3;bytes=key.length-remainder;h1=seed;c1=3432918353;c2=461845907;i=0;while(i<bytes){k1=key.charCodeAt(i)&255|(key.charCodeAt(++i)&255)<<8|(key.charCodeAt(++i)&255)<<16|(key.charCodeAt(++i)&255)<<24;++i;k1=(k1&65535)*c1+(((k1>>>16)*c1&65535)<<16)&4294967295;k1=k1<<15|k1>>>17;k1=(k1&65535)*c2+(((k1>>>16)*c2&65535)<<16)&4294967295;h1^=k1;h1=h1<<13|h1>>>19;h1b=(h1&65535)*5+(((h1>>>16)*5&65535)<<16)&4294967295;h1=(h1b&65535)+27492+(((h1b>>>16)+58964&65535)<<16)}k1=0;switch(remainder){case 3:k1^=(key.charCodeAt(i+2)&255)<<16;case 2:k1^=(key.charCodeAt(i+1)&255)<<8;case 1:k1^=key.charCodeAt(i)&255;k1=(k1&65535)*c1+(((k1>>>16)*c1&65535)<<16)&4294967295;k1=k1<<15|k1>>>17;k1=(k1&65535)*c2+(((k1>>>16)*c2&65535)<<16)&4294967295;h1^=k1}h1^=key.length;h1^=h1>>>16;h1=(h1&65535)*2246822507+(((h1>>>16)*2246822507&65535)<<16)&4294967295;h1^=h1>>>13;h1=(h1&65535)*3266489909+(((h1>>>16)*3266489909&65535)<<16)&4294967295;h1^=h1>>>16;return h1>>>0}},{}],4:[function(require,module,exports){function ParkMiller(seed){this._seed=seed%2147483647;if(this._seed<=0)this._seed+=2147483646}ParkMiller.prototype.next=function(){return this._seed=this._seed*16807%2147483647};ParkMiller.prototype.nextFloat=function(opt_minOrMax,opt_max){var value=(this.next()-1)/2147483646;var min,max;if(typeof opt_max=="number"){min=opt_minOrMax;max=opt_max}else if(typeof opt_minOrMax=="number"){min=0;max=opt_minOrMax}else{return value}return min+value*(max-min)};ParkMiller.prototype.nextInt=function(minOrMax,opt_max){return Math.floor(this.nextFloat(minOrMax,opt_max))};exports.ParkMiller=ParkMiller},{}]},{},[]);var procedural=require("procedural");var avatar=procedural("avatar").doNotHash("blockSize").takes("username").takes("size",function validate(avatar,blocks){return typeof blocks=="number"&&blocks>0&&!(blocks%2)}).takes("blockSize",function validate(avatar,px){return typeof px=="number"&&px>0}).provides("hueAngle",function(avatar){return avatar.getRandGen("color").nextInt(360)}).provides("background",function(avatar){return"hsl("+avatar.hueAngle+", 100%, 50%)"}).provides("foreground",function(avatar){var hueAngle=(avatar.hueAngle+180)%360;return"hsl("+hueAngle+", 100%, 50%)"}).provides("mirrorVertically",function(avatar){return avatar.getRandGen("mirror").nextFloat()<=.25}).generates("block").takes("x","y",function validate(block,xy){return typeof xy=="number"&&xy>=0&&xy<block.avatar.size}).provides("color",function(block){if(block.getRandGen().nextFloat()>.5){return block.avatar.foreground}else{return block.avatar.background}}).done().provides("url",function(avatar){var canvas=document.createElement("canvas"),context=canvas.getContext("2d");canvas.width=avatar.size*avatar.blockSize;canvas.height=avatar.size*avatar.blockSize;context.fillStyle=avatar.background;context.fillRect(0,0,avatar.size,avatar.size);var finalX=avatar.mirrorVertically?avatar.size:avatar.size/2,finalY=avatar.mirrorVertically?avatar.size/2:avatar.size,blockSize=avatar.blockSize;for(var y=0;y<finalY;y++){for(var x=0;x<finalX;x++){var realX=x*blockSize,realY=y*blockSize;var block=avatar.block(x,y);context.fillStyle=block.color;context.fillRect(realX,realY,blockSize,blockSize);if(avatar.mirrorVertically){var mirroredY=avatar.size*blockSize-realY-blockSize;context.fillRect(realX,mirroredY,blockSize,blockSize)}else{var mirroredX=avatar.size*blockSize-realX-blockSize;context.fillRect(mirroredX,realY,blockSize,blockSize)}}}return canvas.toDataURL()});document.body.innerHTML='<p><code>img.src = avatar(<input id="username" size="8" value="bob">, <input id="size" size="2" value="16">, <input id="blockSize" size="2" value="4">).url;</code></p><p><img></p><pre></pre>';var img=document.querySelector("img"),pre=document.querySelector("pre"),username=document.querySelector("#username"),size=document.querySelector("#size"),blockSize=document.querySelector("#blockSize");username.onkeyup=size.onkeyup=blockSize.onkeyup=function(){var avatarInstance=avatar(username.value,parseInt(size.value,10),parseInt(blockSize.value,10));img.src=avatarInstance.url;pre.textContent=JSON.stringify(avatarInstance,null," ")};username.onkeyup();
{
"name": "requirebin-sketch",
"version": "1.0.0",
"dependencies": {
"procedural": "0.1.3"
}
}
<style type='text/css'>html, body { margin: 0; padding: 0; border: 0; }
body, html { height: 100%; width: 100%; }</style>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment