@vizzly-testing/honeydiff 0.15.0 → 0.16.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/CHANGELOG.md CHANGED
@@ -7,6 +7,27 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.16.0] - 2026-08-03
11
+
12
+ ### Added
13
+
14
+ - Added complete, data-only comparison results with reconciled pixel counts, exact retained regions, Delta E summaries, and explicit height-change data.
15
+ - Added `analyze` for SSIM, MS-SSIM, and GMSD across Rust, Node, and the CLI.
16
+ - Added optional diff, mask, and side-by-side artifacts without putting image data in the result.
17
+
18
+ ### Changed
19
+
20
+ - Replaced metric include flags with four comparison settings: threshold, antialiasing, minimum region size, and height alignment.
21
+ - Node and CLI now return the same nested analysis shape serialized by Rust.
22
+ - Small suppressed regions now use bounded aggregate statistics instead of one object per region.
23
+ - `images_differ` and fingerprints now derive from the same comparison result.
24
+
25
+ ### Removed
26
+
27
+ - Removed early-exit results, public diff-pixel coordinates, packed mask payloads, and encoded image data from comparison results.
28
+
29
+ **Full Changelog**: https://github.com/vizzly-testing/honeydiff/compare/v0.15.0...v0.16.0
30
+
10
31
  ## [0.15.0] - 2026-08-03
11
32
 
12
33
  ### Changed
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Stubborn Mule Software, LLC
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -25,26 +25,21 @@ x64/ARM64, and Windows x64.
25
25
  ## Quick Start
26
26
 
27
27
  ```js
28
- import { compare, quickCompare } from '@vizzly-testing/honeydiff';
28
+ import { compare, imagesDiffer } from '@vizzly-testing/honeydiff';
29
29
 
30
- let changed = await quickCompare('baseline.png', 'current.png');
30
+ let options = {
31
+ threshold: 2,
32
+ antialiasing: true,
33
+ minimumRegionPixels: 2,
34
+ alignHeightChanges: false,
35
+ };
31
36
 
32
- if (changed) {
33
- console.log('Visual change detected');
34
- }
35
-
36
- let result = await compare('baseline.png', 'current.png', {
37
- threshold: 2.0,
38
- includeClusters: true,
39
- diffPath: 'artifacts/diff.png',
40
- maskPath: 'artifacts/mask.png',
41
- overlayPath: 'artifacts/overlay.png',
42
- overwrite: true,
43
- });
37
+ let result = await compare('baseline.png', 'current.png', options);
44
38
 
45
- console.log(result.isDifferent);
46
- console.log(result.diffPercentage);
47
- console.log(result.diffClusters);
39
+ console.log(result.different);
40
+ console.log(result.pixels.changed.total);
41
+ console.log(result.difference.regions);
42
+ console.log(await imagesDiffer('baseline.png', 'current.png', options));
48
43
  ```
49
44
 
50
45
  ## Why Honeydiff?
@@ -54,13 +49,13 @@ pieces visual regression systems usually need once screenshots get real:
54
49
 
55
50
  - CIEDE2000 perceptual color thresholds, with `2.0` as the default.
56
51
  - Zero perceptual tolerance with `threshold: 0`; disable AA and set
57
- `minClusterSize: 1` for strict rendered-pixel matching.
52
+ `minimumRegionPixels: 1` for strict rendered-pixel matching.
58
53
  - Conservative anti-aliasing detection for font and sub-pixel rendering noise.
59
54
  - RGBA-aware comparison that detects opacity changes and ignores RGB payload
60
55
  hidden behind full transparency.
61
56
  - Variable-height screenshot support for full-page comparisons.
62
- - Diff, mask, and overlay image artifacts for debugging failures.
63
- - Spatial clusters, intensity stats, SSIM, GMSD, and diff fingerprints.
57
+ - Diff, mask, and side-by-side image artifacts for debugging failures.
58
+ - Exact region data, Delta E summaries, SSIM, MS-SSIM, GMSD, and diff fingerprints.
64
59
  - WCAG contrast screening and color vision deficiency simulation.
65
60
  - Async and sync APIs with TypeScript definitions included.
66
61
 
@@ -71,12 +66,10 @@ pieces visual regression systems usually need once screenshots get real:
71
66
  ```js
72
67
  import { compare } from '@vizzly-testing/honeydiff';
73
68
 
74
- let result = await compare('before.png', 'after.png', {
75
- threshold: 2.0,
76
- });
69
+ let result = await compare('before.png', 'after.png');
77
70
 
78
- if (result.isDifferent) {
79
- console.log(`${result.diffPercentage.toFixed(2)}% of pixels changed`);
71
+ if (result.different) {
72
+ console.log(`${result.pixels.changed.total} pixels changed`);
80
73
  }
81
74
  ```
82
75
 
@@ -97,61 +90,44 @@ let result = await compare(baseline, current);
97
90
  ```js
98
91
  import { compare } from '@vizzly-testing/honeydiff';
99
92
 
100
- let result = await compare('baseline.png', 'current.png', {
101
- diffPath: 'artifacts/diff.png',
102
- maskPath: 'artifacts/mask.png',
103
- overlayPath: 'artifacts/overlay.png',
104
- overwrite: true,
105
- });
93
+ let result = await compare(
94
+ 'baseline.png',
95
+ 'current.png',
96
+ {},
97
+ {
98
+ diffPath: 'artifacts/diff.png',
99
+ maskPath: 'artifacts/mask.png',
100
+ sideBySidePath: 'artifacts/side-by-side.png',
101
+ overwrite: true,
102
+ }
103
+ );
106
104
  ```
107
105
 
108
- ### Group Differences Into Regions
106
+ Artifact settings are a separate fourth argument. Honeydiff writes the files
107
+ from the same comparison state but doesn't put image data in the result.
108
+
109
+ ### Inspect Changed Regions
109
110
 
110
111
  ```js
111
112
  import { compare } from '@vizzly-testing/honeydiff';
112
113
 
113
- let result = await compare('baseline.png', 'current.png', {
114
- includeClusters: true,
115
- minClusterSize: 2,
116
- clusterMerge: true,
117
- });
114
+ let result = await compare('baseline.png', 'current.png');
118
115
 
119
- for (let cluster of result.diffClusters ?? []) {
120
- console.log(cluster.pixelCount, cluster.boundingBox);
116
+ for (let region of result.difference.regions) {
117
+ console.log(region.pixels, region.bounds, region.deltaE);
121
118
  }
122
119
  ```
123
120
 
124
- ### Persist Authoritative Mask Evidence
125
-
126
- Use diff-mask evidence when another system needs durable spatial facts
127
- without decoding the generated PNG:
128
-
129
- ```js
130
- let result = await compare('baseline.png', 'current.png', {
131
- includeDiffMaskEvidence: true,
132
- maskPath: 'artifacts/mask.png',
133
- });
134
-
135
- console.log(result.diffMaskEvidence?.analysisContractHash);
136
- console.log(result.diffMaskEvidence?.components);
137
- ```
138
-
139
- The components are exact 8-connected groups of retained mask pixels. They are
140
- separate from `diffClusters`, whose optional merge heuristics are intended for
141
- diagnostic presentation.
142
-
143
121
  ### Add Perceptual Metrics
144
122
 
145
123
  ```js
146
- import { compare } from '@vizzly-testing/honeydiff';
124
+ import { analyze } from '@vizzly-testing/honeydiff';
147
125
 
148
- let result = await compare('baseline.png', 'current.png', {
149
- includeSSIM: true,
150
- includeGMSD: true,
151
- });
126
+ let result = await analyze('baseline.png', 'current.png');
152
127
 
153
- console.log(result.perceptualScore);
154
- console.log(result.gmsdScore);
128
+ console.log(result.perception.ssim);
129
+ console.log(result.perception.msSsim);
130
+ console.log(result.perception.gmsd);
155
131
  ```
156
132
 
157
133
  ### Align One Added Or Removed Block
@@ -159,17 +135,14 @@ console.log(result.gmsdScore);
159
135
  ```js
160
136
  let result = await compare('baseline.png', 'current.png', {
161
137
  alignHeightChanges: true,
162
- maskPath: 'artifacts/mask.png',
163
138
  });
164
139
  ```
165
140
 
166
141
  This opt-in handles one exact added or removed block of rows, such as a banner,
167
- while keeping separate changes below it visible. Ambiguous matches,
168
- bottom-only growth, and comparisons using `maxDiffs` use the normal comparison.
169
- It does not handle multiple blocks, partial-width movement, or fuzzy matching.
170
- An accepted comparison temporarily uses one additional RGBA frame. SSIM and
171
- GMSD compare the unaligned overlapping portions, and the side-by-side overlay
172
- uses the original images.
142
+ while keeping separate changes below it visible. Ambiguous matches and
143
+ bottom-only growth use the normal comparison. It does not handle multiple
144
+ blocks, partial-width movement, or fuzzy matching. `analyze` applies the same
145
+ accepted row mapping before it calculates perceptual scores.
173
146
 
174
147
  ### Screenshot Contrast
175
148
 
@@ -201,60 +174,26 @@ await saveColorBlindnessSimulation(
201
174
 
202
175
  | Option | Default | Notes |
203
176
  | --- | --- | --- |
204
- | `threshold` | `2.0` | CIEDE2000 Delta E threshold. Use `0` for zero perceptual tolerance. |
205
- | `antialiasing` | `true` | Ignore likely anti-aliased pixels. |
206
- | `alignHeightChanges` | `false` | Align one conservatively detected added or removed block of rows. |
207
- | `maxDiffs` | unlimited | Stop after a maximum number of differing pixels. Capped results classify from the pixels observed before early exit, without cluster filtering. |
208
- | `includeDiffPixels` | `false` | Return individual differing pixel positions and intensities. |
209
- | `includeClusters` | `false` | Return connected regions of visual change. |
210
- | `includeDiffMaskEvidence` | `false` | Return authoritative diff-mask identity, dimensions, and exact components. |
211
- | `minClusterSize` | `2` | Filter tiny isolated clusters as noise. |
212
- | `clusterMerge` | disabled | Merge nearby clusters into logical text-like regions. |
213
- | `includeSSIM` | `false` | Calculate structural similarity. More expensive on large images. |
214
- | `includeGMSD` | `false` | Calculate fast edge-sensitive structural difference. |
215
- | `diffPath` | unset | Save a highlighted diff image. |
216
- | `maskPath` | unset | Save a binary diff mask. |
217
- | `overlayPath` | unset | Save an overlay comparison image. |
218
- | `overwrite` | `false` | Replace existing artifact files. |
177
+ | `threshold` | `2` | CIEDE2000 tolerance. Use `0` for zero perceptual tolerance. |
178
+ | `antialiasing` | `true` | Suppress conservative raster-coverage movement. |
179
+ | `minimumRegionPixels` | `2` | Suppress smaller 8-connected visual regions. |
180
+ | `alignHeightChanges` | `false` | Align one unambiguous inserted or removed row block. |
219
181
 
220
- ## Result Shape
182
+ There are no include flags. `compare` always returns its complete data.
221
183
 
222
- ```ts
223
- interface DiffResult {
224
- isDifferent: boolean;
225
- diffPercentage: number;
226
- totalPixels: number;
227
- diffPixels: number;
228
- diffMaskPixels: number;
229
- diffMaskComplete: boolean;
230
- maskSemanticsVersion: string;
231
- diffMaskEvidence: DiffMaskEvidence | null;
232
- aaPixelsIgnored: number;
233
- aaPercentage: number;
234
- boundingBox: BoundingBox | null;
235
- heightDiff: HeightDiff | null;
236
- diffPixelsList: DiffPixel[] | null;
237
- diffClusters: DiffCluster[] | null;
238
- intensityStats: IntensityStats | null;
239
- perceptualScore: number | null;
240
- gmsdScore: number | null;
241
- }
242
- ```
184
+ ## Result Shape
243
185
 
244
- `diffPixels` is the raw post-threshold/anti-aliasing count.
245
- `diffMaskPixels` is the retained count represented by the binary mask after
246
- `minClusterSize` filtering. It always matches the mask's nontransparent pixel
247
- count. If `diffMaskComplete` is `false`, `maxDiffs` stopped the scan early;
248
- the partial mask is useful for diagnostics but not complete spatial proof.
249
- Requesting `diffPixelsList` or `diffClusters` never changes that retained result.
250
- `diffMaskEvidence` is returned only when `includeDiffMaskEvidence` is enabled. Its
251
- native analysis hash covers every normalized option that can change retained
252
- pixels, and its component pixel counts sum to `diffMaskPixels`.
186
+ The result separates every pixel into rendered-equal, suppressed, visual-change,
187
+ or structural-change populations. Those counts always add up to `pixels.total`.
253
188
 
254
- The package also exports `version` from its installed manifest and
255
- `maskSemanticsVersion` from the native comparison engine.
189
+ `difference.regions` contains exact maximal 8-connected retained regions with
190
+ bounds, centroid, density, perimeter, visual/structural composition, and real
191
+ Delta E statistics where visual samples exist. `heightChange` explains added or
192
+ removed rows and whether alignment was applied.
256
193
 
257
- See [index.d.ts](./index.d.ts) for the full API surface.
194
+ The result is JSON-native data. It never contains a `Buffer`, base64 image,
195
+ packed mask, bitmap-sized array, or per-pixel coordinate list. See
196
+ [`index.d.ts`](./index.d.ts) for the complete shape.
258
197
 
259
198
  ## Thresholds
260
199
 
@@ -294,6 +233,8 @@ for the current benchmark notes.
294
233
 
295
234
  ```js
296
235
  import {
236
+ analyze,
237
+ analyzeSync,
297
238
  analyzeWcagAllCvd,
298
239
  analyzeWcagContrast,
299
240
  analyzeWcagForCvd,
@@ -309,8 +250,8 @@ import {
309
250
  getImageMetadataFromFile,
310
251
  getImageMetadataFromFileSync,
311
252
  getImageMetadataSync,
312
- quickCompare,
313
- quickCompareSync,
253
+ imagesDiffer,
254
+ imagesDifferSync,
314
255
  saveAllColorBlindnessSimulations,
315
256
  saveColorBlindnessSimulation,
316
257
  saveWcagOverlay,
package/examples/basic.js CHANGED
@@ -1,81 +1,21 @@
1
- /**
2
- * Basic usage example for @vizzly-testing/honeydiff
3
- */
1
+ import { analyze, compare, imagesDiffer } from '../index.js';
4
2
 
5
- import * as honeydiff from '../index.js';
6
-
7
- let baseline = '../../tests/fixtures/screenshots/vizzly-baseline.png';
8
- let current = '../../tests/fixtures/screenshots/vizzly-with-diff.png';
9
-
10
- async function main() {
11
- console.log('Honeydiff Node.js examples\n');
12
-
13
- console.log('1. Quick comparison');
14
- let isDifferent = await honeydiff.quickCompare(baseline, current);
15
- console.log(` Result: ${isDifferent ? 'DIFFERENT' : 'IDENTICAL'}\n`);
16
-
17
- console.log('2. Detailed comparison');
18
- let result = await honeydiff.compare(baseline, current, {
19
- threshold: 0,
20
- antialiasing: true,
21
- includeSSIM: true,
22
- includeGMSD: true,
23
- includeClusters: true,
24
- includeDiffPixels: true,
25
- minClusterSize: 1,
26
- });
27
-
28
- console.log(` Diff percentage: ${result.diffPercentage.toFixed(2)}%`);
29
- console.log(` Different pixels: ${result.diffPixels} / ${result.totalPixels}`);
30
- console.log(` AA pixels ignored: ${result.aaPixelsIgnored}`);
31
-
32
- if (result.perceptualScore !== null) {
33
- console.log(` SSIM score: ${result.perceptualScore.toFixed(4)}`);
34
- }
35
-
36
- if (result.gmsdScore !== null) {
37
- console.log(` GMSD score: ${result.gmsdScore.toFixed(4)}`);
38
- }
39
-
40
- if (result.boundingBox) {
41
- console.log(
42
- ` Bounding box: (${result.boundingBox.x}, ${result.boundingBox.y}) ${result.boundingBox.width}x${result.boundingBox.height}`
43
- );
44
- }
45
-
46
- if (result.diffClusters && result.diffClusters.length > 0) {
47
- console.log(` Found ${result.diffClusters.length} diff clusters`);
48
- for (let [index, cluster] of result.diffClusters.slice(0, 3).entries()) {
49
- console.log(
50
- ` Cluster ${index + 1}: ${cluster.pixelCount} pixels at (${cluster.centerOfMass[0].toFixed(0)}, ${cluster.centerOfMass[1].toFixed(0)})`
51
- );
52
- }
53
- }
54
-
55
- console.log('\n3. Synchronous comparison');
56
- let syncResult = honeydiff.compareSync(baseline, current, {
57
- threshold: 2,
58
- includeClusters: true,
59
- });
60
- console.log(` Diff percentage: ${syncResult.diffPercentage.toFixed(2)}%`);
61
-
62
- console.log('\n4. Image dimensions');
63
- let dimensions = await honeydiff.getDimensions(baseline);
64
- console.log(` Dimensions: ${dimensions.width}x${dimensions.height}`);
65
-
66
- console.log('\n5. Diff artifacts');
67
- let artifactResult = await honeydiff.compare(baseline, current, {
68
- threshold: 0,
69
- diffPath: '/tmp/honeydiff-diff.png',
70
- maskPath: '/tmp/honeydiff-mask.png',
71
- overlayPath: '/tmp/honeydiff-side-by-side.png',
72
- overwrite: true,
73
- });
74
- console.log(` Artifact diff percentage: ${artifactResult.diffPercentage.toFixed(2)}%`);
75
- console.log(' Artifacts saved to /tmp/honeydiff-*.png');
3
+ let baseline = process.argv[2];
4
+ let current = process.argv[3];
5
+ if (!baseline || !current) {
6
+ throw new Error('Usage: node examples/basic.js <baseline.png> <current.png>');
76
7
  }
77
-
78
- main().catch((err) => {
79
- console.error('Error:', err);
80
- process.exit(1);
81
- });
8
+ let options = { threshold: 2, antialiasing: true, minimumRegionPixels: 2 };
9
+ let result = await compare(baseline, current, options);
10
+
11
+ console.log(`Different: ${result.different}`);
12
+ console.log(`Changed: ${result.pixels.changed.total} / ${result.pixels.total}`);
13
+ console.log(`Regions: ${result.difference.regions.length}`);
14
+ console.log(
15
+ `Boolean agrees: ${(await imagesDiffer(baseline, current, options)) === result.different}`
16
+ );
17
+
18
+ let detailed = await analyze(baseline, current, options);
19
+ console.log(`SSIM: ${detailed.perception.ssim.toFixed(4)}`);
20
+ console.log(`MS-SSIM: ${detailed.perception.msSsim.toFixed(4)}`);
21
+ console.log(`GMSD: ${detailed.perception.gmsd.toFixed(4)}`);
@@ -1,40 +1,9 @@
1
- /**
2
- * Example: Using Buffers instead of file paths
3
- */
4
-
5
1
  import fs from 'node:fs';
6
- import * as honeydiff from '../index.js';
7
-
8
- async function main() {
9
- console.log('Buffer Example\n');
10
-
11
- // Load images as buffers
12
- let img1Buffer = fs.readFileSync('../../tests/fixtures/screenshots/vizzly-baseline.png');
13
- let img2Buffer = fs.readFileSync('../../tests/fixtures/screenshots/vizzly-with-diff.png');
14
-
15
- console.log(`Loaded buffers: ${img1Buffer.length} and ${img2Buffer.length} bytes\n`);
16
-
17
- // Compare using buffers
18
- let result = await honeydiff.compare(img1Buffer, img2Buffer, {
19
- threshold: 0,
20
- includeSSIM: true,
21
- });
22
-
23
- console.log('Comparison result:');
24
- console.log(` Different: ${result.isDifferent}`);
25
- console.log(` Diff percentage: ${result.diffPercentage.toFixed(2)}%`);
26
- console.log(` SSIM: ${result.perceptualScore?.toFixed(4) || 'N/A'}`);
2
+ import { compare } from '../index.js';
27
3
 
28
- // Mixed: buffer + file path
29
- console.log('\nMixed comparison (buffer + file path):');
30
- let mixedResult = await honeydiff.compare(
31
- img1Buffer,
32
- '../../tests/fixtures/screenshots/vizzly-with-diff.png'
33
- );
34
- console.log(` Diff percentage: ${mixedResult.diffPercentage.toFixed(2)}%`);
35
- }
4
+ let baseline = fs.readFileSync(process.argv[2] ?? 'baseline.png');
5
+ let current = fs.readFileSync(process.argv[3] ?? 'current.png');
6
+ let result = await compare(baseline, current);
36
7
 
37
- main().catch((err) => {
38
- console.error('Error:', err);
39
- process.exit(1);
40
- });
8
+ console.log(`Different: ${result.different}`);
9
+ console.log(`Changed fraction: ${result.difference.fraction}`);
@@ -1,78 +1,25 @@
1
- /**
2
- * TypeScript example with full type safety
3
- */
4
-
5
- import { type CompareOptions, compare, type DiffResult, quickCompare } from '../index';
6
-
7
- async function main(): Promise<void> {
8
- console.log('TypeScript Example\n');
9
-
10
- // Quick comparison
11
- let isDifferent: boolean = await quickCompare(
12
- '../../tests/fixtures/screenshots/vizzly-baseline.png',
13
- '../../tests/fixtures/screenshots/vizzly-with-diff.png'
14
- );
15
- console.log(`Quick result: ${isDifferent ? 'DIFFERENT' : 'IDENTICAL'}\n`);
16
-
17
- // Detailed comparison with full typing
18
- let options: CompareOptions = {
19
- threshold: 2,
20
- antialiasing: true,
21
- includeSSIM: true,
22
- includeClusters: true,
23
- includeDiffPixels: false,
24
- };
25
-
26
- let result: DiffResult = await compare(
27
- '../../tests/fixtures/screenshots/vizzly-baseline.png',
28
- '../../tests/fixtures/screenshots/vizzly-with-diff.png',
29
- options
30
- );
31
-
32
- console.log('Detailed results:');
33
- console.log(` Diff: ${result.diffPercentage.toFixed(2)}%`);
34
- console.log(` Different pixels: ${result.diffPixels} / ${result.totalPixels}`);
35
-
36
- // Type-safe access to optional fields
37
- if (result.perceptualScore !== null) {
38
- let ssim: number = result.perceptualScore;
39
- console.log(` SSIM: ${ssim.toFixed(4)}`);
40
-
41
- if (ssim > 0.95) {
42
- console.log(' → Perceptually very similar');
43
- } else if (ssim > 0.8) {
44
- console.log(' → Somewhat similar');
45
- } else {
46
- console.log(' → Perceptually different');
47
- }
48
- }
49
-
50
- if (result.boundingBox) {
51
- console.log(
52
- ` Changes in region: (${result.boundingBox.x}, ${result.boundingBox.y}) ${result.boundingBox.width}x${result.boundingBox.height}`
53
- );
54
- }
55
-
56
- if (result.diffClusters && result.diffClusters.length > 0) {
57
- console.log(`\nFound ${result.diffClusters.length} separate diff regions:`);
58
- result.diffClusters.slice(0, 5).forEach((cluster, i) => {
59
- let [cx, cy] = cluster.centerOfMass;
60
- console.log(
61
- ` ${i + 1}. ${cluster.pixelCount} pixels at (${cx.toFixed(0)}, ${cy.toFixed(0)})`
62
- );
63
- });
64
- }
65
-
66
- if (result.intensityStats) {
67
- console.log('\nIntensity statistics:');
68
- console.log(` Range: ${result.intensityStats.min} - ${result.intensityStats.max}`);
69
- console.log(` Mean: ${result.intensityStats.mean.toFixed(1)}`);
70
- console.log(` Median: ${result.intensityStats.median}`);
71
- console.log(` Std Dev: ${result.intensityStats.stdDev.toFixed(1)}`);
72
- }
73
- }
74
-
75
- main().catch((err: Error) => {
76
- console.error('Error:', err.message);
77
- process.exit(1);
78
- });
1
+ import {
2
+ analyze,
3
+ type CompareOptions,
4
+ compare,
5
+ computeFingerprintSync,
6
+ type DiffAnalysis,
7
+ imagesDiffer,
8
+ } from '../index.js';
9
+
10
+ let options: CompareOptions = {
11
+ threshold: 2,
12
+ antialiasing: true,
13
+ minimumRegionPixels: 2,
14
+ };
15
+ let result: DiffAnalysis = await compare('baseline.png', 'current.png', options);
16
+
17
+ console.log(`${result.pixels.changed.total} changed pixels`);
18
+ console.log(`${result.difference.regions.length} exact regions`);
19
+ console.log(
20
+ `boolean agrees: ${(await imagesDiffer('baseline.png', 'current.png', options)) === result.different}`
21
+ );
22
+ console.log(`fingerprint: ${computeFingerprintSync(result)?.hash ?? 'identical'}`);
23
+
24
+ let detailed = await analyze('baseline.png', 'current.png', options);
25
+ console.log(`SSIM ${detailed.perception.ssim}, MS-SSIM ${detailed.perception.msSsim}`);