Deep dive into Logo Soup’s normalization algorithm
Logo Soup uses a four-step algorithm to make logos look good together. All processing happens client-side using the Canvas API—no server, no AI, fully deterministic.
First, Logo Soup analyzes each logo to find the actual content boundaries, ignoring any whitespace or padding baked into the image.
// Downscale to ~2048 pixels for fast analysisconst ratio = totalPixels > 2048 ? Math.sqrt(2048 / totalPixels) : 1;const scaledWidth = Math.round(width * ratio);const scaledHeight = Math.round(height * ratio);// Render to canvas and extract pixel datactx.drawImage(img, 0, 0, scaledWidth, scaledHeight);const imageData = ctx.getImageData(0, 0, scaledWidth, scaledHeight);
The algorithm:
Detects transparency: Analyzes the perimeter pixels to determine if the logo has a transparent background or an opaque one
Identifies background color: For opaque logos, clusters perimeter colors into buckets and finds the dominant background color
Finds content pixels: Scans all pixels and marks those with sufficient contrast or alpha as “content”
Computes bounding box: Tracks min/max X/Y of content pixels to get the true content boundaries
// Contrast threshold (default 10 on 0-255 scale)const contrastDistanceSq = contrastThreshold ** 2 * 3;for (let i = 0; i < pixelCount; i++) { const pixel = data32[i]; const alpha = pixel >>> 24; if (alpha <= contrastThreshold) continue; // Too transparent const r = pixel & 0xff; const g = (pixel >>> 8) & 0xff; const b = (pixel >>> 16) & 0xff; const dr = r - bgR; const dg = g - bgG; const db = b - bgB; const distSq = dr * dr + dg * dg + db * db; if (distSq < contrastDistanceSq) continue; // Too similar to background // This pixel is content minX = Math.min(minX, x); maxX = Math.max(maxX, x); minY = Math.min(minY, y); maxY = Math.max(maxY, y);}
Why downscale first?
Analyzing a 2000×2000 logo at full resolution means processing 4 million pixels. Downscaling to ~2048 total pixels (e.g., 45×45) reduces this to ~2000 pixels—a 2000× speedup with negligible loss in boundary accuracy.
The result is a content bounding box that represents the logo without padding:
type BoundingBox = { x: number; // Top-left X y: number; // Top-left Y width: number; // Content width height: number; // Content height};
Bold, dense logos visually dominate light, thin logos even when they’re the same size. Logo Soup measures pixel density (visual weight) and adjusts sizing to compensate.
The Helmholtz irradiation illusion causes light content on dark backgrounds to appear larger and bolder than it is. Logo Soup detects background luminance and applies compensatory scaling.
// Only applies to opaque logos (transparent logos don't have background)if (backgroundLuminance !== undefined) { const darkness = 1 - backgroundLuminance; // 0 = white, 1 = black const density = pixelDensity ?? 0.5; // Scale down by up to 8% based on darkness × density const irradiationScale = 1 - darkness * density * 0.08; normalizedWidth *= irradiationScale; normalizedHeight *= irradiationScale;}
Why multiply by density?The irradiation effect is more pronounced on dense/bold logos because more surface area “blooms.” A thin logo on black won’t bloom as much as a thick one.Example: White logo on black background (darkness = 1.0)
After normalization, logos may still look misaligned because their visual weight center doesn’t match their geometric center. Logo Soup computes the visual center during content detection:
This produces a visual center that accounts for the logo’s shape and density distribution:
type VisualCenter = { x: number; // Global center X in original image y: number; // Global center Y in original image offsetX: number; // Offset from geometric center (X) offsetY: number; // Offset from geometric center (Y)};
Use getVisualCenterTransform() to apply the correction as a CSS transform:
const transform = getVisualCenterTransform(logo, "visual-center-y");// Returns: "translate(-2.3px, 1.5px)" or undefined if offset is negligible
Alignment modes:
"bounds": No transform (geometric center)
"visual-center-y": Vertical alignment only (default for horizontal strips)