Dit script moet niet direct worden geïnstalleerd - het is een bibliotheek voor andere scripts om op te nemen met de meta-richtlijn // @require https://update.greasyfork.org/scripts/490509/1347118/svg-identicon.js
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
typeof define === 'function' && define.amd ? define(['exports'], factory) :
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global));
})(this, (exports => {
class Svg {
constructor({
size,
shape,
margin,
border,
foreground,
background
}={}) {
this.size = size;
this.shape = shape;
this.border = border ? ({}).toString.call(border) === '[object String]' ? {
width: 1,
color: border
} : border : border;
this.margin = margin;
this.foreground = this.color(...foreground);
this.background = this.color(...background);
this.rectangles = [];
const baseMargin = Math.floor(size * this.margin);
this.pixel = Math.floor((size - (baseMargin * 2)) / 5);
}
color(r, g, b, a=1) {
return [r, g, b, a].reduce((acc,channel,idx) => {
if (idx === 3) {
if (channel < 1) {
acc += Math.round(channel * 255).toString(16).padStart(2, '0');
}
} else {
acc += channel.toString(16).padStart(2, '0');
}
return acc
}, '#')
}
getDump() {
const [fg, bg, stroke, pixel] = [
this.foreground,
this.background,
this.size * 0.005,
this.pixel
];
// https://github.com/ygoe/qrcode-generator/blob/5bb2d93e10/js/qrcode.js#L531-L662
const pointEquals = function (a, b) {
return a[0] === b[0] && a[1] === b[1];
};
// Mark all four edges of each square in clockwise drawing direction
const edges = [];
this.rectangles.forEach(({color, x: x0, y: y0}) => {
if (color !== bg) {
const x1 = x0 + this.pixel;
const y1 = y0 + this.pixel;
edges.push([[x0, y0], [x1, y0]]); // top edge (to right)
edges.push([[x1, y0], [x1, y1]]); // right edge (down)
edges.push([[x1, y1], [x0, y1]]); // bottom edge (to left)
edges.push([[x0, y1], [x0, y0]]); // left edge (up)
}
});
// Edges that exist in both directions cancel each other (connecting the rectangles)
for (let i = edges.length - 1; i >= 0; i--) {
for (let j = i - 1; j >= 0; j--) {
if (pointEquals(edges[i][0], edges[j][1]) &&
pointEquals(edges[i][1], edges[j][0])) {
// First remove index i, it's greater than j
edges.splice(i, 1);
edges.splice(j, 1);
i--;
break;
}
}
}
let polygons = [];
while (edges.length > 0) {
// Pick a random edge and follow its connected edges to form a path (remove used edges)
// If there are multiple connected edges, pick the first
// Stop when the starting point of this path is reached
let polygon = [];
polygons.push(polygon);
let edge = edges.splice(0, 1)[0];
polygon.push(edge[0]);
polygon.push(edge[1]);
do {
let foundEdge = false;
for (let i = 0; i < edges.length; i++) {
if (pointEquals(edges[i][0], edge[1])) {
// Found an edge that starts at the last edge's end
foundEdge = true;
edge = edges.splice(i, 1)[0];
let p1 = polygon[polygon.length - 2]; // polygon's second-last point
let p2 = polygon[polygon.length - 1]; // polygon's current end
let p3 = edge[1]; // new point
// Extend polygon end if it's continuing in the same direction
if (p1[0] === p2[0] && // polygon ends vertical
p2[0] === p3[0]) { // new point is vertical, too
polygon[polygon.length - 1][1] = p3[1];
}
else if (p1[1] === p2[1] && // polygon ends horizontal
p2[1] === p3[1]) { // new point is horizontal, too
polygon[polygon.length - 1][0] = p3[0];
}
else {
polygon.push(p3); // new direction
}
break;
}
}
if (!foundEdge)
throw new Error("no next edge found at", edge[1]);
}
while (!pointEquals(polygon[polygon.length - 1], polygon[0]));
// Move polygon's start and end point into a corner
if (polygon[0][0] === polygon[1][0] &&
polygon[polygon.length - 2][0] === polygon[polygon.length - 1][0]) {
// start/end is along a vertical line
polygon.length--;
polygon[0][1] = polygon[polygon.length - 1][1];
}
else if (polygon[0][1] === polygon[1][1] &&
polygon[polygon.length - 2][1] === polygon[polygon.length - 1][1]) {
// start/end is along a horizontal line
polygon.length--;
polygon[0][0] = polygon[polygon.length - 1][0];
}
}
// Repeat until there are no more unused edges
// If two paths touch in at least one point, pick such a point and include one path in the other's sequence of points
for (let i = 0; i < polygons.length; i++) {
const polygon = polygons[i];
for (let j = 0; j < polygon.length; j++) {
const point = polygon[j];
for (let k = i + 1; k < polygons.length; k++) {
const polygon2 = polygons[k];
for (let l = 0; l < polygon2.length - 1; l++) { // exclude end point (same as start)
const point2 = polygon2[l];
if (pointEquals(point, point2)) {
// Embed polygon2 into polygon
if (l > 0) {
// Touching point is not other polygon's start/end
polygon.splice.apply(polygon, [j + 1, 0].concat(
polygon2.slice(1, l + 1)));
}
polygon.splice.apply(polygon, [j + 1, 0].concat(
polygon2.slice(l + 1)));
polygons.splice(k, 1);
k--;
break;
}
}
}
}
}
// Generate SVG path data
let d = "";
for (let i = 0; i < polygons.length; i++) {
const polygon = polygons[i];
d += "M" + polygon[0][0] + "," + polygon[0][1];
for (let j = 1; j < polygon.length; j++) {
if (polygon[j][0] === polygon[j - 1][0])
d += "v" + (polygon[j][1] - polygon[j - 1][1]);
else
d += "h" + (polygon[j][0] - polygon[j - 1][0]);
}
d += "z";
}
let base;
switch (this.shape) {
case 'rect': {
const borderWidth = (this.border ? this.border.width : 0);
const origin = this.border ? borderWidth / 2 : 0;
const width = this.size - borderWidth;
base = `<path fill='${bg}' d='M${origin} ${origin}h${width}v${width}H${origin}z'${this.border ? ` stroke-width='${this.border.width}' stroke='${this.border.color}'` : ''}/>`;
break;
}
case 'circle': {
const borderWidth = (this.border ? this.border.width : 0);
const width = (this.size / 2);
base = `<circle cx='${width}' cy='${width}' r='${width - borderWidth}' fill='${bg}'${this.border ? ` stroke-width='${this.border.width}' stroke='${this.border.color}'` : ''}/>`;
break;
}
default: {
throw new Error(`shape must be rect or circle. ${this.shape} is not allowed`);
}
}
return `<svg xmlns='http://www.w3.org/2000/svg' width='${this.size}' height='${this.size}'>${base}<path d='${d}' fill='${fg}' stroke='${fg}' stroke-width='${stroke}' width='${pixel}' height='${pixel}'/></svg>`
}
getBase64() {
return btoa(this.getDump());
}
}
class Identicon {
constructor(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, 1],
margin: 0.08,
size: 64,
saturation: 0.7,
brightness: 0.5,
shape: 'rect',
border: false
};
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.shape = this.options.shape || this.defaults.shape;
this.border = this.options.border !== undefined ? this.options.border : this.defaults.border;
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
const hue = parseInt(this.hash.substring(this.hash.length - 7), 16) / 0xfffffff;
const saturation = this.options.saturation || this.defaults.saturation;
const brightness = this.options.brightness || this.defaults.brightness;
this.foreground = this.options.foreground || this.hsl2rgb(hue, saturation, brightness).map(Math.round);
}
image() {
return new Svg({
size: this.size,
shape: this.shape,
margin: this.margin,
border: this.border,
foreground: this.foreground,
background: this.background
})
}
render() {
const image = this.image();
const size = this.size;
const pixel = image.pixel;
const margin = Math.floor((size - pixel * 5) / 2);
const bg = image.color.apply(image, this.background);
const 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
for (let i = 0; i < 15; i++) {
const color = parseInt(this.hash.charAt(i), 16) % 2 ? bg : fg;
if (i < 5) {
this.rectangle({
x: 2 * pixel + margin,
y: i * pixel + margin,
w: pixel,
h: pixel,
color,
image
});
} else if (i < 10) {
const y = (i - 5) * pixel + margin;
this.rectangle({
x: 1 * pixel + margin,
y,
w: pixel,
h: pixel,
color,
image
});
this.rectangle({
x: 3 * pixel + margin,
y,
w: pixel,
h: pixel,
color,
image
});
} else if (i < 15) {
const y = (i - 10) * pixel + margin;
this.rectangle({
x: 0 * pixel + margin,
y,
w: pixel,
h: pixel,
color,
image
});
this.rectangle({
x: 4 * pixel + margin,
y,
w: pixel,
h: pixel,
color,
image
});
}
}
return image;
}
rectangle({ x, y, w, h, color, image }={}) {
image.rectangles.push({ x, y, w, h, color });
}
// adapted from: https://gist.github.com/aemkei/1325937
hsl2rgb(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
];
}
toURI() {
return `data:image/svg+xml;base64,${this.render().getBase64()}`
}
toString(raw) {
if (raw) return this.render().getDump();
else return this.render().getBase64();
}
}
exports.Identicon = Identicon;
}));