JSPM

  • ESM via JSPM
  • ES Module Entrypoint
  • Export Map
  • Keywords
  • License
  • Repository URL
  • TypeScript Types
  • README
  • Created
  • Published
  • Downloads 17
  • Score
    100M100P100Q53922F
  • License MIT

Fast WebP (static and animated) image processing in the browser using WebAssembly

Package Exports

    This package does not declare an exports field, so the exports above have been automatically detected and optimized by JSPM instead. If any package subpath is missing, it is recommended to post an issue to the original package (magic-webp) to support the "exports" field. If that is not possible, create a JSPM override to customize the exports field for this package.

    Readme

    🎨 magic-webp

    Fast WebP image processing in the browser using WebAssembly

    npm version License: MIT TypeScript WebAssembly

    Process WebP images (static and animated) directly in the browser with native performance. Built on top of Google's libwebp compiled to WebAssembly.

    🎮 Live DemoFeaturesInstallationQuick StartAPIDevelopment


    ✨ Features

    • 🖼️ WebP Support — Both static and animated WebP images
    • ✂️ Crop — Extract regions (preserves animation frames)
    • 📐 Resize — Multiple modes: cover, contain, fill, inside, outside
    • 🎚️ Quality Control — Adjustable output quality (0-100, lossless)
    • 🚀 Fast — Native libwebp with SIMD optimizations (5-10x faster)
    • 🌐 Browser-first — No server required, runs entirely client-side
    • 🔒 Thread-safe — Automatic operation queuing for concurrent calls
    • 📦 Zero dependencies — Pure WebAssembly, no external libraries

    📦 Installation

    npm install magic-webp
    # or
    pnpm add magic-webp
    # or
    yarn add magic-webp

    🚀 Quick Start

    Step 1: Copy worker.js to your public folder

    # Copy from node_modules
    cp node_modules/magic-webp/src-js/worker.ts public/worker.js
    
    # Or download from GitHub
    # https://github.com/medzhidov/magic-webp/blob/master/src-js/worker.ts

    Step 2: Use the simple API

    import { MagicWebpWorker } from 'magic-webp';
    
    // Initialize worker
    const webp = new MagicWebpWorker('/worker.js');
    
    // Load image
    const file = document.querySelector('input[type="file"]').files[0];
    await webp.load(file);
    
    // Resize (returns Blob directly!)
    const blob = await webp.resize(400, 400, { mode: 'cover', quality: 75 });  // 75 = balanced (recommended)
    
    // Use the result
    const url = URL.createObjectURL(blob);
    document.querySelector('img').src = url;
    
    // Clean up when done
    webp.terminate();

    That's it! No manual Worker management, no message passing, just simple async calls.

    ✨ Benefits: Non-blocking UI, better performance, automatic request queuing

    ⚠️ Important: Web Worker Requirements

    1. Same-Origin Policy

    • Worker file must be served from the same domain as your app
    • ❌ Won't work: new MagicWebpWorker('https://cdn.example.com/worker.js')
    • ✅ Works: new MagicWebpWorker('/worker.js') (same domain)

    2. Module Type

    • Worker must be loaded as ES module (type: 'module')
    • Already handled by MagicWebpWorker constructor

    3. CORS Headers (if serving from different path)

    • If worker is on subdomain, ensure proper CORS headers:
      Access-Control-Allow-Origin: *

    4. File Serving

    • Worker file must be accessible via HTTP/HTTPS
    • ❌ Won't work with file:// protocol (local files)
    • ✅ Use local dev server: npx serve or python -m http.server

    5. Build Tools

    • Vite: Worker is automatically bundled
      const webp = new MagicWebpWorker(
        new URL('./worker.ts', import.meta.url).href
      );
    • Webpack: Use worker-loader or native Worker support
    • Create React App: Place worker in public/ folder

    Common Issues:

    // ❌ WRONG: Cross-origin
    const webp = new MagicWebpWorker('https://cdn.com/worker.js');
    // Error: Failed to construct 'Worker': Script at '...' cannot be accessed from origin '...'
    
    // ✅ CORRECT: Same origin
    const webp = new MagicWebpWorker('/worker.js');
    
    // ✅ CORRECT: Relative path
    const webp = new MagicWebpWorker('./worker.js');
    
    // ✅ CORRECT: Vite/Webpack (bundled)
    const webp = new MagicWebpWorker(
      new URL('./worker.ts', import.meta.url).href
    );

    Alternative: Main Thread (Simpler, but blocks UI)

    import { MagicWebp } from 'magic-webp';
    
    const file = document.querySelector('input[type="file"]').files[0];
    const img = await MagicWebp.fromBlob(file);
    const resized = await img.resize(400, 400, { mode: 'cover', quality: 75 });  // 75 = balanced
    const blob = resized.toBlob();

    ⚠️ Note: Main thread usage blocks the UI during processing. Use MagicWebpWorker for production apps.

    📖 API

    import { MagicWebpWorker } from 'magic-webp';
    
    // Initialize
    const webp = new MagicWebpWorker('/worker.js');
    
    // Load image
    await webp.load(file);  // File or Blob
    
    // Get dimensions
    console.log(webp.width, webp.height);
    
    // Crop
    const blob = await webp.crop(x, y, width, height, quality);
    
    // Resize
    const blob = await webp.resize(width, height, { mode, position, quality });
    
    // Clean up
    webp.terminate();

    MagicWebp (Main Thread)

    import { MagicWebp } from 'magic-webp';
    
    // Load from File/Blob
    const img = await MagicWebp.fromBlob(blob);
    const img = await MagicWebp.fromFile(file);
    
    // Load from URL
    const img = await MagicWebp.fromUrl('https://example.com/image.webp');
    
    // Load from Uint8Array
    const img = await MagicWebp.fromBytes(uint8Array);

    Transformations

    All transformation methods are async and return Promise<MagicWebp>.

    Crop

    // Crop 200x200 region starting at (50, 50)
    const cropped = await img.crop(50, 50, 200, 200, quality);

    Resize

    // Cover - fills dimensions, crops excess (default)
    const cover = await img.resize(400, 400, { mode: 'cover' });
    
    // Contain - fits inside dimensions, preserves aspect ratio
    const contain = await img.resize(400, 400, { mode: 'contain' });
    
    // Fill - stretches to exact dimensions (may distort)
    const fill = await img.resize(400, 400, { mode: 'fill' });
    
    // Inside - like contain, but never enlarges
    const inside = await img.resize(400, 400, { mode: 'inside' });
    
    // Outside - like cover, but never reduces
    const outside = await img.resize(400, 400, { mode: 'outside' });
    
    // With position (for cover/outside modes)
    const banner = await img.resize(1200, 400, {
      mode: 'cover',
      position: 'top',  // 'center', 'top', 'bottom', 'left', 'right', etc.
      quality: 75       // 0-100, default 75 (balanced - recommended)
    });

    Output

    // As Blob
    const blob = img.toBlob();
    
    // As Uint8Array
    const bytes = img.toBytes();
    
    // As Data URL
    const dataUrl = await img.toDataUrl();
    
    // As Object URL
    const objectUrl = img.toObjectUrl();

    Resize Options

    interface ResizeOptions {
      mode?: 'cover' | 'contain' | 'fill' | 'inside' | 'outside';  // default: 'cover'
      position?: 'center' | 'top' | 'bottom' | 'left' | 'right' |  // default: 'center'
                 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';
      quality?: number;  // 0-100, default: 75 (balanced)
    }

    💡 Quality Recommendations:

    Quality File Size Visual Quality Use Case Recommended For
    60-70 Smallest Visible artifacts Thumbnails, previews Low priority images
    75-85 Medium Good balance Most web images Default (75)
    90-95 Large Excellent Important photos Hero images, portfolios
    100 Largest Perfect (lossless) Archival, editing When quality is critical

    Quality Recommendations

    • 60-70: High compression, visible artifacts (good for thumbnails)
    • 75-85: Balanced quality/size (recommended for most cases)
    • 90-95: High quality, minimal artifacts (for important images)
    • 100: Lossless, largest file size (perfect quality preservation)

    Supported Formats

    Input:

    • ✅ Static WebP
    • ✅ Animated WebP (multi-frame)

    Output:

    • ✅ Static WebP (from static input)
    • ✅ Animated WebP (from animated input, preserves all frames and timing)

    Note: All operations (crop, resize) work on both static and animated WebP. For animated images, each frame is processed individually while preserving animation metadata (timing, loop count, etc.).

    💡 Examples

    import { MagicWebpWorker } from 'magic-webp';
    
    const webp = new MagicWebpWorker('/worker.js');
    await webp.load(file);
    
    // Avatar - square 200x200, centered (high quality for profile pics)
    const avatar = await webp.resize(200, 200, { mode: 'cover', quality: 85 });
    
    // Product preview - fit inside 300x300 (balanced quality)
    const preview = await webp.resize(300, 300, { mode: 'contain', quality: 75 });
    
    // Banner - 1200x400, crop from top (high quality for hero images)
    const banner = await webp.resize(1200, 400, {
      mode: 'cover',
      position: 'top',
      quality: 90
    });
    
    // Thumbnail - never enlarge (lower quality for small images)
    const thumb = await webp.resize(150, 150, { mode: 'inside', quality: 65 });
    
    // Crop specific region (balanced quality)
    const cropped = await webp.crop(50, 50, 200, 200, 75);
    
    webp.terminate();

    With Main Thread

    import { MagicWebp } from 'magic-webp';
    
    const img = await MagicWebp.fromBlob(file);
    
    // Chaining operations
    const result = await img
      .crop(100, 100, 400, 400)
      .then(cropped => cropped.resize(200, 200, { mode: 'contain' }));
    
    // Concurrent processing (automatically queued)
    const [avatar, thumb, banner] = await Promise.all([
      img.resize(200, 200, { mode: 'cover' }),
      img.resize(150, 150, { mode: 'inside' }),
      img.resize(1200, 400, { mode: 'cover', position: 'top' })
    ]);

    Animated WebP

    // Works with both static and animated WebP
    // For animated images, all frames are processed while preserving timing
    
    const webp = new MagicWebpWorker('/worker.js');
    await webp.load(animatedWebpFile);
    
    // All frames will be resized
    const resized = await webp.resize(400, 400, { mode: 'cover' });

    🔧 Advanced Usage

    Memory Management

    // Worker automatically manages memory, but you should terminate when done
    const webp = new MagicWebpWorker('/worker.js');
    
    // ... use worker ...
    
    // Clean up when component unmounts or app closes
    webp.terminate();

    Error Handling

    const webp = new MagicWebpWorker('/worker.js');
    
    try {
      await webp.load(file);
      const blob = await webp.resize(400, 400, { mode: 'cover' });
    } catch (error) {
      console.error('Processing failed:', error);
      // Handle error (show message to user, retry, etc.)
    }

    Multiple Workers (Parallel Processing)

    // Create multiple workers for parallel processing
    const workers = [
      new MagicWebpWorker('/worker.js'),
      new MagicWebpWorker('/worker.js'),
      new MagicWebpWorker('/worker.js')
    ];
    
    // Process multiple images in parallel
    const results = await Promise.all(
      files.map((file, i) => {
        const worker = workers[i % workers.length];
        return worker.load(file).then(() =>
          worker.resize(400, 400, { mode: 'cover' })
        );
      })
    );
    
    // Clean up
    workers.forEach(w => w.terminate());

    Browser Compatibility

    • ✅ Chrome 80+
    • ✅ Firefox 79+
    • ✅ Safari 14+
    • ✅ Edge 80+
    • ❌ IE 11 (no WebAssembly support)

    Check before using:

    if (typeof WebAssembly === 'undefined') {
      console.error('WebAssembly not supported');
      // Fallback to server-side processing
    }
    
    if (typeof Worker === 'undefined') {
      console.warn('Web Workers not supported, using main thread');
      // Use MagicWebp instead of MagicWebpWorker
    }

    🛠️ Development

    Prerequisites

    • Node.js 18+
    • pnpm (recommended) or npm
    • Emscripten SDK (automatically installed)

    Setup

    # Clone repository
    git clone https://github.com/medzhidov/magic-webp.git
    cd magic-webp
    
    # Install dependencies
    pnpm install
    
    # Build WASM module
    pnpm build:wasm
    
    # Run demo
    pnpm demo:watch

    Project Structure

    magic-webp/
    ├── src-c/           # C source code
    │   ├── animation.c  # WebP animation processing
    │   └── magic_webp.c # Core functions
    ├── src-js/          # TypeScript API
    │   ├── index.ts     # Main API
    │   └── *.test.ts    # Tests
    ├── demo/            # Demo application
    │   ├── index.html
    │   ├── main.ts
    │   └── worker.ts    # Web Worker for processing
    ├── tests/           # Native C tests
    ├── libwebp/         # Google's libwebp (submodule)
    └── pkg/             # Built WASM output

    Build Commands

    # Build WASM module
    pnpm build:wasm
    
    # Run TypeScript tests
    pnpm test
    
    # Run native C tests
    pnpm test:native
    
    # Run demo (dev server)
    pnpm demo:watch
    
    # Build demo for production
    pnpm demo:build

    How It Works

    1. C Code (src-c/) - Uses libwebp's WebPPicture API for high-quality image processing
    2. Animation Support - Processes each frame individually, preserving timing and metadata
    3. Emscripten - Compiles C code to WebAssembly with SIMD optimizations
    4. TypeScript API (src-js/) - Provides clean, type-safe interface
    5. Operation Queue - Ensures thread-safety by serializing WASM calls
    6. Web Worker (demo) - Keeps UI responsive during processing

    Performance

    • 5-10x faster than pure JavaScript implementations
    • SIMD optimizations (SSE2, NEON) for resize operations
    • Optimized cover mode - single pass resize+crop (2x faster)
    • Minimal memory - in-place operations where possible

    📋 Quick Reference

    Resize Modes

    Mode Behavior Use Case
    cover Fills dimensions, crops excess Avatars, thumbnails
    contain Fits inside, preserves aspect Product images, previews
    fill Stretches to exact size Backgrounds (may distort)
    inside Like contain, never enlarges Thumbnails of small images
    outside Like cover, never reduces Cropping large images

    Position Options (for cover/outside)

    Position Description
    center Center of image (default)
    top Top center
    bottom Bottom center
    left Left center
    right Right center
    top-left Top left corner
    top-right Top right corner
    bottom-left Bottom left corner
    bottom-right Bottom right corner

    Quality Guidelines

    Quality File Size Visual Quality Use Case
    60-70 Smallest Visible artifacts Thumbnails, previews
    75-85 Medium Good balance Most web images
    90-95 Large Excellent Important images, photos
    100 Largest Perfect (lossless) Archival, editing

    📄 License

    MIT © Ilia Medzhidov

    🙏 Acknowledgments


    Made with ❤️ for the web