-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
9856f76
commit 7db56ca
Showing
2 changed files
with
309 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,194 @@ | ||
// returns a function that calls `fn` when args are != from the previous call | ||
// Otherwise (same args), do nothing. | ||
// A special case of memoize. | ||
function cacheFunctionFor( fn ) { | ||
let old_args = [] | ||
return (...args) => { | ||
if(args != old_args){ | ||
old_args = args | ||
fn(...args) | ||
} | ||
else return; | ||
} | ||
} | ||
|
||
let $ = function(selector){ | ||
if(typeof selector == 'string'){ return document.querySelector(selector) } | ||
if(typeof selector == 'object' && selector.nodeType == 1){ return selector } | ||
throw TypeError("$() received an invalid input (" + selector + "). Must be a string or DOMNode! ") | ||
} | ||
|
||
// from pairsArray [[name,x], ...] to object {name: x, ...} | ||
let fromPairs = (pairsArr) => { | ||
let o = {} | ||
pairsArr.map( ([name,img]) => { o[name] = img}) | ||
return o; | ||
} | ||
|
||
/* | ||
* Dynamic catties avatar using ES6 and canvas. | ||
* Author: barrabinfc | ||
* | ||
* Usage: | ||
* | ||
* <div class="avatar" data-seed="myusername"> | ||
* <img alt="myusername" class="small"> | ||
* </div> | ||
* <div class="avatar" data-seed="otherusername"> | ||
* <img alt="otherusername" class="big"> | ||
* </div> | ||
* <script> | ||
* var cat = new Meowatar({selector: '.avatar'}) | ||
* </script> | ||
* | ||
* | ||
*/ | ||
class Meowatar { | ||
constructor(opts){ | ||
this.options = Object.assign( {selector: '.avatar', | ||
img_tag: 'img', | ||
assets_path: '/avatars/', | ||
seed: 0, | ||
w: 256, | ||
h: 256, | ||
parts: { | ||
'body': {url_prefix: 'body', qtd: 15}, | ||
'fur': {url_prefix: 'fur', qtd: 10}, | ||
'eyes': {url_prefix: 'eyes', qtd: 15}, | ||
'mouth': {url_prefix: 'mouth', qtd: 10 }, | ||
'acessorie': {url_prefix: 'accessorie', qtd: 20}, | ||
//'zz': {url_prefix: 'zz', qtd: 2} | ||
}} , opts ) | ||
this.seed = this.options.seed; | ||
this.addClassOnceFor = cacheFunctionFor( this.addClass ) | ||
this.addCanvas() | ||
this.render() | ||
} | ||
|
||
// get the image URL for body `part` `i` | ||
cattyURL(part, i){ | ||
return this.options.assets_path + (this.options.parts[part].url_prefix + '_' + | ||
((i % this.options.parts[part].qtd )+1) + '.png') | ||
} | ||
|
||
|
||
// set seed from a Number or a String. | ||
seedStart( startSeed ){ | ||
let parseStr = ( seed ) => { return seed.split('').reduce( (a,b) => {return a + b.charCodeAt()}, 0) } | ||
let error = () => { throw TypeError("meowatar - Seed must be a number or string") } | ||
|
||
this._seed = ((typeof startSeed == 'number') | ||
? startSeed | ||
: (typeof startSeed == 'string') | ||
? parseStr(startSeed) | ||
: error() ) | ||
} | ||
|
||
/* pseudo-random with seed reproducibility | ||
* Repeats every 233280 iteractions | ||
* https://en.wikipedia.org/wiki/Linear_congruential_generator | ||
*/ | ||
seededRandom(max, min) { | ||
max = max || 1000; | ||
min = min || 0; | ||
|
||
this._seed = (this._seed * 9301 + 49297) % 233280; | ||
var rnd = this._seed / 233280; | ||
|
||
return Math.floor( min + rnd * (max - min)); | ||
} | ||
|
||
addCanvas() { | ||
this.containers = document.querySelectorAll(this.options.selector) | ||
this.canvas_el = document.createElement('canvas') | ||
this.canvas_el.width = this.options.w; this.canvas_el.height = this.options.h; | ||
} | ||
getDataURL(){ return this.canvas_el.toDataURL(); } | ||
|
||
|
||
|
||
// load a single image Element | ||
loadImage(name, img_uri){ | ||
return new Promise( (resolve, reject) => { | ||
var newImg = new Image() | ||
newImg.src = img_uri | ||
newImg.onload = () => { resolve(newImg) } | ||
newImg.onerror = () => { reject(newImg) } | ||
return newImg | ||
}) | ||
} | ||
|
||
/* | ||
* Load a group of images: [['name',imgURL], ...] , returns a promise | ||
*/ | ||
preloadImages( imagesURL ) { | ||
return Promise.all( imagesURL.map( ([name,url]) => { | ||
return this.loadImage(name,url).then( (img) => { | ||
return [name, img] | ||
}) | ||
})) | ||
} | ||
|
||
getCtx(){ | ||
if(!this.canvas_el) | ||
this.addCanvas() | ||
return this.canvas_el.getContext('2d') | ||
} | ||
|
||
drawImg( img ){ | ||
this._ctx.drawImage(img, 0,0, this.options.w, this.options.h ) | ||
} | ||
|
||
addClass( el, klass ) { | ||
$(el).className += (' ' + klass) | ||
} | ||
|
||
// show avatar on the <img> tag of parent Element | ||
showImage( container_el , dataURL ) { | ||
let img = $(container_el).querySelector( this.options.img_tag ) | ||
img.src = dataURL | ||
} | ||
|
||
/* | ||
* Render every selector matched | ||
*/ | ||
render(){ | ||
this.containers.forEach( (el) => { | ||
let seed = el.getAttribute('data-seed') || this.seed | ||
window.requestAnimationFrame( this.draw.bind( this, seed , el) ) | ||
}) | ||
} | ||
|
||
/* Create a new avatar from the seed. | ||
The same seed will yield always the same avatar | ||
Renders to EL | ||
*/ | ||
draw( startSeed=Math.floor( Math.random()*1000) , el=this.containers[0]) { | ||
this.seedStart(startSeed) | ||
this._ctx = this.getCtx() | ||
|
||
let _imagesURL = [ ['body', this.cattyURL('body',this.seededRandom())], | ||
['fur', this.cattyURL('fur', this.seededRandom())], | ||
['eyes', this.cattyURL('eyes',this.seededRandom())], | ||
['mouth', this.cattyURL('mouth',this.seededRandom())], | ||
['acessorie', this.cattyURL('acessorie',this.seededRandom())], | ||
] | ||
|
||
// load all images | ||
this.preloadImages( _imagesURL).then( (imgs) => { | ||
let _images = fromPairs(imgs) | ||
|
||
// now draw it on <el> | ||
this._ctx.clearRect(0,0, this.options.w, this.options.h) | ||
this.drawImg( _images.body ) | ||
this.drawImg( _images.fur ) | ||
this.drawImg( _images.eyes ) | ||
this.drawImg( _images.mouth ) | ||
this.drawImg( _images.acessorie ) | ||
|
||
// and show as a <img> tag | ||
this.showImage( el , this.getDataURL() ) | ||
this.addClassOnceFor( el, 'loaded') | ||
}) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,115 @@ | ||
<!DOCTYPE html> | ||
<html lang="en"> | ||
<head> | ||
<title>Cat avatar generator</title> | ||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> | ||
<meta http-equiv="Content-Language" content="en-ca" /> | ||
<link rel="Shortcut Icon" href="favicon.png" type="image/x-icon" /> | ||
|
||
<style type="text/css" media="screen"> | ||
body { margin: 2em; padding: 0; background: #000; color: #666; font-size: .8rem; text-align: center; } | ||
#wrapper { text-align: left; background: #fff; max-width: 300px; padding: 1rem; margin: 0 auto; border: 1em #333 solid; text-align: center; } | ||
h1 { font-variant: small-caps; color: hotpink; font-size: 1.8rem; font-family: Ubuntu, Arial, sans; font-weight: bold; margin: 0 0 0.3rem 0; } | ||
|
||
|
||
.card { | ||
display: flex; | ||
align-items: baseline; | ||
} | ||
.small { 50%; overflow: hidden; width: 2.5em; height: 2.5em;} | ||
|
||
|
||
.avatar { display: block; | ||
transition: transform 0.15s ease-in-out; | ||
transform: scale(0.8); | ||
} | ||
.avatar.inline { display: inline-block; } | ||
.avatar.loaded { transform: scale(1.0); } | ||
|
||
#main-avatar > img { | ||
z-index: 10; | ||
} | ||
#main-avatar.loaded.shadow:after { | ||
display: block; | ||
content: ' '; | ||
z-index: 5; | ||
width: 50%; | ||
height: inherit; | ||
margin: 0px auto; | ||
border-radius: 50%; | ||
box-shadow: 0 0 30px 5px rgba(0, 0, 0, 0.58); | ||
transform: translate(0,-5px); | ||
} | ||
|
||
a { color: #00aad4;} | ||
a:hover { color: #222;} | ||
.smallbutton { font-size: 1rem; margin-bottom: 0.8rem; } | ||
|
||
input[type=text]{width:90%} | ||
.bigbutton { font-weight: bold; font-size: 1.3rem; margin-bottom: 1.3rem; } | ||
|
||
@media (max-width: 500px) { | ||
body { margin: 0; padding: 0; background: #FFF; font-size: 1rem; } | ||
#wrapper { text-align: left; background: #fff; max-width: 100%; padding: 0; margin: 0 auto; border: none; text-align: center; } | ||
h1 { font-size: 1.6rem; margin: 0.2rem 0 0.2rem 0; } | ||
} | ||
</style> | ||
</head> | ||
<body> | ||
|
||
|
||
<div id="wrapper"> | ||
|
||
<h1>Cat avatar generator</h1> | ||
|
||
<div class="avatar shadow" id="main-avatar"> | ||
<img alt="Avatar profile"> | ||
</div> | ||
|
||
<form onSubmit="return onSubmit()"> | ||
<br/> | ||
<br/> | ||
Your name?*<br/> | ||
<input class="smallbutton" type="text" name="seed" id="name" value="linux" autofocus /> | ||
<input class="bigbutton" type="submit" value="Generate new cat" /> | ||
</form> | ||
|
||
<br/> | ||
<em>(* empty=random)</em><br/><br/> | ||
Artworks: CC-By David Revoy | ||
<div class="avatar inline shadow" data-seed="David Revoy"> | ||
<img alt="Avatar2" class="small"> | ||
</div> | ||
<a href="http://www.peppercarrot.com" title="my webcomic">[site]</a> | ||
<br/> | ||
Code: CC-By Andreas Gohr | ||
<a href="https://www.splitbrain.org/blog/2007-01/20_monsterid_as_gravatar_fallback" title="original author">[site]</a> | ||
<div class="avatar inline shadow" data-seed="Andreas Gohr"> | ||
<img alt="Avatar Image" class="small"> | ||
</div> | ||
Javascript version by barrabinfc | ||
<a href="github.com/barrabinfc" title="javascript author">[site]</a> | ||
<div class="avatar inline shadow" data-seed="barrabinfc"> | ||
<img alt="Avatar Image" class="small"> | ||
</div> | ||
|
||
<br/> | ||
<a href="2016_cat-generator.zip" title="original author">[Zip sources and artwork]</a> | ||
|
||
</div> | ||
<script src="/cat-generator.js"></script> | ||
<script> | ||
let options = {seed: 'linux', selector: '.avatar'}; | ||
var cat = new Meowatar(options); | ||
|
||
function onSubmit( ev ){ | ||
|
||
let username = document.querySelector('input[name="seed"]').value | ||
cat.draw( username || undefined , document.querySelector('#main-avatar') ) | ||
|
||
return false; | ||
} | ||
</script> | ||
|
||
</body> | ||
</html> |