@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/index.d.ts
CHANGED
|
@@ -1,886 +1,261 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* TypeScript definitions for @vizzly-testing/honeydiff
|
|
3
|
-
* High-performance image diffing for Node.js
|
|
4
|
-
*/
|
|
1
|
+
/** Data-only Node.js API for Honeydiff image analysis. */
|
|
5
2
|
|
|
6
|
-
|
|
7
|
-
// Core Types
|
|
8
|
-
// ============================================================================
|
|
3
|
+
export type ImageInput = string | Buffer;
|
|
9
4
|
|
|
10
|
-
export interface
|
|
11
|
-
/** X coordinate of top-left corner */
|
|
12
|
-
x: number;
|
|
13
|
-
/** Y coordinate of top-left corner */
|
|
14
|
-
y: number;
|
|
15
|
-
/** Width of the bounding box */
|
|
5
|
+
export interface ImageSize {
|
|
16
6
|
width: number;
|
|
17
|
-
/** Height of the bounding box */
|
|
18
7
|
height: number;
|
|
19
8
|
}
|
|
20
9
|
|
|
21
|
-
export interface
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
/** Height of the second image in pixels */
|
|
25
|
-
height2: number;
|
|
26
|
-
/** Number of extra pixels due to height difference */
|
|
27
|
-
extraPixels: number;
|
|
10
|
+
export interface BoundingBox extends ImageSize {
|
|
11
|
+
x: number;
|
|
12
|
+
y: number;
|
|
28
13
|
}
|
|
29
14
|
|
|
30
|
-
export interface
|
|
31
|
-
/** X coordinate of the differing pixel */
|
|
15
|
+
export interface Centroid {
|
|
32
16
|
x: number;
|
|
33
|
-
/** Y coordinate of the differing pixel */
|
|
34
17
|
y: number;
|
|
35
|
-
/** Intensity of the difference (0-255, higher = more different) */
|
|
36
|
-
intensity: number;
|
|
37
18
|
}
|
|
38
19
|
|
|
39
|
-
export interface
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
20
|
+
export interface PopulationSummary {
|
|
21
|
+
count: number;
|
|
22
|
+
fraction: number;
|
|
23
|
+
bounds: BoundingBox | null;
|
|
24
|
+
centroid: Centroid | null;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface ComponentSizeSummary {
|
|
43
28
|
min: number;
|
|
44
|
-
|
|
29
|
+
max: number;
|
|
45
30
|
mean: number;
|
|
46
|
-
|
|
47
|
-
median: number;
|
|
48
|
-
/** Standard deviation of difference intensities */
|
|
49
|
-
stdDev: number;
|
|
31
|
+
standardDeviation: number;
|
|
50
32
|
}
|
|
51
33
|
|
|
52
|
-
export interface
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
/** Center of mass [x, y] coordinates */
|
|
56
|
-
centerOfMass: [number, number];
|
|
57
|
-
/** Average intensity of differences in this cluster */
|
|
58
|
-
avgIntensity: number;
|
|
59
|
-
/** Bounding box containing this cluster */
|
|
60
|
-
boundingBox: BoundingBox;
|
|
34
|
+
export interface SmallRegionSummary extends PopulationSummary {
|
|
35
|
+
componentCount: number;
|
|
36
|
+
componentSizes: ComponentSizeSummary | null;
|
|
61
37
|
}
|
|
62
38
|
|
|
63
|
-
export interface
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
39
|
+
export interface PixelAnalysis {
|
|
40
|
+
total: number;
|
|
41
|
+
renderedEqual: number;
|
|
42
|
+
suppressed: {
|
|
43
|
+
belowThreshold: PopulationSummary;
|
|
44
|
+
antialiasing: PopulationSummary;
|
|
45
|
+
smallRegions: SmallRegionSummary;
|
|
46
|
+
};
|
|
47
|
+
changed: {
|
|
48
|
+
total: number;
|
|
49
|
+
visual: number;
|
|
50
|
+
structural: number;
|
|
51
|
+
added: number;
|
|
52
|
+
removed: number;
|
|
53
|
+
};
|
|
68
54
|
}
|
|
69
55
|
|
|
70
|
-
export interface
|
|
71
|
-
|
|
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 */
|
|
56
|
+
export interface SpatialSummary {
|
|
57
|
+
bounds: BoundingBox;
|
|
86
58
|
pixelCount: number;
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
components: DiffMaskComponent[];
|
|
59
|
+
density: number;
|
|
60
|
+
centroid: Centroid;
|
|
61
|
+
perimeter: number;
|
|
91
62
|
}
|
|
92
63
|
|
|
93
|
-
export interface
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
/** Percentage of pixels that differ (0.0 - 100.0) */
|
|
100
|
-
diffPercentage: number;
|
|
101
|
-
/** Total number of pixels compared */
|
|
102
|
-
totalPixels: number;
|
|
103
|
-
/**
|
|
104
|
-
* Number of pixels that differ (raw count, unaffected by minClusterSize filtering)
|
|
105
|
-
* Note: This may be > 0 even when isDifferent is false if small clusters were filtered as noise
|
|
106
|
-
*/
|
|
107
|
-
diffPixels: number;
|
|
108
|
-
/** Number of retained pixels represented by the diff mask */
|
|
109
|
-
diffMaskPixels: number;
|
|
110
|
-
/** False when maxDiffs stopped the scan before the full image was evaluated */
|
|
111
|
-
diffMaskComplete: boolean;
|
|
112
|
-
/** Version of the pixel-retention semantics used by the diff mask */
|
|
113
|
-
maskSemanticsVersion: string;
|
|
114
|
-
/** Authoritative diff-mask evidence (null unless includeDiffMaskEvidence is enabled) */
|
|
115
|
-
diffMaskEvidence: DiffMaskEvidence | null;
|
|
116
|
-
/** Number of pixels ignored due to anti-aliasing detection */
|
|
117
|
-
aaPixelsIgnored: number;
|
|
118
|
-
/** Percentage of pixels ignored as anti-aliasing (0.0 - 100.0) */
|
|
119
|
-
aaPercentage: number;
|
|
120
|
-
/** Bounding box containing all differences (null if identical) */
|
|
121
|
-
boundingBox: BoundingBox | null;
|
|
122
|
-
/** Height difference information (null if same height) */
|
|
123
|
-
heightDiff: HeightDiff | null;
|
|
124
|
-
/** List of differing pixels with positions and intensities (null unless includeDiffPixels is enabled) */
|
|
125
|
-
diffPixelsList: DiffPixel[] | null;
|
|
126
|
-
/** Clustered groups of connected differing pixels (null unless includeClusters is enabled) */
|
|
127
|
-
diffClusters: DiffCluster[] | null;
|
|
128
|
-
/** Statistical metrics about difference intensities (null unless includeDiffPixels is enabled) */
|
|
129
|
-
intensityStats: IntensityStats | null;
|
|
130
|
-
/** SSIM score, approximately -1.0 to 1.0 (null unless includeSSIM is enabled) */
|
|
131
|
-
perceptualScore: number | null;
|
|
132
|
-
/**
|
|
133
|
-
* GMSD (Gradient Magnitude Similarity Deviation) score (null unless includeGMSD is enabled)
|
|
134
|
-
* Lower values (closer to 0.0) indicate more similar images
|
|
135
|
-
* Typical range: 0.0 to ~0.3 for natural images
|
|
136
|
-
*/
|
|
137
|
-
gmsdScore: number | null;
|
|
64
|
+
export interface DeltaESummary {
|
|
65
|
+
sampleCount: number;
|
|
66
|
+
min: number;
|
|
67
|
+
max: number;
|
|
68
|
+
mean: number;
|
|
69
|
+
standardDeviation: number;
|
|
138
70
|
}
|
|
139
71
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
// Options
|
|
148
|
-
// ============================================================================
|
|
149
|
-
|
|
150
|
-
/**
|
|
151
|
-
* Options for merging nearby clusters
|
|
152
|
-
*
|
|
153
|
-
* These heuristics are designed to merge fragmented text regions (like individual
|
|
154
|
-
* characters in a date string) into logical regions while avoiding merging
|
|
155
|
-
* unrelated visual changes.
|
|
156
|
-
*
|
|
157
|
-
* Inspired by the Stroke Width Transform (SWT) text detection algorithm:
|
|
158
|
-
* Epshtein, B., Ofek, E., & Wexler, Y. (2010). "Detecting Text in Natural Scenes
|
|
159
|
-
* with Stroke Width Transform." CVPR 2010.
|
|
160
|
-
*/
|
|
161
|
-
export interface ClusterMergeOptions {
|
|
162
|
-
/**
|
|
163
|
-
* Maximum horizontal distance to merge clusters in same Y-band (pixels)
|
|
164
|
-
*
|
|
165
|
-
* Clusters within this horizontal distance and overlapping Y-ranges
|
|
166
|
-
* will be merged. Suitable for merging characters in text.
|
|
167
|
-
* @default 15
|
|
168
|
-
*/
|
|
169
|
-
horizontalDistance?: number;
|
|
72
|
+
export interface DiffRegion {
|
|
73
|
+
spatial: SpatialSummary;
|
|
74
|
+
visualPixels: number;
|
|
75
|
+
addedPixels: number;
|
|
76
|
+
removedPixels: number;
|
|
77
|
+
appearance: DeltaESummary | null;
|
|
78
|
+
}
|
|
170
79
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
80
|
+
export interface DiffAnalysis {
|
|
81
|
+
different: boolean;
|
|
82
|
+
settings: {
|
|
83
|
+
threshold: number;
|
|
84
|
+
antialiasing: boolean;
|
|
85
|
+
minimumRegionPixels: number;
|
|
86
|
+
alignHeightChanges: boolean;
|
|
87
|
+
};
|
|
88
|
+
images: {
|
|
89
|
+
baseline: ImageSize;
|
|
90
|
+
current: ImageSize;
|
|
91
|
+
canvas: ImageSize;
|
|
92
|
+
};
|
|
93
|
+
pixels: PixelAnalysis;
|
|
94
|
+
difference: {
|
|
95
|
+
fraction: number;
|
|
96
|
+
spatial: SpatialSummary | null;
|
|
97
|
+
appearance: DeltaESummary | null;
|
|
98
|
+
regions: DiffRegion[];
|
|
99
|
+
};
|
|
100
|
+
heightChange: {
|
|
101
|
+
direction: 'added' | 'removed';
|
|
102
|
+
rowCount: number;
|
|
103
|
+
startRow: number;
|
|
104
|
+
alignmentApplied: boolean;
|
|
105
|
+
} | null;
|
|
106
|
+
}
|
|
179
107
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
108
|
+
export interface PerceptionAnalysis {
|
|
109
|
+
ssim: number;
|
|
110
|
+
msSsim: number;
|
|
111
|
+
gmsd: number;
|
|
112
|
+
measured: {
|
|
113
|
+
width: number;
|
|
114
|
+
pairedRows: number;
|
|
115
|
+
baselineExcludedRows: number;
|
|
116
|
+
currentExcludedRows: number;
|
|
117
|
+
};
|
|
118
|
+
}
|
|
188
119
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
* Additional SWT-inspired heuristic to prevent merging dissimilar
|
|
193
|
-
* regions.
|
|
194
|
-
* @default 3.0
|
|
195
|
-
*/
|
|
196
|
-
maxWidthRatio?: number;
|
|
120
|
+
export interface ImageAnalysis {
|
|
121
|
+
comparison: DiffAnalysis;
|
|
122
|
+
perception: PerceptionAnalysis;
|
|
197
123
|
}
|
|
198
124
|
|
|
199
125
|
export interface CompareOptions {
|
|
200
|
-
/**
|
|
201
|
-
* Perceptual color difference threshold using CIEDE2000 (Delta E units)
|
|
202
|
-
*
|
|
203
|
-
* The CIEDE2000 standard provides intuitive, perceptually uniform thresholds:
|
|
204
|
-
* - 0.0 = No Delta E tolerance; AA and cluster filtering remain independent
|
|
205
|
-
* - 1.0 = Just Noticeable Difference (JND) - barely perceptible to trained observers
|
|
206
|
-
* - 2.0 = Recommended default - ignores sub-pixel rendering variance, catches real differences
|
|
207
|
-
* - 3.0+ = Permissive - high tolerance for rendering variations
|
|
208
|
-
*
|
|
209
|
-
* @default 2.0
|
|
210
|
-
*/
|
|
126
|
+
/** CIEDE2000 threshold. Default: 2. */
|
|
211
127
|
threshold?: number;
|
|
212
|
-
|
|
213
|
-
/**
|
|
214
|
-
* Enable anti-aliasing detection to ignore AA differences
|
|
215
|
-
*
|
|
216
|
-
* Suppression requires a luminance ramp plus strict cross-image evidence that
|
|
217
|
-
* raster coverage moved to a neighboring pixel. Stable edge recolors remain differences.
|
|
218
|
-
* @default true
|
|
219
|
-
*/
|
|
128
|
+
/** Ignore detected raster anti-aliasing movement. Default: true. */
|
|
220
129
|
antialiasing?: boolean;
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
* Falls back to the normal same-coordinate comparison when the match is
|
|
225
|
-
* ambiguous or maxDiffs is set.
|
|
226
|
-
* @default false
|
|
227
|
-
*/
|
|
130
|
+
/** Smallest retained 8-connected visual region. Default: 2. */
|
|
131
|
+
minimumRegionPixels?: number;
|
|
132
|
+
/** Detect and align one unambiguous inserted or removed row block. Default: false. */
|
|
228
133
|
alignHeightChanges?: boolean;
|
|
134
|
+
}
|
|
229
135
|
|
|
230
|
-
|
|
231
|
-
* Maximum number of differences to detect before stopping
|
|
232
|
-
* Capped comparisons classify from observed pixel evidence because early
|
|
233
|
-
* exit cannot determine whether a complete cluster meets minClusterSize.
|
|
234
|
-
* @default undefined (unlimited)
|
|
235
|
-
*/
|
|
236
|
-
maxDiffs?: number;
|
|
237
|
-
|
|
238
|
-
/**
|
|
239
|
-
* Include list of differing pixels with positions and intensities
|
|
240
|
-
* WARNING: This can use significant memory for images with many differences
|
|
241
|
-
* @default false
|
|
242
|
-
*/
|
|
243
|
-
includeDiffPixels?: boolean;
|
|
244
|
-
|
|
245
|
-
/**
|
|
246
|
-
* Include clustered groups of connected differing pixels
|
|
247
|
-
* Clusters are sorted by size (largest first)
|
|
248
|
-
* @default false
|
|
249
|
-
*/
|
|
250
|
-
includeClusters?: boolean;
|
|
251
|
-
|
|
252
|
-
/**
|
|
253
|
-
* Include authoritative diff-mask dimensions, identity, and exact
|
|
254
|
-
* connected-component facts.
|
|
255
|
-
* @default false
|
|
256
|
-
*/
|
|
257
|
-
includeDiffMaskEvidence?: boolean;
|
|
258
|
-
|
|
259
|
-
/**
|
|
260
|
-
* Calculate SSIM (Structural Similarity Index) perceptual score
|
|
261
|
-
* This metric measures the source images independently of threshold and AA
|
|
262
|
-
* filtering. Transparent inputs are evaluated over black and white mattes,
|
|
263
|
-
* keeping the more conservative score.
|
|
264
|
-
* WARNING: SSIM can use substantial memory on large images.
|
|
265
|
-
* @default false
|
|
266
|
-
*/
|
|
267
|
-
includeSSIM?: boolean;
|
|
268
|
-
|
|
269
|
-
/**
|
|
270
|
-
* Calculate GMSD (Gradient Magnitude Similarity Deviation) score
|
|
271
|
-
*
|
|
272
|
-
* GMSD is very fast and highly sensitive to edge/structural changes.
|
|
273
|
-
* Useful for detecting border thickness changes, font weight shifts,
|
|
274
|
-
* and icon updates.
|
|
275
|
-
* A score of 0 does not prove image identity; uniform changes can have no
|
|
276
|
-
* spatial variation in gradient degradation.
|
|
277
|
-
*
|
|
278
|
-
* **Note:** For variable-height comparisons, GMSD is calculated on the
|
|
279
|
-
* overlapping region shared by both images.
|
|
280
|
-
*
|
|
281
|
-
* Reference: Xue et al. 2014 - "Gradient Magnitude Similarity Deviation:
|
|
282
|
-
* A Highly Efficient Perceptual Image Quality Index"
|
|
283
|
-
*
|
|
284
|
-
* @default false
|
|
285
|
-
*/
|
|
286
|
-
includeGMSD?: boolean;
|
|
287
|
-
|
|
288
|
-
/**
|
|
289
|
-
* Minimum cluster size to count as a real difference
|
|
290
|
-
*
|
|
291
|
-
* Clusters smaller than this threshold are filtered out as noise.
|
|
292
|
-
* This helps ignore run-to-run rendering variance (scattered single pixels)
|
|
293
|
-
* while still catching real visual changes (grouped pixel differences).
|
|
294
|
-
*
|
|
295
|
-
* - 1 = Any surviving diff pixel counts
|
|
296
|
-
* - 2 = Default - filters single isolated pixels as noise
|
|
297
|
-
* - 3+ = More aggressive noise filtering
|
|
298
|
-
*
|
|
299
|
-
* When set to a value > 1, clustering is automatically enabled.
|
|
300
|
-
* @default 2
|
|
301
|
-
*/
|
|
302
|
-
minClusterSize?: number;
|
|
303
|
-
|
|
304
|
-
/**
|
|
305
|
-
* Merge nearby clusters into logical regions
|
|
306
|
-
*
|
|
307
|
-
* When enabled, nearby clusters are merged using horizontal-biased heuristics
|
|
308
|
-
* that work well for text regions. This helps consolidate fragmented text
|
|
309
|
-
* changes (e.g., "2024-01-01" showing as 59 clusters) into logical regions.
|
|
310
|
-
*
|
|
311
|
-
* Automatically enables clustering when set.
|
|
312
|
-
*
|
|
313
|
-
* @default undefined (no merging)
|
|
314
|
-
*
|
|
315
|
-
* @example
|
|
316
|
-
* ```typescript
|
|
317
|
-
* // Simple: enable merging with sensible defaults
|
|
318
|
-
* { clusterMerge: true }
|
|
319
|
-
*
|
|
320
|
-
* // Advanced: tune the merging behavior
|
|
321
|
-
* { clusterMerge: { horizontalDistance: 20, yBandTolerance: 10 } }
|
|
322
|
-
* ```
|
|
323
|
-
*/
|
|
324
|
-
clusterMerge?: boolean | ClusterMergeOptions;
|
|
325
|
-
|
|
326
|
-
/**
|
|
327
|
-
* Path to save the diff image (highlighted differences)
|
|
328
|
-
* @default undefined
|
|
329
|
-
*/
|
|
136
|
+
export interface ArtifactOutput {
|
|
330
137
|
diffPath?: string;
|
|
331
|
-
|
|
332
|
-
/**
|
|
333
|
-
* Path to save the mask image (binary diff mask)
|
|
334
|
-
* @default undefined
|
|
335
|
-
*/
|
|
336
138
|
maskPath?: string;
|
|
337
|
-
|
|
338
|
-
/**
|
|
339
|
-
* Path to save the overlay image (side-by-side comparison)
|
|
340
|
-
* @default undefined
|
|
341
|
-
*/
|
|
342
|
-
overlayPath?: string;
|
|
343
|
-
|
|
344
|
-
/**
|
|
345
|
-
* Whether to overwrite existing output files
|
|
346
|
-
* @default false
|
|
347
|
-
*/
|
|
139
|
+
sideBySidePath?: string;
|
|
348
140
|
overwrite?: boolean;
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
* Color to use for highlighting differences in diff/mask output
|
|
352
|
-
*
|
|
353
|
-
* Accepts either:
|
|
354
|
-
* - Hex string: "ff0000", "#ff0000", "ff0000ff" (with alpha)
|
|
355
|
-
* - RGBA array: [255, 0, 0] or [255, 0, 0, 255]
|
|
356
|
-
*
|
|
357
|
-
* @default "ff0000" (red)
|
|
358
|
-
*
|
|
359
|
-
* @example
|
|
360
|
-
* ```typescript
|
|
361
|
-
* // Green highlight using hex
|
|
362
|
-
* { diffMaskColor: "00ff00" }
|
|
363
|
-
*
|
|
364
|
-
* // Blue with 50% transparency using RGBA array
|
|
365
|
-
* { diffMaskColor: [0, 0, 255, 128] }
|
|
366
|
-
*
|
|
367
|
-
* // Magenta using hex with #
|
|
368
|
-
* { diffMaskColor: "#ff00ff" }
|
|
369
|
-
* ```
|
|
370
|
-
*/
|
|
371
|
-
diffMaskColor?: string | [number, number, number] | [number, number, number, number];
|
|
141
|
+
/** Opaque artifact color as six-digit hex or integer RGB channels. */
|
|
142
|
+
color?: string | [number, number, number];
|
|
372
143
|
}
|
|
373
144
|
|
|
374
|
-
|
|
375
|
-
// Input Types
|
|
376
|
-
// ============================================================================
|
|
377
|
-
|
|
378
|
-
/** Image input can be a file path or a Buffer containing image data */
|
|
379
|
-
export type ImageInput = string | Buffer;
|
|
380
|
-
|
|
381
|
-
// ============================================================================
|
|
382
|
-
// API Functions
|
|
383
|
-
// ============================================================================
|
|
384
|
-
|
|
145
|
+
export const version: string;
|
|
385
146
|
/**
|
|
386
|
-
*
|
|
387
|
-
*
|
|
388
|
-
* before native worker processing begins.
|
|
389
|
-
*
|
|
390
|
-
* @param img1 - First image (file path or Buffer)
|
|
391
|
-
* @param img2 - Second image (file path or Buffer)
|
|
392
|
-
* @param options - Comparison options
|
|
393
|
-
* @returns Promise resolving to detailed diff results
|
|
394
|
-
*
|
|
395
|
-
* @example
|
|
396
|
-
* ```typescript
|
|
397
|
-
* const result = await compare('baseline.png', 'current.png', {
|
|
398
|
-
* threshold: 2.0, // CIEDE2000 Delta E (default)
|
|
399
|
-
* includeSSIM: true
|
|
400
|
-
* });
|
|
401
|
-
*
|
|
402
|
-
* console.log(`Diff: ${result.diffPercentage.toFixed(2)}%`);
|
|
403
|
-
* console.log(`SSIM: ${result.perceptualScore}`);
|
|
404
|
-
* ```
|
|
147
|
+
* Set one process-wide HoneyDiff compute budget before the first operation.
|
|
148
|
+
* Repeating the same value is safe; changing it after work starts throws.
|
|
405
149
|
*/
|
|
150
|
+
export function configureThreads(threads: number): void;
|
|
406
151
|
export function compare(
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
options?: CompareOptions
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
/**
|
|
413
|
-
* Quick asynchronous comparison (returns only boolean)
|
|
414
|
-
*
|
|
415
|
-
* @param img1 - First image (file path or Buffer)
|
|
416
|
-
* @param img2 - Second image (file path or Buffer)
|
|
417
|
-
* @returns Promise resolving to true if different, false if identical
|
|
418
|
-
*
|
|
419
|
-
* @example
|
|
420
|
-
* ```typescript
|
|
421
|
-
* if (await quickCompare('baseline.png', 'current.png')) {
|
|
422
|
-
* console.log('Visual regression detected!');
|
|
423
|
-
* }
|
|
424
|
-
* ```
|
|
425
|
-
*/
|
|
426
|
-
export function quickCompare(img1: ImageInput, img2: ImageInput): Promise<boolean>;
|
|
427
|
-
|
|
428
|
-
/**
|
|
429
|
-
* Compare two images synchronously (blocks event loop)
|
|
430
|
-
*
|
|
431
|
-
* Use this only when you need blocking behavior.
|
|
432
|
-
* For most cases, prefer the async compare() function.
|
|
433
|
-
*
|
|
434
|
-
* @param img1 - First image (file path or Buffer)
|
|
435
|
-
* @param img2 - Second image (file path or Buffer)
|
|
436
|
-
* @param options - Comparison options
|
|
437
|
-
* @returns Detailed diff results
|
|
438
|
-
*
|
|
439
|
-
* @example
|
|
440
|
-
* ```typescript
|
|
441
|
-
* const result = compareSync('baseline.png', 'current.png', {
|
|
442
|
-
* threshold: 3.0, // More lenient threshold
|
|
443
|
-
* includeClusters: true
|
|
444
|
-
* });
|
|
445
|
-
*
|
|
446
|
-
* if (result.diffClusters) {
|
|
447
|
-
* console.log(`Found ${result.diffClusters.length} diff regions`);
|
|
448
|
-
* }
|
|
449
|
-
* ```
|
|
450
|
-
*/
|
|
152
|
+
baseline: ImageInput,
|
|
153
|
+
current: ImageInput,
|
|
154
|
+
options?: CompareOptions,
|
|
155
|
+
output?: ArtifactOutput
|
|
156
|
+
): Promise<DiffAnalysis>;
|
|
451
157
|
export function compareSync(
|
|
452
|
-
|
|
453
|
-
|
|
158
|
+
baseline: ImageInput,
|
|
159
|
+
current: ImageInput,
|
|
160
|
+
options?: CompareOptions,
|
|
161
|
+
output?: ArtifactOutput
|
|
162
|
+
): DiffAnalysis;
|
|
163
|
+
export function imagesDiffer(
|
|
164
|
+
baseline: ImageInput,
|
|
165
|
+
current: ImageInput,
|
|
454
166
|
options?: CompareOptions
|
|
455
|
-
):
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
// ============================================================================
|
|
474
|
-
// Dimensions API
|
|
475
|
-
// ============================================================================
|
|
167
|
+
): Promise<boolean>;
|
|
168
|
+
export function imagesDifferSync(
|
|
169
|
+
baseline: ImageInput,
|
|
170
|
+
current: ImageInput,
|
|
171
|
+
options?: CompareOptions
|
|
172
|
+
): boolean;
|
|
173
|
+
export function analyze(
|
|
174
|
+
baseline: ImageInput,
|
|
175
|
+
current: ImageInput,
|
|
176
|
+
options?: CompareOptions,
|
|
177
|
+
output?: ArtifactOutput
|
|
178
|
+
): Promise<ImageAnalysis>;
|
|
179
|
+
export function analyzeSync(
|
|
180
|
+
baseline: ImageInput,
|
|
181
|
+
current: ImageInput,
|
|
182
|
+
options?: CompareOptions,
|
|
183
|
+
output?: ArtifactOutput
|
|
184
|
+
): ImageAnalysis;
|
|
476
185
|
|
|
477
186
|
export interface ImageDimensions {
|
|
478
|
-
/** Image width in pixels */
|
|
479
187
|
width: number;
|
|
480
|
-
/** Image height in pixels */
|
|
481
188
|
height: number;
|
|
482
189
|
}
|
|
483
|
-
|
|
484
|
-
export interface ImageMetadata {
|
|
485
|
-
/** Image width in pixels */
|
|
486
|
-
width: number;
|
|
487
|
-
/** Image height in pixels */
|
|
488
|
-
height: number;
|
|
489
|
-
/** Size of the image data in bytes */
|
|
190
|
+
export interface ImageMetadata extends ImageDimensions {
|
|
490
191
|
fileSizeBytes: number;
|
|
491
|
-
/** Detected image format (e.g., "png", "jpeg", "webp") or null if unknown */
|
|
492
192
|
format: string | null;
|
|
493
193
|
}
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
* Fast utility to retrieve image width and height without loading the full image data.
|
|
499
|
-
* Useful for pre-flight checks before comparison.
|
|
500
|
-
*
|
|
501
|
-
* @param img - Image (file path or Buffer)
|
|
502
|
-
* @returns Promise resolving to { width, height } in pixels
|
|
503
|
-
*
|
|
504
|
-
* @example
|
|
505
|
-
* ```typescript
|
|
506
|
-
* const dims = await getDimensions('screenshot.png');
|
|
507
|
-
* console.log(`Image is ${dims.width}x${dims.height} pixels`);
|
|
508
|
-
* // Output: Image is 1920x1080 pixels
|
|
509
|
-
* ```
|
|
510
|
-
*/
|
|
511
|
-
export function getDimensions(img: ImageInput): Promise<ImageDimensions>;
|
|
512
|
-
|
|
513
|
-
/**
|
|
514
|
-
* Get image dimensions synchronously (blocks event loop)
|
|
515
|
-
*
|
|
516
|
-
* Fast utility to retrieve image width and height without loading the full image data.
|
|
517
|
-
* Use this only when you need blocking behavior.
|
|
518
|
-
*
|
|
519
|
-
* @param img - Image (file path or Buffer)
|
|
520
|
-
* @returns { width, height } in pixels
|
|
521
|
-
*
|
|
522
|
-
* @example
|
|
523
|
-
* ```typescript
|
|
524
|
-
* const dims = getDimensionsSync('screenshot.png');
|
|
525
|
-
* console.log(`Image is ${dims.width}x${dims.height} pixels`);
|
|
526
|
-
* // Output: Image is 1920x1080 pixels
|
|
527
|
-
* ```
|
|
528
|
-
*/
|
|
529
|
-
export function getDimensionsSync(img: ImageInput): ImageDimensions;
|
|
530
|
-
|
|
531
|
-
// ============================================================================
|
|
532
|
-
// Image Metadata API
|
|
533
|
-
// ============================================================================
|
|
534
|
-
|
|
535
|
-
/**
|
|
536
|
-
* Get image metadata asynchronously (recommended)
|
|
537
|
-
*
|
|
538
|
-
* Retrieves image dimensions, file size, and format detection.
|
|
539
|
-
* Useful for storage tracking and billing purposes.
|
|
540
|
-
*
|
|
541
|
-
* @param img - Image Buffer
|
|
542
|
-
* @returns Promise resolving to ImageMetadata
|
|
543
|
-
*
|
|
544
|
-
* @example
|
|
545
|
-
* ```typescript
|
|
546
|
-
* const buffer = fs.readFileSync('screenshot.png');
|
|
547
|
-
* const metadata = await getImageMetadata(buffer);
|
|
548
|
-
* console.log(`Image: ${metadata.width}x${metadata.height}`);
|
|
549
|
-
* console.log(`Size: ${metadata.fileSizeBytes} bytes`);
|
|
550
|
-
* console.log(`Format: ${metadata.format}`);
|
|
551
|
-
* // Output: Image: 1920x1080
|
|
552
|
-
* // Size: 245760 bytes
|
|
553
|
-
* // Format: png
|
|
554
|
-
* ```
|
|
555
|
-
*/
|
|
556
|
-
export function getImageMetadata(img: Buffer): Promise<ImageMetadata>;
|
|
557
|
-
|
|
558
|
-
/**
|
|
559
|
-
* Get image metadata synchronously (blocks event loop)
|
|
560
|
-
*
|
|
561
|
-
* Retrieves image dimensions, file size, and format detection.
|
|
562
|
-
* Use this only when you need blocking behavior.
|
|
563
|
-
*
|
|
564
|
-
* @param img - Image Buffer
|
|
565
|
-
* @returns ImageMetadata
|
|
566
|
-
*
|
|
567
|
-
* @example
|
|
568
|
-
* ```typescript
|
|
569
|
-
* const buffer = fs.readFileSync('screenshot.png');
|
|
570
|
-
* const metadata = getImageMetadataSync(buffer);
|
|
571
|
-
* console.log(`${metadata.width}x${metadata.height}, ${metadata.fileSizeBytes} bytes`);
|
|
572
|
-
* ```
|
|
573
|
-
*/
|
|
574
|
-
export function getImageMetadataSync(img: Buffer): ImageMetadata;
|
|
575
|
-
|
|
576
|
-
/**
|
|
577
|
-
* Get image metadata from file asynchronously (recommended)
|
|
578
|
-
*
|
|
579
|
-
* Retrieves image dimensions, file size (from disk), and format detection.
|
|
580
|
-
* The file size is read from filesystem metadata for accuracy.
|
|
581
|
-
*
|
|
582
|
-
* @param path - Path to the image file
|
|
583
|
-
* @returns Promise resolving to ImageMetadata
|
|
584
|
-
*
|
|
585
|
-
* @example
|
|
586
|
-
* ```typescript
|
|
587
|
-
* const metadata = await getImageMetadataFromFile('screenshot.png');
|
|
588
|
-
* console.log(`Image: ${metadata.width}x${metadata.height}`);
|
|
589
|
-
* console.log(`Size: ${metadata.fileSizeBytes} bytes`);
|
|
590
|
-
* console.log(`Format: ${metadata.format}`);
|
|
591
|
-
* ```
|
|
592
|
-
*/
|
|
194
|
+
export function getDimensions(image: ImageInput): Promise<ImageDimensions>;
|
|
195
|
+
export function getDimensionsSync(image: ImageInput): ImageDimensions;
|
|
196
|
+
export function getImageMetadata(image: Buffer): Promise<ImageMetadata>;
|
|
197
|
+
export function getImageMetadataSync(image: Buffer): ImageMetadata;
|
|
593
198
|
export function getImageMetadataFromFile(path: string): Promise<ImageMetadata>;
|
|
594
|
-
|
|
595
|
-
/**
|
|
596
|
-
* Get image metadata from file synchronously (blocks event loop)
|
|
597
|
-
*
|
|
598
|
-
* Retrieves image dimensions, file size (from disk), and format detection.
|
|
599
|
-
* Use this only when you need blocking behavior.
|
|
600
|
-
*
|
|
601
|
-
* @param path - Path to the image file
|
|
602
|
-
* @returns ImageMetadata
|
|
603
|
-
*
|
|
604
|
-
* @example
|
|
605
|
-
* ```typescript
|
|
606
|
-
* const metadata = getImageMetadataFromFileSync('screenshot.png');
|
|
607
|
-
* console.log(`${metadata.width}x${metadata.height}, ${metadata.fileSizeBytes} bytes`);
|
|
608
|
-
* ```
|
|
609
|
-
*/
|
|
610
199
|
export function getImageMetadataFromFileSync(path: string): ImageMetadata;
|
|
611
200
|
|
|
612
|
-
// ============================================================================
|
|
613
|
-
// Screenshot Contrast Screening API
|
|
614
|
-
// ============================================================================
|
|
615
|
-
|
|
616
|
-
/**
|
|
617
|
-
* Options for screenshot contrast screening with WCAG color-pair math.
|
|
618
|
-
*/
|
|
619
201
|
export interface WcagOptions {
|
|
620
|
-
/**
|
|
621
|
-
* Edge detection threshold (0-255)
|
|
622
|
-
* Differences below this threshold are not considered edges
|
|
623
|
-
* @default 60
|
|
624
|
-
*/
|
|
625
202
|
edgeThreshold?: number;
|
|
626
|
-
|
|
627
|
-
/**
|
|
628
|
-
* Minimum region size (in pixels)
|
|
629
|
-
* Regions smaller than this are filtered out to reduce noise
|
|
630
|
-
* @default 50
|
|
631
|
-
*/
|
|
632
203
|
minRegionSize?: number;
|
|
633
|
-
|
|
634
|
-
/**
|
|
635
|
-
* Maximum contrast threshold
|
|
636
|
-
* Set above 0 to exclude regions with contrast above this ratio. 0 reports
|
|
637
|
-
* all detected failures.
|
|
638
|
-
* @default 0
|
|
639
|
-
*/
|
|
640
204
|
maxContrastThreshold?: number;
|
|
641
|
-
|
|
642
|
-
/**
|
|
643
|
-
* Check WCAG AA color-pair thresholds (4.5:1 for normal text, 3.0:1 for large text)
|
|
644
|
-
* @default true
|
|
645
|
-
*/
|
|
646
205
|
checkAA?: boolean;
|
|
647
|
-
|
|
648
|
-
/**
|
|
649
|
-
* Check WCAG AAA color-pair thresholds (7.0:1 for normal text, 4.5:1 for large text)
|
|
650
|
-
* @default false
|
|
651
|
-
*/
|
|
652
206
|
checkAAA?: boolean;
|
|
653
207
|
}
|
|
654
|
-
|
|
655
|
-
/**
|
|
656
|
-
* A single screenshot contrast warning region.
|
|
657
|
-
*/
|
|
658
208
|
export interface ContrastViolation {
|
|
659
|
-
/** Bounding box containing this warning region */
|
|
660
209
|
boundingBox: BoundingBox;
|
|
661
|
-
|
|
662
|
-
/** List of pixel coordinates [x, y] in this warning region */
|
|
663
210
|
pixels: [number, number][];
|
|
664
|
-
|
|
665
|
-
/** Center of mass [x, y] of the warning region */
|
|
666
211
|
centerOfMass: [number, number];
|
|
667
|
-
|
|
668
|
-
/** Number of pixels in this warning region */
|
|
669
212
|
pixelCount: number;
|
|
670
|
-
|
|
671
|
-
/** Foreground color [r, g, b, a] (0-255) */
|
|
672
213
|
foregroundColor: [number, number, number, number];
|
|
673
|
-
|
|
674
|
-
/** Background color [r, g, b, a] (0-255) */
|
|
675
214
|
backgroundColor: [number, number, number, number];
|
|
676
|
-
|
|
677
|
-
/** Foreground relative luminance (0.0-1.0, sRGB) */
|
|
678
215
|
foregroundLuminance: number;
|
|
679
|
-
|
|
680
|
-
/** Background relative luminance (0.0-1.0, sRGB) */
|
|
681
216
|
backgroundLuminance: number;
|
|
682
|
-
|
|
683
|
-
/** Average contrast ratio for this region (1.0-21.0) */
|
|
684
217
|
contrastRatio: number;
|
|
685
|
-
|
|
686
|
-
/** Minimum contrast ratio in this region (worst case) */
|
|
687
218
|
minContrastRatio: number;
|
|
688
|
-
|
|
689
|
-
/** Maximum contrast ratio in this region (best case) */
|
|
690
219
|
maxContrastRatio: number;
|
|
691
|
-
|
|
692
|
-
/** Whether this region fails WCAG AA for normal text (< 4.5:1) */
|
|
693
220
|
failsAaNormal: boolean;
|
|
694
|
-
|
|
695
|
-
/** Whether this region fails WCAG AA for large text (< 3.0:1) */
|
|
696
221
|
failsAaLarge: boolean;
|
|
697
|
-
|
|
698
|
-
/** Whether this region fails WCAG AAA for normal text (< 7.0:1) */
|
|
699
222
|
failsAaaNormal: boolean;
|
|
700
|
-
|
|
701
|
-
/** Whether this region fails WCAG AAA for large text (< 4.5:1) */
|
|
702
223
|
failsAaaLarge: boolean;
|
|
703
224
|
}
|
|
704
|
-
|
|
705
|
-
/**
|
|
706
|
-
* Complete screenshot contrast screening result.
|
|
707
|
-
*/
|
|
708
225
|
export interface WcagAnalysis {
|
|
709
|
-
/** Total number of edges analyzed */
|
|
710
226
|
totalEdges: number;
|
|
711
|
-
|
|
712
|
-
/** Number of edges passing WCAG AA for normal text */
|
|
713
227
|
aaNormalPass: number;
|
|
714
|
-
|
|
715
|
-
/** Number of edges passing WCAG AA for large text */
|
|
716
228
|
aaLargePass: number;
|
|
717
|
-
|
|
718
|
-
/** Number of edges passing WCAG AAA for normal text */
|
|
719
229
|
aaaNormalPass: number;
|
|
720
|
-
|
|
721
|
-
/** Number of edges passing WCAG AAA for large text */
|
|
722
230
|
aaaLargePass: number;
|
|
723
|
-
|
|
724
|
-
/** List of contrast warning regions found */
|
|
725
231
|
violations: ContrastViolation[];
|
|
726
|
-
|
|
727
|
-
/** Percentage of edges passing WCAG AA for normal text (0.0-100.0) */
|
|
728
232
|
aaNormalPassPercentage: number;
|
|
729
|
-
|
|
730
|
-
/** Percentage of edges passing WCAG AA for large text (0.0-100.0) */
|
|
731
233
|
aaLargePassPercentage: number;
|
|
732
|
-
|
|
733
|
-
/** Percentage of edges passing WCAG AAA for normal text (0.0-100.0) */
|
|
734
234
|
aaaNormalPassPercentage: number;
|
|
735
|
-
|
|
736
|
-
/** Percentage of edges passing WCAG AAA for large text (0.0-100.0) */
|
|
737
235
|
aaaLargePassPercentage: number;
|
|
738
236
|
}
|
|
739
|
-
|
|
740
|
-
/**
|
|
741
|
-
* Screen screenshot contrast warnings asynchronously (recommended)
|
|
742
|
-
*
|
|
743
|
-
* Detects color boundaries in an image and measures each color pair with the
|
|
744
|
-
* WCAG contrast formula. Treat results as screening warnings, not full WCAG
|
|
745
|
-
* conformance, because screenshots do not expose text size, boldness, DOM
|
|
746
|
-
* foreground/background, compositing, or semantic roles.
|
|
747
|
-
*
|
|
748
|
-
* @param img - Image to analyze (file path or Buffer)
|
|
749
|
-
* @param options - Screenshot contrast screening options
|
|
750
|
-
* @returns Promise resolving to detailed screenshot contrast screening result
|
|
751
|
-
*
|
|
752
|
-
* @example
|
|
753
|
-
* ```typescript
|
|
754
|
-
* const analysis = await analyzeWcagContrast('screenshot.png', {
|
|
755
|
-
* edgeThreshold: 60,
|
|
756
|
-
* checkAA: true,
|
|
757
|
-
* checkAAA: true
|
|
758
|
-
* });
|
|
759
|
-
*
|
|
760
|
-
* console.log(`Total edges: ${analysis.totalEdges}`);
|
|
761
|
-
* console.log(`AA pass rate: ${analysis.aaNormalPassPercentage.toFixed(1)}%`);
|
|
762
|
-
* console.log(`Found ${analysis.violations.length} violations`);
|
|
763
|
-
*
|
|
764
|
-
* for (let violation of analysis.violations.slice(0, 5)) {
|
|
765
|
-
* console.log(`Region at (${violation.boundingBox.x}, ${violation.boundingBox.y})`);
|
|
766
|
-
* console.log(` Contrast: ${violation.contrastRatio.toFixed(2)}:1`);
|
|
767
|
-
* console.log(` Fails AA: ${violation.failsAaNormal}`);
|
|
768
|
-
* }
|
|
769
|
-
* ```
|
|
770
|
-
*/
|
|
771
|
-
export function analyzeWcagContrast(img: ImageInput, options?: WcagOptions): Promise<WcagAnalysis>;
|
|
772
|
-
|
|
773
|
-
/**
|
|
774
|
-
* Screen screenshot contrast warnings synchronously (blocks event loop)
|
|
775
|
-
*
|
|
776
|
-
* Use this only when you need blocking behavior.
|
|
777
|
-
* For most cases, prefer the async analyzeWcagContrast() function.
|
|
778
|
-
*
|
|
779
|
-
* @param img - Image to analyze (file path or Buffer)
|
|
780
|
-
* @param options - Screenshot contrast screening options
|
|
781
|
-
* @returns Detailed screenshot contrast screening result
|
|
782
|
-
*
|
|
783
|
-
* @example
|
|
784
|
-
* ```typescript
|
|
785
|
-
* const analysis = analyzeWcagContrastSync('screenshot.png', {
|
|
786
|
-
* minRegionSize: 50
|
|
787
|
-
* });
|
|
788
|
-
*
|
|
789
|
-
* if (analysis.violations.length > 0) {
|
|
790
|
-
* console.log('Accessibility issues found!');
|
|
791
|
-
* }
|
|
792
|
-
* ```
|
|
793
|
-
*/
|
|
794
|
-
export function analyzeWcagContrastSync(img: ImageInput, options?: WcagOptions): WcagAnalysis;
|
|
795
|
-
|
|
796
|
-
/**
|
|
797
|
-
* Options for saving WCAG overlay images
|
|
798
|
-
*/
|
|
799
237
|
export interface WcagOutputOptions {
|
|
800
|
-
/**
|
|
801
|
-
* Highlight color for violations [r, g, b, a] (0-255)
|
|
802
|
-
* @default [255, 0, 0, 180] (semi-transparent red)
|
|
803
|
-
*/
|
|
804
238
|
highlightColor?: [number, number, number, number];
|
|
805
|
-
|
|
806
|
-
/**
|
|
807
|
-
* Whether to overwrite existing output file
|
|
808
|
-
* @default false
|
|
809
|
-
*/
|
|
810
239
|
overwrite?: boolean;
|
|
811
240
|
}
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
* Useful for visual debugging of accessibility issues.
|
|
818
|
-
*
|
|
819
|
-
* @param img - Original image (file path or Buffer)
|
|
820
|
-
* @param analysis - Screenshot contrast screening result from analyzeWcagContrast()
|
|
821
|
-
* @param outputPath - Path to save the overlay image
|
|
822
|
-
* @param options - Output options
|
|
823
|
-
* @returns Promise that resolves when the file is saved
|
|
824
|
-
*
|
|
825
|
-
* @example
|
|
826
|
-
* ```typescript
|
|
827
|
-
* const analysis = await analyzeWcagContrast('screenshot.png');
|
|
828
|
-
*
|
|
829
|
-
* await saveWcagOverlay(
|
|
830
|
-
* 'screenshot.png',
|
|
831
|
-
* analysis,
|
|
832
|
-
* 'violations-overlay.png',
|
|
833
|
-
* { highlightColor: [255, 0, 0, 180] }
|
|
834
|
-
* );
|
|
835
|
-
*
|
|
836
|
-
* console.log('Overlay saved to violations-overlay.png');
|
|
837
|
-
* ```
|
|
838
|
-
*/
|
|
241
|
+
export function analyzeWcagContrast(
|
|
242
|
+
image: ImageInput,
|
|
243
|
+
options?: WcagOptions
|
|
244
|
+
): Promise<WcagAnalysis>;
|
|
245
|
+
export function analyzeWcagContrastSync(image: ImageInput, options?: WcagOptions): WcagAnalysis;
|
|
839
246
|
export function saveWcagOverlay(
|
|
840
|
-
|
|
247
|
+
image: ImageInput,
|
|
841
248
|
analysis: WcagAnalysis,
|
|
842
249
|
outputPath: string,
|
|
843
250
|
options?: WcagOutputOptions
|
|
844
251
|
): Promise<void>;
|
|
845
|
-
|
|
846
|
-
/**
|
|
847
|
-
* Save WCAG violation overlay image synchronously (blocks event loop)
|
|
848
|
-
*
|
|
849
|
-
* Use this only when you need blocking behavior.
|
|
850
|
-
* For most cases, prefer the async saveWcagOverlay() function.
|
|
851
|
-
*
|
|
852
|
-
* @param img - Original image (file path or Buffer)
|
|
853
|
-
* @param analysis - Screenshot contrast screening result from analyzeWcagContrastSync()
|
|
854
|
-
* @param outputPath - Path to save the overlay image
|
|
855
|
-
* @param options - Output options
|
|
856
|
-
*
|
|
857
|
-
* @example
|
|
858
|
-
* ```typescript
|
|
859
|
-
* const analysis = analyzeWcagContrastSync('screenshot.png');
|
|
860
|
-
* saveWcagOverlaySync('screenshot.png', analysis, 'violations.png');
|
|
861
|
-
* ```
|
|
862
|
-
*/
|
|
863
252
|
export function saveWcagOverlaySync(
|
|
864
|
-
|
|
253
|
+
image: ImageInput,
|
|
865
254
|
analysis: WcagAnalysis,
|
|
866
255
|
outputPath: string,
|
|
867
256
|
options?: WcagOutputOptions
|
|
868
257
|
): void;
|
|
869
258
|
|
|
870
|
-
// ============================================================================
|
|
871
|
-
// Color Vision Deficiency (CVD) Simulation API
|
|
872
|
-
// ============================================================================
|
|
873
|
-
|
|
874
|
-
/**
|
|
875
|
-
* Types of color vision deficiency (color blindness) that can be simulated.
|
|
876
|
-
*
|
|
877
|
-
* - `protanopia` - Red-blind (~1% of males). Reds appear dark/muddy, red-green confusion.
|
|
878
|
-
* - `deuteranopia` - Green-blind (~1% of males, most common). Greens shift toward red/brown.
|
|
879
|
-
* - `tritanopia` - Blue-blind (~0.003% of population). Blues appear greenish, yellow-purple confusion.
|
|
880
|
-
* - `achromatopsia` - Complete color blindness (very rare). Only luminance is perceived.
|
|
881
|
-
*
|
|
882
|
-
* Short aliases are also accepted: `protan`, `deutan`, `tritan`, `achroma`, `monochromacy`
|
|
883
|
-
*/
|
|
884
259
|
export type ColorBlindnessType =
|
|
885
260
|
| 'protanopia'
|
|
886
261
|
| 'deuteranopia'
|
|
@@ -891,456 +266,71 @@ export type ColorBlindnessType =
|
|
|
891
266
|
| 'tritan'
|
|
892
267
|
| 'achroma'
|
|
893
268
|
| 'monochromacy';
|
|
894
|
-
|
|
895
|
-
/**
|
|
896
|
-
* Information about a color blindness type
|
|
897
|
-
*/
|
|
898
269
|
export interface ColorBlindnessTypeInfo {
|
|
899
|
-
/** The canonical type identifier (e.g., "protanopia") */
|
|
900
270
|
type: string;
|
|
901
|
-
/** Human-readable name (e.g., "Protanopia") */
|
|
902
271
|
name: string;
|
|
903
|
-
/** Description of what colors are affected */
|
|
904
272
|
description: string;
|
|
905
|
-
/** Approximate prevalence in the population */
|
|
906
273
|
prevalence: string;
|
|
907
274
|
}
|
|
908
|
-
|
|
909
|
-
/**
|
|
910
|
-
* Comprehensive screenshot contrast screening report for all color vision deficiency types.
|
|
911
|
-
*
|
|
912
|
-
* Contains contrast screening for normal vision and all three main types
|
|
913
|
-
* of dichromatic color blindness (protanopia, deuteranopia, tritanopia).
|
|
914
|
-
*/
|
|
915
275
|
export interface CvdWcagReport {
|
|
916
|
-
/** Contrast screening for normal (typical) color vision */
|
|
917
276
|
normalVision: WcagAnalysis;
|
|
918
|
-
|
|
919
|
-
/** Contrast screening simulating protanopia (red-blind) */
|
|
920
277
|
protanopia: WcagAnalysis;
|
|
921
|
-
|
|
922
|
-
/** Contrast screening simulating deuteranopia (green-blind) */
|
|
923
278
|
deuteranopia: WcagAnalysis;
|
|
924
|
-
|
|
925
|
-
/** Contrast screening simulating tritanopia (blue-blind) */
|
|
926
279
|
tritanopia: WcagAnalysis;
|
|
927
|
-
|
|
928
|
-
/** Whether any CVD type has violations */
|
|
929
280
|
hasAnyViolations: boolean;
|
|
930
|
-
|
|
931
|
-
/** Total number of violations across all CVD types */
|
|
932
281
|
totalViolations: number;
|
|
933
|
-
|
|
934
|
-
/**
|
|
935
|
-
* Estimated count of violations unique to colorblind users.
|
|
936
|
-
* These are violations present in CVD analysis but not in normal vision.
|
|
937
|
-
*/
|
|
938
282
|
cvdOnlyViolationCount: number;
|
|
939
283
|
}
|
|
940
|
-
|
|
941
|
-
/**
|
|
942
|
-
* Simulate color blindness for an image asynchronously (recommended)
|
|
943
|
-
*
|
|
944
|
-
* Transforms the input image to show how it appears to someone with the
|
|
945
|
-
* specified type of color vision deficiency. Complete dichromacy uses the
|
|
946
|
-
* piecewise Brettel, Viénot & Mollon 1997 LMS projection model.
|
|
947
|
-
*
|
|
948
|
-
* @param img - Image to transform (file path or Buffer)
|
|
949
|
-
* @param cvdType - Type of color blindness to simulate
|
|
950
|
-
* @returns Promise resolving to a Buffer containing the simulated PNG image
|
|
951
|
-
*
|
|
952
|
-
* @example
|
|
953
|
-
* ```typescript
|
|
954
|
-
* import { simulateColorBlindness } from '@vizzly-testing/honeydiff';
|
|
955
|
-
* import fs from 'fs';
|
|
956
|
-
*
|
|
957
|
-
* // Simulate how an image appears to someone with red-green color blindness
|
|
958
|
-
* const simulated = await simulateColorBlindness('chart.png', 'deuteranopia');
|
|
959
|
-
*
|
|
960
|
-
* // Save the result
|
|
961
|
-
* fs.writeFileSync('chart-deuteranopia.png', simulated);
|
|
962
|
-
*
|
|
963
|
-
* // Or use directly with image comparison
|
|
964
|
-
* const result = await compare('chart.png', simulated);
|
|
965
|
-
* ```
|
|
966
|
-
*/
|
|
967
284
|
export function simulateColorBlindness(
|
|
968
|
-
|
|
969
|
-
|
|
285
|
+
image: ImageInput,
|
|
286
|
+
type: ColorBlindnessType
|
|
970
287
|
): Promise<Buffer>;
|
|
971
|
-
|
|
972
|
-
/**
|
|
973
|
-
* Simulate color blindness for an image synchronously (blocks event loop)
|
|
974
|
-
*
|
|
975
|
-
* Use this only when you need blocking behavior.
|
|
976
|
-
* For most cases, prefer the async simulateColorBlindness() function.
|
|
977
|
-
*
|
|
978
|
-
* @param img - Image to transform (file path or Buffer)
|
|
979
|
-
* @param cvdType - Type of color blindness to simulate
|
|
980
|
-
* @returns Buffer containing the simulated PNG image
|
|
981
|
-
*
|
|
982
|
-
* @example
|
|
983
|
-
* ```typescript
|
|
984
|
-
* const simulated = simulateColorBlindnessSync('button.png', 'protanopia');
|
|
985
|
-
* fs.writeFileSync('button-protanopia.png', simulated);
|
|
986
|
-
* ```
|
|
987
|
-
*/
|
|
988
|
-
export function simulateColorBlindnessSync(img: ImageInput, cvdType: ColorBlindnessType): Buffer;
|
|
989
|
-
|
|
990
|
-
/**
|
|
991
|
-
* Simulate and save color blindness image to file asynchronously (recommended)
|
|
992
|
-
*
|
|
993
|
-
* Convenience function that simulates CVD and saves the output in one step.
|
|
994
|
-
*
|
|
995
|
-
* @param img - Image to transform (file path or Buffer)
|
|
996
|
-
* @param cvdType - Type of color blindness to simulate
|
|
997
|
-
* @param outputPath - Path where the simulated image should be saved
|
|
998
|
-
* @returns Promise that resolves when the file is saved
|
|
999
|
-
*
|
|
1000
|
-
* @example
|
|
1001
|
-
* ```typescript
|
|
1002
|
-
* // Save a single simulation
|
|
1003
|
-
* await saveColorBlindnessSimulation('ui.png', 'deuteranopia', 'ui-deutan.png');
|
|
1004
|
-
*
|
|
1005
|
-
* // Generate simulations for all types you care about
|
|
1006
|
-
* for (const type of ['protanopia', 'deuteranopia', 'tritanopia']) {
|
|
1007
|
-
* await saveColorBlindnessSimulation('ui.png', type, `ui-${type}.png`);
|
|
1008
|
-
* }
|
|
1009
|
-
* ```
|
|
1010
|
-
*/
|
|
288
|
+
export function simulateColorBlindnessSync(image: ImageInput, type: ColorBlindnessType): Buffer;
|
|
1011
289
|
export function saveColorBlindnessSimulation(
|
|
1012
|
-
|
|
1013
|
-
|
|
290
|
+
image: ImageInput,
|
|
291
|
+
type: ColorBlindnessType,
|
|
1014
292
|
outputPath: string
|
|
1015
293
|
): Promise<void>;
|
|
1016
|
-
|
|
1017
|
-
/**
|
|
1018
|
-
* Simulate and save color blindness image to file synchronously (blocks event loop)
|
|
1019
|
-
*
|
|
1020
|
-
* Use this only when you need blocking behavior.
|
|
1021
|
-
*
|
|
1022
|
-
* @param img - Image to transform (file path or Buffer)
|
|
1023
|
-
* @param cvdType - Type of color blindness to simulate
|
|
1024
|
-
* @param outputPath - Path where the simulated image should be saved
|
|
1025
|
-
*
|
|
1026
|
-
* @example
|
|
1027
|
-
* ```typescript
|
|
1028
|
-
* saveColorBlindnessSimulationSync('chart.png', 'tritanopia', 'chart-tritan.png');
|
|
1029
|
-
* ```
|
|
1030
|
-
*/
|
|
1031
294
|
export function saveColorBlindnessSimulationSync(
|
|
1032
|
-
|
|
1033
|
-
|
|
295
|
+
image: ImageInput,
|
|
296
|
+
type: ColorBlindnessType,
|
|
1034
297
|
outputPath: string
|
|
1035
298
|
): void;
|
|
1036
|
-
|
|
1037
|
-
/**
|
|
1038
|
-
* Generate and save all CVD simulations to files asynchronously (recommended)
|
|
1039
|
-
*
|
|
1040
|
-
* Generates simulated images for all CVD types (protanopia, deuteranopia,
|
|
1041
|
-
* tritanopia, achromatopsia), saving each with a suffix indicating the type.
|
|
1042
|
-
*
|
|
1043
|
-
* @param img - Image to transform (file path or Buffer)
|
|
1044
|
-
* @param outputPrefix - Base path for output files (without extension)
|
|
1045
|
-
* @param extension - File extension to use (default: "png")
|
|
1046
|
-
* @returns Promise that resolves when all files are saved
|
|
1047
|
-
*
|
|
1048
|
-
* @example
|
|
1049
|
-
* ```typescript
|
|
1050
|
-
* // Creates: dashboard_protanopia.png, dashboard_deuteranopia.png,
|
|
1051
|
-
* // dashboard_tritanopia.png, dashboard_achromatopsia.png
|
|
1052
|
-
* await saveAllColorBlindnessSimulations('dashboard.png', 'dashboard', 'png');
|
|
1053
|
-
*
|
|
1054
|
-
* // With different extension
|
|
1055
|
-
* await saveAllColorBlindnessSimulations('ui.png', './artifacts/ui', 'jpg');
|
|
1056
|
-
* ```
|
|
1057
|
-
*/
|
|
1058
299
|
export function saveAllColorBlindnessSimulations(
|
|
1059
|
-
|
|
300
|
+
image: ImageInput,
|
|
1060
301
|
outputPrefix: string,
|
|
1061
302
|
extension?: string
|
|
1062
303
|
): Promise<void>;
|
|
1063
|
-
|
|
1064
|
-
/**
|
|
1065
|
-
* Generate and save all CVD simulations to files synchronously (blocks event loop)
|
|
1066
|
-
*
|
|
1067
|
-
* Use this only when you need blocking behavior.
|
|
1068
|
-
*
|
|
1069
|
-
* @param img - Image to transform (file path or Buffer)
|
|
1070
|
-
* @param outputPrefix - Base path for output files (without extension)
|
|
1071
|
-
* @param extension - File extension to use (default: "png")
|
|
1072
|
-
*
|
|
1073
|
-
* @example
|
|
1074
|
-
* ```typescript
|
|
1075
|
-
* saveAllColorBlindnessSimulationsSync('button.png', 'button');
|
|
1076
|
-
* ```
|
|
1077
|
-
*/
|
|
1078
304
|
export function saveAllColorBlindnessSimulationsSync(
|
|
1079
|
-
|
|
305
|
+
image: ImageInput,
|
|
1080
306
|
outputPrefix: string,
|
|
1081
307
|
extension?: string
|
|
1082
308
|
): void;
|
|
1083
|
-
|
|
1084
|
-
/**
|
|
1085
|
-
* Screen contrast for a specific color blindness type asynchronously (recommended)
|
|
1086
|
-
*
|
|
1087
|
-
* Simulates the image as seen by someone with the specified color vision
|
|
1088
|
-
* deficiency, then runs screenshot contrast screening with WCAG color-pair
|
|
1089
|
-
* math. This finds detected color-pair warnings that only appear for
|
|
1090
|
-
* colorblind users.
|
|
1091
|
-
*
|
|
1092
|
-
* @param img - Image to analyze (file path or Buffer)
|
|
1093
|
-
* @param cvdType - Type of color blindness to simulate
|
|
1094
|
-
* @param options - Screenshot contrast screening options
|
|
1095
|
-
* @returns Promise resolving to screening results for the simulated image
|
|
1096
|
-
*
|
|
1097
|
-
* @example
|
|
1098
|
-
* ```typescript
|
|
1099
|
-
* // Check if colors are distinguishable for deuteranopia users
|
|
1100
|
-
* const analysis = await analyzeWcagForCvd('ui.png', 'deuteranopia', {
|
|
1101
|
-
* checkAA: true,
|
|
1102
|
-
* checkAAA: false
|
|
1103
|
-
* });
|
|
1104
|
-
*
|
|
1105
|
-
* if (analysis.violations.length > 0) {
|
|
1106
|
-
* console.log(`Found ${analysis.violations.length} contrast issues`);
|
|
1107
|
-
* console.log('for users with red-green color blindness');
|
|
1108
|
-
* }
|
|
1109
|
-
* ```
|
|
1110
|
-
*/
|
|
1111
309
|
export function analyzeWcagForCvd(
|
|
1112
|
-
|
|
1113
|
-
|
|
310
|
+
image: ImageInput,
|
|
311
|
+
type: ColorBlindnessType,
|
|
1114
312
|
options?: WcagOptions
|
|
1115
313
|
): Promise<WcagAnalysis>;
|
|
1116
|
-
|
|
1117
|
-
/**
|
|
1118
|
-
* Screen contrast for a specific color blindness type synchronously (blocks event loop)
|
|
1119
|
-
*
|
|
1120
|
-
* Use this only when you need blocking behavior.
|
|
1121
|
-
*
|
|
1122
|
-
* @param img - Image to analyze (file path or Buffer)
|
|
1123
|
-
* @param cvdType - Type of color blindness to simulate
|
|
1124
|
-
* @param options - Screenshot contrast screening options
|
|
1125
|
-
* @returns Screening results for the simulated image
|
|
1126
|
-
*
|
|
1127
|
-
* @example
|
|
1128
|
-
* ```typescript
|
|
1129
|
-
* const analysis = analyzeWcagForCvdSync('chart.png', 'protanopia');
|
|
1130
|
-
* console.log(`Pass rate: ${analysis.aaNormalPassPercentage.toFixed(1)}%`);
|
|
1131
|
-
* ```
|
|
1132
|
-
*/
|
|
1133
314
|
export function analyzeWcagForCvdSync(
|
|
1134
|
-
|
|
1135
|
-
|
|
315
|
+
image: ImageInput,
|
|
316
|
+
type: ColorBlindnessType,
|
|
1136
317
|
options?: WcagOptions
|
|
1137
318
|
): WcagAnalysis;
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
* Screen contrast for all CVD types asynchronously (recommended)
|
|
1141
|
-
*
|
|
1142
|
-
* Runs screenshot contrast screening for normal vision and all three main
|
|
1143
|
-
* types of dichromatic color blindness.
|
|
1144
|
-
*
|
|
1145
|
-
* @param img - Image to analyze (file path or Buffer)
|
|
1146
|
-
* @param options - Screenshot contrast screening options (same options used for all analyses)
|
|
1147
|
-
* @returns Promise resolving to a report containing screening results for each vision type
|
|
1148
|
-
*
|
|
1149
|
-
* @example
|
|
1150
|
-
* ```typescript
|
|
1151
|
-
* const report = await analyzeWcagAllCvd('dashboard.png', {
|
|
1152
|
-
* checkAA: true,
|
|
1153
|
-
* checkAAA: true
|
|
1154
|
-
* });
|
|
1155
|
-
*
|
|
1156
|
-
* console.log('Contrast Screening Report:');
|
|
1157
|
-
* console.log(`Normal vision warnings: ${report.normalVision.violations.length}`);
|
|
1158
|
-
* console.log(`Protanopia warnings: ${report.protanopia.violations.length}`);
|
|
1159
|
-
* console.log(`Deuteranopia warnings: ${report.deuteranopia.violations.length}`);
|
|
1160
|
-
* console.log(`Tritanopia warnings: ${report.tritanopia.violations.length}`);
|
|
1161
|
-
*
|
|
1162
|
-
* if (report.hasAnyViolations) {
|
|
1163
|
-
* console.log(`\nTotal issues found: ${report.totalViolations}`);
|
|
1164
|
-
* console.log(`Issues only affecting colorblind users: ~${report.cvdOnlyViolationCount}`);
|
|
1165
|
-
* }
|
|
1166
|
-
* ```
|
|
1167
|
-
*/
|
|
1168
|
-
export function analyzeWcagAllCvd(img: ImageInput, options?: WcagOptions): Promise<CvdWcagReport>;
|
|
1169
|
-
|
|
1170
|
-
/**
|
|
1171
|
-
* Screen contrast for all CVD types synchronously (blocks event loop)
|
|
1172
|
-
*
|
|
1173
|
-
* Use this only when you need blocking behavior.
|
|
1174
|
-
*
|
|
1175
|
-
* @param img - Image to analyze (file path or Buffer)
|
|
1176
|
-
* @param options - Screenshot contrast screening options
|
|
1177
|
-
* @returns Report containing screening results for each vision type
|
|
1178
|
-
*
|
|
1179
|
-
* @example
|
|
1180
|
-
* ```typescript
|
|
1181
|
-
* const report = analyzeWcagAllCvdSync('ui.png');
|
|
1182
|
-
*
|
|
1183
|
-
* if (report.hasAnyViolations) {
|
|
1184
|
-
* console.log('Contrast warnings detected!');
|
|
1185
|
-
* }
|
|
1186
|
-
* ```
|
|
1187
|
-
*/
|
|
1188
|
-
export function analyzeWcagAllCvdSync(img: ImageInput, options?: WcagOptions): CvdWcagReport;
|
|
1189
|
-
|
|
1190
|
-
/**
|
|
1191
|
-
* Get metadata about all supported color blindness types
|
|
1192
|
-
*
|
|
1193
|
-
* Returns information about each CVD type including name, description,
|
|
1194
|
-
* and prevalence. Useful for building UI selectors or generating reports.
|
|
1195
|
-
*
|
|
1196
|
-
* @returns Array of color blindness type information
|
|
1197
|
-
*
|
|
1198
|
-
* @example
|
|
1199
|
-
* ```typescript
|
|
1200
|
-
* const types = getColorBlindnessTypes();
|
|
1201
|
-
*
|
|
1202
|
-
* for (const cvd of types) {
|
|
1203
|
-
* console.log(`${cvd.name} (${cvd.type})`);
|
|
1204
|
-
* console.log(` ${cvd.description}`);
|
|
1205
|
-
* console.log(` Affects: ${cvd.prevalence}`);
|
|
1206
|
-
* }
|
|
1207
|
-
*
|
|
1208
|
-
* // Output:
|
|
1209
|
-
* // Protanopia (protanopia)
|
|
1210
|
-
* // Red-blind: reds appear dark, red-green confusion
|
|
1211
|
-
* // Affects: ~1% of males
|
|
1212
|
-
* // Deuteranopia (deuteranopia)
|
|
1213
|
-
* // Green-blind: greens shift to brown, red-green confusion
|
|
1214
|
-
* // Affects: ~1% of males
|
|
1215
|
-
* // ...
|
|
1216
|
-
* ```
|
|
1217
|
-
*/
|
|
319
|
+
export function analyzeWcagAllCvd(image: ImageInput, options?: WcagOptions): Promise<CvdWcagReport>;
|
|
320
|
+
export function analyzeWcagAllCvdSync(image: ImageInput, options?: WcagOptions): CvdWcagReport;
|
|
1218
321
|
export function getColorBlindnessTypes(): ColorBlindnessTypeInfo[];
|
|
1219
322
|
|
|
1220
|
-
// ============================================================================
|
|
1221
|
-
// Diff Fingerprint API
|
|
1222
|
-
// ============================================================================
|
|
1223
|
-
|
|
1224
|
-
/**
|
|
1225
|
-
* Magnitude of a diff, bucketed into categories
|
|
1226
|
-
*/
|
|
1227
323
|
export type DiffMagnitude = 'tiny' | 'small' | 'medium' | 'large' | 'massive';
|
|
1228
|
-
|
|
1229
|
-
/**
|
|
1230
|
-
* A compact fingerprint representing a diff's key characteristics.
|
|
1231
|
-
* Used for grouping similar diffs across comparisons.
|
|
1232
|
-
*/
|
|
1233
324
|
export interface DiffFingerprint {
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
/** Normalized sizes of clusters (area as ratio of total image area) */
|
|
1241
|
-
clusterSizes: number[];
|
|
1242
|
-
|
|
1243
|
-
/** Average intensity across all clusters (0-255 scale) */
|
|
1244
|
-
avgIntensity: number;
|
|
1245
|
-
|
|
1246
|
-
/** Average cluster density (pixels / bounding_box_area) */
|
|
1247
|
-
avgDensity: number;
|
|
1248
|
-
|
|
1249
|
-
/**
|
|
1250
|
-
* Spatial distribution hash using a 4x4 grid (16 zones).
|
|
1251
|
-
* Each bit represents whether that zone contains a cluster center.
|
|
1252
|
-
*/
|
|
325
|
+
regionCount: number;
|
|
326
|
+
regionPositions: [number, number][];
|
|
327
|
+
regionSizes: number[];
|
|
328
|
+
meanDeltaE: number | null;
|
|
329
|
+
averageDensity: number;
|
|
1253
330
|
zoneMask: number;
|
|
1254
|
-
|
|
1255
|
-
/** Overall diff percentage bucketed into categories */
|
|
1256
331
|
diffMagnitude: DiffMagnitude;
|
|
1257
|
-
|
|
1258
|
-
/** Pre-computed coarse hash for fast grouping (hex string) */
|
|
1259
332
|
hash: string;
|
|
1260
333
|
}
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
* Compute a fingerprint from a diff result (synchronous)
|
|
1264
|
-
*
|
|
1265
|
-
* Creates a compact fingerprint that can be used to identify and group similar diffs
|
|
1266
|
-
* across different image comparisons. Requires that the diff result was computed with
|
|
1267
|
-
* `includeClusters: true`.
|
|
1268
|
-
*
|
|
1269
|
-
* @param diffResult - The result from a compare() call with clusters enabled
|
|
1270
|
-
* @param imageWidth - Width of the compared images in pixels
|
|
1271
|
-
* @param imageHeight - Height of the compared images in pixels
|
|
1272
|
-
* @returns A DiffFingerprint object, or null if no clusters are present
|
|
1273
|
-
*
|
|
1274
|
-
* @example
|
|
1275
|
-
* ```typescript
|
|
1276
|
-
* const result = await compare('baseline.png', 'current.png', {
|
|
1277
|
-
* includeClusters: true
|
|
1278
|
-
* });
|
|
1279
|
-
*
|
|
1280
|
-
* const fingerprint = computeFingerprintSync(result, 1920, 1080);
|
|
1281
|
-
* if (fingerprint) {
|
|
1282
|
-
* console.log(`Hash: ${fingerprint.hash}`);
|
|
1283
|
-
* console.log(`Zones: ${fingerprint.zoneMask.toString(2).padStart(16, '0')}`);
|
|
1284
|
-
* }
|
|
1285
|
-
* ```
|
|
1286
|
-
*/
|
|
1287
|
-
export function computeFingerprintSync(
|
|
1288
|
-
diffResult: DiffResult,
|
|
1289
|
-
imageWidth: number,
|
|
1290
|
-
imageHeight: number
|
|
1291
|
-
): DiffFingerprint | null;
|
|
1292
|
-
|
|
1293
|
-
/**
|
|
1294
|
-
* Compare two fingerprints and return a similarity score (synchronous)
|
|
1295
|
-
*
|
|
1296
|
-
* Returns a score from 0.0 (completely different) to 1.0 (identical).
|
|
1297
|
-
* The similarity is computed using weighted factors:
|
|
1298
|
-
* - Zone mask overlap (40%): Same regions affected
|
|
1299
|
-
* - Cluster count similarity (20%): Similar number of change regions
|
|
1300
|
-
* - Diff magnitude match (15%): Similar overall change size
|
|
1301
|
-
* - Density similarity (15%): Similar change patterns (solid vs scattered)
|
|
1302
|
-
* - Intensity similarity (10%): Similar change intensity
|
|
1303
|
-
*
|
|
1304
|
-
* @param fp1 - First fingerprint to compare
|
|
1305
|
-
* @param fp2 - Second fingerprint to compare
|
|
1306
|
-
* @returns Similarity score from 0.0 to 1.0
|
|
1307
|
-
*
|
|
1308
|
-
* @example
|
|
1309
|
-
* ```typescript
|
|
1310
|
-
* const similarity = fingerprintSimilaritySync(fp1, fp2);
|
|
1311
|
-
* if (similarity > 0.8) {
|
|
1312
|
-
* console.log('These diffs are likely the same change!');
|
|
1313
|
-
* }
|
|
1314
|
-
* ```
|
|
1315
|
-
*/
|
|
1316
|
-
export function fingerprintSimilaritySync(fp1: DiffFingerprint, fp2: DiffFingerprint): number;
|
|
1317
|
-
|
|
1318
|
-
/**
|
|
1319
|
-
* Get the coarse hash of a fingerprint (synchronous)
|
|
1320
|
-
*
|
|
1321
|
-
* Returns a hex string that can be used for fast exact-match grouping.
|
|
1322
|
-
* Diffs with the same coarse hash are very likely to be the same visual change.
|
|
1323
|
-
*
|
|
1324
|
-
* The hash combines:
|
|
1325
|
-
* - Zone mask (16 bits): Which regions are affected
|
|
1326
|
-
* - Diff magnitude (3 bits): Overall change size
|
|
1327
|
-
* - Cluster count bucket (2 bits): Number of change regions
|
|
1328
|
-
*
|
|
1329
|
-
* @param fingerprint - The fingerprint to hash
|
|
1330
|
-
* @returns A 16-character hex string
|
|
1331
|
-
*
|
|
1332
|
-
* @example
|
|
1333
|
-
* ```typescript
|
|
1334
|
-
* const hash = fingerprintHashSync(fingerprint);
|
|
1335
|
-
* console.log(`Hash: ${hash}`); // e.g., "0000000000000021"
|
|
1336
|
-
*
|
|
1337
|
-
* // Use for grouping
|
|
1338
|
-
* const groups = new Map<string, DiffFingerprint[]>();
|
|
1339
|
-
* for (const fp of fingerprints) {
|
|
1340
|
-
* const h = fingerprintHashSync(fp);
|
|
1341
|
-
* if (!groups.has(h)) groups.set(h, []);
|
|
1342
|
-
* groups.get(h)!.push(fp);
|
|
1343
|
-
* }
|
|
1344
|
-
* ```
|
|
1345
|
-
*/
|
|
334
|
+
export function computeFingerprintSync(analysis: DiffAnalysis): DiffFingerprint | null;
|
|
335
|
+
export function fingerprintSimilaritySync(left: DiffFingerprint, right: DiffFingerprint): number;
|
|
1346
336
|
export function fingerprintHashSync(fingerprint: DiffFingerprint): string;
|