Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/sanity-labs/logo-soup/llms.txt

Use this file to discover all available pages before exploring further.

Overview

The createLogoSoup() function creates the core processing engine that handles image loading, measurement, normalization, and caching. It returns a LogoSoupEngine instance with an imperative subscribe/getSnapshot interface. This is the foundation of all framework adapters. For most use cases, you’ll use a framework-specific wrapper (React hook, Vue composable, etc.), but the engine can be used directly for custom integrations.

Signature

function createLogoSoup(): LogoSoupEngine

Return Type

Returns a LogoSoupEngine object with the following methods:
process
(options: ProcessOptions) => void
required
Triggers a processing run with the given options. Call this whenever your input logos or options change.Any in-flight processing from a previous call is automatically cancelled.
subscribe
(listener: () => void) => () => void
required
Subscribes to state changes. The listener is called whenever the engine’s state updates.Returns an unsubscribe function to remove the listener.
getSnapshot
() => LogoSoupState
required
Returns the current immutable state snapshot.Important: Returns the same reference if nothing has changed, enabling efficient change detection.
destroy
() => void
required
Cleans up resources:
  • Revokes all blob URLs created for cropped images
  • Cancels any in-flight processing
  • Clears the image cache
  • Removes all listeners
Always call this when you’re done with the engine to prevent memory leaks.

State Shape

The LogoSoupState object returned by getSnapshot() has the following structure:
status
'idle' | 'loading' | 'ready' | 'error'
required
Current processing status:
  • idle — Initial state before first process() call
  • loading — Loading or measuring images
  • ready — Processing complete, normalized logos available
  • error — All logos failed to load
normalizedLogos
NormalizedLogo[]
required
Array of successfully processed and normalized logos. Empty if not in ready state or if all logos failed.See types for the complete structure.
error
Error | null
Error object if status is error, otherwise null.

Usage Example

Basic Usage

import { createLogoSoup } from '@sanity-labs/logo-soup';

const engine = createLogoSoup();

// Subscribe to state changes
const unsubscribe = engine.subscribe(() => {
  const { status, normalizedLogos, error } = engine.getSnapshot();
  
  if (status === 'loading') {
    console.log('Loading logos...');
  }
  
  if (status === 'ready') {
    console.log('Normalized logos:', normalizedLogos);
  }
  
  if (status === 'error') {
    console.error('Failed to load logos:', error);
  }
});

// Trigger processing
engine.process({
  logos: [
    { src: '/logos/acme.svg', alt: 'Acme Corp' },
    { src: '/logos/globex.svg', alt: 'Globex' },
    { src: '/logos/initech.svg', alt: 'Initech' },
  ],
  baseSize: 48,
  scaleFactor: 0.5,
  densityAware: true,
});

// Clean up when done
// unsubscribe();
// engine.destroy();

Rendering to DOM

import {
  createLogoSoup,
  getVisualCenterTransform,
} from '@sanity-labs/logo-soup';

const engine = createLogoSoup();
const container = document.getElementById('logos')!;

engine.subscribe(() => {
  const { status, normalizedLogos } = engine.getSnapshot();
  
  if (status !== 'ready') return;
  
  container.innerHTML = normalizedLogos
    .map((logo) => {
      const transform = getVisualCenterTransform(logo, 'visual-center-y');
      return `<img
        src="${logo.src}"
        alt="${logo.alt}"
        width="${logo.normalizedWidth}"
        height="${logo.normalizedHeight}"
        style="transform: ${transform ?? 'none'}"
      />`;
    })
    .join('');
});

engine.process({
  logos: ['/logos/acme.svg', '/logos/globex.svg'],
});

Integration with Reactivity Systems

The subscribe/getSnapshot pattern is designed to integrate with any framework’s reactivity system:
import { useSyncExternalStore } from 'react';
import { createLogoSoup } from '@sanity-labs/logo-soup';

function useLogoSoup(options) {
  const engine = useRef(createLogoSoup()).current;
  
  useEffect(() => {
    engine.process(options);
  }, [engine, options]);
  
  const state = useSyncExternalStore(
    engine.subscribe,
    engine.getSnapshot,
    engine.getSnapshot,
  );
  
  useEffect(() => () => engine.destroy(), [engine]);
  
  return state;
}

Caching Behavior

The engine maintains an internal cache of loaded images and measurements:
  • Cache key: Image src URL
  • Cache invalidation: When contrastThreshold, densityAware, or backgroundColor options change, the entire cache is cleared
  • Cache pruning: When a logo is removed from the logos array, its cache entry (including blob URLs) is automatically cleaned up
  • Synchronous fast path: If all logos are cached and no new measurements are needed, process() completes synchronously

Performance Considerations

  1. Synchronous when cached: Subsequent process() calls with the same logos (and measurement options) complete instantly
  2. Automatic cancellation: Calling process() while another is in-flight cancels the previous one
  3. Efficient change detection: getSnapshot() returns the same object reference if nothing changed, enabling === checks
  4. Memory management: Always call destroy() to prevent blob URL leaks