/** * Jdenticon 3.2.0 * http://jdenticon.com * * Built: 2022-08-07T11:23:11.640Z * * MIT License * * Copyright (c) 2014-2021 Daniel Mester Pirttijärvi * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ (()=>{ /** * Parses a substring of the hash as a number. * @param {number} startPosition * @param {number=} octets */ class HSLColor { constructor(h, s, l) { this.h = h; this.s = s; this.l = l; // this.a = a; } toString() { const h = this.h, s = this.s, l = this.l/*, a = this.a*/, hex = v => { v = between(0, 255, v * 255).toString(16); return v.slice(0,2).padStart(2, "0"); }; let r=l, g=l, b=l; if (0 != s) { let q=l<0.5?l*(s+1):l+s-l*s, p=l*2-q, hue2rgb = h => { h += h<0 ? 1 : (h>1 ? -1 : 0); return (h*6<1) ? (p+(q-p)*h*6) : ((h*2<1) ? q : ((h*3<2) ? p+(q-p)*(2/3-h)*6 : p)); }; r = hue2rgb(h+1/3); g = hue2rgb(h); b = hue2rgb(h-1/3); } return '#' + hex(r) + hex(g) + hex(b) // isNaN(a)?100:a*100); } } const between = (l, h, v) => Math.max(l, Math.min(h, v)), parseHex = (hash, startPosition, octets) => parseInt(hash.substr(startPosition, octets), 16), /** * Converts an HSL color to a hexadecimal RGB color. This function will correct the lightness for the "dark" hues * @param {number} hue Hue in range [0, 1] * @param {number} saturation Saturation in range [0, 1] * @param {number} lightness Lightness in range [0, 1] * @returns {string} */ correctedHsl = (hue, saturation, lightness) => { // The corrector specifies the perceived middle lightness for each hue var correctors = [ 0.55, 0.5, 0.5, 0.46, 0.6, 0.55, 0.55 ], corrector = correctors[(hue * 6 + 0.5) | 0]; // Adjust the input lightness relative to the corrector lightness = lightness < 0.5 ? lightness * corrector * 2 : corrector + (lightness - 0.5) * (1 - corrector) * 2; return new HSLColor(hue, saturation, lightness).toString(); }; /** * Represents a point. */ class Point { constructor(x, y) { this.x = x; this.y = y; } } /** * Translates and rotates a point before being passed on to the canvas context. This was previously done by the canvas context itself, * but this caused a rendering issue in Chrome on sizes > 256 where the rotation transformation of inverted paths was not done properly. */ class Transform { constructor(x, y, size, rotation) { this.u/*_x*/ = x; this.v/*_y*/ = y; this.K/*_size*/ = size; this.Z/*_rotation*/ = rotation; } /** * Transforms the specified point based on the translation and rotation specification for this Transform. * @param {number} x x-coordinate * @param {number} y y-coordinate * @param {number=} w The width of the transformed rectangle. If greater than 0, this will ensure the returned point is of the upper left corner of the transformed rectangle. * @param {number=} h The height of the transformed rectangle. If greater than 0, this will ensure the returned point is of the upper left corner of the transformed rectangle. */ L/*transformIconPoint*/(x, y, w, h) { var right = this.u/*_x*/ + this.K/*_size*/, bottom = this.v/*_y*/ + this.K/*_size*/, rotation = this.Z/*_rotation*/; return rotation === 1 ? new Point(right - y - (h || 0), this.v/*_y*/ + x) : rotation === 2 ? new Point(right - x - (w || 0), bottom - y - (h || 0)) : rotation === 3 ? new Point(this.u/*_x*/ + y, bottom - x - (w || 0)) : new Point(this.u/*_x*/ + x, this.v/*_y*/ + y); } } var NO_TRANSFORM = new Transform(0, 0, 0, 0); /** * Provides helper functions for rendering common basic shapes. */ class Graphics { constructor(renderer) { /** * @type {Renderer} * @private */ this.M/*_renderer*/ = renderer; /** * @type {Transform} */ this.A/*currentTransform*/ = NO_TRANSFORM; } /** * Adds a polygon to the underlying renderer. * @param {Array} points The points of the polygon clockwise on the format [ x0, y0, x1, y1, ..., xn, yn ] * @param {boolean=} invert Specifies if the polygon will be inverted. */ g/*addPolygon*/(points, invert) { var this$1 = this; var di = invert ? -2 : 2, transformedPoints = []; for (var i = invert ? points.length - 2 : 0; i < points.length && i >= 0; i += di) { transformedPoints.push(this$1.A/*currentTransform*/.L/*transformIconPoint*/(points[i], points[i + 1])); } this.M/*_renderer*/.g/*addPolygon*/(transformedPoints); } /** * Adds a polygon to the underlying renderer. * Source: http://stackoverflow.com/a/2173084 * @param {number} x The x-coordinate of the upper left corner of the rectangle holding the entire ellipse. * @param {number} y The y-coordinate of the upper left corner of the rectangle holding the entire ellipse. * @param {number} size The size of the ellipse. * @param {boolean=} invert Specifies if the ellipse will be inverted. */ h/*addCircle*/(x, y, size, invert) { var p = this.A/*currentTransform*/.L/*transformIconPoint*/(x, y, size, size); this.M/*_renderer*/.h/*addCircle*/(p, size, invert); } /** * Adds a rectangle to the underlying renderer. * @param {number} x The x-coordinate of the upper left corner of the rectangle. * @param {number} y The y-coordinate of the upper left corner of the rectangle. * @param {number} w The width of the rectangle. * @param {number} h The height of the rectangle. * @param {boolean=} invert Specifies if the rectangle will be inverted. */ i/*addRectangle*/(x, y, w, h, invert) { this.g/*addPolygon*/([ x, y, x + w, y, x + w, y + h, x, y + h ], invert); } /** * Adds a right triangle to the underlying renderer. * @param {number} x The x-coordinate of the upper left corner of the rectangle holding the triangle. * @param {number} y The y-coordinate of the upper left corner of the rectangle holding the triangle. * @param {number} w The width of the triangle. * @param {number} h The height of the triangle. * @param {number} r The rotation of the triangle (clockwise). 0 = right corner of the triangle in the lower left corner of the bounding rectangle. * @param {boolean=} invert Specifies if the triangle will be inverted. */ j/*addTriangle*/(x, y, w, h, r, invert) { var points = [ x + w, y, x + w, y + h, x, y + h, x, y ]; points.splice(((r || 0) % 4) * 2, 2); this.g/*addPolygon*/(points, invert); } /** * Adds a rhombus to the underlying renderer. * @param {number} x The x-coordinate of the upper left corner of the rectangle holding the rhombus. * @param {number} y The y-coordinate of the upper left corner of the rectangle holding the rhombus. * @param {number} w The width of the rhombus. * @param {number} h The height of the rhombus. * @param {boolean=} invert Specifies if the rhombus will be inverted. */ N/*addRhombus*/(x, y, w, h, invert) { this.g/*addPolygon*/([ x + w / 2, y, x + w, y + h / 2, x + w / 2, y + h, x, y + h / 2 ], invert); } } /** * @param {number} index * @param {Graphics} g * @param {number} cell * @param {number} positionIndex */ function centerShape(index, g, cell, positionIndex) { index = index % 14; var k, m, w, h, inner, outer; !index ? ( k = cell * 0.42, g.g/*addPolygon*/([ 0, 0, cell, 0, cell, cell - k * 2, cell - k, cell, 0, cell ])) : index == 1 ? ( w = 0 | (cell * 0.5), h = 0 | (cell * 0.8), g.j/*addTriangle*/(cell - w, 0, w, h, 2)) : index == 2 ? ( w = 0 | (cell / 3), g.i/*addRectangle*/(w, w, cell - w, cell - w)) : index == 3 ? ( inner = cell * 0.1, // Use fixed outer border widths in small icons to ensure the border is drawn outer = cell < 6 ? 1 : cell < 8 ? 2 : (0 | (cell * 0.25)), inner = inner > 1 ? (0 | inner) : // large icon => truncate decimals inner > 0.5 ? 1 : // medium size icon => fixed width inner, // small icon => anti-aliased border g.i/*addRectangle*/(outer, outer, cell - inner - outer, cell - inner - outer)) : index == 4 ? ( m = 0 | (cell * 0.15), w = 0 | (cell * 0.5), g.h/*addCircle*/(cell - w - m, cell - w - m, w)) : index == 5 ? ( inner = cell * 0.1, outer = inner * 4, // Align edge to nearest pixel in large icons outer > 3 && (outer = 0 | outer), g.i/*addRectangle*/(0, 0, cell, cell), g.g/*addPolygon*/([ outer, outer, cell - inner, outer, outer + (cell - outer - inner) / 2, cell - inner ], true)) : index == 6 ? g.g/*addPolygon*/([ 0, 0, cell, 0, cell, cell * 0.7, cell * 0.4, cell * 0.4, cell * 0.7, cell, 0, cell ]) : index == 7 ? g.j/*addTriangle*/(cell / 2, cell / 2, cell / 2, cell / 2, 3) : index == 8 ? ( g.i/*addRectangle*/(0, 0, cell, cell / 2), g.i/*addRectangle*/(0, cell / 2, cell / 2, cell / 2), g.j/*addTriangle*/(cell / 2, cell / 2, cell / 2, cell / 2, 1)) : index == 9 ? ( inner = cell * 0.14, // Use fixed outer border widths in small icons to ensure the border is drawn outer = cell < 4 ? 1 : cell < 6 ? 2 : (0 | (cell * 0.35)), inner = cell < 8 ? inner : // small icon => anti-aliased border (0 | inner), // large icon => truncate decimals g.i/*addRectangle*/(0, 0, cell, cell), g.i/*addRectangle*/(outer, outer, cell - outer - inner, cell - outer - inner, true)) : index == 10 ? ( inner = cell * 0.12, outer = inner * 3, g.i/*addRectangle*/(0, 0, cell, cell), g.h/*addCircle*/(outer, outer, cell - inner - outer, true)) : index == 11 ? g.j/*addTriangle*/(cell / 2, cell / 2, cell / 2, cell / 2, 3) : index == 12 ? ( m = cell * 0.25, g.i/*addRectangle*/(0, 0, cell, cell), g.N/*addRhombus*/(m, m, cell - m, cell - m, true)) : // 13 ( !positionIndex && ( m = cell * 0.4, w = cell * 1.2, g.h/*addCircle*/(m, m, w) ) ); } /** * @param {number} index * @param {Graphics} g * @param {number} cell */ function outerShape(index, g, cell) { index = index % 4; var m; !index ? g.j/*addTriangle*/(0, 0, cell, cell, 0) : index == 1 ? g.j/*addTriangle*/(0, cell / 2, cell, cell / 2, 0) : index == 2 ? g.N/*addRhombus*/(0, 0, cell, cell) : // 3 ( m = cell / 6, g.h/*addCircle*/(m, m, cell - 2 * m) ); } /** * Prepares a measure to be used as a measure in an SVG path, by * rounding the measure to a single decimal. This reduces the file * size of the generated SVG with more than 50% in some cases. */ function svgValue(value) { return ((value * 10 + 0.5) | 0) / 10; } /** * Represents an SVG path element. */ class SvgPath { constructor() { /** * This property holds the data string (path.d) of the SVG path. * @type {string} */ this.B/*dataString*/ = ""; } /** * Adds a polygon with the current fill color to the SVG path. * @param points An array of Point objects. */ g/*addPolygon*/(points) { var dataString = ""; for (var i = 0; i < points.length; i++) { dataString += (i ? "L" : "M") + svgValue(points[i].x) + " " + svgValue(points[i].y); } this.B/*dataString*/ += dataString + "Z"; } /** * Adds a circle with the current fill color to the SVG path. * @param {Point} point The upper left corner of the circle bounding box. * @param {number} diameter The diameter of the circle. * @param {boolean} counterClockwise True if the circle is drawn counter-clockwise (will result in a hole if rendered on a clockwise path). */ h/*addCircle*/(point, diameter, counterClockwise) { var sweepFlag = counterClockwise ? 0 : 1, svgRadius = svgValue(diameter / 2), svgDiameter = svgValue(diameter), svgArc = "a" + svgRadius + "," + svgRadius + " 0 1," + sweepFlag + " "; this.B/*dataString*/ += "M" + svgValue(point.x) + " " + svgValue(point.y + diameter / 2) + svgArc + svgDiameter + ",0" + svgArc + (-svgDiameter) + ",0"; } } /** * Renderer producing SVG output. * @implements {Renderer} */ class SvgRenderer { constructor(target) { /** * @type {SvgPath} * @private */ this.C/*_path*/; /** * @type {Object.} * @private */ this.D/*_pathsByColor*/ = { }; /** * @type {SvgElement|SvgWriter} * @private */ this.R/*_target*/ = target; /** * @type {number} */ this.k/*iconSize*/ = target.k/*iconSize*/; } /** * Fills the background with the specified color. * @param {string} fillColor Fill color on the format #rrggbb[aa]. */ m/*setBackground*/(fillColor) { var match = /^(#......)(..)?/.exec(fillColor), opacity = match[2] ? parseHex(match[2], 0) / 255 : 1; this.R/*_target*/.m/*setBackground*/(match[1], opacity); } /** * Marks the beginning of a new shape of the specified color. Should be ended with a call to endShape. * @param {string} color Fill color on format #xxxxxx. */ O/*beginShape*/(color) { this.C/*_path*/ = this.D/*_pathsByColor*/[color] || (this.D/*_pathsByColor*/[color] = new SvgPath()); } /** * Marks the end of the currently drawn shape. */ P/*endShape*/() { } /** * Adds a polygon with the current fill color to the SVG. * @param points An array of Point objects. */ g/*addPolygon*/(points) { this.C/*_path*/.g/*addPolygon*/(points); } /** * Adds a circle with the current fill color to the SVG. * @param {Point} point The upper left corner of the circle bounding box. * @param {number} diameter The diameter of the circle. * @param {boolean} counterClockwise True if the circle is drawn counter-clockwise (will result in a hole if rendered on a clockwise path). */ h/*addCircle*/(point, diameter, counterClockwise) { this.C/*_path*/.h/*addCircle*/(point, diameter, counterClockwise); } /** * Called when the icon has been completely drawn. */ finish() { var pathsByColor = this.D/*_pathsByColor*/; for (var color in pathsByColor) { // hasOwnProperty cannot be shadowed in pathsByColor // eslint-disable-next-line no-prototype-builtins if (pathsByColor.hasOwnProperty(color)) { this.R/*_target*/.S/*appendPath*/(color, pathsByColor[color].B/*dataString*/); } } } } /** * Renderer producing SVG output. */ class SvgWriter { constructor(iconSize) { /** * @type {number} */ this.k/*iconSize*/ = iconSize; /** * @type {string} * @private */ this.F = ''; } /** * Fills the background with the specified color. * @param {string} fillColor Fill color on the format #rrggbb. * @param {number} opacity Opacity in the range [0.0, 1.0]. */ m/*setBackground*/(fillColor, opacity) { if (opacity) { this.F += ''; } } /** * Writes a path to the SVG string. * @param {string} color Fill color on format #rrggbb. * @param {string} dataString The SVG path data string. */ S/*appendPath*/(color, dataString) { this.F += ''; } /** * Gets the rendered image as an SVG string. */ toString() { return this.F + ""; } } /** * Draws an identicon as an SVG string. * @param {*} hashOrValue - A hexadecimal hash string or any value that will be hashed by Jdenticon. * @returns {string} SVG string */ window.identiconSvg = hash => { const writer = new SvgWriter(50), renderer = new SvgRenderer(writer), config = { p/*colorSaturation*/: 0.5, H/*grayscaleSaturation*/: 0, q/*colorLightness*/: value => between(0, 1, 0.4 + value * (0.8 - 0.4)), I/*grayscaleLightness*/: value => between(0, 1, 0.3 + value * (0.9 - 0.3)) }; var index; // Calculate padding and round to nearest integer var size = renderer.k/*iconSize*/; var padding = (0.5 + size * 0.08) | 0; size -= padding * 2; const graphics = new Graphics(renderer), // Calculate cell size and ensure it is an integer cell = 0 | (size / 4), // Since the cell size is integer based, the actual icon will be slightly smaller than specified => center icon s = 0 | (padding + size / 2 - cell * 2), // AVAILABLE COLORS hue = parseHex(hash, -7) / 0xfffffff, // Available colors for this icon availableColors = [ // Dark gray correctedHsl(hue, config.H/*grayscaleSaturation*/, config.I/*grayscaleLightness*/(0)), // Mid color correctedHsl(hue, config.p/*colorSaturation*/, config.q/*colorLightness*/(0.5)), // Light gray correctedHsl(hue, config.H/*grayscaleSaturation*/, config.I/*grayscaleLightness*/(1)), // Light color correctedHsl(hue, config.p/*colorSaturation*/, config.q/*colorLightness*/(1)), // Dark color correctedHsl(hue, config.p/*colorSaturation*/, config.q/*colorLightness*/(0)) ], // The index of the selected colors selectedColorIndexes = [], isDuplicate = values => { if (values.indexOf(index) >= 0) { for (var i = 0; i < values.length; i++) { if (selectedColorIndexes.indexOf(values[i]) >= 0) { return true; } } } }, renderShape = (colorIndex, shapes, index, rotationIndex, positions) => { var shapeIndex = parseHex(hash, index, 1); var r = rotationIndex ? parseHex(hash, rotationIndex, 1) : 0; renderer.O/*beginShape*/(availableColors[selectedColorIndexes[colorIndex]]); for (var i = 0; i < positions.length; i++) { graphics.A/*currentTransform*/ = new Transform(s + positions[i][0] * cell, s + positions[i][1] * cell, cell, r++ % 4); shapes(shapeIndex, graphics, cell, i); } renderer.P/*endShape*/(); }; for (var i = 0; i < 3; i++) { index = parseHex(hash, 8 + i, 1) % availableColors.length; if (isDuplicate([0, 4]) || // Disallow dark gray and dark color combo isDuplicate([2, 3])) { // Disallow light gray and light color combo index = 1; } selectedColorIndexes.push(index); } // ACTUAL RENDERING // Sides renderShape(0, outerShape, 2, 3, [[1, 0], [2, 0], [2, 3], [1, 3], [0, 1], [3, 1], [3, 2], [0, 2]]); // Corners renderShape(1, outerShape, 4, 5, [[0, 0], [3, 0], [3, 3], [0, 3]]); // Center renderShape(2, centerShape, 1, null, [[1, 1], [2, 1], [2, 2], [1, 2]]); renderer.finish(); return writer; }; })();