Skip to content

Commit

Permalink
Custom hash function parameter
Browse files Browse the repository at this point in the history
  • Loading branch information
laurentpayot committed Jun 12, 2023
1 parent ec4fc50 commit 7379901
Show file tree
Hide file tree
Showing 10 changed files with 68 additions and 20 deletions.
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
# Minidenticons changelog

# 4.2.0

_2023-06-12_

### New features

- Added a custom hash function optional parameter to the `minidenticon()` function. The included custom element does not use this parameter.

# 4.1.0

Expand Down
6 changes: 4 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ Minidenticons uses [ES modules](https://jakearchibald.com/2017/es-modules-in-bro

```html
<script type="module">
import { minidenticonSvg } from 'https://cdn.jsdelivr.net/npm/minidenticons@4.1.0/minidenticons.min.js'
import { minidenticonSvg } from 'https://cdn.jsdelivr.net/npm/minidenticons@4.2.0/minidenticons.min.js'
</script>
```

Expand Down Expand Up @@ -94,13 +94,15 @@ Play with [the demo](https://laurentpayot.github.io/minidenticons/) to find a co
Instead of using the custom element, you can also use the `minidenticon()` function to generate SVG strings in your client (or your server).

```typescript
minidenticon(seed: string, saturation?: number|string, lightness?: number|string): string
minidenticon(seed: string, saturation?: number|string, lightness?: number|string, hashFn?: (str: string) => number): string
```

The `minidenticon()` function will return a SVG string generated from its seed string argument. The seed argument can be a username, but actually any string used as an identifier.

Optional saturation and lightness arguments should be percentages; that is, numbers (or strings) between 0 and 100.

If you need to, you can use your own hash function as argument of the fourth parameter (optional). Your custom hash function must take a string and return a number. The last 15 bits of the integer part of the hash will be used to draw the squares. The included custom element does not use this last parameter.

Note that the `minidenticon()` function itself is *not* memoized.

### NodeJS
Expand Down
2 changes: 1 addition & 1 deletion index.d.ts
Original file line number Diff line number Diff line change
@@ -1 +1 @@
export function minidenticon(seed: string, saturation?: number|string, lightness?: number|string): string
export function minidenticon(seed: string, saturation?: number|string, lightness?: number|string, hashFn?: (str: string) => number): string
4 changes: 2 additions & 2 deletions minidenticons.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ function simpleHash(str) {
/**
* @type {import('.').minidenticon}
*/
export function minidenticon(seed="", saturation=DEFAULT_SATURATION, lightness=DEFAULT_LIGHTNESS) {
const hash = simpleHash(seed)
export function minidenticon(seed="", saturation=DEFAULT_SATURATION, lightness=DEFAULT_LIGHTNESS, hashFn=simpleHash) {
const hash = hashFn(seed)
// console.log("%c" + hash.toString(2).padStart(32, "0"), "font-family:monospace") // uncomment to debug
const hue = (hash % COLORS_NB) * (360 / COLORS_NB)
return [...Array(seed ? 25 : 0)].reduce((acc, e, i) =>
Expand Down
2 changes: 1 addition & 1 deletion minidenticons.min.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion minidenticons.min.js.map

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion no-custom-element.min.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "minidenticons",
"version": "4.1.0",
"version": "4.2.0",
"description": "Super lightweight SVG identicon (icon avatar) generator",
"author": "Laurent Payot",
"license": "MIT",
Expand Down Expand Up @@ -58,6 +58,6 @@
"serve": "python3 -m http.server"
},
"devDependencies": {
"terser": "^5.17.7"
"terser": "5.16.2"
}
}
14 changes: 9 additions & 5 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

45 changes: 40 additions & 5 deletions tests.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,53 @@ import { minidenticon as minidenticon_NO_CE } from './no-custom-element.min.js'

const COLLISION_TESTS_NUMBER = 10_000


function alternateTerribleHash(str) {
return str.split('')
.reduce((hash, char) => hash + char.charCodeAt(0), 123456)
}

console.time("\nTests duration")

// minidenticon tests

new Array(minidenticon_CE, minidenticon_NO_CE).forEach(minidenticon => {

// non-integer hue normal for empty string
assert.equal(minidenticon(""), '<svg viewBox="-1.5 -1.5 8 8" xmlns="http://www.w3.org/2000/svg" fill="hsl(40 95% 45%)"></svg>')
assert.equal(minidenticon("foo"), '<svg viewBox="-1.5 -1.5 8 8" xmlns="http://www.w3.org/2000/svg" fill="hsl(120 95% 45%)"><rect x="0" y="0" width="1" height="1"/><rect x="1" y="0" width="1" height="1"/><rect x="1" y="4" width="1" height="1"/><rect x="2" y="0" width="1" height="1"/><rect x="2" y="2" width="1" height="1"/><rect x="2" y="3" width="1" height="1"/><rect x="2" y="4" width="1" height="1"/><rect x="4" y="0" width="1" height="1"/><rect x="3" y="0" width="1" height="1"/><rect x="3" y="4" width="1" height="1"/></svg>')
assert.equal(minidenticon("foo", 75), '<svg viewBox="-1.5 -1.5 8 8" xmlns="http://www.w3.org/2000/svg" fill="hsl(120 75% 45%)"><rect x="0" y="0" width="1" height="1"/><rect x="1" y="0" width="1" height="1"/><rect x="1" y="4" width="1" height="1"/><rect x="2" y="0" width="1" height="1"/><rect x="2" y="2" width="1" height="1"/><rect x="2" y="3" width="1" height="1"/><rect x="2" y="4" width="1" height="1"/><rect x="4" y="0" width="1" height="1"/><rect x="3" y="0" width="1" height="1"/><rect x="3" y="4" width="1" height="1"/></svg>')
assert.equal(minidenticon("foo", undefined, 75), '<svg viewBox="-1.5 -1.5 8 8" xmlns="http://www.w3.org/2000/svg" fill="hsl(120 95% 75%)"><rect x="0" y="0" width="1" height="1"/><rect x="1" y="0" width="1" height="1"/><rect x="1" y="4" width="1" height="1"/><rect x="2" y="0" width="1" height="1"/><rect x="2" y="2" width="1" height="1"/><rect x="2" y="3" width="1" height="1"/><rect x="2" y="4" width="1" height="1"/><rect x="4" y="0" width="1" height="1"/><rect x="3" y="0" width="1" height="1"/><rect x="3" y="4" width="1" height="1"/></svg>')
assert.equal(minidenticon("laurent"), '<svg viewBox="-1.5 -1.5 8 8" xmlns="http://www.w3.org/2000/svg" fill="hsl(160 95% 45%)"><rect x="0" y="0" width="1" height="1"/><rect x="0" y="2" width="1" height="1"/><rect x="0" y="3" width="1" height="1"/><rect x="1" y="1" width="1" height="1"/><rect x="2" y="2" width="1" height="1"/><rect x="2" y="3" width="1" height="1"/><rect x="4" y="0" width="1" height="1"/><rect x="4" y="2" width="1" height="1"/><rect x="4" y="3" width="1" height="1"/><rect x="3" y="1" width="1" height="1"/></svg>')
assert.equal(
minidenticon(""),
'<svg viewBox="-1.5 -1.5 8 8" xmlns="http://www.w3.org/2000/svg" fill="hsl(40 95% 45%)"></svg>'
)

assert.equal(
minidenticon("laurent"),
'<svg viewBox="-1.5 -1.5 8 8" xmlns="http://www.w3.org/2000/svg" fill="hsl(160 95% 45%)"><rect x="0" y="0" width="1" height="1"/><rect x="0" y="2" width="1" height="1"/><rect x="0" y="3" width="1" height="1"/><rect x="1" y="1" width="1" height="1"/><rect x="2" y="2" width="1" height="1"/><rect x="2" y="3" width="1" height="1"/><rect x="4" y="0" width="1" height="1"/><rect x="4" y="2" width="1" height="1"/><rect x="4" y="3" width="1" height="1"/><rect x="3" y="1" width="1" height="1"/></svg>'
)

assert.equal(
minidenticon("foo"),
'<svg viewBox="-1.5 -1.5 8 8" xmlns="http://www.w3.org/2000/svg" fill="hsl(120 95% 45%)"><rect x="0" y="0" width="1" height="1"/><rect x="1" y="0" width="1" height="1"/><rect x="1" y="4" width="1" height="1"/><rect x="2" y="0" width="1" height="1"/><rect x="2" y="2" width="1" height="1"/><rect x="2" y="3" width="1" height="1"/><rect x="2" y="4" width="1" height="1"/><rect x="4" y="0" width="1" height="1"/><rect x="3" y="0" width="1" height="1"/><rect x="3" y="4" width="1" height="1"/></svg>'
)

assert.equal(
minidenticon("foo", 75),
'<svg viewBox="-1.5 -1.5 8 8" xmlns="http://www.w3.org/2000/svg" fill="hsl(120 75% 45%)"><rect x="0" y="0" width="1" height="1"/><rect x="1" y="0" width="1" height="1"/><rect x="1" y="4" width="1" height="1"/><rect x="2" y="0" width="1" height="1"/><rect x="2" y="2" width="1" height="1"/><rect x="2" y="3" width="1" height="1"/><rect x="2" y="4" width="1" height="1"/><rect x="4" y="0" width="1" height="1"/><rect x="3" y="0" width="1" height="1"/><rect x="3" y="4" width="1" height="1"/></svg>'
)

assert.equal(
minidenticon("foo", undefined, 75),
'<svg viewBox="-1.5 -1.5 8 8" xmlns="http://www.w3.org/2000/svg" fill="hsl(120 95% 75%)"><rect x="0" y="0" width="1" height="1"/><rect x="1" y="0" width="1" height="1"/><rect x="1" y="4" width="1" height="1"/><rect x="2" y="0" width="1" height="1"/><rect x="2" y="2" width="1" height="1"/><rect x="2" y="3" width="1" height="1"/><rect x="2" y="4" width="1" height="1"/><rect x="4" y="0" width="1" height="1"/><rect x="3" y="0" width="1" height="1"/><rect x="3" y="4" width="1" height="1"/></svg>'
)

assert.equal(
minidenticon("foo", undefined, undefined, alternateTerribleHash),
'<svg viewBox="-1.5 -1.5 8 8" xmlns="http://www.w3.org/2000/svg" fill="hsl(120 95% 45%)"><rect x="0" y="2" width="1" height="1"/><rect x="1" y="2" width="1" height="1"/><rect x="1" y="3" width="1" height="1"/><rect x="1" y="4" width="1" height="1"/><rect x="2" y="3" width="1" height="1"/><rect x="2" y="4" width="1" height="1"/><rect x="4" y="2" width="1" height="1"/><rect x="3" y="2" width="1" height="1"/><rect x="3" y="3" width="1" height="1"/><rect x="3" y="4" width="1" height="1"/></svg>'
)

assert.notEqual(
minidenticon("foo"),
minidenticon("foo", undefined, undefined, alternateTerribleHash)
)

for (let saturation = 0; saturation < 100; saturation += 5) {
for (let lightness = 0; lightness < COLLISION_TESTS_NUMBER; lightness += 5) {
Expand Down

0 comments on commit 7379901

Please sign in to comment.