@vizzly-testing/honeydiff 0.15.0 → 0.17.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 +35 -0
- package/LICENSE +21 -0
- package/README.md +80 -119
- package/examples/basic.js +19 -79
- package/examples/buffers.js +6 -37
- package/examples/typescript.ts +25 -78
- package/index.d.ts +173 -1183
- package/index.js +91 -7
- package/package.json +4 -3
- package/platforms/index-aarch64-apple-darwin.node +0 -0
- package/platforms/index-aarch64-unknown-linux-gnu.node +0 -0
- package/platforms/index-x86_64-pc-windows-msvc.node +0 -0
- package/platforms/index-x86_64-unknown-linux-gnu.node +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,41 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.17.0] - 2026-09-06
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- Added `HONEYDIFF_THREADS` and `configureThreads()` for one process-wide
|
|
15
|
+
Honeydiff compute budget.
|
|
16
|
+
|
|
17
|
+
### Changed
|
|
18
|
+
|
|
19
|
+
- Reduced `analyze` CPU and temporary memory by sharing luminance and structural
|
|
20
|
+
metric work, with an exact-content fast path.
|
|
21
|
+
|
|
22
|
+
**Full Changelog**: https://github.com/vizzly-testing/honeydiff/compare/v0.16.0...v0.17.0
|
|
23
|
+
|
|
24
|
+
## [0.16.0] - 2026-08-03
|
|
25
|
+
|
|
26
|
+
### Added
|
|
27
|
+
|
|
28
|
+
- Added complete, data-only comparison results with reconciled pixel counts, exact retained regions, Delta E summaries, and explicit height-change data.
|
|
29
|
+
- Added `analyze` for SSIM, MS-SSIM, and GMSD across Rust, Node, and the CLI.
|
|
30
|
+
- Added optional diff, mask, and side-by-side artifacts without putting image data in the result.
|
|
31
|
+
|
|
32
|
+
### Changed
|
|
33
|
+
|
|
34
|
+
- Replaced metric include flags with four comparison settings: threshold, antialiasing, minimum region size, and height alignment.
|
|
35
|
+
- Node and CLI now return the same nested analysis shape serialized by Rust.
|
|
36
|
+
- Small suppressed regions now use bounded aggregate statistics instead of one object per region.
|
|
37
|
+
- `images_differ` and fingerprints now derive from the same comparison result.
|
|
38
|
+
|
|
39
|
+
### Removed
|
|
40
|
+
|
|
41
|
+
- Removed early-exit results, public diff-pixel coordinates, packed mask payloads, and encoded image data from comparison results.
|
|
42
|
+
|
|
43
|
+
**Full Changelog**: https://github.com/vizzly-testing/honeydiff/compare/v0.15.0...v0.16.0
|
|
44
|
+
|
|
10
45
|
## [0.15.0] - 2026-08-03
|
|
11
46
|
|
|
12
47
|
### 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,
|
|
28
|
+
import { compare, imagesDiffer } from '@vizzly-testing/honeydiff';
|
|
29
29
|
|
|
30
|
-
let
|
|
30
|
+
let options = {
|
|
31
|
+
threshold: 2,
|
|
32
|
+
antialiasing: true,
|
|
33
|
+
minimumRegionPixels: 2,
|
|
34
|
+
alignHeightChanges: false,
|
|
35
|
+
};
|
|
31
36
|
|
|
32
|
-
|
|
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.
|
|
46
|
-
console.log(result.
|
|
47
|
-
console.log(result.
|
|
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
|
-
`
|
|
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
|
|
63
|
-
-
|
|
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.
|
|
79
|
-
console.log(`${result.
|
|
71
|
+
if (result.different) {
|
|
72
|
+
console.log(`${result.pixels.changed.total} pixels changed`);
|
|
80
73
|
}
|
|
81
74
|
```
|
|
82
75
|
|
|
@@ -97,79 +90,79 @@ let result = await compare(baseline, current);
|
|
|
97
90
|
```js
|
|
98
91
|
import { compare } from '@vizzly-testing/honeydiff';
|
|
99
92
|
|
|
100
|
-
let result = await compare(
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
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
|
-
|
|
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
|
|
120
|
-
console.log(
|
|
116
|
+
for (let region of result.difference.regions) {
|
|
117
|
+
console.log(region.pixels, region.bounds, region.deltaE);
|
|
121
118
|
}
|
|
122
119
|
```
|
|
123
120
|
|
|
124
|
-
###
|
|
125
|
-
|
|
126
|
-
Use diff-mask evidence when another system needs durable spatial facts
|
|
127
|
-
without decoding the generated PNG:
|
|
121
|
+
### Add Perceptual Metrics
|
|
128
122
|
|
|
129
123
|
```js
|
|
130
|
-
|
|
131
|
-
includeDiffMaskEvidence: true,
|
|
132
|
-
maskPath: 'artifacts/mask.png',
|
|
133
|
-
});
|
|
124
|
+
import { analyze } from '@vizzly-testing/honeydiff';
|
|
134
125
|
|
|
135
|
-
|
|
136
|
-
|
|
126
|
+
let result = await analyze('baseline.png', 'current.png');
|
|
127
|
+
|
|
128
|
+
console.log(result.perception.ssim);
|
|
129
|
+
console.log(result.perception.msSsim);
|
|
130
|
+
console.log(result.perception.gmsd);
|
|
137
131
|
```
|
|
138
132
|
|
|
139
|
-
|
|
140
|
-
separate from `diffClusters`, whose optional merge heuristics are intended for
|
|
141
|
-
diagnostic presentation.
|
|
133
|
+
### Bound CPU Usage
|
|
142
134
|
|
|
143
|
-
|
|
135
|
+
Use one process-wide limit when Honeydiff runs inside a worker service:
|
|
144
136
|
|
|
145
|
-
```
|
|
146
|
-
|
|
137
|
+
```bash
|
|
138
|
+
HONEYDIFF_THREADS=4 node visual-tests.js
|
|
139
|
+
```
|
|
147
140
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
includeGMSD: true,
|
|
151
|
-
});
|
|
141
|
+
Applications that cannot set the environment before startup can configure the
|
|
142
|
+
same limit before their first operation:
|
|
152
143
|
|
|
153
|
-
|
|
154
|
-
|
|
144
|
+
```js
|
|
145
|
+
import { configureThreads } from '@vizzly-testing/honeydiff';
|
|
146
|
+
|
|
147
|
+
configureThreads(4);
|
|
155
148
|
```
|
|
156
149
|
|
|
150
|
+
Repeating the same value is safe. Changing it after Honeydiff work starts
|
|
151
|
+
throws instead of silently creating an oversubscribed pool.
|
|
152
|
+
|
|
157
153
|
### Align One Added Or Removed Block
|
|
158
154
|
|
|
159
155
|
```js
|
|
160
156
|
let result = await compare('baseline.png', 'current.png', {
|
|
161
157
|
alignHeightChanges: true,
|
|
162
|
-
maskPath: 'artifacts/mask.png',
|
|
163
158
|
});
|
|
164
159
|
```
|
|
165
160
|
|
|
166
161
|
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
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
GMSD compare the unaligned overlapping portions, and the side-by-side overlay
|
|
172
|
-
uses the original images.
|
|
162
|
+
while keeping separate changes below it visible. Ambiguous matches and
|
|
163
|
+
bottom-only growth use the normal comparison. It does not handle multiple
|
|
164
|
+
blocks, partial-width movement, or fuzzy matching. `analyze` applies the same
|
|
165
|
+
accepted row mapping before it calculates perceptual scores.
|
|
173
166
|
|
|
174
167
|
### Screenshot Contrast
|
|
175
168
|
|
|
@@ -201,60 +194,26 @@ await saveColorBlindnessSimulation(
|
|
|
201
194
|
|
|
202
195
|
| Option | Default | Notes |
|
|
203
196
|
| --- | --- | --- |
|
|
204
|
-
| `threshold` | `2
|
|
205
|
-
| `antialiasing` | `true` |
|
|
206
|
-
| `
|
|
207
|
-
| `
|
|
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. |
|
|
197
|
+
| `threshold` | `2` | CIEDE2000 tolerance. Use `0` for zero perceptual tolerance. |
|
|
198
|
+
| `antialiasing` | `true` | Suppress conservative raster-coverage movement. |
|
|
199
|
+
| `minimumRegionPixels` | `2` | Suppress smaller 8-connected visual regions. |
|
|
200
|
+
| `alignHeightChanges` | `false` | Align one unambiguous inserted or removed row block. |
|
|
219
201
|
|
|
220
|
-
|
|
202
|
+
There are no include flags. `compare` always returns its complete data.
|
|
221
203
|
|
|
222
|
-
|
|
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
|
-
```
|
|
204
|
+
## Result Shape
|
|
243
205
|
|
|
244
|
-
|
|
245
|
-
|
|
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`.
|
|
206
|
+
The result separates every pixel into rendered-equal, suppressed, visual-change,
|
|
207
|
+
or structural-change populations. Those counts always add up to `pixels.total`.
|
|
253
208
|
|
|
254
|
-
|
|
255
|
-
|
|
209
|
+
`difference.regions` contains exact maximal 8-connected retained regions with
|
|
210
|
+
bounds, centroid, density, perimeter, visual/structural composition, and real
|
|
211
|
+
Delta E statistics where visual samples exist. `heightChange` explains added or
|
|
212
|
+
removed rows and whether alignment was applied.
|
|
256
213
|
|
|
257
|
-
|
|
214
|
+
The result is JSON-native data. It never contains a `Buffer`, base64 image,
|
|
215
|
+
packed mask, bitmap-sized array, or per-pixel coordinate list. See
|
|
216
|
+
[`index.d.ts`](./index.d.ts) for the complete shape.
|
|
258
217
|
|
|
259
218
|
## Thresholds
|
|
260
219
|
|
|
@@ -294,6 +253,8 @@ for the current benchmark notes.
|
|
|
294
253
|
|
|
295
254
|
```js
|
|
296
255
|
import {
|
|
256
|
+
analyze,
|
|
257
|
+
analyzeSync,
|
|
297
258
|
analyzeWcagAllCvd,
|
|
298
259
|
analyzeWcagContrast,
|
|
299
260
|
analyzeWcagForCvd,
|
|
@@ -309,8 +270,8 @@ import {
|
|
|
309
270
|
getImageMetadataFromFile,
|
|
310
271
|
getImageMetadataFromFileSync,
|
|
311
272
|
getImageMetadataSync,
|
|
312
|
-
|
|
313
|
-
|
|
273
|
+
imagesDiffer,
|
|
274
|
+
imagesDifferSync,
|
|
314
275
|
saveAllColorBlindnessSimulations,
|
|
315
276
|
saveColorBlindnessSimulation,
|
|
316
277
|
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
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
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
|
-
|
|
79
|
-
|
|
80
|
-
|
|
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)}`);
|
package/examples/buffers.js
CHANGED
|
@@ -1,40 +1,9 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Example: Using Buffers instead of file paths
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
1
|
import fs from 'node:fs';
|
|
6
|
-
import
|
|
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
|
-
|
|
29
|
-
|
|
30
|
-
|
|
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
|
-
|
|
38
|
-
|
|
39
|
-
process.exit(1);
|
|
40
|
-
});
|
|
8
|
+
console.log(`Different: ${result.different}`);
|
|
9
|
+
console.log(`Changed fraction: ${result.difference.fraction}`);
|
package/examples/typescript.ts
CHANGED
|
@@ -1,78 +1,25 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
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}`);
|