Greasy Fork is available in English.

svg-identicon

An optimized, SVG only version of identicon.js

Script này sẽ không được không được cài đặt trực tiếp. Nó là một thư viện cho các script khác để bao gồm các chỉ thị meta // @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;
}));