# BlazeDiff > BlazeDiff is a high-performance diff ecosystem for JavaScript applications. Originally built as a pixel-perfect image comparison library that's 1.5x faster than [pixelmatch](https://github.com/mapbox/pixelmatch), BlazeDiff has evolved into a comprehensive suite of blazing-fast diff tools including image comparison, object diffing, perceptual quality metrics, web components, and React components for visualizing differences. **Install (npm):** `npm install @blazediff/core` **Install (JSR):** `deno add jsr:@blazediff/core` ## Performance - **Native (Rust)**: 3-4x faster than odiff, 8x faster than pixelmatch on 4K images - **Image Pixel-by-Pixel (JS)**: ~50% faster than pixelmatch (up to 88% on identical images) - **SSIM**: ~25% faster than ssim.js, ~70% faster with Hitchhiker's SSIM - **Object Diff**: ~55% faster than microdiff (up to 96% on identical arrays) --- # Packages # @blazediff/core High-performance pixel-by-pixel image comparison library. 1.5x faster than [pixelmatch](https://github.com/mapbox/pixelmatch) while maintaining identical accuracy. [View Detailed Benchmarks](https://github.com/teimurjan/blazediff/blob/main/BENCHMARKS.md) ## Installation ```sh npm install @blazediff/core ``` ## Features - **1.5x faster** than pixelmatch on average, **88% faster** on identical images - **100% API compatible** with pixelmatch - drop-in replacement - **Zero dependencies** ## API Reference ### `blazediff(image1, image2, output, width, height, options?)` Compares two images pixel by pixel and returns the number of different pixels. #### Parameters | Parameter | Type | Description | | --------- | --------------------------------------------------- | ------------------------------------------- | | `image1` | `Buffer \| Uint8Array \| Uint8ClampedArray` | Image data of the first image | | `image2` | `Buffer \| Uint8Array \| Uint8ClampedArray` | Image data of the second image | | `output` | `Buffer \| Uint8Array \| Uint8ClampedArray \| null` | Output buffer for the diff image (optional) | | `width` | `number` | Width of the images in pixels | | `height` | `number` | Height of the images in pixels | | `options` | `Options` | Comparison options (optional) | ##### Options | Option | Type | Default | Description | | ----------------- | --------- | ------------- | ------------------------------------------------ | | `threshold` | `number` | `0.1` | Matching threshold (0-1). Lower = more sensitive | | `includeAA` | `boolean` | `false` | Include anti-aliased pixels in diff count | | `alpha` | `number` | `0.1` | Opacity of original image in diff output | | `aaColor` | `[R,G,B]` | `[255,255,0]` | Color of anti-aliased pixels (yellow) | | `diffColor` | `[R,G,B]` | `[255,0,0]` | Color of different pixels (red) | | `diffColorAlt` | `[R,G,B]` | `null` | Alternative color for dark differences | | `diffMask` | `boolean` | `false` | Draw diff as a mask with transparent background | | `fastBufferCheck` | `boolean` | `true` | Use fast buffer comparison for identical images | > **Info:** **Threshold Guidelines:** - `0.0` - Exact match only - `0.05` - Strict comparison - `0.1` - Default balanced comparison - `0.2` - Lenient comparison ## Links - [GitHub Repository](https://github.com/teimurjan/blazediff) - [NPM Package](https://www.npmjs.com/package/@blazediff/core) - [Examples →](/examples/image-comparison) --- # @blazediff/ssim Fast SSIM (Structural Similarity Index) implementations for perceptual image quality assessment. Includes standard SSIM, MS-SSIM (Multi-Scale SSIM), and Hitchhiker's SSIM for various use cases and performance requirements. ## Installation ```sh npm install @blazediff/ssim ``` ## Features - **Three variants** - Standard SSIM, MS-SSIM, and Hitchhiker's SSIM (~4x faster) - **MATLAB-compatible** - Standard SSIM matches reference implementation with \<0.01% error - **SSIM map output** - Optional grayscale visualization of similarity ## API Reference ### `ssim(image1, image2, output, width, height, options?)` Compares two images using standard SSIM metric and returns a similarity score. #### Parameters | Parameter | Type | Description | | --------- | ----------------------------------------------------------- | ------------------------------------------------- | | `image1` | `Buffer`, `Uint8Array`, or `Uint8ClampedArray` | Image data of the first image | | `image2` | `Buffer`, `Uint8Array`, or `Uint8ClampedArray` | Image data of the second image | | `output` | `Buffer`, `Uint8Array`, `Uint8ClampedArray`, or `undefined` | Optional output buffer for SSIM map visualization | | `width` | `number` | Width of the images in pixels | | `height` | `number` | Height of the images in pixels | | `options` | `SsimOptions` | Comparison options (optional) | ##### Options | Option | Type | Default | Description | | ------------ | -------- | ------- | --------------------------------- | | `windowSize` | `number` | `11` | Size of the Gaussian window | | `k1` | `number` | `0.01` | Algorithm parameter for luminance | | `k2` | `number` | `0.03` | Algorithm parameter for contrast | | `L` | `number` | `255` | Dynamic range of pixel values | ##### Returns `number` - SSIM score between 0 and 1 ### `msssim(image1, image2, output, width, height, options?)` Compares two images using MS-SSIM (Multi-Scale SSIM) metric. #### Parameters | Parameter | Type | Description | | --------- | ----------------------------------------------------------- | --------------------------------------------------- | | `image1` | `Buffer`, `Uint8Array`, or `Uint8ClampedArray` | Image data of the first image | | `image2` | `Buffer`, `Uint8Array`, or `Uint8ClampedArray` | Image data of the second image | | `output` | `Buffer`, `Uint8Array`, `Uint8ClampedArray`, or `undefined` | Optional output buffer for SSIM map at finest scale | | `width` | `number` | Width of the images in pixels | | `height` | `number` | Height of the images in pixels | | `options` | `MsssimOptions` | Comparison options (optional) | ##### Options | Option | Type | Default | Description | | ------------ | ---------------------- | ------------------------------------------ | --------------------------- | | `windowSize` | `number` | `11` | Size of the Gaussian window | | `scales` | `number` | `5` | Number of scales to use | | `weights` | `number[]` | `[0.0448, 0.2856, 0.3001, 0.2363, 0.1333]` | Weights for each scale | | `method` | `'product'` or `'sum'` | `'product'` | Aggregation method | ##### Returns `number` - MS-SSIM score between 0 and 1 ### `hitchhikersSSIM(image1, image2, output, width, height, options?)` Compares two images using Hitchhiker's SSIM (fast rectangular-window version). #### Parameters | Parameter | Type | Description | | --------- | ----------------------------------------------------------- | ----------------------------------- | | `image1` | `Buffer`, `Uint8Array`, or `Uint8ClampedArray` | Image data of the first image | | `image2` | `Buffer`, `Uint8Array`, or `Uint8ClampedArray` | Image data of the second image | | `output` | `Buffer`, `Uint8Array`, `Uint8ClampedArray`, or `undefined` | Optional output buffer for SSIM map | | `width` | `number` | Width of the images in pixels | | `height` | `number` | Height of the images in pixels | | `options` | `HitchhikersSsimOptions` | Comparison options (optional) | ##### Options | Option | Type | Default | Description | | -------------- | --------- | ------------ | ------------------------------------------------------ | | `windowSize` | `number` | `11` | Size of the rectangular window | | `windowStride` | `number` | `windowSize` | Stride for window sliding (non-overlapping by default) | | `covPooling` | `boolean` | `true` | Use Coefficient of Variation pooling (recommended) | | `k1` | `number` | `0.01` | Algorithm parameter for luminance | | `k2` | `number` | `0.03` | Algorithm parameter for contrast | | `L` | `number` | `255` | Dynamic range of pixel values | ##### Returns `number` - SSIM score between 0 and 1 **Performance**: ~4x faster than standard SSIM using integral images for O(1) window computation. ### Score Interpretation | Score Range | Similarity Level | Description | | ----------- | ---------------- | ------------------------------------------------------- | | `1.0` | Identical | Images are identical or perceptually identical | | `0.99+` | Excellent | Extremely high similarity (minor compression artifacts) | | `0.95-0.99` | Very Good | High similarity (small compression or noise) | | `0.90-0.95` | Good | Noticeable but acceptable differences | | `0.80-0.90` | Fair | Significant but tolerable differences | | `<0.80` | Poor | Major structural differences | > **Info:** **Threshold Guidelines:** - Use threshold `>0.99` for strict visual regression testing - Use threshold `>0.95` for standard visual regression testing - Scores below `0.90` indicate substantial visual differences ## Usage Examples ```typescript import ssim from "@blazediff/ssim/ssim"; // Basic usage const score = ssim(img1.data, img2.data, undefined, width, height); // With SSIM map output const output = new Uint8ClampedArray(width * height * 4); const score = ssim(img1.data, img2.data, output, width, height); // With custom options const score = ssim(img1.data, img2.data, undefined, width, height, { windowSize: 8, k1: 0.01, k2: 0.03, }); ``` ```typescript import msssim from "@blazediff/ssim/msssim"; // Basic usage const score = msssim(img1.data, img2.data, undefined, width, height); // With SSIM map at finest scale const output = new Uint8ClampedArray(width * height * 4); const score = msssim(img1.data, img2.data, output, width, height); // With custom scales and weights const score = msssim(img1.data, img2.data, undefined, width, height, { scales: 3, weights: [0.33, 0.33, 0.34], method: "sum", }); ``` ```typescript import hitchhikersSSIM from "@blazediff/ssim/hitchhikers-ssim"; // Basic usage with CoV pooling (recommended) const score = hitchhikersSSIM(img1.data, img2.data, undefined, width, height); // With mean pooling (traditional) const score = hitchhikersSSIM(img1.data, img2.data, undefined, width, height, { covPooling: true, }); // With custom window and stride const score = hitchhikersSSIM(img1.data, img2.data, undefined, width, height, { windowSize: 16, windowStride: 8, // Overlapping windows covPooling: true, }); ``` ## CLI Usage All three variants are available via the `@blazediff/cli` CLI: ```bash # Standard SSIM blazediff-cli ssim image1.png image2.png # MS-SSIM blazediff-cli msssim image1.png image2.png # Hitchhiker's SSIM blazediff-cli hitchhikers-ssim image1.png image2.png # With options blazediff-cli hitchhikers-ssim image1.png image2.png --window-size 16 --no-cov-pooling ``` ## Links - [GitHub Repository](https://github.com/teimurjan/blazediff) - [NPM Package](https://www.npmjs.com/package/@blazediff/ssim) - [SSIM Paper](https://ieeexplore.ieee.org/document/1284395) - Wang et al. (2004) - [MS-SSIM Paper](https://ieeexplore.ieee.org/document/1292216) - Wang et al. (2003) - [Hitchhiker's SSIM Paper](https://ieeexplore.ieee.org/document/9345560) - Venkataramanan et al. (2021) - [Examples →](/examples/image-comparison) --- # @blazediff/gmsd Fast single-threaded GMSD (Gradient Magnitude Similarity Deviation) metric for perceptual image quality assessment. Perfect for CI visual testing where you need a similarity score rather than pixel-by-pixel differences. ## Installation ```sh npm install @blazediff/gmsd ``` ## Features - **Gradient-based** - Measures structural similarity via gradient magnitudes, tolerant to compression artifacts - **GMS map output** - Optional grayscale visualization of gradient similarity [Mathematical details (FORMULA.md)](https://github.com/teimurjan/blazediff/blob/main/packages/gmsd/FORMULA.md) ## API Reference ### `gmsd(image1, image2, output, width, height, options?)` Compares two images using GMSD metric and returns a similarity score. #### Parameters | Parameter | Type | Description | | --------- | -------------------------------------------- | ------------------------------------------------ | | `image1` | `Buffer`, `Uint8Array`, or `Uint8ClampedArray` | Image data of the first image | | `image2` | `Buffer`, `Uint8Array`, or `Uint8ClampedArray` | Image data of the second image | | `output` | `Buffer`, `Uint8Array`, `Uint8ClampedArray`, or `undefined` | Optional output buffer for GMS map visualization | | `width` | `number` | Width of the images in pixels | | `height` | `number` | Height of the images in pixels | | `options` | `GmsdOptions` | Comparison options (optional) | ##### Options | Option | Type | Default | Description | | ------------ | -------- | ------- | ----------------------------------------------------- | | `downsample` | `0` or `1` | `0` | Downsample factor (0 = no downsampling, 1 = half) | | `c` | `number` | `170` | Constant for numerical stability (tuned for Prewitt) | ##### Returns `number` - Difference score between 0 and 1: - `0.0` - Images are identical or perceptually identical - `0.0-0.05` - Very low difference (minor compression artifacts) - `0.05-0.15` - Low difference (noticeable but small changes) - `0.15-0.35` - Moderate similarity (significant structural differences) - `>0.35` - High difference (major differences) > **Info:** **Score Guidelines:** - Use threshold `>0.0` for strict regression testing - Use threshold `>0.15` for loose regression testing with compression - Scores below `0.35` indicate substantial visual differences ## Links - [GitHub Repository](https://github.com/teimurjan/blazediff) - [NPM Package](https://www.npmjs.com/package/@blazediff/gmsd) - [FORMULA.md](https://github.com/teimurjan/blazediff/blob/main/packages/gmsd/FORMULA.md) - Mathematical foundation - [Original GMSD Paper](http://www4.comp.polyu.edu.hk/~cslzhang/IQA/TIP_IQA_GMSD.pdf) - Xue et al. (2014) - [Examples →](/examples/image-comparison) --- # @blazediff/object Structural object comparison with path tracking, cycle detection, and CREATE/REMOVE/CHANGE types. ## Installation ```sh npm install @blazediff/object ``` ## Features - **Path tracking** for nested modifications - **Handles** primitives, objects, arrays, dates, regex, and circular references - **Consistent shapes** for V8 optimization (type, path, value, oldValue) ## Quick Start ```javascript import diff from '@blazediff/object'; const oldObj = { name: "John", age: 30, city: "NYC", skills: ["JavaScript", "TypeScript"] }; const newObj = { name: "John", age: 31, city: "San Francisco", skills: ["JavaScript", "TypeScript", "Go"], active: true }; const changes = diff(oldObj, newObj); console.log(changes); ``` **Output:** ```json [ { "type": 2, "path": ["age"], "value": 31, "oldValue": 30 }, { "type": 2, "path": ["city"], "value": "San Francisco", "oldValue": "NYC" }, { "type": 0, "path": ["skills", 2], "value": "Go", "oldValue": undefined }, { "type": 0, "path": ["active"], "value": true, "oldValue": undefined } ] ``` ## API Reference ### `diff(oldObj, newObj, options?)` Compares two objects and returns an array of differences. #### Parameters | Parameter | Type | Description | | --------- | -------- | ------------------------------------- | | `oldObj` | `any` | The original object to compare from | | `newObj` | `any` | The new object to compare to | | `options` | `object` | Configuration options (optional) | #### Options | Option | Type | Default | Description | | --------------- | --------- | ------- | ---------------------------------------------- | | `detectCycles` | `boolean` | `true` | Enable circular reference detection | #### Returns Returns `Difference[]` - Array of difference objects with consistent structure: ```typescript interface Difference { type: DifferenceType; path: (string | number)[]; value: any; oldValue: any; } ``` ### Difference Types > **Info:** **Difference Types** are represented as numbers for optimal performance: | Type | Name | Description | | ---- | -------- | -------------------------------------- | | `0` | `CREATE` | Property or array element was added | | `1` | `REMOVE` | Property or array element was deleted | | `2` | `CHANGE` | Property or array element was modified | All difference objects maintain consistent shape with `type`, `path`, `value`, and `oldValue` fields for optimal V8 performance. ## Links - [GitHub Repository](https://github.com/teimurjan/blazediff) - [NPM Package](https://www.npmjs.com/package/@blazediff/object) - [Examples →](/examples/object-comparison) --- # @blazediff/cli CLI for image comparison. Wraps the native Rust binary (fastest), JS pixel diff, GMSD, SSIM, MS-SSIM, and Hitchhiker's SSIM. [View Detailed Benchmarks](https://github.com/teimurjan/blazediff/blob/main/BENCHMARKS.md) ## Installation ### Global Installation ```sh npm install -g @blazediff/cli ``` ### Local Installation ```sh npm install --save-dev @blazediff/cli ``` ### Using npx ```sh npx blazediff-cli image1.png image2.png diff.png ``` ## Available Commands ### `blazediff-cli core-native` (default) Native Rust binary with SIMD optimization. The fastest option - **3-4x faster** than odiff on large images. #### Basic Usage ```bash # Default command (core-native) blazediff-cli image1.png image2.png diff.png # Or explicitly blazediff-cli core-native image1.png image2.png diff.png ``` #### With Options ```bash blazediff-cli image1.png image2.png diff.png --threshold 0.05 --antialiasing ``` #### Options ```bash blazediff-cli core-native [output] [options] Options: -t, --threshold Color difference threshold (0-1, default: 0.1) -a, --antialiasing Enable anti-aliasing detection --diff-mask Output only differences (transparent background) -c, --compression PNG compression level (0-9, default: 0) --interpret Run structured interpretation (region detection + classification) --output-format Output format: png (default) or html (interpret report) -h, --help Display help ``` #### Exit Codes - `0` - Images are identical - `1` - Images have differences - `2` - Error (file not found, invalid format, etc.) ```bash blazediff-cli image1.png image2.png diff.png if [ $? -eq 0 ]; then echo "Images match!" else echo "Images differ!" fi ``` > **Info:** **Why so fast?** Uses a two-pass block-based algorithm with SIMD acceleration (NEON on ARM, SSE4.1 on x86). The cold pass quickly identifies unchanged blocks, then the hot pass only processes changed regions. #### Interpret Mode Add `--interpret` to get structured diff analysis — region detection, content-aware classification (Addition, Deletion, Shift, ContentChange, ColorChange, RenderingNoise), severity scoring, and human-readable summaries. ```bash # JSON output to stdout blazediff-cli image1.png image2.png --interpret # With diff image + interpretation blazediff-cli image1.png image2.png diff.png --interpret # HTML report blazediff-cli image1.png image2.png report.html --output-format html ``` ##### Example ```bash $ blazediff-cli image1.png image2.png --interpret { "summary": "Moderate visual change detected (1.87% of image, 10 regions).\n...", "severity": "Medium", "diffPercentage": 1.87, "regions": [...] } $ blazediff-cli image1.png image2.png report.html --output-format html # writes report.html with side-by-side images and clickable region rows ``` ### `blazediff-cli core` Pure JavaScript pixel-by-pixel comparison. Slower than `core-native` but offers more customization options like custom diff colors and color spaces. #### Basic Usage ```bash blazediff-cli core image1.png image2.png ``` #### Save Diff Image ```bash blazediff-cli core image1.png image2.png --output diff.png ``` #### Options ```bash blazediff-cli core [options] Options: -o, --output Output diff image path -t, --threshold Matching threshold (0-1, default: 0.1) -a, --alpha Opacity of original in diff (0-1, default: 0.1) --diff-color Color for different pixels (default: 255,0,0) --aa-color Color for anti-aliased pixels (default: 255,255,0) --include-aa Include anti-aliased pixels in diff count --diff-mask Output diff mask with transparent background --codec Image codec (pngjs, sharp, jsquash-png) -h, --help Display help ``` #### Exit Codes - `0` - Images are identical or within threshold - `1` - Images have differences beyond threshold ```bash blazediff-cli core image1.png image2.png if [ $? -eq 0 ]; then echo "Images match!" else echo "Images differ!" fi ``` ### `blazediff-cli gmsd` GMSD (Gradient Magnitude Similarity Deviation) perceptual quality assessment. Returns a similarity score from 0-1. #### Basic Usage ```bash blazediff-cli gmsd image1.png image2.png ``` #### Save GMS Map ```bash blazediff-cli gmsd image1.png image2.png --output gms-map.png ``` #### Options ```bash blazediff-cli gmsd [options] Options: -o, --output Output GMS map image path --downsample Downsample factor (0 or 1, default: 0) -h, --help Display help ``` #### Output Prints the GMSD score (lower = more similar): - `0.0` - Images are identical - `0.0-0.05` - Very low difference - `0.05-0.15` - Low difference - `0.15-0.35` - Moderate difference - `>0.35` - High difference #### Example ```bash $ blazediff-cli gmsd reference.png test.png GMSD: 0.0234 $ blazediff-cli gmsd reference.png test.png --output gms-map.png GMSD: 0.0234 GMS map saved to: gms-map.png ``` ### `blazediff-cli ssim` Standard SSIM (Structural Similarity Index) with Gaussian weighting. MATLAB-compatible with \<0.01% error. #### Basic Usage ```bash blazediff-cli ssim image1.png image2.png ``` #### Save SSIM Map ```bash blazediff-cli ssim image1.png image2.png --output ssim-map.png ``` #### Options ```bash blazediff-cli ssim [options] Options: -o, --output Output SSIM map image path --window-size Gaussian window size (default: 11) --k1 Algorithm parameter (default: 0.01) --k2 Algorithm parameter (default: 0.03) -h, --help Display help ``` #### Output Prints the SSIM score (higher = more similar): - `1.0` - Identical images - `0.99+` - Excellent similarity - `0.95-0.99` - Very good similarity - `0.90-0.95` - Good similarity - `0.80-0.90` - Fair similarity - `<0.80` - Poor similarity #### Example ```bash $ blazediff-cli ssim reference.png test.png SSIM: 0.9876 $ blazediff-cli ssim reference.png test.png --output ssim-map.png --window-size 8 SSIM: 0.9823 SSIM map saved to: ssim-map.png ``` ### `blazediff-cli msssim` MS-SSIM (Multi-Scale SSIM) for better perceptual correlation. Analyzes images at multiple scales. #### Basic Usage ```bash blazediff-cli msssim image1.png image2.png ``` #### Save MS-SSIM Map ```bash blazediff-cli msssim image1.png image2.png --output msssim-map.png ``` #### Options ```bash blazediff-cli msssim [options] Options: -o, --output Output SSIM map at finest scale --window-size Gaussian window size (default: 11) --scales Number of scales (default: 5) --method Aggregation method: product or sum (default: product) -h, --help Display help ``` Prints the MS-SSIM score (higher = more similar, same scale as SSIM). ```bash $ blazediff-cli msssim reference.png test.png MS-SSIM: 0.9912 ``` ### `blazediff-cli hitchhikers-ssim` Hitchhiker's SSIM - fast rectangular-window SSIM using integral images. ~4x faster than standard SSIM. #### Basic Usage ```bash blazediff-cli hitchhikers-ssim image1.png image2.png ``` #### Save SSIM Map ```bash blazediff-cli hitchhikers-ssim image1.png image2.png --output ssim-map.png ``` #### Options ```bash blazediff-cli hitchhikers-ssim [options] Options: -o, --output Output SSIM map image path --window-size Rectangular window size (default: 11) --window-stride Window stride (default: window-size) --no-cov-pooling Disable CoV pooling (use mean pooling) --k1 Algorithm parameter (default: 0.01) --k2 Algorithm parameter (default: 0.03) -h, --help Display help ``` Prints the SSIM score (higher = more similar, same scale as SSIM). CoV pooling is enabled by default. ```bash $ blazediff-cli hitchhikers-ssim reference.png test.png SSIM: 0.9597 ``` ## When to Use Each Algorithm | Algorithm | Best for | |-----------|----------| | `core-native` (default) | Maximum speed, CI/CD, large images, `--interpret` analysis | | `core` | Custom diff colors, color space control, no native deps | | `gmsd` | Similarity score, compression-tolerant | | `ssim` | MATLAB-compatible, research | | `msssim` | Multi-scale, varying resolutions | | `hitchhikers-ssim` | Fast SSIM (~4x), large batches | ## Links - [GitHub Repository](https://github.com/teimurjan/blazediff) - [NPM Package](https://www.npmjs.com/package/@blazediff/cli) - [Examples →](/examples/image-comparison) --- # @blazediff/core-native The fastest single-threaded image diff in the world. Native Rust implementation with SIMD optimization, **3-4x faster** and **3x smaller** than [odiff](https://github.com/dmtrKovalenko/odiff). [View Detailed Benchmarks](https://github.com/teimurjan/blazediff/blob/main/BENCHMARKS.md) > **Warning:** This package was previously published as [`@blazediff/bin`](https://www.npmjs.com/package/@blazediff/bin), which is now deprecated. Please use `@blazediff/core-native` instead. ## Installation ```sh npm install @blazediff/core-native ``` Also available as a Rust crate: [`cargo install blazediff`](https://crates.io/crates/blazediff) Pre-built binaries are included for all major platforms - no compilation required: - macOS ARM64 (Apple Silicon) & x64 (Intel) - Linux ARM64 & x64 - Windows ARM64 & x64 ## Features - **PNG, JPEG & QOI support** - auto-detected by file extension - **3-4x faster** than odiff, **3x smaller** binaries (~700KB-900KB vs ~2-3MB) - **SIMD-accelerated** - NEON on ARM, SSE4.1 on x86 - **Block-based optimization** - skips unchanged regions - **Interpret mode** - region detection, classification, severity scoring, human-readable summaries ### Vendored Libraries - [libspng](https://libspng.org/) - Fast PNG decoding/encoding with SIMD - [libjpeg-turbo](https://libjpeg-turbo.org/) - High-performance JPEG codec with SIMD - [qoi](https://github.com/aldanor/qoi-rust) - QOI (Quite OK Image) format for fast lossless compression ## API Reference ### `compare(basePath, comparePath, diffOutput, options?)` Compares two images (PNG, JPEG, or QOI) and generates a diff image. Format is auto-detected from file extension. #### Parameters | Parameter | Type | Description | | ------------- | ----------------- | ------------------------------------- | | `basePath` | `string` | Path to the base/expected image | | `comparePath` | `string` | Path to the comparison/actual image | | `diffOutput` | `string` | Path where the diff image will be saved | | `options` | `BlazeDiffOptions` | Comparison options (optional) | ##### Options | Option | Type | Default | Description | | ------------------ | --------- | ------- | -------------------------------------------------------- | | `threshold` | `number` | `0.1` | Color difference threshold (0.0-1.0). Lower = more strict | | `antialiasing` | `boolean` | `false` | Enable anti-aliasing detection | | `diffMask` | `boolean` | `false` | Output only differences with transparent background | | `interpret` | `boolean` | `false` | Run structured interpretation (region detection + classification) | | `outputFormat` | `"png" \| "html"` | `"png"` | Output format — `"html"` generates an interactive interpret report | #### Return Types ```typescript type BlazeDiffResult = | { match: true; interpretation?: InterpretResult } | { match: false; reason: "layout-diff" } | { match: false; reason: "pixel-diff"; diffCount: number; diffPercentage: number; interpretation?: InterpretResult } | { match: false; reason: "file-not-exists"; file: string }; ``` When `interpret: true` or `outputFormat: "html"`, the result includes an `interpretation` field with structured analysis. > **Info:** **Threshold Guidelines:** - `0.0` - Exact match only - `0.05` - Strict comparison - `0.1` - Default balanced comparison - `0.2` - Lenient comparison ## Usage ### Programmatic API ```typescript import { compare } from '@blazediff/core-native'; const result = await compare('expected.png', 'actual.png', 'diff.png', { threshold: 0.1, antialiasing: true, }); if (result.match) { console.log('Images are identical!'); } else if (result.reason === 'pixel-diff') { console.log(`${result.diffCount} pixels differ (${result.diffPercentage.toFixed(2)}%)`); } else if (result.reason === 'layout-diff') { console.log('Images have different dimensions'); } ``` ### CLI Usage ```bash # Compare two PNG images npx blazediff expected.png actual.png diff.png # Compare two JPEG images npx blazediff expected.jpg actual.jpg diff.jpg # Compare two QOI images npx blazediff expected.qoi actual.qoi diff.qoi # Mixed formats (PNG input, QOI output - recommended for smallest diff files) npx blazediff expected.png actual.png diff.qoi # With options npx blazediff expected.png actual.png diff.png --threshold 0.05 --antialiasing # With higher PNG compression (smaller output file, slower) npx blazediff expected.png actual.png diff.png -c 6 # With JPEG quality setting npx blazediff expected.jpg actual.jpg diff.jpg -q 85 # Output as text format npx blazediff expected.png actual.png diff.png --output-format text ``` ### CLI Options ```bash blazediff [OPTIONS] [OUTPUT] Arguments: First image path (PNG, JPEG, or QOI) Second image path (PNG, JPEG, or QOI) [OUTPUT] Output diff image path (optional, format detected from extension) Options: -t, --threshold Color difference threshold (0.0-1.0) [default: 0.1] -a, --antialiasing Enable anti-aliasing detection --diff-mask Output only differences (transparent background) -c, --compression PNG compression level (0-9, 0=fastest, 9=smallest) [default: 0] -q, --quality JPEG quality (1-100) [default: 90] --interpret Run structured interpretation after diff --output-format Output format (json or text) [default: json] -h, --help Print help -V, --version Print version ``` ### Supported Formats | Format | Extensions | Notes | |--------|------------|-------| | PNG | `.png` | Lossless, supports transparency | | JPEG | `.jpg`, `.jpeg` | Lossy, smaller file sizes | | QOI | `.qoi` | Fast lossless, ideal for diff outputs (12x smaller than uncompressed PNG) | Input images can be mixed formats (e.g., compare PNG to JPEG). Output format is determined by the output file extension. > **Tip:** **Use QOI for diff outputs:** QOI excels at encoding diff images with large uniform areas, producing files 12x smaller than PNG (level 0) while being faster to encode. ### Exit Codes - `0` - Images are identical - `1` - Images differ (includes layout/size mismatch) - `2` - Error (file not found, invalid format, etc.) ### Interpret Structured diff analysis. Takes two images, returns classified change regions with human-readable summaries. **Pipeline:** change mask → morph close → connected components → per-region evidence extraction → classify → describe **Evidence per region:** - Dual-image gradient comparison (edges in both images + spatial correlation) - YIQ color delta with uniformity analysis (mean, max, stddev) - Background distance (changed pixels vs local unchanged pixels) - Shape statistics (fill ratio, border density, occupancy) **Six-label decision tree:** | Type | Signal | |---|---| | `RenderingNoise` | Tiny (≤25px) or sparse + low color delta | | `Addition` | Blends with background in img1, distinct in img2 | | `Deletion` | Distinct in img1, blends with background in img2 | | `ColorChange` | Edge structure preserved across both images + uniform color shift | | `ContentChange` | Fallback — structural change | | `Shift` | Post-hoc: matched Addition+Deletion pair with similar luminance | ```typescript import { compare, interpret } from '@blazediff/core-native'; const result = await interpret('expected.png', 'actual.png'); console.log(result.summary); // "Moderate visual change detected (1.87% of image, 10 regions). // Content changed: 4 regions (bottom, center). // Content added: 3 regions (right, bottom, bottom-left)." // Via compare() const diff = await compare('expected.png', 'actual.png', 'diff.png', { interpret: true, }); // Interactive HTML report await compare('expected.png', 'actual.png', 'report.html', { outputFormat: 'html', }); ``` ```bash npx blazediff expected.png actual.png --interpret npx blazediff expected.png actual.png report.html --output-format html ``` Severity: Low (<1%), Medium (1-10%), High (>10%). See [Interpret example →](/examples/interpret) for interactive demo. ## Performance Benchmarked on Apple M1 Max with 5600x3200 4K images: | Tool | Time | Comparison | |------|------|------------| | **blazediff** | ~327ms | - | | odiff | ~1215ms | 3.7x slower | Binary sizes (stripped, LTO optimized): | Platform | blazediff | odiff | |----------|-----------|-------| | macOS ARM64 | 702 KB | 2.2 MB | | Linux x64 | 869 KB | 2.9 MB | | Windows x64 | 915 KB | 3.0 MB | > **Info:** **Why so fast?** BlazeDiff uses a two-pass block-based algorithm with SIMD acceleration. The cold pass quickly identifies unchanged 8x8 blocks using 32-bit integer comparison, then the hot pass only processes changed regions with YIQ perceptual color difference. ## Links - [GitHub Repository](https://github.com/teimurjan/blazediff) - [NPM Package](https://www.npmjs.com/package/@blazediff/core-native) - [Rust Crate](https://crates.io/crates/blazediff) - [Examples →](/examples/image-comparison) --- # @blazediff/matcher Core snapshot comparison logic that powers [@blazediff/jest](/docs/jest), [@blazediff/vitest](/docs/vitest), and [@blazediff/bun](/docs/bun). Most users should use those framework packages directly. ## Installation ```sh npm install @blazediff/matcher ``` ## Quick Start ```typescript import { getOrCreateSnapshot } from '@blazediff/matcher'; const result = await getOrCreateSnapshot( imageBuffer, // or file path { method: 'core', failureThreshold: 0.01, failureThresholdType: 'percent', }, { testPath: '/path/to/test.spec.ts', testName: 'should render correctly', } ); if (result.pass) { console.log(`✓ Snapshot ${result.snapshotStatus}`); } else { console.log(`✗ ${result.diffPercentage}% different`); } ``` ## API Reference ### getOrCreateSnapshot(received, options, testContext) Main function for snapshot comparison and management. | Parameter | Type | Description | |-----------|------|-------------| | `received` | `ImageInput` | Image to compare (file path or buffer with dimensions) | | `options` | `MatcherOptions` | Comparison options | | `testContext` | `TestContext` | Test information (testPath, testName) | **Returns**: `Promise` ### MatcherOptions Core comparison options: | Option | Type | Default | Description | |--------|------|---------|-------------| | `method` | `ComparisonMethod` | - | Comparison algorithm: `'core'`, `'core-native'`, `'ssim'`, `'msssim'`, `'hitchhikers-ssim'`, `'gmsd'` | | `failureThreshold` | `number` | `0` | Number of pixels or percentage difference allowed | | `failureThresholdType` | `'pixel' \| 'percent'` | `'pixel'` | How to interpret failureThreshold | | `snapshotsDir` | `string` | `'__snapshots__'` | Directory to store snapshots (relative to test file) | | `snapshotIdentifier` | `string` | auto-generated | Custom identifier for the snapshot file | | `updateSnapshots` | `boolean` | `false` | Force update snapshots | Method-specific options: | Option | Type | Default | Description | |--------|------|---------|-------------| | `threshold` | `number` | `0.1` | Color difference threshold (0-1) for `core`/`core-native` methods | | `antialiasing` | `boolean` | `false` | Enable anti-aliasing detection (`core-native` method) | | `includeAA` | `boolean` | `false` | Include anti-aliased pixels in diff count (`core` method) | | `windowSize` | `number` | `11` | Window size for SSIM variants | | `k1` | `number` | `0.01` | k1 constant for SSIM | | `k2` | `number` | `0.03` | k2 constant for SSIM | | `downsample` | `0 \| 1` | `0` | Downsample factor for GMSD | | `runInWorker` | `boolean` | `true` | Run image I/O and comparison in a worker thread for better performance | ### ComparisonResult Result object returned by `getOrCreateSnapshot`: | Field | Type | Description | |-------|------|-------------| | `pass` | `boolean` | Whether the comparison passed | | `message` | `string` | Human-readable message describing the result | | `snapshotStatus` | `SnapshotStatus` | Status: `'added'`, `'matched'`, `'updated'`, `'failed'` | | `diffCount` | `number?` | Number of different pixels (pixel-based methods) | | `diffPercentage` | `number?` | Percentage of different pixels | | `score` | `number?` | Similarity score (SSIM: 1 = identical, GMSD: 0 = identical) | | `baselinePath` | `string?` | Path to baseline snapshot | | `receivedPath` | `string?` | Path to received image (saved on failure) | | `diffPath` | `string?` | Path to diff visualization | ### ImageInput ```typescript type ImageInput = | string // File path | { data: Uint8Array | Uint8ClampedArray | Buffer; width: number; height: number; }; ``` ## Comparison Methods Available methods: `core`, `core-native`, `ssim`, `msssim`, `hitchhikers-ssim`, `gmsd`. See [@blazediff/core](/docs/core), [@blazediff/core-native](/docs/core-native), [@blazediff/ssim](/docs/ssim), and [@blazediff/gmsd](/docs/gmsd) for algorithm details. ## Links - [GitHub Repository](https://github.com/teimurjan/blazediff) - [NPM Package](https://www.npmjs.com/package/@blazediff/matcher) - [@blazediff/core](/docs/core) - Core comparison algorithm - [@blazediff/ssim](/docs/ssim) - SSIM algorithms - [@blazediff/gmsd](/docs/gmsd) - GMSD algorithm - [@blazediff/core-native](/docs/core-native) - Rust-native bindings --- # @blazediff/jest `toMatchImageSnapshot()` for Jest. Auto-registers on import, tracks snapshot state, supports all comparison methods. ## Installation ```sh npm install --save-dev @blazediff/jest ``` **Peer dependencies**: Jest >= 27.0.0 ## Quick Start ```typescript import '@blazediff/jest'; describe('Visual Regression Tests', () => { it('should match screenshot', async () => { const screenshot = await page.screenshot(); await expect(screenshot).toMatchImageSnapshot({ method: 'core', }); }); }); ``` > **Info:** The matcher auto-registers when you import `@blazediff/jest`. No additional setup required! ## API Reference ### toMatchImageSnapshot(options?) Jest matcher for image snapshot comparison. ```typescript await expect(imageInput).toMatchImageSnapshot(options?); ``` #### Parameters | Parameter | Type | Description | |-----------|------|-------------| | `imageInput` | `ImageInput` | Image to compare (file path or buffer with dimensions) | | `options` | `Partial` | Optional comparison options | #### Options | Option | Type | Default | Description | |--------|------|---------|-------------| | `method` | `ComparisonMethod` | `'core'` | Comparison algorithm | | `failureThreshold` | `number` | `0` | Allowed difference (pixels or percentage) | | `failureThresholdType` | `'pixel' \| 'percent'` | `'pixel'` | Threshold interpretation | | `snapshotsDir` | `string` | `'__snapshots__'` | Snapshot directory (relative to test) | | `snapshotIdentifier` | `string` | auto-generated | Custom snapshot filename | | `updateSnapshots` | `boolean` | `false` | Force snapshot update | | `threshold` | `number` | `0.1` | Color threshold for `core`/`core-native` (0-1) | | `runInWorker` | `boolean` | `true` | Run comparison in worker thread for better performance | See [@blazediff/matcher](/docs/matcher) for all available options. ## Comparison Methods ### `core` - Pure JavaScript (Default) ```typescript await expect(screenshot).toMatchImageSnapshot({ method: 'core', // Fast, works with buffers and file paths }); ``` ### `core-native` - Rust Native (Fastest) ```typescript await expect('/path/to/image.png').toMatchImageSnapshot({ method: 'core-native', // Requires file paths }); ``` ### `ssim` - Perceptual Similarity ```typescript await expect(screenshot).toMatchImageSnapshot({ method: 'ssim', // Structural similarity }); ``` ### `gmsd` - Gradient-based ```typescript await expect(screenshot).toMatchImageSnapshot({ method: 'gmsd', // Detects structural changes }); ``` ## Usage Patterns ### Basic Snapshot Test ```typescript import '@blazediff/jest'; it('renders homepage correctly', async () => { const screenshot = await browser.screenshot(); await expect(screenshot).toMatchImageSnapshot({ method: 'core', }); }); ``` ### Custom Thresholds Allow small differences while catching regressions: ```typescript // Allow up to 100 pixels difference await expect(screenshot).toMatchImageSnapshot({ method: 'core', failureThreshold: 100, failureThresholdType: 'pixel', }); // Allow up to 0.5% difference await expect(screenshot).toMatchImageSnapshot({ method: 'core', failureThreshold: 0.5, failureThresholdType: 'percent', }); ``` > **Note:** Use percentage-based thresholds for responsive images that may change size across different viewports. ### Update Snapshots ```bash # Update all failing snapshots jest -u # Update snapshots for specific test file jest -u src/components/Button.test.tsx # Update snapshots matching pattern jest -u --testNamePattern="renders correctly" # Using environment variable JEST_UPDATE_SNAPSHOTS=true jest ``` Programmatically: ```typescript await expect(screenshot).toMatchImageSnapshot({ method: 'core', updateSnapshots: true, }); ``` ### Custom Snapshot Directory ```typescript await expect(screenshot).toMatchImageSnapshot({ method: 'core', snapshotsDir: '__image_snapshots__', // Custom directory }); ``` ### Custom Snapshot Identifier ```typescript await expect(screenshot).toMatchImageSnapshot({ method: 'core', snapshotIdentifier: 'homepage-desktop-1920x1080', }); ``` ### Negation Test that images are intentionally different: ```typescript const before = await page.screenshot(); await page.click('.toggle-theme'); const after = await page.screenshot(); // Assert that theme toggle changed the UI await expect(after).not.toMatchImageSnapshot({ method: 'core', snapshotIdentifier: 'before-theme-toggle', }); ``` ## Configuration ### Global Setup To avoid importing in every test file, configure Jest: ```javascript // jest.config.js module.exports = { setupFilesAfterEnv: ['/jest.setup.js'], }; ``` ```javascript // jest.setup.js import '@blazediff/jest'; ``` ## Links - [GitHub Repository](https://github.com/teimurjan/blazediff) - [NPM Package](https://www.npmjs.com/package/@blazediff/jest) - [@blazediff/matcher](/docs/matcher) - Core matcher logic - [Jest Documentation](https://jestjs.io/) --- # @blazediff/vitest `toMatchImageSnapshot()` for Vitest. Auto-registers on import, tracks snapshot state, supports all comparison methods. ## Installation ```sh npm install --save-dev @blazediff/vitest ``` **Peer dependencies**: Vitest >= 1.0.0 ## Quick Start ```typescript import { expect, it } from 'vitest'; import '@blazediff/vitest'; it('should match screenshot', async () => { const screenshot = await page.screenshot(); await expect(screenshot).toMatchImageSnapshot({ method: 'core', }); }); ``` > **Info:** The matcher auto-registers when you import `@blazediff/vitest`. No additional setup required! ## API Reference ### toMatchImageSnapshot(options?) Vitest matcher for image snapshot comparison. ```typescript await expect(imageInput).toMatchImageSnapshot(options?); ``` #### Parameters | Parameter | Type | Description | |-----------|------|-------------| | `imageInput` | `ImageInput` | Image to compare (file path or buffer with dimensions) | | `options` | `Partial` | Optional comparison options | #### Options | Option | Type | Default | Description | |--------|------|---------|-------------| | `method` | `ComparisonMethod` | `'core'` | Comparison algorithm | | `failureThreshold` | `number` | `0` | Allowed difference (pixels or percentage) | | `failureThresholdType` | `'pixel' \| 'percent'` | `'pixel'` | Threshold interpretation | | `snapshotsDir` | `string` | `'__snapshots__'` | Snapshot directory (relative to test) | | `snapshotIdentifier` | `string` | auto-generated | Custom snapshot filename | | `updateSnapshots` | `boolean` | `false` | Force snapshot update | | `threshold` | `number` | `0.1` | Color threshold for `core`/`core-native` (0-1) | | `runInWorker` | `boolean` | `true` | Run comparison in worker thread for better performance | See [@blazediff/matcher](/docs/matcher) for all available options. ## Comparison Methods ### `core` - Pure JavaScript (Default) ```typescript await expect(screenshot).toMatchImageSnapshot({ method: 'core', // Fast, works with buffers and file paths }); ``` ### `core-native` - Rust Native (Fastest) ```typescript await expect('/path/to/image.png').toMatchImageSnapshot({ method: 'core-native', // Requires file paths }); ``` ### `ssim` - Perceptual Similarity ```typescript await expect(screenshot).toMatchImageSnapshot({ method: 'ssim', // Structural similarity }); ``` ### `gmsd` - Gradient-based ```typescript await expect(screenshot).toMatchImageSnapshot({ method: 'gmsd', // Detects structural changes }); ``` ## Usage Patterns ### Basic Snapshot Test ```typescript import { expect, it } from 'vitest'; import '@blazediff/vitest'; it('renders homepage correctly', async () => { const screenshot = await browser.screenshot(); await expect(screenshot).toMatchImageSnapshot({ method: 'core', }); }); ``` ### Custom Thresholds Allow small differences while catching regressions: ```typescript // Allow up to 100 pixels difference await expect(screenshot).toMatchImageSnapshot({ method: 'core', failureThreshold: 100, failureThresholdType: 'pixel', }); // Allow up to 0.5% difference await expect(screenshot).toMatchImageSnapshot({ method: 'core', failureThreshold: 0.5, failureThresholdType: 'percent', }); ``` > **Note:** Use percentage-based thresholds for responsive images that may change size across different viewports. ### Update Snapshots ```bash # Update all failing snapshots vitest -u # Update snapshots for specific test file vitest -u src/components/Button.test.ts # Update snapshots matching pattern vitest -u --testNamePattern="renders correctly" # Using environment variable VITEST_UPDATE_SNAPSHOTS=true vitest ``` Programmatically: ```typescript await expect(screenshot).toMatchImageSnapshot({ method: 'core', updateSnapshots: true, }); ``` ### Custom Snapshot Directory ```typescript await expect(screenshot).toMatchImageSnapshot({ method: 'core', snapshotsDir: '__image_snapshots__', // Custom directory }); ``` ### Custom Snapshot Identifier ```typescript await expect(screenshot).toMatchImageSnapshot({ method: 'core', snapshotIdentifier: 'homepage-desktop-1920x1080', }); ``` ### Negation Test that images are intentionally different: ```typescript const before = await page.screenshot(); await page.click('.toggle-theme'); const after = await page.screenshot(); // Assert that theme toggle changed the UI await expect(after).not.toMatchImageSnapshot({ method: 'core', snapshotIdentifier: 'before-theme-toggle', }); ``` ## Configuration ### Global Setup To avoid importing in every test file, configure Vitest: ```typescript // vitest.config.ts import { defineConfig } from 'vitest/config'; export default defineConfig({ test: { setupFiles: ['./vitest.setup.ts'], }, }); ``` ```typescript // vitest.setup.ts import '@blazediff/vitest'; ``` ## Links - [GitHub Repository](https://github.com/teimurjan/blazediff) - [NPM Package](https://www.npmjs.com/package/@blazediff/vitest) - [@blazediff/matcher](/docs/matcher) - Core matcher logic - [Vitest Documentation](https://vitest.dev/) --- # @blazediff/bun `toMatchImageSnapshot()` for Bun. Auto-registers on import, supports all comparison methods. ## Installation ```sh npm install --save-dev @blazediff/bun ``` **Peer dependencies**: Bun >= 1.0.0 ## Quick Start ```typescript import { expect, it } from 'bun:test'; import '@blazediff/bun'; it('should match screenshot', async () => { const screenshot = await page.screenshot(); await expect(screenshot).toMatchImageSnapshot({ method: 'core', snapshotIdentifier: 'homepage', }); }); ``` > **Info:** The matcher auto-registers when you import `@blazediff/bun`. No additional setup required! > **Warning:** Unlike Jest/Vitest, Bun has limited context exposure. Always provide a `snapshotIdentifier` for reliable snapshot management. ## API Reference ### toMatchImageSnapshot(options?) Bun test matcher for image snapshot comparison. ```typescript await expect(imageInput).toMatchImageSnapshot(options?); ``` #### Parameters | Parameter | Type | Description | |-----------|------|-------------| | `imageInput` | `ImageInput` | Image to compare (file path or buffer with dimensions) | | `options` | `Partial` | Optional comparison options | #### Options | Option | Type | Default | Description | |--------|------|---------|-------------| | `method` | `ComparisonMethod` | `'core'` | Comparison algorithm | | `snapshotIdentifier` | `string` | `'snapshot'` | **Required**: Snapshot filename identifier | | `failureThreshold` | `number` | `0` | Allowed difference (pixels or percentage) | | `failureThresholdType` | `'pixel' \| 'percent'` | `'pixel'` | Threshold interpretation | | `snapshotsDir` | `string` | `'__snapshots__'` | Snapshot directory (relative to test) | | `updateSnapshots` | `boolean` | `false` | Force snapshot update | | `threshold` | `number` | `0.1` | Color threshold for `core`/`core-native` (0-1) | | `runInWorker` | `boolean` | `true` | Run comparison in worker thread for better performance | See [@blazediff/matcher](/docs/matcher) for all available options. ## Comparison Methods ### `core` - Pure JavaScript (Default) ```typescript await expect(screenshot).toMatchImageSnapshot({ method: 'core', snapshotIdentifier: 'my-test', }); ``` ### `core-native` - Rust Native (Fastest) ```typescript await expect('/path/to/image.png').toMatchImageSnapshot({ method: 'core-native', snapshotIdentifier: 'my-test', }); ``` ### `ssim` - Perceptual Similarity ```typescript await expect(screenshot).toMatchImageSnapshot({ method: 'ssim', snapshotIdentifier: 'my-test', }); ``` ### `gmsd` - Gradient-based ```typescript await expect(screenshot).toMatchImageSnapshot({ method: 'gmsd', snapshotIdentifier: 'my-test', }); ``` ## Usage Patterns ### Basic Snapshot Test ```typescript import { expect, it } from 'bun:test'; import '@blazediff/bun'; it('renders homepage correctly', async () => { const screenshot = await browser.screenshot(); await expect(screenshot).toMatchImageSnapshot({ method: 'core', snapshotIdentifier: 'homepage', }); }); ``` ### Custom Thresholds Allow small differences while catching regressions: ```typescript // Allow up to 100 pixels difference await expect(screenshot).toMatchImageSnapshot({ method: 'core', snapshotIdentifier: 'homepage', failureThreshold: 100, failureThresholdType: 'pixel', }); // Allow up to 0.5% difference await expect(screenshot).toMatchImageSnapshot({ method: 'core', snapshotIdentifier: 'homepage', failureThreshold: 0.5, failureThresholdType: 'percent', }); ``` > **Note:** Use percentage-based thresholds for responsive images that may change size across different viewports. ### Update Snapshots ```bash # Update all failing snapshots (recommended) bun test --update-snapshots # Using environment variable BUN_UPDATE_SNAPSHOTS=true bun test ``` > **Info:** The Bun's `--update-snapshots` flag is consumed by Bun internally and won't update image snapshots. Programmatically: ```typescript await expect(screenshot).toMatchImageSnapshot({ method: 'core', snapshotIdentifier: 'homepage', updateSnapshots: true, }); ``` ### Custom Snapshot Directory ```typescript await expect(screenshot).toMatchImageSnapshot({ method: 'core', snapshotIdentifier: 'homepage', snapshotsDir: '__image_snapshots__', }); ``` ## Bun-Specific Notes Unlike Jest and Vitest, Bun has limited test context exposure. Always provide a `snapshotIdentifier`: ```typescript // Good await expect(screenshot).toMatchImageSnapshot({ method: 'core', snapshotIdentifier: 'my-component', }); // Avoid - falls back to generic "snapshot" await expect(screenshot).toMatchImageSnapshot({ method: 'core', }); ``` ## Links - [GitHub Repository](https://github.com/teimurjan/blazediff) - [NPM Package](https://www.npmjs.com/package/@blazediff/bun) - [@blazediff/matcher](/docs/matcher) - Core matcher logic - [Bun Documentation](https://bun.sh/) --- # @blazediff/ui Framework-agnostic Web Components for building image comparison interfaces. Works with any JavaScript framework or vanilla HTML. ## Installation ```sh npm install @blazediff/ui ``` ## Features - **Framework agnostic**: Works with React, Vue, Angular, Svelte, or Vanilla JS - **Web Components**: Standard-based custom elements - **Multiple modes**: Swipe, two-up, onion skin, and difference visualization - **Customizable**: Full styling control via CSS classes - **Accessible**: Keyboard navigation and proper event handling - **Lightweight**: Zero dependencies (except @blazediff/core) ## Quick Start ### HTML ```html ``` ### JavaScript ```javascript import "@blazediff/ui"; // The components are now available globally document.body.innerHTML = ` `; ``` ## API Reference ### `` Interactive swipe component for comparing two images with a draggable divider. #### Attributes | Attribute | Type | Default | Description | | ----------------- | -------- | ---------- | ------------------------------ | | `src1` | `string` | - | URL of the first image | | `src2` | `string` | - | URL of the second image | | `alt1` | `string` | `"Before"` | Alt text for the first image | | `alt2` | `string` | `"After"` | Alt text for the second image | | `class-container` | `string` | - | CSS class for the container | | `class-image1` | `string` | - | CSS class for the first image | | `class-image2` | `string` | - | CSS class for the second image | | `class-divider` | `string` | - | CSS class for the divider | #### Events | Event | Detail | Description | | ----------------- | ---------------------- | ------------------------------------------- | | `position-change` | `{ position: number }` | Fired when divider position changes (0-100) | ### `` Side-by-side comparison component with dimension information. #### Attributes | Attribute | Type | Default | Description | | ----------------------- | -------- | ------- | --------------------------------- | | `src1` | `string` | - | URL of the first image | | `src2` | `string` | - | URL of the second image | | `class-container` | `string` | - | CSS class for the main container | | `class-container-inner` | `string` | - | CSS class for the inner container | | `class-panel` | `string` | - | CSS class for each panel | | `class-image` | `string` | - | CSS class for images | | `class-dimension-info` | `string` | - | CSS class for dimension info text | #### Events | Event | Detail | Description | | --------------- | ------------------------------------------------------ | ------------------------------ | | `images-loaded` | `{ image1: {width, height}, image2: {width, height} }` | Fired when both images load | | `load-error` | `{ error }` | Fired when image loading fails | ### `` Overlay comparison with opacity control slider. #### Attributes | Attribute | Type | Default | Description | | ------------------------ | -------- | ------------ | -------------------------------- | | `src1` | `string` | - | URL of the base image | | `src2` | `string` | - | URL of the overlay image | | `opacity` | `number` | `50` | Initial opacity (0-100) | | `class-container` | `string` | - | CSS class for the main container | | `class-image-container` | `string` | - | CSS class for image container | | `class-image` | `string` | - | CSS class for images | | `class-slider-container` | `string` | - | CSS class for slider container | | `class-slider` | `string` | - | CSS class for the slider | | `class-slider-label` | `string` | - | CSS class for slider label | | `text-slider-label` | `string` | `"Opacity:"` | Text for the slider label | #### Events | Event | Detail | Description | | ---------------- | ------------------------------------------------------ | ------------------------------ | | `opacity-change` | `{ opacity: number }` | Fired when opacity changes | | `images-loaded` | `{ image1: {width, height}, image2: {width, height} }` | Fired when both images load | | `load-error` | `{ error }` | Fired when image loading fails | ### `` Shows pixel differences using the BlazeDiff comparison algorithm. #### Attributes | Attribute | Type | Default | Description | | ----------------- | --------- | ------- | --------------------------- | | `src1` | `string` | - | URL of the first image | | `src2` | `string` | - | URL of the second image | | `threshold` | `number` | `0.1` | Difference threshold (0-1) | | `include-aa` | `boolean` | `false` | Include anti-aliased pixels | | `alpha` | `number` | `0.1` | Opacity of original image | | `class-container` | `string` | - | CSS class for the container | | `class-canvas` | `string` | - | CSS class for the canvas | #### Events | Event | Detail | Description | | --------------- | ---------------------------------------------------------------- | ------------------------------- | | `diff-complete` | `{ diffCount: number, totalPixels: number, percentage: number }` | Fired when comparison completes | | `diff-error` | `{ error }` | Fired when comparison fails | ## Links - [GitHub Repository](https://github.com/teimurjan/blazediff) - [NPM Package](https://www.npmjs.com/package/@blazediff/ui) - [Examples →](/examples/web-components) --- # @blazediff/react React components for high-performance image comparison. Built with TypeScript and optimized for modern React applications. ## Installation ```sh npm install @blazediff/react ``` ## Features - **React 16.8+ compatible**: Works with hooks and modern React features - **TypeScript first**: Full type safety and IntelliSense - **Web Components based**: Wraps @blazediff/ui components for React - **Performance focused**: Built on top of high-performance web components - **Accessible**: Proper event handling and React patterns ## Quick Start ```jsx import { SwipeMode } from '@blazediff/react'; function App() { return ( console.log(position)} /> ); } ``` ## API Reference ### `` Interactive swipe component for comparing two images with a draggable divider. #### Props | Prop | Type | Default | Description | | ------------------ | --------------------------------- | ------- | ------------------------------------ | | `src1` | `string` (required) | - | URL of the first image | | `src2` | `string` (required) | - | URL of the second image | | `alt1` | `string` | - | Alt text for the first image | | `alt2` | `string` | - | Alt text for the second image | | `className` | `string` | - | CSS class for the root element | | `containerClassName` | `string` | - | CSS class for the container | | `image1ClassName` | `string` | - | CSS class for the first image | | `image2ClassName` | `string` | - | CSS class for the second image | | `dividerClassName` | `string` | - | CSS class for the divider | | `onPositionChange` | `(position: number) => void` | - | Callback when divider position changes | ### `` Side-by-side comparison component with dimension information. #### Props | Prop | Type | Default | Description | | ------------------------- | ----------------------------------------------------------- | ------- | --------------------------------- | | `src1` | `string` (required) | - | URL of the first image | | `src2` | `string` (required) | - | URL of the second image | | `className` | `string` | - | CSS class for the root element | | `containerClassName` | `string` | - | CSS class for the main container | | `containerInnerClassName` | `string` | - | CSS class for the inner container | | `panelClassName` | `string` | - | CSS class for each panel | | `imageClassName` | `string` | - | CSS class for images | | `dimensionInfoClassName` | `string` | - | CSS class for dimension info text | | `onImagesLoaded` | `(detail: {image1: {width, height}, image2: {width, height}}) => void` | - | Callback when images load | | `onLoadError` | `(error: unknown) => void` | - | Callback when loading fails | ### `` Overlay comparison with opacity control slider. #### Props | Prop | Type | Default | Description | | -------------------------- | ----------------------------------------------------------- | ------- | -------------------------------- | | `src1` | `string` (required) | - | URL of the base image | | `src2` | `string` (required) | - | URL of the overlay image | | `opacity` | `number` | - | Initial opacity (0-100) | | `className` | `string` | - | CSS class for the root element | | `containerClassName` | `string` | - | CSS class for the main container | | `imageContainerClassName` | `string` | - | CSS class for image container | | `imageClassName` | `string` | - | CSS class for images | | `sliderContainerClassName` | `string` | - | CSS class for slider container | | `sliderClassName` | `string` | - | CSS class for the slider | | `sliderLabelClassName` | `string` | - | CSS class for slider label | | `sliderLabelText` | `string` | - | Text for the slider label | | `onOpacityChange` | `(opacity: number) => void` | - | Callback when opacity changes | | `onImagesLoaded` | `(detail: {image1: {width, height}, image2: {width, height}}) => void` | - | Callback when images load | | `onLoadError` | `(error: unknown) => void` | - | Callback when loading fails | ### `` Shows pixel differences using the BlazeDiff comparison algorithm. #### Props | Prop | Type | Default | Description | | ------------------- | ----------------------------------------------------------------- | ------- | ------------------------------- | | `src1` | `string` (required) | - | URL of the first image | | `src2` | `string` (required) | - | URL of the second image | | `threshold` | `number` | - | Difference threshold (0-1) | | `includeAA` | `boolean` | - | Include anti-aliased pixels | | `alpha` | `number` | - | Opacity of original image | | `className` | `string` | - | CSS class for the root element | | `containerClassName`| `string` | - | CSS class for the container | | `canvasClassName` | `string` | - | CSS class for the canvas | | `onDiffComplete` | `(detail: {diffCount, totalPixels, percentage}) => void` | - | Callback on completion | | `onDiffError` | `(error: unknown) => void` | - | Callback on error | ## Links - [GitHub Repository](https://github.com/teimurjan/blazediff) - [NPM Package](https://www.npmjs.com/package/@blazediff/react) - [Examples →](/examples/react) --- # Examples # Image Comparison Learn how to use BlazeDiff for different types of comparisons - binary, object, and pixel-by-pixel image comparison. ## Installation ```bash npm install @blazediff/core ``` ## Overview BlazeDiff provides multiple comparison methods optimized for different use cases. Try the interactive demos below to see each method in action. ### Pixel-by-pixel Comparison ```ts import blazediff from "@blazediff/core"; // loadImage can either be a browser function or a server function const img1 = await loadImage( "https://raw.githubusercontent.com/teimurjan/blazediff/refs/heads/main/fixtures/blazediff/3a.png" ); const img2 = await loadImage( "https://raw.githubusercontent.com/teimurjan/blazediff/refs/heads/main/fixtures/blazediff/3b.png" ); const output = new Uint8Array(img1.width * img1.height * 4); const width = img1.width; const height = img1.height; const diff = blazediff(img1, img2, output, width, height); ``` ```ts import blazediff from "@blazediff/core"; // loadImage can either be a browser function or a server function const img1 = await loadImage( "https://raw.githubusercontent.com/teimurjan/blazediff/refs/heads/main/fixtures/blazediff/3a.png" ); const img2 = await loadImage( "https://raw.githubusercontent.com/teimurjan/blazediff/refs/heads/main/fixtures/blazediff/3b.png" ); const output = new Uint8Array(img1.width * img1.height * 4); const width = img1.width; const height = img1.height; const diff = blazediff(img1, img2, output, width, height, { threshold: 0.5 }); ``` ```ts import blazediff from "@blazediff/core"; // loadImage can either be a browser function or a server function const img1 = await loadImage( "https://raw.githubusercontent.com/teimurjan/blazediff/refs/heads/main/fixtures/blazediff/3a.png" ); const img2 = await loadImage( "https://raw.githubusercontent.com/teimurjan/blazediff/refs/heads/main/fixtures/blazediff/3b.png" ); const output = new Uint8Array(img1.width * img1.height * 4); const width = img1.width; const height = img1.height; const diff = blazediff(img1, img2, output, width, height, { aaColor: [255, 0, 0], diffColor: [0, 255, 0], }); ``` ### Perceptual Comparison #### GMSD (Gradient Magnitude Similarity Deviation) ```ts const img1 = await loadImage( "https://raw.githubusercontent.com/teimurjan/blazediff/refs/heads/main/fixtures/blazediff/3a.png" ); const img2 = await loadImage( "https://raw.githubusercontent.com/teimurjan/blazediff/refs/heads/main/fixtures/blazediff/3b.png" ); const width = img1.width; const height = img1.height; const score = gmsd(img1, img2, undefined, width, height); ``` ```ts const img1 = await loadImage( "https://raw.githubusercontent.com/teimurjan/blazediff/refs/heads/main/fixtures/blazediff/3a.png" ); const img2 = await loadImage( "https://raw.githubusercontent.com/teimurjan/blazediff/refs/heads/main/fixtures/blazediff/3b.png" ); const width = img1.width; const height = img1.height; const score = gmsd(img1, img2, undefined, width, height, { c: 100 }); ``` #### SSIM (Structural Similarity Index) ```ts import ssim from "@blazediff/ssim/ssim"; const img1 = await loadImage( "https://raw.githubusercontent.com/teimurjan/blazediff/refs/heads/main/fixtures/blazediff/3a.png" ); const img2 = await loadImage( "https://raw.githubusercontent.com/teimurjan/blazediff/refs/heads/main/fixtures/blazediff/3b.png" ); const output = new Uint8Array(img1.width * img1.height * 4); const width = img1.width; const height = img1.height; const score = ssim(img1, img2, output, width, height); ``` ```ts import hitchhikersSSIM from "@blazediff/ssim/hitchhikers-ssim"; const img1 = await loadImage( "https://raw.githubusercontent.com/teimurjan/blazediff/refs/heads/main/fixtures/blazediff/3a.png" ); const img2 = await loadImage( "https://raw.githubusercontent.com/teimurjan/blazediff/refs/heads/main/fixtures/blazediff/3b.png" ); const output = new Uint8Array(img1.width * img1.height * 4); const width = img1.width; const height = img1.height; const score = ssim(img1, img2, output, width, height); ``` --- # Web Components Use BlazeDiff UI components in any framework or vanilla HTML. ## Installation ```bash npm install @blazediff/ui ``` ## Overview - You can find the full source code for the stories below in the [GitHub repository](https://github.com/teimurjan/blazediff/refs/heads/main/packages/ui/stories). - You can also view the stories in the [Storybook](https://blazediff-ui-storybook.vercel.app/). --- # React Components Build interactive image comparison components with React. ## Installation ```bash npm install @blazediff/react ``` ## Overview - You can find the full source code for the stories below in the [GitHub repository](https://github.com/teimurjan/blazediff/refs/heads/main/packages/react/stories). - You can also view the stories in the [Storybook](https://blazediff-react-storybook.vercel.app/). --- # Interpret Takes a raw pixel diff and tells you _what_ changed, _where_, and _how much_. No ML models — a deterministic pipeline that runs in the same binary as the diff itself. ## How it works 1. Pixel diff → binary change mask 2. Morphological close → bridge small gaps 3. Connected components → isolate regions 4. Per-region evidence extraction: - **Dual-image gradients** — edges in both images + spatial correlation to detect structural preservation - **Color delta distribution** — mean, max, and stddev of YIQ distance to separate uniform recolors from patchy texture changes - **Background distance** — how much changed pixels blend with local unchanged pixels in each image 5. Six-label decision tree classifies each region 6. Post-hoc shift detection matches Addition+Deletion pairs ## Demo ```ts import { interpret } from "@blazediff/core-native"; const result = await interpret("fixtures/3a.png", "fixtures/3b.png"); for (const region of result.regions) { console.log(`${region.position}: ${region.changeType} (${region.percentage.toFixed(2)}%)`); } ``` ```ts import { interpret } from "@blazediff/core-native"; const result = await interpret("fixtures/3a.png", "fixtures/3a.png"); console.log(result.summary); // "Images are identical" console.log(result.regions.length); // 0 ``` ## Change types | Type | Meaning | |---|---| | `Addition` | Content appeared — blends with background in before image, distinct in after | | `Deletion` | Content removed — distinct in before, blends with background in after | | `Shift` | Content moved — matched Addition+Deletion pair with similar luminance | | `ColorChange` | Recolor — edge structure preserved across both images, uniform color shift | | `ContentChange` | Structural change — edges differ between images | | `RenderingNoise` | Sub-pixel artifacts — filtered from output | ---