<canvas>. That means performance matters. Here’s what we did to make it fast, how we measure it, and what you can do to keep it that way.
Optimization techniques
Single-pass pixel scanning
Early versions ran three separate pixel scans per logo: one for the bounding box, one for the visual center, and one for pixel density. Each scan created a new canvas, drew the image at full resolution, then walked every pixel. For a 400x400 image, that’s 160k pixels, three times over. The current implementation collapses all three into a single pass over aUint32Array:
Downsampling
Source images are drawn onto a canvas at a fixed pixel budget of ~2,048 total pixels, regardless of the original resolution. A 2000x1000 image is analyzed at roughly 45x23 pixels. This makes measurement cost O(1) relative to image resolution while preserving enough detail for accurate content detection.Canvas reuse
Instead of creating a new<canvas> element for each measurement, the engine maintains pooled canvas contexts at module level. The canvas is only resized when the dimensions change, otherwise it’s cleared and reused. This avoids repeated DOM allocation and garbage collection pressure.
Result caching
Once an image is loaded and measured, theMeasurementResult is cached by URL. Changing baseSize or scaleFactor (which only affect normalization math, not pixel analysis) reuses cached measurements without re-scanning. The cache is only invalidated when contrastThreshold, densityAware, or backgroundColor change, since those affect the pixel scan itself.
Cancellation
Ifprocess() is called while a previous run is still loading images, the in-flight work is cancelled and only the latest call’s results are emitted. This prevents wasted work during rapid option changes.
Benchmarks
Every pull request automatically runs a benchmark suite against real SVG logos from the test set. Here are typical numbers from CI:
Detection dominates the cost. Once measurements are cached, subsequent layout updates (changing
baseSize, scaleFactor, or alignBy) are effectively free — nearly 400x faster than a full mount.
Feature cost breakdown
The CI suite also measures how expensive individual features are relative to having them off:CI regression detection
The benchmark suite uses Welch’s t-test to compare HEAD against the base branch on every PR. A regression is flagged only when all three conditions are met:- The change is statistically significant (p < 0.05)
- The relative change exceeds 5%
- The absolute change exceeds 100us
Tips for your application
Provide backgroundColor for opaque logos
When Logo Soup doesn’t know the background color, it samples the image perimeter to auto-detect it. If you know the background (e.g., your page is white), pass backgroundColor explicitly to skip the detection step and improve accuracy.
Disable features you don’t need
Each feature has a measurable cost. If you don’t need density compensation, visual center alignment, or content cropping, turning them off saves work:Cache-friendly option changes
ChangingbaseSize, scaleFactor, densityFactor, or alignBy reuses cached measurements and only runs cheap normalization math. Changing contrastThreshold, densityAware, or backgroundColor invalidates the cache and triggers a full re-scan. Structure your UI so the expensive options are set once and the cheap ones are interactive.
Keep logo sets reasonable
The library processes logos in parallel usingPromise.allSettled, but each logo still needs its own pixel scan on first load. For very large sets (50+ logos), consider lazy-loading logos that are off-screen.
Server-side pre-computation
If you want to skip client-side pixel scanning entirely, the Node.js adapter lets you pre-computeMeasurementResult data at build time or in API routes using @napi-rs/canvas. The client then calls createNormalizedLogo with the pre-computed data — pure math, no canvas, no async.