@vizzly-testing/honeydiff 0.13.1 → 0.15.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,30 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.15.0] - 2026-08-03
11
+
12
+ ### Changed
13
+
14
+ - ✨ Improve diffs for single-block height shifts (#39)
15
+
16
+ **Full Changelog**: https://github.com/vizzly-testing/honeydiff/compare/v0.14.0...v0.15.0
17
+
18
+ ## [0.14.0] - 2026-07-24
19
+
20
+ ### Added
21
+ - Added opt-in, versioned `diffMaskEvidence` with a native analysis
22
+ identity, bitmap dimensions, completeness, and exact 8-connected component
23
+ facts derived from the retained pixels used by mask artifacts.
24
+
25
+ ### Changed
26
+ - Named retained mask facts consistently around Honeydiff's diff-mask artifact:
27
+ `diffMaskPixels`, `diffMaskComplete`, `diffMaskEvidence`, and
28
+ `includeDiffMaskEvidence`.
29
+ - Diff-mask artifact generation reuses requested native evidence instead
30
+ of repeating the comparison to recover filtered coordinates.
31
+
32
+ **Full Changelog**: https://github.com/vizzly-testing/honeydiff/compare/v0.13.1...v0.14.0
33
+
10
34
  ## [0.13.1] - 2026-07-23
11
35
 
12
36
  ### Changed
@@ -18,14 +42,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
18
42
  ## [0.13.0] - 2026-07-23
19
43
 
20
44
  ### Added
21
- - Exposed `effectiveDiffPixels`, `effectiveMaskComplete`,
22
- `maskSemanticsVersion`, and the installed package `version` for auditable
23
- mask consumers.
45
+ - Exposed retained mask pixel count, completeness, semantics version, and the
46
+ installed package version for auditable mask consumers.
24
47
 
25
48
  ### Fixed
26
49
  - Kept binary mask alpha opaque even when a transparent display diff color is
27
50
  requested.
28
- - Kept requested pixel and cluster diagnostics from changing the effective mask
51
+ - Kept requested pixel and cluster diagnostics from changing the diff mask
29
52
  or variable-height classification.
30
53
 
31
54
  **Full Changelog**: https://github.com/vizzly-testing/honeydiff/compare/v0.12.0...v0.13.0
package/README.md CHANGED
@@ -121,6 +121,25 @@ for (let cluster of result.diffClusters ?? []) {
121
121
  }
122
122
  ```
123
123
 
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
+
124
143
  ### Add Perceptual Metrics
125
144
 
126
145
  ```js
@@ -135,6 +154,23 @@ console.log(result.perceptualScore);
135
154
  console.log(result.gmsdScore);
136
155
  ```
137
156
 
157
+ ### Align One Added Or Removed Block
158
+
159
+ ```js
160
+ let result = await compare('baseline.png', 'current.png', {
161
+ alignHeightChanges: true,
162
+ maskPath: 'artifacts/mask.png',
163
+ });
164
+ ```
165
+
166
+ 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.
173
+
138
174
  ### Screenshot Contrast
139
175
 
140
176
  ```js
@@ -167,9 +203,11 @@ await saveColorBlindnessSimulation(
167
203
  | --- | --- | --- |
168
204
  | `threshold` | `2.0` | CIEDE2000 Delta E threshold. Use `0` for zero perceptual tolerance. |
169
205
  | `antialiasing` | `true` | Ignore likely anti-aliased pixels. |
206
+ | `alignHeightChanges` | `false` | Align one conservatively detected added or removed block of rows. |
170
207
  | `maxDiffs` | unlimited | Stop after a maximum number of differing pixels. Capped results classify from the pixels observed before early exit, without cluster filtering. |
171
208
  | `includeDiffPixels` | `false` | Return individual differing pixel positions and intensities. |
172
209
  | `includeClusters` | `false` | Return connected regions of visual change. |
210
+ | `includeDiffMaskEvidence` | `false` | Return authoritative diff-mask identity, dimensions, and exact components. |
173
211
  | `minClusterSize` | `2` | Filter tiny isolated clusters as noise. |
174
212
  | `clusterMerge` | disabled | Merge nearby clusters into logical text-like regions. |
175
213
  | `includeSSIM` | `false` | Calculate structural similarity. More expensive on large images. |
@@ -187,9 +225,10 @@ interface DiffResult {
187
225
  diffPercentage: number;
188
226
  totalPixels: number;
189
227
  diffPixels: number;
190
- effectiveDiffPixels: number;
191
- effectiveMaskComplete: boolean;
228
+ diffMaskPixels: number;
229
+ diffMaskComplete: boolean;
192
230
  maskSemanticsVersion: string;
231
+ diffMaskEvidence: DiffMaskEvidence | null;
193
232
  aaPixelsIgnored: number;
194
233
  aaPercentage: number;
195
234
  boundingBox: BoundingBox | null;
@@ -203,11 +242,14 @@ interface DiffResult {
203
242
  ```
204
243
 
205
244
  `diffPixels` is the raw post-threshold/anti-aliasing count.
206
- `effectiveDiffPixels` is the retained count represented by the binary mask after
245
+ `diffMaskPixels` is the retained count represented by the binary mask after
207
246
  `minClusterSize` filtering. It always matches the mask's nontransparent pixel
208
- count. If `effectiveMaskComplete` is `false`, `maxDiffs` stopped the scan early;
247
+ count. If `diffMaskComplete` is `false`, `maxDiffs` stopped the scan early;
209
248
  the partial mask is useful for diagnostics but not complete spatial proof.
210
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`.
211
253
 
212
254
  The package also exports `version` from its installed manifest and
213
255
  `maskSemanticsVersion` from the native comparison engine.
package/index.d.ts CHANGED
@@ -60,6 +60,36 @@ export interface DiffCluster {
60
60
  boundingBox: BoundingBox;
61
61
  }
62
62
 
63
+ export interface DiffMaskComponent {
64
+ /** Number of retained pixels in this exact 8-connected component */
65
+ pixelCount: number;
66
+ /** Bitmap-pixel bounds around the component */
67
+ boundingBox: BoundingBox;
68
+ }
69
+
70
+ export interface DiffMaskEvidence {
71
+ /** Version of this evidence result shape */
72
+ contractVersion: string;
73
+ /** Native identity of normalized options that can change retained pixels */
74
+ analysisContractHash: string;
75
+ /** Version of Honeydiff's retained-pixel semantics */
76
+ semanticsVersion: string;
77
+ /** Version of the connected-component semantics */
78
+ componentSemanticsVersion: string;
79
+ /** Bitmap coordinate-space version */
80
+ coordinateSpaceVersion: string;
81
+ /** Diff-mask width in bitmap pixels */
82
+ width: number;
83
+ /** Diff-mask height in bitmap pixels */
84
+ height: number;
85
+ /** Number of retained pixels represented by the mask */
86
+ pixelCount: number;
87
+ /** False when maxDiffs stopped the comparison early */
88
+ complete: boolean;
89
+ /** Deterministically ordered exact connected components */
90
+ components: DiffMaskComponent[];
91
+ }
92
+
63
93
  export interface DiffResult {
64
94
  /**
65
95
  * Whether the images are different based on the comparison threshold
@@ -75,12 +105,14 @@ export interface DiffResult {
75
105
  * Note: This may be > 0 even when isDifferent is false if small clusters were filtered as noise
76
106
  */
77
107
  diffPixels: number;
78
- /** Number of retained pixels represented by the effective diff mask */
79
- effectiveDiffPixels: number;
108
+ /** Number of retained pixels represented by the diff mask */
109
+ diffMaskPixels: number;
80
110
  /** False when maxDiffs stopped the scan before the full image was evaluated */
81
- effectiveMaskComplete: boolean;
82
- /** Version of the pixel-retention semantics used by the effective mask */
111
+ diffMaskComplete: boolean;
112
+ /** Version of the pixel-retention semantics used by the diff mask */
83
113
  maskSemanticsVersion: string;
114
+ /** Authoritative diff-mask evidence (null unless includeDiffMaskEvidence is enabled) */
115
+ diffMaskEvidence: DiffMaskEvidence | null;
84
116
  /** Number of pixels ignored due to anti-aliasing detection */
85
117
  aaPixelsIgnored: number;
86
118
  /** Percentage of pixels ignored as anti-aliasing (0.0 - 100.0) */
@@ -108,7 +140,7 @@ export interface DiffResult {
108
140
  /** Installed @vizzly-testing/honeydiff package version */
109
141
  export const version: string;
110
142
 
111
- /** Native effective-mask semantics implemented by this package */
143
+ /** Native diff-mask semantics implemented by this package */
112
144
  export const maskSemanticsVersion: string;
113
145
 
114
146
  // ============================================================================
@@ -187,6 +219,14 @@ export interface CompareOptions {
187
219
  */
188
220
  antialiasing?: boolean;
189
221
 
222
+ /**
223
+ * Align one conservatively detected added or removed block of rows.
224
+ * Falls back to the normal same-coordinate comparison when the match is
225
+ * ambiguous or maxDiffs is set.
226
+ * @default false
227
+ */
228
+ alignHeightChanges?: boolean;
229
+
190
230
  /**
191
231
  * Maximum number of differences to detect before stopping
192
232
  * Capped comparisons classify from observed pixel evidence because early
@@ -209,6 +249,13 @@ export interface CompareOptions {
209
249
  */
210
250
  includeClusters?: boolean;
211
251
 
252
+ /**
253
+ * Include authoritative diff-mask dimensions, identity, and exact
254
+ * connected-component facts.
255
+ * @default false
256
+ */
257
+ includeDiffMaskEvidence?: boolean;
258
+
212
259
  /**
213
260
  * Calculate SSIM (Structural Similarity Index) perceptual score
214
261
  * This metric measures the source images independently of threshold and AA
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vizzly-testing/honeydiff",
3
- "version": "0.13.1",
3
+ "version": "0.15.0",
4
4
  "description": "High-performance image diffing for Node.js - Native bindings to Honeydiff Rust library",
5
5
  "type": "module",
6
6
  "main": "index.js",