RepoPilotOpen in app →

evanw/thumbhash

A very compact representation of an image placeholder

Mixed

Stale — last commit 2y ago

worst of 4 axes
Use as dependencyMixed

last commit was 2y ago; no tests detected…

Fork & modifyHealthy

Has a license, tests, and CI — clean foundation to fork and modify.

Learn fromHealthy

Documented and popular — useful reference codebase to read through.

Deploy as-isMixed

last commit was 2y ago; no CI workflows detected

  • 6 active contributors
  • MIT licensed
  • Stale — last commit 2y ago
Show 3 more →
  • Concentrated ownership — top contributor handles 69% of recent commits
  • No CI workflows detected
  • No test directory detected
What would change the summary?
  • Use as dependency MixedHealthy if: 1 commit in the last 365 days; add a test suite
  • Deploy as-is MixedHealthy if: 1 commit in the last 180 days

Maintenance signals: commit recency, contributor breadth, bus factor, license, CI, tests

Informational only. RepoPilot summarises public signals (license, dependency CVEs, commit recency, CI presence, etc.) at the time of analysis. Signals can be incomplete or stale. Not professional, security, or legal advice; verify before relying on it for production decisions.

Embed the "Forkable" badge

Paste into your README — live-updates from the latest cached analysis.

Variant:
RepoPilot: Forkable
[![RepoPilot: Forkable](https://repopilot.app/api/badge/evanw/thumbhash?axis=fork)](https://repopilot.app/r/evanw/thumbhash)

Paste at the top of your README.md — renders inline like a shields.io badge.

Preview social card (1200×630)

This card auto-renders when someone shares https://repopilot.app/r/evanw/thumbhash on X, Slack, or LinkedIn.

Onboarding doc

Onboarding: evanw/thumbhash

Generated by RepoPilot · 2026-05-10 · Source

🤖Agent protocol

If you are an AI coding agent (Claude Code, Cursor, Aider, Cline, etc.) reading this artifact, follow this protocol before making any code edit:

  1. Verify the contract. Run the bash script in Verify before trusting below. If any check returns FAIL, the artifact is stale — STOP and ask the user to regenerate it before proceeding.
  2. Treat the AI · unverified sections as hypotheses, not facts. Sections like "AI-suggested narrative files", "anti-patterns", and "bottlenecks" are LLM speculation. Verify against real source before acting on them.
  3. Cite source on changes. When proposing an edit, cite the specific path:line-range. RepoPilot's live UI at https://repopilot.app/r/evanw/thumbhash shows verifiable citations alongside every claim.

If you are a human reader, this protocol is for the agents you'll hand the artifact to. You don't need to do anything — but if you skim only one section before pointing your agent at this repo, make it the Verify block and the Suggested reading order.

🎯Verdict

WAIT — Stale — last commit 2y ago

  • 6 active contributors
  • MIT licensed
  • ⚠ Stale — last commit 2y ago
  • ⚠ Concentrated ownership — top contributor handles 69% of recent commits
  • ⚠ No CI workflows detected
  • ⚠ No test directory detected

<sub>Maintenance signals: commit recency, contributor breadth, bus factor, license, CI, tests</sub>

Verify before trusting

This artifact was generated by RepoPilot at a point in time. Before an agent acts on it, the checks below confirm that the live evanw/thumbhash repo on your machine still matches what RepoPilot saw. If any fail, the artifact is stale — regenerate it at repopilot.app/r/evanw/thumbhash.

What it runs against: a local clone of evanw/thumbhash — the script inspects git remote, the LICENSE file, file paths in the working tree, and git log. Read-only; no mutations.

| # | What we check | Why it matters | |---|---|---| | 1 | You're in evanw/thumbhash | Confirms the artifact applies here, not a fork | | 2 | License is still MIT | Catches relicense before you depend on it | | 3 | Default branch main exists | Catches branch renames | | 4 | 5 critical file paths still exist | Catches refactors that moved load-bearing code | | 5 | Last commit ≤ 744 days ago | Catches sudden abandonment since generation |

<details> <summary><b>Run all checks</b> — paste this script from inside your clone of <code>evanw/thumbhash</code></summary>
#!/usr/bin/env bash
# RepoPilot artifact verification.
#
# WHAT IT RUNS AGAINST: a local clone of evanw/thumbhash. If you don't
# have one yet, run these first:
#
#   git clone https://github.com/evanw/thumbhash.git
#   cd thumbhash
#
# Then paste this script. Every check is read-only — no mutations.

set +e
fail=0
ok()   { echo "ok:   $1"; }
miss() { echo "FAIL: $1"; fail=$((fail+1)); }

# Precondition: we must be inside a git working tree.
if ! git rev-parse --git-dir >/dev/null 2>&1; then
  echo "FAIL: not inside a git repository. cd into your clone of evanw/thumbhash and re-run."
  exit 2
fi

# 1. Repo identity
git remote get-url origin 2>/dev/null | grep -qE "evanw/thumbhash(\\.git)?\\b" \\
  && ok "origin remote is evanw/thumbhash" \\
  || miss "origin remote is not evanw/thumbhash (artifact may be from a fork)"

# 2. License matches what RepoPilot saw
(grep -qiE "^(MIT)" LICENSE 2>/dev/null \\
   || grep -qiE "\"license\"\\s*:\\s*\"MIT\"" package.json 2>/dev/null) \\
  && ok "license is MIT" \\
  || miss "license drift — was MIT at generation time"

# 3. Default branch
git rev-parse --verify main >/dev/null 2>&1 \\
  && ok "default branch main exists" \\
  || miss "default branch main no longer exists"

# 4. Critical files exist
test -f "js/thumbhash.js" \\
  && ok "js/thumbhash.js" \\
  || miss "missing critical file: js/thumbhash.js"
test -f "swift/ThumbHash.swift" \\
  && ok "swift/ThumbHash.swift" \\
  || miss "missing critical file: swift/ThumbHash.swift"
test -f "rust/src/lib.rs" \\
  && ok "rust/src/lib.rs" \\
  || miss "missing critical file: rust/src/lib.rs"
test -f "java/com/madebyevan/thumbhash/ThumbHash.java" \\
  && ok "java/com/madebyevan/thumbhash/ThumbHash.java" \\
  || miss "missing critical file: java/com/madebyevan/thumbhash/ThumbHash.java"
test -f "README.md" \\
  && ok "README.md" \\
  || miss "missing critical file: README.md"

# 5. Repo recency
days_since_last=$(( ( $(date +%s) - $(git log -1 --format=%at 2>/dev/null || echo 0) ) / 86400 ))
if [ "$days_since_last" -le 744 ]; then
  ok "last commit was $days_since_last days ago (artifact saw ~714d)"
else
  miss "last commit was $days_since_last days ago — artifact may be stale"
fi

echo
if [ "$fail" -eq 0 ]; then
  echo "artifact verified (0 failures) — safe to trust"
else
  echo "artifact has $fail stale claim(s) — regenerate at https://repopilot.app/r/evanw/thumbhash"
  exit 1
fi

Each check prints ok: or FAIL:. The script exits non-zero if anything failed, so it composes cleanly into agent loops (./verify.sh || regenerate-and-retry).

</details>

TL;DR

ThumbHash is a compact image placeholder encoding algorithm that generates very small strings (typically <25 bytes) representing blurred image previews with encoded metadata. Unlike BlurHash, it simultaneously captures detail, aspect ratio, accurate color information, and alpha transparency in a single deterministic hash, making it ideal for embedding inline with image metadata for smooth progressive image loading. Language-specific monorepo: js/ contains the canonical TypeScript implementation with .d.ts types, swift/ThumbHash.swift is the pure Swift port, java/com/madebyevan/thumbhash/ThumbHash.java is the Java implementation, and rust/src/lib.rs is the Rust crate. The examples/ directory mirrors this structure with platform-specific demos (examples/browser for web, examples/ios and examples/macos for Apple platforms, examples/node for Node.js, examples/rust for CLI). Each implementation encodes/decodes the same hash format deterministically.

👥Who it's for

Full-stack developers and mobile engineers building image-heavy applications (web, iOS, macOS, Android) who need to display smooth loading states without additional network requests. It's particularly valuable for e-commerce platforms, photo galleries, and content-heavy UIs where inline placeholder data reduces visual jank during image load.

🌱Maturity & risk

Production-ready and actively maintained. The repo contains battle-tested implementations across 4 major languages (JavaScript, Rust, Swift, Java), a live interactive demo at evanw.github.io/thumbhash, and multiple external implementations (Go, Perl, PHP, Ruby) adopted by the community. Single-author repository (evanw) with no visible CI setup mentioned in the file structure, but the algorithmic focus and language parity suggest stability over churn.

Low risk overall, but several constraints: the repo is a single-author project with no automated CI/testing infrastructure visible in the file list, making dependency auditing manual. The Sharp dependency (0.33.1) in examples/node/package.json is a native module that can have platform-specific build issues. The algorithm parameters are non-configurable by design, so users cannot tune output quality vs. size—this is a feature but means contributions are constrained to correctness or performance, not flexibility.

Active areas of work

No specific commit history or open PRs are visible in the provided data, but the presence of examples across all 4 supported languages and a polished interactive demo suggests active maintenance. The existence of external community ports (Go, Perl, PHP, Ruby cited in README) indicates the format is stable and the spec is well-understood, so work is likely focused on minor fixes and documentation rather than breaking changes.

🚀Get running

git clone https://github.com/evanw/thumbhash.git && cd thumbhash && cd js && npm install (for JavaScript) or cd examples/node && npm install (to run the Node example). For Rust: cd rust && cargo build. For Swift: open examples/ios/Example.xcodeproj or examples/macos/Example.xcodeproj in Xcode.

Daily commands: JavaScript: cd js && node (REPL) or cd examples/node && node index.js (processes examples/flower.jpg). Rust: cd examples/rust && cargo run. Swift: Open examples/ios/Example.xcodeproj or examples/macos/Example.xcodeproj in Xcode and run. Browser: Open examples/browser/index.html directly in a browser (no build step).

🗺️Map of the codebase

  • js/thumbhash.js — Core JavaScript implementation—primary entry point for web/Node.js users encoding and decoding thumbhash placeholders.
  • swift/ThumbHash.swift — Native Swift implementation for iOS/macOS—reference implementation demonstrating the algorithm across platforms.
  • rust/src/lib.rs — Rust implementation providing a performant reference and ensuring algorithm correctness across multiple language bindings.
  • java/com/madebyevan/thumbhash/ThumbHash.java — Java implementation required for Android ecosystem and demonstrates multi-language algorithm consistency.
  • README.md — Documents algorithm purpose, advantages over BlurHash, and directs to all language-specific implementations.
  • js/thumbhash.d.ts — TypeScript definitions for the JavaScript implementation—essential for type safety in Node.js and browser tooling.
  • examples/node/index.js — Demonstrates Node.js usage with sharp library for practical image encoding workflows.

🧩Components & responsibilities

  • Encoder (all language implementations) (DCT math, bit packing, binary serialization) — Accepts raw RGBA pixel data, image dimensions, and optional AC/DC limits; computes average color (DC) and detail coefficients (AC) via DCT; outputs compact binary/string thumbhash.
    • Failure mode: Incorrect color or aspect ratio in placeholder if algorithm has bugs; silent data corruption if bit-packing overflows limits.
  • Decoder (all language implementations) (DCT inverse transform, bit unpacking, RGBA generation) — Parses thumbhash binary, reconstructs DC and AC components, applies inverse DCT to generate 100×100 RGBA pixel array for rendering.
    • Failure mode: Corrupted or blurry placeholder if thumbhash is truncated; color shifts if algorithm implementation diverges across languages.
  • Image pixel extraction (Sharp, platform-specific libraries) (Sharp (Node.js), native image codecs (iOS/macOS/Android/Rust)) — Decodes JPEG/PNG/WebP to RGBA pixel data; handles color space conversion and scaling for encoding workflow.
    • Failure mode: Encoding fails if image format unsupported; color

🛠️How to make changes

Add support for a new language implementation

  1. Study the algorithm in the most readable reference implementation (typically swift/ThumbHash.swift or rust/src/lib.rs) (swift/ThumbHash.swift)
  2. Create a new language directory at the repo root (e.g., /go, /python) mirroring the structure of existing implementations (rust/src/lib.rs)
  3. Implement encode() function accepting image data and returning compact thumbhash string/bytes (js/thumbhash.js)
  4. Implement decode() function accepting thumbhash string/bytes and returning RGBA pixel data for rendering (js/thumbhash.js)
  5. Add example usage file in examples/{language}/ directory demonstrating both encode and decode workflows (examples/node/index.js)
  6. Update README.md to list the new language implementation and link to the external or internal repository (README.md)

Add a new platform example (e.g., Android, web framework)

  1. Create new directory under examples/{platform}/ following existing example structure (examples/browser/index.html)
  2. Import or reference the appropriate language implementation (js/thumbhash.js for web, java/ for Android) (examples/node/index.js)
  3. Demonstrate image loading workflow: encode during image upload, decode for placeholder display, then replace with full image (examples/browser/index.html)
  4. Document platform-specific setup and API usage in example-specific README if non-obvious (examples/ios/ViewController.swift)

Update or optimize the core algorithm across all implementations

  1. Make changes to the reference implementation (recommend Rust for clarity, or Swift for readability) (rust/src/lib.rs)
  2. Port algorithm changes to JavaScript implementation ensuring mathematical equivalence (js/thumbhash.js)
  3. Port algorithm changes to Swift implementation (swift/ThumbHash.swift)
  4. Port algorithm changes to Java implementation (java/com/madebyevan/thumbhash/ThumbHash.java)
  5. Update or add test vectors in examples to verify consistency across implementations (examples/node/index.js)
  6. Update README.md if algorithm behavior or capabilities change (README.md)

🔧Why these technologies

  • Multiple language implementations (JS, Rust, Swift, Java) — Enables thumbhash adoption across web, mobile, and backend ecosystems without external dependencies; each platform can use native implementation for performance.
  • Compact binary encoding (not JSON or text-heavy) — Minimizes placeholder size (<100 bytes typical) for inline storage with image metadata, reducing network overhead vs BlurHash.
  • DCT (Discrete Cosine Transform) algorithm — Efficiently captures both color (DC) and detail (AC components) with automatic parameter selection, encoding more visual information than BlurHash in same space.
  • Sharp library for Node.js examples — Fast, standard image processing library for extracting pixel data in server-side encoding workflows.

⚖️Trade-offs already made

  • Non-configurable algorithm parameters (fully automatic)

    • Why: Simplifies API and eliminates tuning burden for developers, ensuring consistent quality across uses.
    • Consequence: Developers cannot trade off compression vs quality—must accept algorithm's fixed choices; potential limitation for niche use cases requiring custom parameter tuning.
  • Multiple independent language implementations vs single reference

    • Why: Ensures native performance and idiomatic APIs per platform; no runtime overhead from FFI or bindings.
    • Consequence: Requires keeping algorithm in sync across 4+ codebases; higher maintenance burden when algorithm updates or bugs are found.
  • Encodes aspect ratio and alpha channel (vs BlurHash)

    • Why: Provides more accurate placeholders without extra dimensions or post-processing; better UX for varied image formats.
    • Consequence: Slightly increased complexity in algorithm; developers must handle alpha blending if target platform requires opaque pixels.

🚫Non-goals (don't propose these)

  • Does not provide configurable quality/size tradeoffs—all parameters are automatic.
  • Not a streaming or progressive codec—entire thumbhash must be decoded for a usable placeholder.
  • Does not include authentication, security validation, or content verification—assumes trusted image sources.
  • Not an animation or video placeholder library—designed for static images only.

🪤Traps & gotchas

Sharp native dependency: examples/node/package.json depends on Sharp 0.33.1, which requires node-gyp and platform-specific build tools (Python, C++ compiler)—fails on some CI environments without extra setup. No test suite: no tests/ directory or test files visible; validation is manual (checking outputs match across implementations). Algorithm is immutable: the non-configurable design means you cannot tune hash size, quality, or speed—accept what the algorithm outputs or fork. Floating-point precision: the hash encodes floating-point color data; different languages' Math libraries (JavaScript Number vs. Rust f32) may produce slightly different decoded images at extreme precision—insignificant for visual output but worth knowing if byte-exact reproduction is needed.

🏗️Architecture

💡Concepts to learn

  • Discrete Cosine Transform (DCT) — ThumbHash uses DCT basis functions (like JPEG) to decompose images into frequency components; understanding DCT is essential to grasp why the algorithm produces compact yet detailed hashes.
  • Aspect Ratio Encoding — Unlike BlurHash, ThumbHash encodes image width/height ratio directly in the hash; this is a key innovation allowing correct placeholder scaling without additional metadata.
  • Lossless Color Quantization — ThumbHash preserves accurate colors in a compact format by quantizing color space into discrete levels; understanding quantization tradeoffs is critical for evaluating output quality.
  • Alpha Channel Encoding — ThumbHash explicitly supports transparent images (unlike BlurHash), embedding alpha metadata in the hash; relevant for e-commerce and design tools needing accurate previews of PNGs.
  • Base-83 Encoding — ThumbHash hashes are encoded as Base-83 strings for URL-safe compact representation; understanding this encoding scheme (similar to BlurHash) is needed when parsing or generating hashes.
  • Progressive Image Loading — ThumbHash's primary use case is serving as an inline placeholder while the full image loads; understanding lazy-loading patterns and metrics (LCP, CLS) informs when to deploy it.
  • Language Interoperability & Algorithm Porting — This repo is a case study in porting a single algorithm across 4+ languages while maintaining bit-exact output; mastering cross-language testing and avoiding floating-point bugs is essential for contributions.
  • woltapp/blurhash — Direct predecessor and primary competitor—ThumbHash was designed to improve on BlurHash's detail and aspect ratio encoding; understanding BlurHash clarifies ThumbHash's design decisions.
  • galdor/go-thumbhash — Community-maintained Go implementation cited in the README; use as reference for cross-language correctness or to avoid duplicating effort if porting to Go.
  • chromium/chromium — Chromium ships image placeholders in a similar vein; studying its lazy-loading and progressive image strategies informs optimization opportunities for ThumbHash usage in browsers.
  • facebook/draft-js — While unrelated to hashing, it exemplifies rich media handling patterns in large JavaScript ecosystems—relevant for integrating ThumbHash into content-heavy React or Vue applications.
  • mozilla/mozjpeg — High-quality JPEG encoder used by image optimization pipelines; understanding its parameters helps evaluate where ThumbHash fits in end-to-end image delivery.

🪄PR ideas

To work on one of these in Claude Code or Cursor, paste: Implement the "<title>" PR idea from CLAUDE.md, working through the checklist as the task list.

Add Node.js integration tests for examples/node/index.js

The examples/node directory has a package.json with sharp dependency but no test file. There's no validation that the Node.js example actually works end-to-end. Adding tests would ensure the thumbhash generation works correctly with real images (examples/flower.jpg) and that the output matches expectations across Node.js versions.

  • [ ] Create examples/node/tests/index.test.js or examples/node/test.js
  • [ ] Add test cases for encoding examples/flower.jpg using the example code
  • [ ] Verify output hash format and that it can be decoded back
  • [ ] Add npm test script to examples/node/package.json
  • [ ] Test error handling for invalid images or missing files

Add GitHub Actions CI workflow to test all language implementations

The repo has implementations in JavaScript (js/), Rust (rust/), Swift (swift/), and Java (java/) but no visible CI configuration (.github/workflows). Contributors could break changes across languages without catching them. A multi-language test workflow would ensure quality across all implementations.

  • [ ] Create .github/workflows/ci.yml with jobs for JavaScript (Node.js), Rust, Java, and Swift
  • [ ] For JavaScript: run tests in js/ directory (if they exist or create them first)
  • [ ] For Rust: run 'cargo test' on rust/ directory
  • [ ] For Java: compile and test java/com/madebyevan/thumbhash/ThumbHash.java
  • [ ] For Swift: run swift build and swift test if test files exist
  • [ ] Add workflow to run on push and pull requests to main branch

Create comprehensive unit tests for js/thumbhash.js with test vectors

The JavaScript implementation (js/thumbhash.js and js/thumbhash.d.ts) is the most widely used but has no visible tests or .test.js files in the repository. Adding test coverage with known test vectors would prevent regressions and validate the encode/decode functions work correctly across future changes.

  • [ ] Create js/tests/thumbhash.test.js (or js/thumbhash.test.js)
  • [ ] Add unit tests for thumbHashFromDataURL() function
  • [ ] Add unit tests for thumbHashToDataURL() function
  • [ ] Add unit tests for extractSizeAndAspectRatio() function
  • [ ] Include test vectors with known image data and expected hash outputs
  • [ ] Add npm test script to js/package.json pointing to test file
  • [ ] Ensure TypeScript definitions in js/thumbhash.d.ts match test coverage

🌿Good first issues

  • Add a test suite: Create a tests/ directory with JSON fixtures (image hash → expected encoded string) and a test runner (Jest for JS, cargo test for Rust, XCTest for Swift, JUnit for Java) to catch regressions when porting the algorithm. Currently validation is manual and implicit.
  • Write a Python implementation: The README lists 3 external community ports but no official Python package—create python/ with a pure-Python implementation (following js/thumbhash.js structure) and a PyPI setup, then document it in README. High-value contribution with a clear template.
  • Improve browser example: examples/browser/index.html is minimal; add a feature to upload custom JPEG/PNG images, encode them, display both the BlurHash hash and a decoded preview side-by-side, and show the hash size in bytes. Makes the demo more interactive and demonstrates real use cases.

Top contributors

Click to expand

📝Recent commits

Click to expand
  • a652ce6 — fix #30: add an example for node (evanw)
  • 4c3be1e — Merge pull request #20 from daibhin/patch-1 (evanw)
  • f9a1b0a — Add Ruby implementation (daibhin)
  • df761ae — Merge pull request #16 from mauke/patch-1 (evanw)
  • 7ba5c38 — add Perl implementation of ThumbHash (mauke)
  • d5cb27e — Merge pull request #14 from SRWieZ/patch-1 (evanw)
  • 2eb6fb0 — Added PHP Implementation (SRWieZ)
  • ee88db4 — Merge pull request #13 from codenoid/main (evanw)
  • 05ed60b — add "Implementations" section to readme (evanw)
  • f400a9c — add Rust example (codenoid)

🔒Security observations

The ThumbHash codebase shows a generally secure architecture for an image hashing library. The primary concern is dependency management, specifically the use of sharp@0.33.1 which may contain known vulnerabilities. The library itself is relatively simple (focusing on image encoding/hashing) with minimal risk of injection vulnerabilities or hardcoded secrets. No critical security misconfigurations were identified. Recommendations include updating the sharp dependency, implementing regular security audits with npm audit, and establishing a vulnerability disclosure policy. The multi-language implementation approach (JS, Rust, Swift, Java) reduces risk by providing platform-specific implementations without cross-platform dependencies.

  • Medium · Outdated Sharp Dependency — examples/node/package.json. The package.json specifies sharp@0.33.1, which may contain known vulnerabilities. Sharp is a native image processing library with a history of security issues in older versions. The version 0.33.1 is not the latest available and should be updated to ensure security patches are applied. Fix: Update sharp to the latest stable version by running 'npm update sharp' and reviewing the changelog for breaking changes. Consider using dependency scanning tools like 'npm audit' to identify known vulnerabilities.
  • Low · Missing Package Lock Integrity — examples/node/package-lock.json. The package-lock.json file exists but was not provided for review. This file is critical for ensuring reproducible and secure builds, as it locks all transitive dependencies to specific versions. Fix: Ensure package-lock.json is committed to version control and regularly reviewed for security updates. Use 'npm audit' to check for vulnerabilities in locked dependencies.
  • Low · No Security Policy or Vulnerability Disclosure — Repository root. No SECURITY.md or similar vulnerability disclosure policy was found in the repository. This makes it harder for security researchers to responsibly report vulnerabilities. Fix: Create a SECURITY.md file with a vulnerability disclosure policy and contact information, following the GitHub standard format.

LLM-derived; treat as a starting point, not a security audit.


Generated by RepoPilot. Verdict based on maintenance signals — see the live page for receipts. Re-run on a new commit to refresh.

Mixed signals · evanw/thumbhash — RepoPilot