@squoosh-kit/visdif 0.2.1 → 0.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/README.md +224 -0
  2. package/package.json +2 -2
package/README.md ADDED
@@ -0,0 +1,224 @@
1
+ # @squoosh-kit/visdif
2
+
3
+ [![npm version](https://badge.fury.io/js/%40squoosh-kit%2Fvisdif.svg)](https://badge.fury.io/js/%40squoosh-kit%2Fvisdif)
4
+ [![Bun](https://img.shields.io/badge/Bun-000000?logo=bun&logoColor=white)](https://bun.sh/)
5
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) [![License: Apache 2.0](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://www.apache.org/licenses/LICENSE-2.0)
6
+ [![TypeScript](https://img.shields.io/badge/TypeScript-007ACC?logo=typescript&logoColor=white)](https://www.typescriptlang.org/)
7
+
8
+ ![Squoosh-Kit](https://github.com/bnowak008/squoosh-kit/blob/main/squoosh-kit-banner.webp)
9
+
10
+ ## Squoosh-Kit
11
+
12
+ Squoosh-Kit is built on a simple idea: provide a lightweight and modular bridge to the powerful, production-tested codecs from Google's Squoosh project. This package (`@squoosh-kit/visdif`) is one of those modules.
13
+
14
+ **Directly from the Source**
15
+ We don't modify the core VisDif codec. The WebAssembly (`.wasm`) binary is taken directly from the official Squoosh repository builds. This means you get the exact same performance, quality, and reliability you'd expect from Squoosh.
16
+
17
+ **A Thin, Modern Wrapper**
18
+ Our goal is to provide a minimal, modern JavaScript wrapper around the codec. We handle the tricky parts—like loading WASM, managing web workers, and providing a clean, type-safe API—so you can focus on your application. The library is designed to be a thin bridge, not a heavy framework.
19
+
20
+ **Modular by Design**
21
+ We believe you should only install what you need. As a standalone package, `@squoosh-kit/visdif` allows you to add perceptual image comparison to your project without pulling in other unrelated image processing tools.
22
+
23
+ ## Installation
24
+
25
+ ```bash
26
+ bun add @squoosh-kit/visdif
27
+ # or
28
+ npm install @squoosh-kit/visdif
29
+ ```
30
+
31
+ ## Quick Start
32
+
33
+ ```typescript
34
+ import { compare, createVisDiff } from '@squoosh-kit/visdif';
35
+ import type { ImageInput } from '@squoosh-kit/visdif';
36
+
37
+ const original: ImageInput = { data: originalBuffer, width: 800, height: 600 };
38
+ const compressed: ImageInput = {
39
+ data: compressedBuffer,
40
+ width: 800,
41
+ height: 600,
42
+ };
43
+
44
+ // Returns a Butteraugli distance score
45
+ const distance = await compare(original, compressed);
46
+
47
+ console.log(distance);
48
+ // 0.0 = pixel-identical
49
+ // < 1.0 = virtually imperceptible difference
50
+ // 1.0–2.0 = minor visible difference
51
+ // > 3.0 = noticeable quality loss
52
+
53
+ // For repeated comparisons, use a persistent instance
54
+ const differ = createVisDiff('worker');
55
+ const score = await differ(original, compressed);
56
+ await differ.terminate();
57
+ ```
58
+
59
+ ## What is Butteraugli?
60
+
61
+ Butteraugli is a perceptual image similarity metric developed by Google. Unlike PSNR or SSIM, Butteraugli models the human visual system more accurately — it accounts for how the eye perceives differences in edges, textures, and color transitions.
62
+
63
+ The score returned is a Butteraugli distance:
64
+
65
+ - `0.0` — Images are pixel-identical
66
+ - `< 1.0` — Differences are imperceptible to most viewers
67
+ - `1.0–2.0` — Slight quality degradation, noticeable on close inspection
68
+ - `> 3.0` — Visible artifacts; consider increasing codec quality settings
69
+
70
+ VisDif is a good fit for:
71
+
72
+ - Automated quality assurance in image pipelines
73
+ - Tuning codec quality settings to hit a visual quality target
74
+ - Comparing before/after processing to verify lossless operations
75
+ - CI/CD pipelines that check image quality regressions
76
+
77
+ ## Public API
78
+
79
+ Only the following exports are part of the public API and guaranteed to be stable across versions:
80
+
81
+ - `compare(image1, image2, signal?)` - Compute the Butteraugli distance between two images
82
+ - `createVisDiff(mode?)` - Create a reusable comparison function
83
+ - `ImageInput` type - Input image data structure
84
+ - `VisDifFactory` type - Type for reusable comparison functions
85
+
86
+ ## Real-World Examples
87
+
88
+ **Quality-gate a codec setting in CI**
89
+
90
+ ```typescript
91
+ import { compare } from '@squoosh-kit/visdif';
92
+ import { encode } from '@squoosh-kit/avif';
93
+
94
+ const original = loadImage('source.png');
95
+ const avifBuffer = await encode(original, { quality: 60 });
96
+ const decoded = decodeAvif(avifBuffer); // your AVIF decoder
97
+
98
+ const distance = await compare(original, decoded);
99
+
100
+ if (distance > 2.0) {
101
+ throw new Error(
102
+ `AVIF quality too low: Butteraugli distance ${distance.toFixed(2)}`
103
+ );
104
+ }
105
+
106
+ console.log(`AVIF quality OK: distance = ${distance.toFixed(3)}`);
107
+ ```
108
+
109
+ **Find the minimum quality that meets a visual threshold**
110
+
111
+ ```typescript
112
+ import { compare } from '@squoosh-kit/visdif';
113
+ import { encode } from '@squoosh-kit/webp';
114
+
115
+ const MAX_DISTANCE = 1.0;
116
+
117
+ for (let quality = 60; quality <= 100; quality += 5) {
118
+ const encoded = await encode(original, { quality });
119
+ const decoded = decodeWebp(encoded); // your WebP decoder
120
+
121
+ const distance = await compare(original, decoded);
122
+
123
+ if (distance <= MAX_DISTANCE) {
124
+ console.log(
125
+ `Minimum quality: ${quality} (distance: ${distance.toFixed(3)})`
126
+ );
127
+ break;
128
+ }
129
+ }
130
+ ```
131
+
132
+ ## API Reference
133
+
134
+ ### `compare(image1, image2, signal?)`
135
+
136
+ Computes the Butteraugli perceptual distance between two images. Both images must have identical dimensions.
137
+
138
+ - `image1` - `ImageInput` object — the reference (original) image
139
+ - `image2` - `ImageInput` object — the image to compare against the reference
140
+ - `signal` - (optional) `AbortSignal` to cancel the operation
141
+ - **Returns** - `Promise<number>` — the Butteraugli distance score (lower = more similar)
142
+
143
+ **Note**: `compare()` uses a global singleton worker. For long-running applications where worker cleanup is important, use `createVisDiff()` instead.
144
+
145
+ ### `createVisDiff(mode?)`
146
+
147
+ Creates a reusable comparison function. More efficient for repeated comparisons.
148
+
149
+ - `mode` - (optional) `'worker'` or `'client'`, defaults to `'worker'`
150
+ - **Returns** - A function with the same signature as `compare()`
151
+
152
+ ## Cancellation Support
153
+
154
+ To cancel a comparison in progress, pass an `AbortSignal`:
155
+
156
+ ```typescript
157
+ const controller = new AbortController();
158
+
159
+ const comparePromise = compare(image1, image2, controller.signal);
160
+ setTimeout(() => controller.abort(), 10000);
161
+
162
+ try {
163
+ const distance = await comparePromise;
164
+ } catch (error) {
165
+ if (error.name === 'AbortError') {
166
+ console.log('Comparison was cancelled');
167
+ }
168
+ }
169
+ ```
170
+
171
+ ## Input Validation
172
+
173
+ Both images are validated before processing:
174
+
175
+ ```typescript
176
+ // Will throw TypeError: image must be an object
177
+ await compare(null, image2);
178
+
179
+ // Will throw TypeError: image.data must be Uint8Array or Uint8ClampedArray
180
+ await compare({ data: [0, 0, 0, 255], width: 32, height: 32 }, image2);
181
+
182
+ // Images must have the same dimensions for meaningful comparison
183
+ ```
184
+
185
+ ### Package Size
186
+
187
+ **Size breakdown:**
188
+
189
+ - JavaScript code: ~4-6KB gzipped
190
+ - TypeScript definitions: ~2KB
191
+ - WASM binary: ~20-30KB gzipped
192
+
193
+ ### Worker Cleanup
194
+
195
+ When using worker mode, clean up when done:
196
+
197
+ ```typescript
198
+ const differ = createVisDiff('worker');
199
+
200
+ try {
201
+ const distance = await differ(original, compressed);
202
+ console.log(`Distance: ${distance}`);
203
+ } finally {
204
+ await differ.terminate();
205
+ }
206
+ ```
207
+
208
+ ## Performance Tips
209
+
210
+ - **Use workers for UI apps** - Butteraugli analysis is CPU-intensive; offload it to avoid blocking the UI
211
+ - **Use client mode in build tools** - Simpler setup for Node/Bun scripts
212
+ - **Cache results** - Butteraugli is deterministic; cache scores for unchanged image pairs
213
+ - **Pair with encoders** - Use alongside `@squoosh-kit/avif`, `@squoosh-kit/webp`, or `@squoosh-kit/mozjpeg` to tune quality settings programmatically
214
+
215
+ ## Works With
216
+
217
+ - **Bun** - First-class support, fastest performance
218
+ - **Node.js** - Works great in server environments
219
+ - **Browsers** - Full Web Worker support for responsive UIs
220
+ - **TypeScript** - Complete type definitions included
221
+
222
+ ## License
223
+
224
+ MIT - use it freely in your projects
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@squoosh-kit/visdif",
3
- "version": "0.2.1",
3
+ "version": "0.2.3",
4
4
  "type": "module",
5
5
  "description": "Butteraugli perceptual image comparison for squoosh-kit, using Emscripten VisDif.",
6
6
  "author": "Bartosz Nowak <bnowak008@gmail.com>",
@@ -10,7 +10,7 @@
10
10
  "url": "https://github.com/bnowak008/squoosh-kit.git",
11
11
  "directory": "packages/visdif"
12
12
  },
13
- "homepage": "https://github.com/bnowak008/squoosh-kit/tree/main/packages/visdif#readme",
13
+ "homepage": "http://squoosh-kit.dev",
14
14
  "publishConfig": {
15
15
  "access": "public",
16
16
  "registry": "https://registry.npmjs.org/"