@vizzly-testing/honeydiff 0.0.2 β†’ 0.1.0

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.
package/README.md CHANGED
@@ -2,28 +2,17 @@
2
2
 
3
3
  [![npm version](https://img.shields.io/npm/v/@vizzly-testing/honeydiff.svg)](https://www.npmjs.com/package/@vizzly-testing/honeydiff)
4
4
  [![npm downloads](https://img.shields.io/npm/dm/@vizzly-testing/honeydiff.svg)](https://www.npmjs.com/package/@vizzly-testing/honeydiff)
5
- [![CI](https://github.com/vizzly-testing/honeydiff/workflows/Node.js%20Bindings/badge.svg)](https://github.com/vizzly-testing/honeydiff/actions)
6
- [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](../../LICENSE)
5
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://github.com/vizzly-testing/honeydiff/blob/main/LICENSE)
7
6
 
8
- High-performance image diffing for Node.js - Native bindings to the Honeydiff Rust library.
7
+ Blazingly fast image comparison for Node.js. Perfect for visual regression testing, screenshot
8
+ validation, and detecting visual changes.
9
9
 
10
- Blazing fast image comparison with support for anti-aliasing detection, perceptual similarity (SSIM), spatial clustering, and variable-height screenshots. Perfect for visual regression testing.
11
-
12
- ## Features
13
-
14
- - **πŸš€ Native Performance** - Rust-powered via Neon bindings (1.1B pixels/sec for Full HD)
15
- - **πŸ“¦ Dual Input** - Works with file paths or Buffers
16
- - **⚑ Async + Sync APIs** - Choose between non-blocking promises or synchronous operations
17
- - **🎯 Full TypeScript Support** - Complete type definitions included
18
- - **πŸ” Advanced Analysis**:
19
- - Anti-aliasing detection
20
- - SSIM perceptual similarity scoring
21
- - Spatial clustering of diff regions
22
- - Intensity statistics (min, max, mean, median, std dev)
23
- - Bounding box detection
24
- - **πŸ“Έ Variable Height Support** - Compare scrollable screenshots with different heights
25
- - **🎨 Visual Artifacts** - Generate diff, mask, and overlay images
26
- - **βš™οΈ Flexible Options** - Pixel tolerance, color thresholds, custom diff colors
10
+ **Why Honeydiff?**
11
+ - **Fast:** Native performance processing 230M+ pixels/sec
12
+ - **Smart:** Intelligent anti-aliasing detection filters rendering artifacts
13
+ - **Flexible:** File paths or Buffers, async or sync APIs
14
+ - **Complete:** TypeScript types, pre-built binaries for all platforms
15
+ - **Advanced:** SSIM perceptual scoring, spatial clustering, variable-height screenshots
27
16
 
28
17
  ## Installation
29
18
 
@@ -31,25 +20,18 @@ Blazing fast image comparison with support for anti-aliasing detection, perceptu
31
20
  npm install @vizzly-testing/honeydiff
32
21
  ```
33
22
 
34
- **Requirements:**
35
- - Node.js 22+
36
-
37
- **Pre-built Binaries:**
38
- This package ships with pre-built native binaries for:
39
- - macOS (Apple Silicon arm64)
40
- - Linux (x64 and arm64)
41
- - Windows (x64)
23
+ **Requirements:** Node.js 22+
42
24
 
43
- No Rust toolchain required! The correct binary is automatically loaded for your platform.
25
+ Pre-built binaries included for macOS (ARM64), Linux (x64/ARM64), and Windows (x64). No compilation
26
+ required.
44
27
 
45
28
  ## Quick Start
46
29
 
47
- ### JavaScript (Async - Recommended)
48
-
30
+ **JavaScript**
49
31
  ```javascript
50
32
  const { compare, quickCompare } = require('@vizzly-testing/honeydiff');
51
33
 
52
- // Quick boolean check
34
+ // Quick check
53
35
  if (await quickCompare('baseline.png', 'current.png')) {
54
36
  console.log('Visual regression detected!');
55
37
  }
@@ -60,112 +42,64 @@ const result = await compare('baseline.png', 'current.png', {
60
42
  includeSSIM: true
61
43
  });
62
44
 
63
- console.log(`Diff: ${result.diffPercentage.toFixed(2)}%`);
64
- console.log(`SSIM: ${result.perceptualScore}`);
45
+ console.log(`Diff: ${result.diffPercentage.toFixed(2)}%, SSIM: ${result.perceptualScore}`);
46
+ // Output: Diff: 3.55%, SSIM: 0.9234
65
47
  ```
66
48
 
67
- ### TypeScript
49
+ **TypeScript**
68
50
 
69
51
  ```typescript
70
52
  import { compare, CompareOptions, DiffResult } from '@vizzly-testing/honeydiff';
71
53
 
72
- const options: CompareOptions = {
54
+ const result: DiffResult = await compare('img1.png', 'img2.png', {
73
55
  pixelTolerance: 5,
74
56
  includeSSIM: true,
75
57
  includeClusters: true
76
- };
77
-
78
- const result: DiffResult = await compare('img1.png', 'img2.png', options);
58
+ });
79
59
 
80
60
  if (result.diffClusters) {
81
61
  console.log(`Found ${result.diffClusters.length} diff regions`);
82
62
  }
63
+ // Output: Found 3 diff regions
83
64
  ```
84
65
 
85
- ## API Reference
86
-
87
- ### Async API (Recommended)
88
-
89
- #### `compare(img1, img2, options?): Promise<DiffResult>`
90
-
91
- Compare two images asynchronously. Non-blocking operation.
92
-
93
- **Parameters:**
94
- - `img1`: `string | Buffer` - First image (file path or Buffer)
95
- - `img2`: `string | Buffer` - Second image (file path or Buffer)
96
- - `options`: `CompareOptions` (optional) - Comparison configuration
97
-
98
- **Returns:** `Promise<DiffResult>` - Detailed diff results
99
-
100
- **Example:**
101
- ```javascript
102
- const result = await compare('baseline.png', 'current.png', {
103
- pixelTolerance: 10,
104
- antialiasing: true,
105
- includeSSIM: true,
106
- diffPath: '/tmp/diff.png'
107
- });
108
- ```
66
+ ## API
109
67
 
110
- #### `quickCompare(img1, img2): Promise<boolean>`
68
+ **Async (recommended)**
69
+ - `compare(img1, img2, options?): Promise<DiffResult>` - Full comparison
70
+ - `quickCompare(img1, img2): Promise<boolean>` - Quick check
111
71
 
112
- Quick async comparison returning only a boolean.
72
+ **Sync (blocks event loop)**
73
+ - `compareSync(img1, img2, options?): DiffResult`
74
+ - `quickCompareSync(img1, img2): boolean`
113
75
 
114
- **Returns:** `Promise<boolean>` - `true` if different, `false` if identical
76
+ Inputs: `string` (file path) or `Buffer`
115
77
 
116
- ### Sync API
78
+ ## Options & Results
117
79
 
118
- #### `compareSync(img1, img2, options?): DiffResult`
119
-
120
- Synchronous comparison (blocks event loop). Use only when necessary.
121
-
122
- #### `quickCompareSync(img1, img2): boolean`
123
-
124
- Synchronous quick comparison returning a boolean.
125
-
126
- ## Options
80
+ <details>
81
+ <summary><strong>CompareOptions</strong></summary>
127
82
 
128
83
  ```typescript
129
84
  interface CompareOptions {
130
- /** Pixel tolerance 0-255 (default: 0) */
131
- pixelTolerance?: number;
132
-
133
- /** YIQ color threshold 0.0-1.0 (default: 0.0 = RGB mode) */
134
- colorThreshold?: number;
135
-
136
- /** Enable anti-aliasing detection (default: true) */
137
- antialiasing?: boolean;
138
-
139
- /** Ignore colors, compare brightness only (default: false) */
140
- ignoreColors?: boolean;
141
-
142
- /** Max differences to find before stopping (default: unlimited) */
143
- maxDiffs?: number;
144
-
145
- /** Include detailed pixel list (default: false) */
146
- includeDiffPixels?: boolean;
147
-
148
- /** Include spatial clusters (default: false) */
149
- includeClusters?: boolean;
150
-
151
- /** Calculate SSIM score (default: false) */
152
- includeSSIM?: boolean;
153
-
154
- /** Path to save diff image */
155
- diffPath?: string;
156
-
157
- /** Path to save binary mask */
158
- maskPath?: string;
159
-
160
- /** Path to save side-by-side overlay */
161
- overlayPath?: string;
162
-
163
- /** Overwrite existing files (default: false) */
164
- overwrite?: boolean;
85
+ pixelTolerance?: number; // 0-255 (default: 0)
86
+ colorThreshold?: number; // 0.0-1.0 for YIQ mode (default: 0.0 = RGB)
87
+ antialiasing?: boolean; // Enable AA detection (default: true)
88
+ ignoreColors?: boolean; // Brightness only (default: false)
89
+ maxDiffs?: number; // Stop after N diffs (default: unlimited)
90
+ includeDiffPixels?: boolean; // Detailed pixel list (default: false)
91
+ includeClusters?: boolean; // Spatial clusters (default: false)
92
+ includeSSIM?: boolean; // SSIM score (default: false)
93
+ diffPath?: string; // Save diff image
94
+ maskPath?: string; // Save mask image
95
+ overlayPath?: string; // Save overlay image
96
+ overwrite?: boolean; // Overwrite files (default: false)
165
97
  }
166
98
  ```
99
+ </details>
167
100
 
168
- ## Result Object
101
+ <details>
102
+ <summary><strong>DiffResult</strong></summary>
169
103
 
170
104
  ```typescript
171
105
  interface DiffResult {
@@ -180,148 +114,172 @@ interface DiffResult {
180
114
  diffPixelsList: DiffPixel[] | null;
181
115
  diffClusters: DiffCluster[] | null;
182
116
  intensityStats: IntensityStats | null;
183
- perceptualScore: number | null; // SSIM 0.0-1.0
117
+ perceptualScore: number | null; // SSIM 0.0-1.0
184
118
  }
185
119
  ```
120
+ </details>
186
121
 
187
- ## Usage Examples
188
-
189
- ### With Buffers
122
+ ## Examples
190
123
 
124
+ **Buffers**
191
125
  ```javascript
192
126
  const fs = require('fs');
193
127
  const { compare } = require('@vizzly-testing/honeydiff');
194
128
 
195
- const img1 = fs.readFileSync('image1.png');
196
- const img2 = fs.readFileSync('image2.png');
197
-
198
- const result = await compare(img1, img2, {
199
- pixelTolerance: 5
200
- });
129
+ let img1 = fs.readFileSync('image1.png');
130
+ let img2 = fs.readFileSync('image2.png');
131
+ let result = await compare(img1, img2, { pixelTolerance: 5 });
201
132
  ```
202
133
 
203
- ### Generate Diff Images
134
+ **Diff Images**
204
135
 
205
136
  ```javascript
206
- const result = await compare('baseline.png', 'current.png', {
207
- diffPath: './output/diff.png',
208
- maskPath: './output/mask.png',
209
- overlayPath: './output/overlay.png',
137
+ let result = await compare('baseline.png', 'current.png', {
138
+ diffPath: './diff.png',
139
+ maskPath: './mask.png',
140
+ overlayPath: './overlay.png',
210
141
  overwrite: true
211
142
  });
212
-
213
- console.log(`Diff images saved with ${result.diffPercentage}% difference`);
214
143
  ```
215
144
 
216
- ### Spatial Clustering
217
-
145
+ **Clustering & Metrics**
218
146
  ```javascript
219
- const result = await compare('img1.png', 'img2.png', {
220
- includeClusters: true
147
+ let result = await compare('img1.png', 'img2.png', {
148
+ includeClusters: true,
149
+ includeSSIM: true,
150
+ includeDiffPixels: true
221
151
  });
222
152
 
153
+ // Spatial clusters
223
154
  if (result.diffClusters) {
224
- result.diffClusters.forEach((cluster, i) => {
225
- console.log(`Cluster ${i + 1}:`);
226
- console.log(` Pixels: ${cluster.pixelCount}`);
227
- console.log(` Center: (${cluster.centerOfMass[0]}, ${cluster.centerOfMass[1]})`);
228
- console.log(` Bounds: ${cluster.boundingBox.width}x${cluster.boundingBox.height}`);
155
+ result.diffClusters.forEach((c, i) => {
156
+ console.log(`Region ${i + 1}: ${c.pixelCount} pixels at (${c.centerOfMass[0]}, ${c.centerOfMass[1]})`);
229
157
  });
230
158
  }
231
- ```
232
-
233
- ### Perceptual Similarity (SSIM)
234
-
235
- ```javascript
236
- const result = await compare('img1.png', 'img2.png', {
237
- includeSSIM: true
238
- });
159
+ // Output:
160
+ // Region 1: 12450 pixels at (245, 189)
161
+ // Region 2: 3891 pixels at (672, 401)
162
+ // Region 3: 1023 pixels at (1156, 823)
239
163
 
164
+ // SSIM score
240
165
  if (result.perceptualScore !== null) {
241
- if (result.perceptualScore > 0.95) {
242
- console.log('Perceptually very similar');
243
- } else if (result.perceptualScore > 0.80) {
244
- console.log('Somewhat similar');
245
- } else {
246
- console.log('Perceptually different');
247
- }
166
+ console.log(`SSIM: ${result.perceptualScore.toFixed(3)}`);
248
167
  }
249
- ```
250
-
251
- ### Intensity Statistics
252
-
253
- ```javascript
254
- const result = await compare('img1.png', 'img2.png', {
255
- includeDiffPixels: true // Enables intensity stats
256
- });
168
+ // Output: SSIM: 0.923
257
169
 
170
+ // Intensity stats
258
171
  if (result.intensityStats) {
259
- console.log(`Intensity range: ${result.intensityStats.min}-${result.intensityStats.max}`);
260
- console.log(`Mean: ${result.intensityStats.mean.toFixed(1)}`);
261
- console.log(`Median: ${result.intensityStats.median}`);
262
- console.log(`Std Dev: ${result.intensityStats.stdDev.toFixed(1)}`);
172
+ console.log(`Range: ${result.intensityStats.min}-${result.intensityStats.max}, Mean: ${result.intensityStats.mean.toFixed(1)}`);
263
173
  }
174
+ // Output: Range: 12-255, Mean: 87.3
264
175
  ```
265
176
 
266
- ## Performance
177
+ **Complete Result Example**
178
+ ```javascript
179
+ let result = await compare('baseline.png', 'current.png', {
180
+ pixelTolerance: 10,
181
+ includeClusters: true
182
+ });
267
183
 
268
- Benchmarks on M-series Mac:
184
+ console.log(JSON.stringify(result, null, 2));
185
+ ```
269
186
 
270
- | Image Size | Time | Throughput |
271
- |------------|------|------------|
272
- | 1920x1080 (Full HD) | ~2ms | 1.1B pixels/sec |
273
- | 3840x2160 (4K) | ~3ms | 3.0B pixels/sec |
187
+ <details>
188
+ <summary>Example output (click to expand)</summary>
189
+
190
+ ```json
191
+ {
192
+ "isDifferent": true,
193
+ "diffPercentage": 3.55,
194
+ "totalPixels": 18309000,
195
+ "diffPixels": 650086,
196
+ "aaPixelsIgnored": 1952904,
197
+ "aaPercentage": 10.67,
198
+ "boundingBox": {
199
+ "x": 124,
200
+ "y": 89,
201
+ "width": 1456,
202
+ "height": 2301
203
+ },
204
+ "heightDiff": {
205
+ "height1": 24162,
206
+ "height2": 24412,
207
+ "extraPixels": 187500
208
+ },
209
+ "diffPixelsList": null,
210
+ "diffClusters": [
211
+ {
212
+ "pixelCount": 12450,
213
+ "centerOfMass": [245, 189]
214
+ },
215
+ {
216
+ "pixelCount": 3891,
217
+ "centerOfMass": [672, 401]
218
+ }
219
+ ],
220
+ "intensityStats": {
221
+ "min": 12,
222
+ "max": 255,
223
+ "mean": 87.3,
224
+ "median": 76,
225
+ "stdDev": 45.2
226
+ },
227
+ "perceptualScore": null
228
+ }
229
+ ```
230
+ </details>
231
+ ```
274
232
 
275
- With diff artifact generation: ~30-35ms for Full HD images
233
+ ## Use Cases
276
234
 
277
- ## Variable Height Support
235
+ - **Visual Regression Testing:** Detect unintended UI changes in your test suite
236
+ - **Screenshot Validation:** Compare generated screenshots against baselines
237
+ - **Design QA:** Validate UI implementations match designs
238
+ - **Cross-browser Testing:** Detect rendering differences across browsers
239
+ - **Scrollable Content:** Compare full-page screenshots with variable heights
278
240
 
279
- Honeydiff can compare images with different heights (same width) - perfect for scrollable screenshots:
241
+ ## Performance
280
242
 
281
- ```javascript
282
- const result = await compare('short.png', 'tall.png');
243
+ Tested on real-world tall screenshots (750 x 24,162 pixels = 18.3M pixels):
283
244
 
284
- if (result.heightDiff) {
285
- console.log(`Height diff: ${result.heightDiff.height1}px vs ${result.heightDiff.height2}px`);
286
- console.log(`Extra pixels: ${result.heightDiff.extraPixels}`);
287
- }
288
- ```
245
+ | Operation | Time | Throughput |
246
+ |-----------|------|------------|
247
+ | Basic comparison | ~79ms | 231.8M pixels/sec |
248
+ | With artifacts (diff/mask/overlay) | ~432ms | 42.4M pixels/sec |
289
249
 
290
- ## Building from Source
250
+ **6x faster than odiff** on large screenshots with excellent multi-core parallelization.
291
251
 
292
- Only needed if you want to contribute or modify the native code.
252
+ *Benchmarked on Apple M-series Mac.*
293
253
 
294
- **Prerequisites:**
295
- - Rust toolchain (install from https://rustup.rs)
296
- - Node.js 22+
254
+ ## Advanced Features
297
255
 
298
- ```bash
299
- # Clone the repository
300
- git clone https://github.com/vizzly-testing/honeydiff
301
- cd honeydiff/crates/honeydiff-node
256
+ **Variable Height Screenshots**
257
+ Compare scrollable content with different heights. Overlapping regions are compared pixel-by-pixel,
258
+ extra rows counted as differences.
302
259
 
303
- # Install dependencies
304
- npm install
260
+ **Color Spaces**
261
+ - RGB mode (default): Exact matching, fastest performance
262
+ - YIQ mode: Perceptual matching weighted for human vision
305
263
 
306
- # Build for your platform
307
- npm run build
264
+ **Spatial Clustering**
265
+ Group nearby differences into regions using 8-connectivity clustering. Useful for identifying distinct changed areas.
308
266
 
309
- # Run examples
310
- node examples/basic.js
311
- ```
267
+ **SSIM Scoring**
268
+ Structural Similarity Index (0.0-1.0) provides perceptual similarity measurement beyond pixel differences.
312
269
 
313
- ### Cross-Platform Builds
270
+ ## Troubleshooting
314
271
 
315
- ```bash
316
- # Build for all supported platforms (requires cross-compilation setup)
317
- npm run cross-build
318
- ```
272
+ **Module not found errors**
273
+ Ensure you're on Node.js 22+ and your platform is supported (macOS ARM64, Linux x64/ARM64, Windows x64).
319
274
 
320
- ## Related Projects
275
+ **Large memory usage with `includeDiffPixels`**
276
+ This option stores every different pixel's position and intensity. Disable for large images with many differences.
321
277
 
322
- - [honeydiff](https://github.com/vizzly-testing/honeydiff) - The core Rust library
323
- - [Vizzly](https://github.com/vizzly-testing/vizzly) - Visual regression testing platform
278
+ **Slow performance with `includeSSIM`**
279
+ SSIM calculation is O(width Γ— height Γ— windowΒ²). Only enable when perceptual scoring is needed.
324
280
 
325
281
  ## License
326
282
 
327
- MIT - See [LICENSE](../../LICENSE) for details
283
+ MIT License
284
+
285
+ Built with native Rust for maximum performance.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vizzly-testing/honeydiff",
3
- "version": "0.0.2",
3
+ "version": "0.1.0",
4
4
  "description": "High-performance image diffing for Node.js - Native bindings to Honeydiff Rust library",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",
@@ -45,6 +45,16 @@
45
45
  "index.d.ts",
46
46
  "load-platform.js",
47
47
  "platforms/*.node",
48
+ "README.md",
49
+ "index.js",
50
+ "index.d.ts",
51
+ "load-platform.js",
52
+ "platforms/*.node",
53
+ "README.md",
54
+ "index.js",
55
+ "index.d.ts",
56
+ "load-platform.js",
57
+ "platforms/*.node",
48
58
  "README.md"
49
59
  ],
50
60
  "engines": {