image-q

repository·main·Indexed 41 days ago

https://github.com/ibezkrovnyi/image-quantization

A TypeScript library for image quantization and color reduction supporting browser and Node.js environments. It features various palette quantizers (NeuQuant, RGBQuant, WuQuant), dithering algorithms (Floyd-Steinberg, Stucki, Atkinson, etc.), and color distance formulas (CIEDE2000, Euclidean, Manhattan). The library provides tools for converting 24-bit images to indexed 8-bit formats, color space conversion utilities, and SSIM calculation.

Tokens
9.6K
Snippets
28
Records
36
Agent score
50%

What's inside image-q

  1. Overview of image-q features and capabilities

    main

    image-q is a TypeScript library for image color number reduction with alpha support. It implements various algorithms and color distance formulas.

    Supported Platforms

    • Browser: Chrome 7.0+, Firefox 4.0+, IE 10+, Opera 11.6+, Safari 5.1+
    • Node.js: 6.0+

    Supported Input Types

    • HTMLImageElement
    • HTMLCanvasElement
    • NodeCanvas
    • ImageData
    • Array
    • CanvasPixelArray
    • Uint8Array
    • Uint32Array

    Output Formats

    • Uint32Array
    • Uint8Array
  2. How Palette Quantizers work

    main

    A Palette Quantizer is an object used to manually control the palette generation process. You can sample one or more PointContainer objects and then run the quantization.

    Manual Workflow (Sync)

    1. Instantiate a quantizer (e.g., new WuQuant(distanceCalculator, colors)).
    2. Call .sample(pointContainer) for each image you want to include in the sampling.
    3. Call .quantizeSync() to get the resulting [[Palette]].

    Generator Workflow (Async-like)

    The .quantize() method returns a generator, allowing you to track progress or wrap the iteration in setTimeout to avoid blocking the main thread.

    Available Quantizers

    • NeuQuant: Original port with integer calculations.
    • NeuQuantFloat: Floating-point calculations.
    • RGBQuant
    • WuQuant
    // Sync usage
    const paletteQuantizer = new WuQuant(distanceCalculator, 256);
    paletteQuantizer.sample(pointContainer1);
    paletteQuantizer.sample(pointContainer2);
    const palette = paletteQuantizer.quantizeSync();
    
    // Generator usage
    const paletteQuantizer = new WuQuant(distanceCalculator, 256);
    paletteQuantizer.sample(pointContainer1);
    const generator = paletteQuantizer.quantize();
    let palette;
    while (true) {
      const result = generator.next();
      if (result.done) break;
      if (result.value.palette) palette = result.palette;
      console.log(`${result.value.progress}% done`);
    }
  3. Install and include image-q in your project

    main

    You can include the image-q library in your project using ES6 modules, CommonJS, or as a global variable in the browser.

    ##### ES6 module
    
    ```javascript
    // will import ESM (ESNext) or UMD version depending on your bundler/node
    import * as iq from 'image-q';
    CommonJS
    var iq = require('image-q');
    As a global variable (Browser)
    <script
      src="<path-to image-q/dist/umd/image-q.js>"
      type="text/javascript"
      charset="utf-8"
    ></script>
  4. Apply a palette to an image (Dithering)

    main

    Once you have a palette (generated via a palette quantizer) and a pointContainer (the source image), you can apply the palette to the image using an ImageQuantizer (like NearestColor). This process effectively reduces the color depth of the image, often using dithering to preserve visual quality.

    // 1. Create image quantizer
    var imageQuantizer = new iq.image.NearestColor(distanceCalculator);
    
    // 2. Apply palette to image
    var resultPointContainer = imageQuantizer.quantizeSync(pointContainer, palette);
    
    // 3. Convert to typed array for use
    var uint8array = resultPointContainer.toUint8Array();
  5. Convert a 24-bit PNG to an indexed 8-bit image

    main

    To reduce the color depth of an image (e.g., converting a 24-bit PNG to an 8-bit indexed format), you must first load the image data into a PointContainer, build a color palette from that container, and then apply the palette back to the container.

    Note: This example uses pngjs for file reading, but image-q can work with various sources via PointContainer methods.

    import { PNG } from 'pngjs';
    import { buildPaletteSync, utils, applyPaletteSync } from 'image-q';
    import fs from 'fs';
    
    // 1. Read file and convert to PointContainer
    const { data, width, height } = PNG.sync.read(fs.readFileSync('file.png'));
    const inPointContainer = utils.PointContainer.fromUint8Array(
      data,
      width,
      height,
    );
    
    // 2. Build the palette from the image
    const palette = buildPaletteSync([inPointContainer]);
    
    // 3. Apply the palette to create the indexed image
    const outPointContainer = applyPaletteSync(inPointContainer, palette);
    
    // Use outPointContainer.toUint8Array() to get the resulting data
  6. Generate a color palette from an image

    main

    To create a palette (a set of colors) from an existing image, follow these steps:

    1. Load the image into a PointContainer using iq.utils.PointContainer.fromHTMLImageElement(img).
    2. Choose a distance calculator (e.g., iq.distance.Euclidean).
    3. Initialize a palette quantizer (e.g., iq.palette.RGBQuant) with the distance calculator and your targetColors count.
    4. Call .sample(pointContainer) on the quantizer to analyze the image colors.
    5. Call .quantizeSync() to retrieve the generated palette.
    // 1. Setup target colors
    var targetColors = 256;
    
    // 2. Create PointContainer from image
    var pointContainer = iq.utils.PointContainer.fromHTMLImageElement(img);
    
    // 3. Setup distance calculator
    var distanceCalculator = new iq.distance.Euclidean();
    
    // 4. Setup palette quantizer
    var paletteQuantizer = new iq.palette.RGBQuant(distanceCalculator, targetColors);
    
    // 5. Sample the image
    paletteQuantizer.sample(pointContainer);
    
    // 6. Get the palette
    var palette = paletteQuantizer.quantizeSync();
  7. Convert PointContainer to Uint8Array or Uint32Array

    main

    After quantization, the resulting PointContainer can be converted into standard typed arrays for use with other libraries (like pngjs) or for direct file writing.

    Available methods:

    • toUint8Array(): Returns a Uint8Array.
    • toUint32Array(): Returns a Uint32Array.
    // Example: writing a PNG using pngjs
    png.data = outPointContainer.toUint8Array();
    fs.writeFileSync('filename.png', PNG.sync.write(png));
  8. Build a color palette with buildPalette

    main

    The buildPalette API quantizes sample images to create a [[Palette]] instance. You can use either the asynchronous (Promise-based) or synchronous version.

    Async version

    Use buildPalette for non-blocking operations. It accepts an array of PointContainer objects and an options object.

    Sync version

    Use buildPaletteSync for immediate execution. Note that the underlying generator is wrapped with setImmediate (polyfilled).

    Options

    • colorDistanceFormula: (string) The formula used to calculate color similarity.
    • paletteQuantization: (string) The algorithm used for palette generation.
    • colors: (number) The target number of colors in the palette.
    • onProgress: (function) A callback receiving the current progress value.
    // Async
    import { buildPalette } from 'image-q';
    const palette = await buildPalette([pointContainer], {
      colorDistanceFormula: 'euclidean',
      paletteQuantization: 'neuquant',
      colors: 128,
      onProgress: (progress) => console.log('applyPalette', progress),
    });
    
    // Sync
    import { buildPaletteSync } from 'image-q';
    const palette = buildPaletteSync([pointContainer], {
      colorDistanceFormula: 'euclidean',
      paletteQuantization: 'neuquant',
      colors: 128,
    });
  9. Use ErrorDiffusionArray for image dithering

    main

    ErrorDiffusionArray is an image quantizer that supports various error propagation modes (dithering kernels) like FloydSteinberg, Stucki, Atkinson, Jarvis, etc. You can use it synchronously or via a generator for progress tracking.

    Synchronous Usage

    Use quantizeSync to process the image immediately.

    Generator Usage

    Use quantize to get a generator. This is useful for tracking progress or preventing UI blocking by wrapping .next() calls in setTimeout to make them asynchronous.

    Supported Kernels

    • FloydSteinberg
    • FalseFloydSteinberg
    • Stucki
    • Atkinson
    • Jarvis
    • Burkes
    • Sierra
    • TwoSierra
    • SierraLite
    const imageQuantizer = new ErrorDiffusionArray(
      distanceCalculator,
      ErrorDiffusionArrayKernel.Jarvis,
    );
    
    // Synchronous
    const outPointContainer = imageQuantizer.quantizeSync(
      inPointContainer,
      palette,
    );
    
    // Generator (for progress tracking)
    const generator = imageQuantizer.quantize(inPointContainer, palette);
    let outPointContainer;
    while (true) {
      const result = generator.next();
      if (result.done) break;
      if (result.value.pointContainer) outPointContainer = result.value.pointContainer;
      console.log(`${result.value.progress}% done`);
    }
  10. Import images into a PointContainer

    main

    The PointContainer class provides several static methods to import image data from different sources.

    Canvas Sources

    • PointContainer.fromHTMLCanvasElement(canvas): From an HTMLCanvasElement.
    • PointContainer.fromImageData(imageData): From ImageData (e.g., from ctx.getImageData()).
    • PointContainer.fromUint8Array(data, width, height): From a Uint8ClampedArray or Uint8Array.

    Other Sources

    • PointContainer.fromHTMLImageElement(img): From an HTMLImageElement.
    • PointContainer.fromImageData(array): From a standard JavaScript Array.
    • PointContainer.fromUint32Array(data, width, height): From a Uint32Array.
    • PointContainer.fromBuffer(buffer, width, height): From a Node.js Buffer.
    const canvas = document.querySelector('#canvas') as HTMLCanvasElement;
    const pointContainer = PointContainer.fromHTMLCanvasElement(canvas);
  11. Apply a palette to an image with applyPalette

    main

    The applyPalette API applies an existing [[Palette]] to a [[PointContainer]], returning a new [[PointContainer]] representing the quantized image.

    Async version

    Use applyPalette for Promise-based workflows.

    Sync version

    Use applyPaletteSync for synchronous workflows.

    Options

    • colorDistanceFormula: (string) The formula used to match pixels to palette colors.
    • imageQuantization: (string) The dithering algorithm used (e.g., 'floyd-steinberg').
    • onProgress: (function) A callback receiving the current progress value.
    // Async
    import { applyPalette } from 'image-q';
    const outPointContainer = await applyPalette(pointContainer, palette, {
      colorDistanceFormula: 'euclidean',
      imageQuantization: 'floyd-steinberg',
      onProgress: (progress) => console.log('applyPalette', progress),
    });
    
    // Sync
    import { applyPaletteSync } from 'image-q';
    const outPointContainer = applyPaletteSync(pointContainer, palette, {
      colorDistanceFormula: 'euclidean',
      imageQuantization: 'floyd-steinberg',
    });