206 lines
7.1 KiB
JavaScript
206 lines
7.1 KiB
JavaScript
/**
|
|
* Identicon.js 2.3.3
|
|
* http://github.com/stewartlord/identicon.js
|
|
*
|
|
* PNGLib required for PNG output
|
|
* http://www.xarg.org/download/pnglib.js
|
|
*
|
|
* Copyright 2018, Stewart Lord
|
|
* Released under the BSD license
|
|
* http://www.opensource.org/licenses/bsd-license.php
|
|
*/
|
|
|
|
(function() {
|
|
var PNGlib;
|
|
if (typeof module !== 'undefined' && typeof module.exports !== 'undefined') {
|
|
PNGlib = require('./pnglib');
|
|
} else {
|
|
PNGlib = window.PNGlib;
|
|
}
|
|
|
|
var Identicon = function(hash, options){
|
|
if (typeof(hash) !== 'string' || hash.length < 15) {
|
|
throw 'A hash of at least 15 characters is required.';
|
|
}
|
|
|
|
this.defaults = {
|
|
background: [240, 240, 240, 255],
|
|
margin: 0.08,
|
|
size: 64,
|
|
saturation: 0.7,
|
|
brightness: 0.5,
|
|
format: 'png'
|
|
};
|
|
|
|
this.options = typeof(options) === 'object' ? options : this.defaults;
|
|
|
|
// backward compatibility with old constructor (hash, size, margin)
|
|
if (typeof(arguments[1]) === 'number') { this.options.size = arguments[1]; }
|
|
if (arguments[2]) { this.options.margin = arguments[2]; }
|
|
|
|
this.hash = hash
|
|
this.background = this.options.background || this.defaults.background;
|
|
this.size = this.options.size || this.defaults.size;
|
|
this.format = this.options.format || this.defaults.format;
|
|
this.margin = this.options.margin !== undefined ? this.options.margin : this.defaults.margin;
|
|
|
|
// foreground defaults to last 7 chars as hue at 70% saturation, 50% brightness
|
|
var hue = parseInt(this.hash.substr(-7), 16) / 0xfffffff;
|
|
var saturation = this.options.saturation || this.defaults.saturation;
|
|
var brightness = this.options.brightness || this.defaults.brightness;
|
|
this.foreground = this.options.foreground || this.hsl2rgb(hue, saturation, brightness);
|
|
};
|
|
|
|
Identicon.prototype = {
|
|
background: null,
|
|
foreground: null,
|
|
hash: null,
|
|
margin: null,
|
|
size: null,
|
|
format: null,
|
|
|
|
image: function(){
|
|
return this.isSvg()
|
|
? new Svg(this.size, this.foreground, this.background)
|
|
: new PNGlib(this.size, this.size, 256);
|
|
},
|
|
|
|
render: function(){
|
|
var image = this.image(),
|
|
size = this.size,
|
|
baseMargin = Math.floor(size * this.margin),
|
|
cell = Math.floor((size - (baseMargin * 2)) / 5),
|
|
margin = Math.floor((size - cell * 5) / 2),
|
|
bg = image.color.apply(image, this.background),
|
|
fg = image.color.apply(image, this.foreground);
|
|
|
|
// the first 15 characters of the hash control the pixels (even/odd)
|
|
// they are drawn down the middle first, then mirrored outwards
|
|
var i, color;
|
|
for (i = 0; i < 15; i++) {
|
|
color = parseInt(this.hash.charAt(i), 16) % 2 ? bg : fg;
|
|
if (i < 5) {
|
|
this.rectangle(2 * cell + margin, i * cell + margin, cell, cell, color, image);
|
|
} else if (i < 10) {
|
|
this.rectangle(1 * cell + margin, (i - 5) * cell + margin, cell, cell, color, image);
|
|
this.rectangle(3 * cell + margin, (i - 5) * cell + margin, cell, cell, color, image);
|
|
} else if (i < 15) {
|
|
this.rectangle(0 * cell + margin, (i - 10) * cell + margin, cell, cell, color, image);
|
|
this.rectangle(4 * cell + margin, (i - 10) * cell + margin, cell, cell, color, image);
|
|
}
|
|
}
|
|
|
|
return image;
|
|
},
|
|
|
|
rectangle: function(x, y, w, h, color, image){
|
|
if (this.isSvg()) {
|
|
image.rectangles.push({x: x, y: y, w: w, h: h, color: color});
|
|
} else {
|
|
var i, j;
|
|
for (i = x; i < x + w; i++) {
|
|
for (j = y; j < y + h; j++) {
|
|
image.buffer[image.index(i, j)] = color;
|
|
}
|
|
}
|
|
}
|
|
},
|
|
|
|
// adapted from: https://gist.github.com/aemkei/1325937
|
|
hsl2rgb: function(h, s, b){
|
|
h *= 6;
|
|
s = [
|
|
b += s *= b < .5 ? b : 1 - b,
|
|
b - h % 1 * s * 2,
|
|
b -= s *= 2,
|
|
b,
|
|
b + h % 1 * s,
|
|
b + s
|
|
];
|
|
|
|
return[
|
|
s[ ~~h % 6 ] * 255, // red
|
|
s[ (h|16) % 6 ] * 255, // green
|
|
s[ (h|8) % 6 ] * 255 // blue
|
|
];
|
|
},
|
|
|
|
toString: function(raw){
|
|
// backward compatibility with old toString, default to base64
|
|
if (raw) {
|
|
return this.render().getDump();
|
|
} else {
|
|
return this.render().getBase64();
|
|
}
|
|
},
|
|
|
|
isSvg: function(){
|
|
return this.format.match(/svg/i)
|
|
}
|
|
};
|
|
|
|
var Svg = function(size, foreground, background){
|
|
this.size = size;
|
|
this.foreground = this.color.apply(this, foreground);
|
|
this.background = this.color.apply(this, background);
|
|
this.rectangles = [];
|
|
};
|
|
|
|
Svg.prototype = {
|
|
size: null,
|
|
foreground: null,
|
|
background: null,
|
|
rectangles: null,
|
|
|
|
color: function(r, g, b, a){
|
|
var values = [r, g, b].map(Math.round);
|
|
values.push((a >= 0) && (a <= 255) ? a/255 : 1);
|
|
return 'rgba(' + values.join(',') + ')';
|
|
},
|
|
|
|
getDump: function(){
|
|
var i,
|
|
xml,
|
|
rect,
|
|
fg = this.foreground,
|
|
bg = this.background,
|
|
stroke = this.size * 0.005;
|
|
|
|
xml = "<svg xmlns='http://www.w3.org/2000/svg'"
|
|
+ " width='" + this.size + "' height='" + this.size + "'"
|
|
+ " style='background-color:" + bg + ";'>"
|
|
+ "<g style='fill:" + fg + "; stroke:" + fg + "; stroke-width:" + stroke + ";'>";
|
|
|
|
for (i = 0; i < this.rectangles.length; i++) {
|
|
rect = this.rectangles[i];
|
|
if (rect.color == bg) continue;
|
|
xml += "<rect "
|
|
+ " x='" + rect.x + "'"
|
|
+ " y='" + rect.y + "'"
|
|
+ " width='" + rect.w + "'"
|
|
+ " height='" + rect.h + "'"
|
|
+ "/>";
|
|
}
|
|
xml += "</g></svg>"
|
|
|
|
return xml;
|
|
},
|
|
|
|
getBase64: function(){
|
|
if ('function' === typeof btoa) {
|
|
return btoa(this.getDump());
|
|
} else if (Buffer) {
|
|
return new Buffer(this.getDump(), 'binary').toString('base64');
|
|
} else {
|
|
throw 'Cannot generate base64 output';
|
|
}
|
|
}
|
|
};
|
|
|
|
if (typeof module !== 'undefined' && typeof module.exports !== 'undefined') {
|
|
module.exports = Identicon;
|
|
} else {
|
|
window.Identicon = Identicon;
|
|
}
|
|
})();
|