stewartlord / identicon.js Goto Github PK
View Code? Open in Web Editor NEWGitHub-style identicons as PNGs or SVGs in JS
License: BSD 2-Clause "Simplified" License
GitHub-style identicons as PNGs or SVGs in JS
License: BSD 2-Clause "Simplified" License
Is there anyway you can start tagging releases? One of our projects relies on this library and the recent updates have caused our builds to fail unexpectedly after unrelated changes.
Maybe I'm doing something wrong, but as far as I can tell, createHashFromString
returns a base 10 number as a string:
createHashFromString((new Date()).toISOString())
"1925920777"
This ends up not providing enough info digits for random identicon generation. I wonder if https://github.com/emn178/js-sha1 should be used?
Hi could you please register this as bower component? that would be really helpful.
I can create PR and do that for you, but please let me know that you would accept a PR.
Hi @stewartlord,
Do you want to strictly follow github identicon style or are you open to pull requests which adds options to have some different behavior (colors, etc)?
Thanks
After the most recent update (for NPM fixes) the above error now occurs. We are not using a module loading system. It seems the PNGLib is not present on window before the main library tries to use it.
I'm going to use this with React, I think it should be available in npm and included all it's dependencies in package.json
Hi,
It looks the only identicon I can generate is a pink square and I have no idea why:
I can see my hash changing in page code and I have no related error in console:
<script src="/themes/Bootstrap3-custom/js/pnglib.js"></script>
<script src="/themes/Bootstrap3-custom/js/identicon.js"></script>
...
<script>
var hash = "tg";
var data = new Identicon(hash).toString();
document.write('<img src="data:image/png;base64,' + data + '">');
</script>
...
<script>
var hash = "test";
var data = new Identicon(hash).toString();
document.write('<img src="data:image/png;base64,' + data + '">');
</script>
...
<script>
var hash = "maoli";
var data = new Identicon(hash).toString();
document.write('<img src="data:image/png;base64,' + data + '">');
</script>
Did I missed something?
EDIT
Removing the hash (var data = new Identicon().toString();) gives me correct identicons. If I use hash a put whatever value (php or not php generated) I have the pink square.
TL;DR
If you just want some copy-pasta to get 30x performance improvement and escape the Angular CommonJS warning in your project, scroll to the end of the issue.
Hi @stewartlord,
First of all, thank you for this library. We have used it for quite a while now at my place of work to distinguish users by generating default avatars from their UUIDs. It served its purpose very well for a long time, and I must prefix any constructive criticism with my long overdue appreciation!
_______ _ _ _ _ _ __ _____ _____ __ _______
|__ __| | | | /\ | \ | | |/ // ____| | __ \ /\ \ / / ____|
| | | |__| | / \ | \| | ' /| (___ | | | | / \ \ /\ / / | __
| | | __ | / /\ \ | . ` | < \___ \ | | | |/ /\ \ \/ \/ /| | |_ |
| | | | | |/ ____ \| |\ | . \ ____) | | |__| / ____ \ /\ / | |__| |
|_| |_| |_/_/ \_\_| \_|_|\_\_____/ |_____/_/ \_\/ \/ \_____|
It's kind of sad, but like many others, the only reason I even thought to visit this project page is because I finally got sick and tired of Angular yelling at me about CommonJS dependencies. Obviously, a tiny CommonJS library like Identicon.js
is not really an issue, since there is no tree shaking to be done in the first place.
ES module usage would be nice, and so I went ahead in my usual autistic fashion and painstakingly migrated your library to TypeScript with proper class syntax and modern ES module exports...
Then I threw that code away.
While rewriting, I realized that the library does a LOT of jumping around between methods and even class/object boundaries and is kind of hard to reason about, which is a big red flag when it comes to performance because if a programmer has a hard time following the code, the optimizing JIT compiler probably also has a hard time understanding the code and therefore cannot optimize it.
Although I have a long history of being an arrogant a**hole and talking down to people, this is intended to be a constructive issue so I have whipped up some numbers for you and also some copy-pasta code that people could paste into their own projects if you would be so kind as to pin this issue (I am too busy/lazy to go make a proper NPM package right now, and I already spent a couple hours on this).
Now to the numbers. First, here is my testing code that I simply dumped in the middle of a REST API call in my project where I would have easy access to lots of hexadecimal UUIDs for use as seeds. I also peppered in some array copying with intermittent strings to make sure the JavaScript optimizer doesn't try anything fancy (i.e., remove any code) that would mess up the performance test.
let seeds: string[] = [];
// allMembers is a list of membership objects from a REST API in my project, but you could substitute
// some random seed generation code and make a big array of your own seeds if you don't trust me
// and want to test yourself
for (const { id, userID, projectID } of allMembers) {
seeds.push(id, userID, projectID);
}
// Make sure to confuse the JIT lol (the strcmp at the end is a function for sorting alphabetically
seeds = [...seeds, "0adf9940-b117-4e6c-82ad-4a345024621d", ...seeds, ...seeds, "0adf9940-b117-4e6c-82ad-4a345024621d", ...seeds].sort(strcmp);
const identicons: string[] = [];
const start = performance.now();
for (const seed of seeds) {
identicons.push(
// My copy-pasta replacement for your library
genIdenticonSVG(seed, {
margin: 0.2,
size: 64,
background: [255, 255, 255, 255],
}),
// Your library, without any tampering or funny business
// (new Identicon(seed, {
// margin: 0.2,
// size: 64,
// background: [255, 255, 255, 255],
// })).toString(),
);
}
console.log(identicons);
console.log(`Took ${Math.floor(performance.now() - start)}ms to produce ${identicons.length} identicons!`);
Okay, so now that we have the testing code out of the way, let's take a look at the numbers!
With my genIdenticonSVG
function
With the Identicon.js
library
I spam refreshed the memberships table in my app to re-run the API call and thus the test. With my function, the test generally took between 22 and 35 milliseconds. It never reached 40 ms. With the Identicon.js
library, it always took at least 1300 ms. That means over a 30x performance improvement, at least on my computer.
So how did I do it? By simplifying the code in an OCD, straightforward manner. Any time I call the Identicon
constructor, it is never to do anything other than immediately call toString()
--there is no reason to have an Identicon
class in the first place. Browsers put in a great deal of work to optimize object property lookups (even on class-based objects) because technically every JS object must behave like a hashmap and can change at any time. When you put everything into a single function scope with const
number/string variables and whatnot, and put all the loops and code right there next to each other, it becomes
I also store rectangles as arrays/tuples ([x, y, width, height, color]
) because, once again, object properties make JS engines suffer while array indexing is much easier for them to make guarantees about and thus optimize.
Now, there are a couple caveats to my code:
I would recommend a simple function like I have to generate the rectangles, and then have separate SVG and PNG functions which both internally call the function to get the rectangles and then do their thing with them because mixing in the
{ index(); getDump(); toString() }
interface into the business code is part of what hurts the performance so much in the first place because it introduces a bunch of dynamic code into an otherwise low-level function
btoa
without a polyfill because I have the privilege of telling users not to use Internet Explorer and I only use the code in a frontend application so no need for Node supportPNGlib
code (which suffers similar performance problems) up-to-dateAt long last, here is the code. It could still use some further cleanup and perf improvements, but I figure it is good enough for now. If I publish it as a library on NPM in the future, I will probably include PNG support and I will make sure to credit you and the people credited in your code (good on you for those big comments pointing to other programmers!) so that I am contributing to open source rather than stealing.
People can easily make this faster by simply removing the options and hard-coding the values they want to use for margin, saturation, etc. If you do need to use different options with this copy-pasta garbage, you could at least generate a fill-out options
object once and pass that so you don't need to run the default (??
) value code every time you generate an identicon with the exact same options.
// adapted from: https://gist.github.com/aemkei/1325937
function hsl2rgb(h: number, s: number, b: number): [number, number, number] {
h *= 6;
const lookup: readonly number[] = [
b += s *= b < .5 ? b : 1 - b,
b - h % 1 * s * 2,
b -= s *= 2,
b,
b + h % 1 * s,
b + s
];
return[
lookup[ ~~h % 6 ] * 255, // red
lookup[ (h|16) % 6 ] * 255, // green
lookup[ (h|8) % 6 ] * 255 // blue
];
}
/**
* Create a CSS color string, such as "rgba(255, 122, 37, 0.6)" from number inputs. Red,
* green, and blue values will be floored to integers. The alpha value will be normalized
* automatically so you can pass a decimal in the range [0, 1] OR you can pass an integer
* in the range [0, 255].
*/
function cssColor(r: number, g: number, b: number, a: number): string {
const int = Math.floor;
return `rgba(${int(r)},${int(g)},${int(b)},${(a >= 0) && (a <= 255) ? a/255 : 1})`;
}
export interface Options {
/**
* The background color, as an RGBA tuple. The red, green, and blue values
* should be integers in the range [0, 255] while the alpha (last number)
* can also be an integer in that range or can be normalized to the [0, 1]
* range (in which case it would be a decimal).
*/
background?: [number, number, number, number];
/**
* Decimal margin to determine spacing between rectangles. Defaults to 0.08.
*/
margin?: number;
/**
* Width and height, in pixels, of the generated SVG viewport. Defaults to 64.
*/
size?: number;
/**
* Do you like color? I like color. Defaults to 0.7.
*/
saturation?: number;
/**
* How bright should the computed foreground color be? Defaults to 0.5.
*/
brightness?: number;
}
/**
* This code was produced by simplifying/optimizing the code from Identicon.js 2.3.3
* http://github.com/stewartlord/identicon.js
*
* Copyright 2018, Stewart Lord <https://github.com/stewartlord>
* Released under the BSD license
* http://www.opensource.org/licenses/bsd-license.php
*
* Also credit to me (Sam Claus <https://github.com/samclaus>) for the rewrite/optimization.
*/
export function genIdenticonSVG(hash: string, options: Partial<Options> = {}): string {
if (hash.length < 15) {
throw new Error("hash for identicon must be at least 15 characters");
}
const bg = cssColor(...(options.background ?? [240, 240, 240, 255]));
const size = options.size ?? 64;
const baseMargin = Math.floor(size * (options.margin ?? 0.08));
const saturation = options.saturation ?? 0.7;
const brightness = options.brightness ?? 0.5;
// foreground defaults to last 7 chars as hue at 70% saturation, 50% brightness
const hue = parseInt(hash.substr(-7), 16) / 0xfffffff;
const fg = cssColor(...hsl2rgb(hue, saturation, brightness), 255);
const cell = Math.floor((size - (baseMargin * 2)) / 5);
const margin = Math.floor((size - cell * 5) / 2);
// X, Y, width, height, color for each rectangle
const rectangles: [number, number, number, number, string][] = [];
// 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(hash.charAt(i), 16) % 2 ? bg : fg;
if (i < 5) {
rectangles.push([2 * cell + margin, i * cell + margin, cell, cell, color]);
} else if (i < 10) {
rectangles.push([1 * cell + margin, (i - 5) * cell + margin, cell, cell, color]);
rectangles.push([3 * cell + margin, (i - 5) * cell + margin, cell, cell, color]);
} else if (i < 15) {
rectangles.push([0 * cell + margin, (i - 10) * cell + margin, cell, cell, color]);
rectangles.push([4 * cell + margin, (i - 10) * cell + margin, cell, cell, color]);
}
}
const stroke = size * 0.005;
let xml = `<svg xmlns='http://www.w3.org/2000/svg' width='${size}' height='${size}' style='background-color:${bg};'><g style='fill:${fg}; stroke:${fg}; stroke-width:${stroke};'>`;
for (const [x, y, w, h, color] of rectangles) {
if (color !== bg) {
xml += `<rect x='${x}' y='${y}' width='${w}' height='${h}'/>`;
}
}
xml += "</g></svg>"
return btoa(xml);
}
Bit by some bad data (totally my fault), what was returned from the API was not a string and this line ended up crashing the app out. If a throw is present, means users have to wrap any call to this API in a try-catch; this is not explicitly mentioned anywhere. Likely best to avoid identicon generation failures generating 500/server errors, if it was a critical portion of an app, then may be a different conclusion.
What are thoughts around generating the hash if it is not present and extending the string to 15 chars if length < 15? More defensive approach.
if (typeof(hash) !== 'string' || hash.length < 15) {
throw 'A hash of at least 15 characters is required.';
}
This project is largely unmaintained. If anyone would like to support a fork I would be happy to link to it from the README on this repo or otherwise handoff maintenance.
This is a very minor issue, which depending on the change could be breaking. However I figured I'd at least document it in the issues list.
The parameters for the library are saturation
and brightness
with the hue being adjusted. The issue is that HSL (Hue, Saturation, Lightness) is being used not HSB/HSV (Hue, Saturation, Brightness/Value) (HSV == HSB, HSV != HSL, HSB != HSL).
Therefore I suggest renaming the brightness
parameter to lightness
.
The reason I ran into this issue was I was porting the colour extraction algorithm to python. I wasn't sure if HSL was being used or HSB. As the function call was hsl(h,s,b)
with a note stating that it had been adapted from else where (Maybe it was adapted to HSB but they forgot to rename etc).
To make matters worst python doesn't have a built-in HSB function, it does have one for HSV (Which is the same thing). And to add to that the python function is for HLS as opposed HSL.
So far the above comments revolve around it being confusing to read the code / port. However It also has a real impact on users of the library. I may have selected a saturation and brightness value from photoshop or http://colorizer.org, that I like. However the library is actually using HSL not HSB/HSV therefore I would be getting different colours than I expected.
I can raise PR if requested, but like I said, I just wanted to document this incase somebody else ran into it.
Margin settings are not being parsed correctly. Assume it is because 0
is evaluated as a falsey value thus letting the .2
default take over, 0.000001
does indeed work.
{
size: 40,
margin: 0,
background: [250, 250, 250, 255],
format: 'svg'
}
By default it is a 5x5 "pixel" grid. How do you increase it to, say 7x7 ?
$ npm install identicon --save
> [email protected] install /Users/anon/Desktop/dev/pholder/node_modules/canvas
> node-gyp rebuild
SOLINK_MODULE(target) Release/canvas-postbuild.node
CXX(target) Release/obj.target/canvas/src/Canvas.o
In file included from ../src/Canvas.cc:20:
../src/JPEGStream.h:10:10: fatal error: 'jpeglib.h' file not found
#include <jpeglib.h>
^
1 error generated.
make: *** [Release/obj.target/canvas/src/Canvas.o] Error 1
gyp ERR! build error
Hi @stewartlord ,
Thanks for the handy library, any chance you can make it importable as an ECMAScript module? Angular, for example, complains about CommonJS or AMD dependencies:
[1] WARNING in /Users/nunoarruda/repos/Pulse-Angular/src/app/profiles/profile-list/profile-list.component.ts depends on 'identicon.js'. CommonJS or AMD dependencies can cause optimization bailouts.
[1] For more info see: https://angular.io/guide/build#configuring-commonjs-dependencies
Here's why: https://angular.io/guide/build#configuring-commonjs-dependencies
The Identicon.toString()
method is synchronous.
Generating an image in node may be an expensive action. This may lead to blocking code.
Is there any performance issue when generating images of size i.e. 512*512px? Does any benchmark exist?
I wonder what would be the performance impact if the image was generated on a server dynamically on every request.
I hope the question is clear and good enough for GitHub issues. Otherwise please kindly ask or redirect me to a proper place.
I've been trying to generate an identicon, passing [255, 69, 0, 1]
as the rgba color (it's #ff4500
), but the produced identicon files don't have any identicon. I tried with black as per the documentation, and it worked.
Thanks in advance! And thanks for a great library ๐
I use the same example code from identicon.js' homepage:
// set up options
var hash = "c157a79031e1c40f85931829bc5fc552"; // 15+ hex chars
var options = {
foreground: [0, 0, 0, 255], // rgba black
background: [255, 255, 255, 255], // rgba white
margin: 0.2, // 20% margin
size: 420, // 420px square
format: 'svg' // use SVG instead of PNG
};
// create a base64 encoded SVG
var data = new Identicon(hash, options).toString();
console.log(data);
It works fine in the browser like this:
PHN2ZyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHdpZHRoPSc0MjAnIGhlaWdodD0nNDIwJyBzdHlsZT0nYmFja2dyb3VuZC1jb2xvcjpyZ2JhKDI1NSwyNTUsMjU1LDEpOyc+PGcgc3R5bGU9J2ZpbGw6cmdiYSgwLDAsMCwxKTsgc3Ryb2tlOnJnYmEoMCwwLDAsMSk7IHN0cm9rZS13aWR0aDoyLjE7Jz48cmVjdCAgeD0nMTg1JyB5PSc4NScgd2lkdGg9JzUwJyBoZWlnaHQ9JzUwJy8+PHJlY3QgIHg9JzE4NScgeT0nMjg1JyB3aWR0aD0nNTAnIGhlaWdodD0nNTAnLz48cmVjdCAgeD0nMTM1JyB5PScxODUnIHdpZHRoPSc1MCcgaGVpZ2h0PSc1MCcvPjxyZWN0ICB4PScyMzUnIHk9JzE4NScgd2lkdGg9JzUwJyBoZWlnaHQ9JzUwJy8+PHJlY3QgIHg9Jzg1JyB5PSc4NScgd2lkdGg9JzUwJyBoZWlnaHQ9JzUwJy8+PHJlY3QgIHg9JzI4NScgeT0nODUnIHdpZHRoPSc1MCcgaGVpZ2h0PSc1MCcvPjxyZWN0ICB4PSc4NScgeT0nMTg1JyB3aWR0aD0nNTAnIGhlaWdodD0nNTAnLz48cmVjdCAgeD0nMjg1JyB5PScxODUnIHdpZHRoPSc1MCcgaGVpZ2h0PSc1MCcvPjxyZWN0ICB4PSc4NScgeT0nMjM1JyB3aWR0aD0nNTAnIGhlaWdodD0nNTAnLz48cmVjdCAgeD0nMjg1JyB5PScyMzUnIHdpZHRoPSc1MCcgaGVpZ2h0PSc1MCcvPjxyZWN0ICB4PSc4NScgeT0nMjg1JyB3aWR0aD0nNTAnIGhlaWdodD0nNTAnLz48cmVjdCAgeD0nMjg1JyB5PScyODUnIHdpZHRoPSc1MCcgaGVpZ2h0PSc1MCcvPjwvZz48L3N2Zz4=
but, emm..... Unexpected results on the server side like this:
iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAMAAAC67D+PAAADAFBMVEXw8PDYACYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADByinnAAABAHRSTlP//wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKmfXxgAAAHlJREFUeNoBbgCR/wAAAAEBAQEBAQAAAAAAAQEBAQEBAAAAAQEBAQEBAQEBAQABAQEBAQEBAQEBAAEBAQEAAAEBAQEAAQEBAQAAAQEBAQAAAAAAAQEAAAAAAAAAAAABAQAAAAAAAQEBAQAAAQEBAQABAQEBAAABAQEBD7oARe51XwsAAAAASUVORK5CYII=
What happend?
Description is out of date:
"description": "GitHub-style identicons in JS with no server-side processing.",
This shows up as a big banner on NPM and in search results which could lead people not to use the package (states no SSR but there is SSR). See here https://www.npmjs.com/package/identicon.js.
@stewartlord Just looking to cut down on memory footprint here, is there any way to not have a base64 version for SVG? According to Chris Coyier here: https://css-tricks.com/probably-dont-base64-svg/ we don't need to base64 svg, unnecessarily comes out 133% bigger.
Is it currently possible to generate just SVG? Tried browsing around the source and using .render() instead of .toString()
-- since I see .toString()
is just .render().getBase64()
-- but that did not work.
Creating PNG images may not be such an up-to-date implementation. Do you know an implementation, which e.g. uses SVG or HTML5 canvas to show the image?
Or would you always choose this PNG implementation? If so, why?
...or when inputted too fast :)
image-rendering: pixelated;
in the examples will make png scaling look crisp even when it's sized up.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.