{"id":50863620,"url":"https://github.com/dannote/whaticon","last_synced_at":"2026-06-14T23:05:36.183Z","repository":{"id":333921700,"uuid":"1139294184","full_name":"dannote/whaticon","owner":"dannote","description":"Find matching Iconify icons by visual similarity","archived":false,"fork":false,"pushed_at":"2026-02-03T09:49:19.000Z","size":1811,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2026-04-27T16:39:00.333Z","etag":null,"topics":["iconify","icons","perceptual-hash","search","svg","typescript","visual-similarity"],"latest_commit_sha":null,"homepage":null,"language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/dannote.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-01-21T19:19:16.000Z","updated_at":"2026-04-14T09:27:05.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/dannote/whaticon","commit_stats":null,"previous_names":["dannote/whaticon"],"tags_count":4,"template":false,"template_full_name":null,"purl":"pkg:github/dannote/whaticon","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dannote%2Fwhaticon","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dannote%2Fwhaticon/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dannote%2Fwhaticon/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dannote%2Fwhaticon/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/dannote","download_url":"https://codeload.github.com/dannote/whaticon/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dannote%2Fwhaticon/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34340834,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-14T02:00:07.365Z","response_time":62,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["iconify","icons","perceptual-hash","search","svg","typescript","visual-similarity"],"created_at":"2026-06-14T23:05:35.530Z","updated_at":"2026-06-14T23:05:36.176Z","avatar_url":"https://github.com/dannote.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# whaticon\n\nFind matching [Iconify](https://iconify.design) icons by visual similarity.\n\nGiven an SVG, `whaticon` renders it and compares against indexed icons using perceptual hashing to find the closest matches.\n\n## Installation\n\n```bash\nnpm install whaticon\n```\n\n## CLI Usage\n\n```bash\n# Find matches for an SVG file\nnpx whaticon icon.svg\n\n# Find similar icons to a known icon\nnpx whaticon --icon lucide:home\n\n# Limit to specific icon sets\nnpx whaticon icon.svg --prefix lucide,tabler\n\n# Prefer certain icon sets (sorted first at equal similarity)\nnpx whaticon icon.svg --prefer lucide\n\n# Adjust threshold and limit\nnpx whaticon icon.svg --threshold 0.9 --limit 5\n\n# Use different index size\nnpx whaticon icon.svg --index full    # 200k+ icons\nnpx whaticon icon.svg --index core    # ~10k icons (faster)\n\n# JSON output\nnpx whaticon icon.svg --json\n```\n\n### Options\n\n| Option | Description |\n|--------|-------------|\n| `-n, --limit \u003cn\u003e` | Maximum results (default: 10) |\n| `-t, --threshold \u003cn\u003e` | Minimum similarity 0-1 (default: 0.8) |\n| `-p, --prefix \u003csets\u003e` | Limit to icon sets (comma-separated) |\n| `--prefer \u003csets\u003e` | Prefer these icon sets (comma-separated) |\n| `-i, --index \u003cvariant\u003e` | Index: `core`, `popular` (default), `full` |\n| `-j, --json` | Output as JSON |\n\n## Index Variants\n\n| Variant | Icons | Size | Description |\n|---------|-------|------|-------------|\n| `core` | ~10k | ~0.4 MB | lucide, heroicons, tabler |\n| `popular` | ~57k | ~1.8 MB | 12 popular sets (default) |\n| `full` | ~200k+ | ~6 MB | All Iconify sets |\n\nThe `popular` index is bundled with the package. Other variants are downloaded on first use to `~/.cache/whaticon/`.\n\n## API Usage\n\n```typescript\nimport { findMatches, loadIndex } from 'whaticon'\nimport { ensureIndex } from 'whaticon/download'\n\n// Ensure index is available (downloads if needed)\nconst { namesGz, hashesGz } = await ensureIndex('popular')\nconst index = loadIndex(namesGz, hashesGz)\n\n// Find matches\nconst svg = '\u003csvg\u003e...\u003c/svg\u003e'\nconst matches = await findMatches(svg, index, {\n  limit: 5,\n  threshold: 0.85,\n  prefixes: ['lucide', 'mdi'],\n  prefer: ['lucide']\n})\n\nconsole.log(matches)\n// [\n//   { name: 'lucide:home', similarity: 0.95 },\n//   { name: 'mdi:home', similarity: 0.92 },\n//   ...\n// ]\n```\n\n### Functions\n\n#### `findMatches(svg, index, options?)`\nFind matching icons from an index.\n\n#### `computeDHash(svg, size?)`\nCompute difference hash for an SVG. Returns 128-byte Buffer.\n\n#### `loadIndex(namesGz, hashesGz)`\nLoad index from gzipped binary files.\n\n#### `ensureIndex(variant)`\nEnsure index is downloaded, returns gzipped buffers.\n\n#### `isIndexDownloaded(variant)`\nCheck if index variant is cached locally.\n\n## Building Custom Index\n\n```typescript\nimport { buildIndex } from 'whaticon'\nimport { gzipSync, writeFileSync } from 'fs'\n\nconst icons = [\n  { name: 'my:icon1', svg: '\u003csvg\u003e...\u003c/svg\u003e' },\n  { name: 'my:icon2', svg: '\u003csvg\u003e...\u003c/svg\u003e' },\n]\n\nconst { names, hashes } = await buildIndex(icons)\n\nwriteFileSync('names.txt.gz', gzipSync(names))\nwriteFileSync('hashes.bin.gz', gzipSync(hashes))\n```\n\n## How It Works\n\n1. **Render**: SVG is rendered to a 33×32 grayscale image using [sharp](https://sharp.pixelplumbing.com/)\n2. **Hash**: A [difference hash (dHash)](https://www.hackerfactor.com/blog/index.php?/archives/529-Kind-of-Like-That.html) is computed — comparing adjacent pixels to produce 1024 bits\n3. **Match**: Hamming distance between hashes determines similarity\n\n### Performance\n\n- **Index build**: ~10,000 icons/sec (sprite sheet batching)\n- **Search**: ~5ms per query (32-bit popcount optimization)\n- **Index size**: ~30 bytes per icon (gzip compressed)\n\n## Index Format\n\nTwo gzip-compressed files:\n- `names.txt.gz` — icon names, one per line\n- `hashes.bin.gz` — 128 bytes per icon, concatenated\n\n## License\n\nMIT\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdannote%2Fwhaticon","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdannote%2Fwhaticon","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdannote%2Fwhaticon/lists"}