@vizzly-testing/honeydiff 0.15.0 → 0.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/index.d.ts CHANGED
@@ -1,886 +1,256 @@
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 BoundingBox {
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 HeightDiff {
22
- /** Height of the first image in pixels */
23
- height1: number;
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 DiffPixel {
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 IntensityStats {
40
- /** Maximum difference intensity observed (0-255) */
41
- max: number;
42
- /** Minimum difference intensity observed (0-255) */
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
- /** Mean (average) difference intensity */
29
+ max: number;
45
30
  mean: number;
46
- /** Median difference intensity */
47
- median: number;
48
- /** Standard deviation of difference intensities */
49
- stdDev: number;
31
+ standardDeviation: number;
50
32
  }
51
33
 
52
- export interface DiffCluster {
53
- /** Number of pixels in this cluster */
54
- pixelCount: number;
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 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;
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 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 */
56
+ export interface SpatialSummary {
57
+ bounds: BoundingBox;
86
58
  pixelCount: number;
87
- /** False when maxDiffs stopped the comparison early */
88
- complete: boolean;
89
- /** Deterministically ordered exact connected components */
90
- components: DiffMaskComponent[];
59
+ density: number;
60
+ centroid: Centroid;
61
+ perimeter: number;
91
62
  }
92
63
 
93
- export interface DiffResult {
94
- /**
95
- * Whether the images are different based on the comparison threshold
96
- * Note: This respects minClusterSize filtering - small clusters may be filtered as noise
97
- */
98
- isDifferent: boolean;
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
- /** Installed @vizzly-testing/honeydiff package version */
141
- export const version: string;
142
-
143
- /** Native diff-mask semantics implemented by this package */
144
- export const maskSemanticsVersion: string;
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
- * Maximum vertical tolerance for "same Y-band" (pixels)
173
- *
174
- * Clusters with Y-ranges within this tolerance of each other are
175
- * considered to be on the same line.
176
- * @default 5
177
- */
178
- yBandTolerance?: number;
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
- * Maximum height ratio between clusters to allow merging
182
- *
183
- * Prevents merging clusters of very different sizes (e.g., a word
184
- * with a large image). Based on SWT heuristic.
185
- * @default 2.0
186
- */
187
- maxHeightRatio?: number;
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
- * Maximum width ratio between clusters to allow merging
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
- * 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
- */
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
-
385
- /**
386
- * Compare two images asynchronously (recommended)
387
- * For large screenshots, prefer file path inputs. Buffer inputs are copied
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
- * ```
405
- */
145
+ export const version: string;
406
146
  export function compare(
407
- img1: ImageInput,
408
- img2: ImageInput,
409
- options?: CompareOptions
410
- ): Promise<DiffResult>;
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
- */
147
+ baseline: ImageInput,
148
+ current: ImageInput,
149
+ options?: CompareOptions,
150
+ output?: ArtifactOutput
151
+ ): Promise<DiffAnalysis>;
451
152
  export function compareSync(
452
- img1: ImageInput,
453
- img2: ImageInput,
153
+ baseline: ImageInput,
154
+ current: ImageInput,
155
+ options?: CompareOptions,
156
+ output?: ArtifactOutput
157
+ ): DiffAnalysis;
158
+ export function imagesDiffer(
159
+ baseline: ImageInput,
160
+ current: ImageInput,
454
161
  options?: CompareOptions
455
- ): DiffResult;
456
-
457
- /**
458
- * Quick synchronous comparison (returns only boolean, blocks event loop)
459
- *
460
- * @param img1 - First image (file path or Buffer)
461
- * @param img2 - Second image (file path or Buffer)
462
- * @returns true if different, false if identical
463
- *
464
- * @example
465
- * ```typescript
466
- * if (quickCompareSync('baseline.png', 'current.png')) {
467
- * console.log('Images differ');
468
- * }
469
- * ```
470
- */
471
- export function quickCompareSync(img1: ImageInput, img2: ImageInput): boolean;
472
-
473
- // ============================================================================
474
- // Dimensions API
475
- // ============================================================================
162
+ ): Promise<boolean>;
163
+ export function imagesDifferSync(
164
+ baseline: ImageInput,
165
+ current: ImageInput,
166
+ options?: CompareOptions
167
+ ): boolean;
168
+ export function analyze(
169
+ baseline: ImageInput,
170
+ current: ImageInput,
171
+ options?: CompareOptions,
172
+ output?: ArtifactOutput
173
+ ): Promise<ImageAnalysis>;
174
+ export function analyzeSync(
175
+ baseline: ImageInput,
176
+ current: ImageInput,
177
+ options?: CompareOptions,
178
+ output?: ArtifactOutput
179
+ ): ImageAnalysis;
476
180
 
477
181
  export interface ImageDimensions {
478
- /** Image width in pixels */
479
182
  width: number;
480
- /** Image height in pixels */
481
183
  height: number;
482
184
  }
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 */
185
+ export interface ImageMetadata extends ImageDimensions {
490
186
  fileSizeBytes: number;
491
- /** Detected image format (e.g., "png", "jpeg", "webp") or null if unknown */
492
187
  format: string | null;
493
188
  }
494
-
495
- /**
496
- * Get image dimensions asynchronously (recommended)
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
- */
189
+ export function getDimensions(image: ImageInput): Promise<ImageDimensions>;
190
+ export function getDimensionsSync(image: ImageInput): ImageDimensions;
191
+ export function getImageMetadata(image: Buffer): Promise<ImageMetadata>;
192
+ export function getImageMetadataSync(image: Buffer): ImageMetadata;
593
193
  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
194
  export function getImageMetadataFromFileSync(path: string): ImageMetadata;
611
195
 
612
- // ============================================================================
613
- // Screenshot Contrast Screening API
614
- // ============================================================================
615
-
616
- /**
617
- * Options for screenshot contrast screening with WCAG color-pair math.
618
- */
619
196
  export interface WcagOptions {
620
- /**
621
- * Edge detection threshold (0-255)
622
- * Differences below this threshold are not considered edges
623
- * @default 60
624
- */
625
197
  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
198
  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
199
  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
200
  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
201
  checkAAA?: boolean;
653
202
  }
654
-
655
- /**
656
- * A single screenshot contrast warning region.
657
- */
658
203
  export interface ContrastViolation {
659
- /** Bounding box containing this warning region */
660
204
  boundingBox: BoundingBox;
661
-
662
- /** List of pixel coordinates [x, y] in this warning region */
663
205
  pixels: [number, number][];
664
-
665
- /** Center of mass [x, y] of the warning region */
666
206
  centerOfMass: [number, number];
667
-
668
- /** Number of pixels in this warning region */
669
207
  pixelCount: number;
670
-
671
- /** Foreground color [r, g, b, a] (0-255) */
672
208
  foregroundColor: [number, number, number, number];
673
-
674
- /** Background color [r, g, b, a] (0-255) */
675
209
  backgroundColor: [number, number, number, number];
676
-
677
- /** Foreground relative luminance (0.0-1.0, sRGB) */
678
210
  foregroundLuminance: number;
679
-
680
- /** Background relative luminance (0.0-1.0, sRGB) */
681
211
  backgroundLuminance: number;
682
-
683
- /** Average contrast ratio for this region (1.0-21.0) */
684
212
  contrastRatio: number;
685
-
686
- /** Minimum contrast ratio in this region (worst case) */
687
213
  minContrastRatio: number;
688
-
689
- /** Maximum contrast ratio in this region (best case) */
690
214
  maxContrastRatio: number;
691
-
692
- /** Whether this region fails WCAG AA for normal text (< 4.5:1) */
693
215
  failsAaNormal: boolean;
694
-
695
- /** Whether this region fails WCAG AA for large text (< 3.0:1) */
696
216
  failsAaLarge: boolean;
697
-
698
- /** Whether this region fails WCAG AAA for normal text (< 7.0:1) */
699
217
  failsAaaNormal: boolean;
700
-
701
- /** Whether this region fails WCAG AAA for large text (< 4.5:1) */
702
218
  failsAaaLarge: boolean;
703
219
  }
704
-
705
- /**
706
- * Complete screenshot contrast screening result.
707
- */
708
220
  export interface WcagAnalysis {
709
- /** Total number of edges analyzed */
710
221
  totalEdges: number;
711
-
712
- /** Number of edges passing WCAG AA for normal text */
713
222
  aaNormalPass: number;
714
-
715
- /** Number of edges passing WCAG AA for large text */
716
223
  aaLargePass: number;
717
-
718
- /** Number of edges passing WCAG AAA for normal text */
719
224
  aaaNormalPass: number;
720
-
721
- /** Number of edges passing WCAG AAA for large text */
722
225
  aaaLargePass: number;
723
-
724
- /** List of contrast warning regions found */
725
226
  violations: ContrastViolation[];
726
-
727
- /** Percentage of edges passing WCAG AA for normal text (0.0-100.0) */
728
227
  aaNormalPassPercentage: number;
729
-
730
- /** Percentage of edges passing WCAG AA for large text (0.0-100.0) */
731
228
  aaLargePassPercentage: number;
732
-
733
- /** Percentage of edges passing WCAG AAA for normal text (0.0-100.0) */
734
229
  aaaNormalPassPercentage: number;
735
-
736
- /** Percentage of edges passing WCAG AAA for large text (0.0-100.0) */
737
230
  aaaLargePassPercentage: number;
738
231
  }
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
232
  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
233
  highlightColor?: [number, number, number, number];
805
-
806
- /**
807
- * Whether to overwrite existing output file
808
- * @default false
809
- */
810
234
  overwrite?: boolean;
811
235
  }
812
-
813
- /**
814
- * Save WCAG violation overlay image asynchronously (recommended)
815
- *
816
- * Generates an image with contrast violations highlighted in red (or custom color).
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
- */
236
+ export function analyzeWcagContrast(
237
+ image: ImageInput,
238
+ options?: WcagOptions
239
+ ): Promise<WcagAnalysis>;
240
+ export function analyzeWcagContrastSync(image: ImageInput, options?: WcagOptions): WcagAnalysis;
839
241
  export function saveWcagOverlay(
840
- img: ImageInput,
242
+ image: ImageInput,
841
243
  analysis: WcagAnalysis,
842
244
  outputPath: string,
843
245
  options?: WcagOutputOptions
844
246
  ): 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
247
  export function saveWcagOverlaySync(
864
- img: ImageInput,
248
+ image: ImageInput,
865
249
  analysis: WcagAnalysis,
866
250
  outputPath: string,
867
251
  options?: WcagOutputOptions
868
252
  ): void;
869
253
 
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
254
  export type ColorBlindnessType =
885
255
  | 'protanopia'
886
256
  | 'deuteranopia'
@@ -891,456 +261,71 @@ export type ColorBlindnessType =
891
261
  | 'tritan'
892
262
  | 'achroma'
893
263
  | 'monochromacy';
894
-
895
- /**
896
- * Information about a color blindness type
897
- */
898
264
  export interface ColorBlindnessTypeInfo {
899
- /** The canonical type identifier (e.g., "protanopia") */
900
265
  type: string;
901
- /** Human-readable name (e.g., "Protanopia") */
902
266
  name: string;
903
- /** Description of what colors are affected */
904
267
  description: string;
905
- /** Approximate prevalence in the population */
906
268
  prevalence: string;
907
269
  }
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
270
  export interface CvdWcagReport {
916
- /** Contrast screening for normal (typical) color vision */
917
271
  normalVision: WcagAnalysis;
918
-
919
- /** Contrast screening simulating protanopia (red-blind) */
920
272
  protanopia: WcagAnalysis;
921
-
922
- /** Contrast screening simulating deuteranopia (green-blind) */
923
273
  deuteranopia: WcagAnalysis;
924
-
925
- /** Contrast screening simulating tritanopia (blue-blind) */
926
274
  tritanopia: WcagAnalysis;
927
-
928
- /** Whether any CVD type has violations */
929
275
  hasAnyViolations: boolean;
930
-
931
- /** Total number of violations across all CVD types */
932
276
  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
277
  cvdOnlyViolationCount: number;
939
278
  }
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
279
  export function simulateColorBlindness(
968
- img: ImageInput,
969
- cvdType: ColorBlindnessType
280
+ image: ImageInput,
281
+ type: ColorBlindnessType
970
282
  ): 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
- */
283
+ export function simulateColorBlindnessSync(image: ImageInput, type: ColorBlindnessType): Buffer;
1011
284
  export function saveColorBlindnessSimulation(
1012
- img: ImageInput,
1013
- cvdType: ColorBlindnessType,
285
+ image: ImageInput,
286
+ type: ColorBlindnessType,
1014
287
  outputPath: string
1015
288
  ): 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
289
  export function saveColorBlindnessSimulationSync(
1032
- img: ImageInput,
1033
- cvdType: ColorBlindnessType,
290
+ image: ImageInput,
291
+ type: ColorBlindnessType,
1034
292
  outputPath: string
1035
293
  ): 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
294
  export function saveAllColorBlindnessSimulations(
1059
- img: ImageInput,
295
+ image: ImageInput,
1060
296
  outputPrefix: string,
1061
297
  extension?: string
1062
298
  ): 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
299
  export function saveAllColorBlindnessSimulationsSync(
1079
- img: ImageInput,
300
+ image: ImageInput,
1080
301
  outputPrefix: string,
1081
302
  extension?: string
1082
303
  ): 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
304
  export function analyzeWcagForCvd(
1112
- img: ImageInput,
1113
- cvdType: ColorBlindnessType,
305
+ image: ImageInput,
306
+ type: ColorBlindnessType,
1114
307
  options?: WcagOptions
1115
308
  ): 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
309
  export function analyzeWcagForCvdSync(
1134
- img: ImageInput,
1135
- cvdType: ColorBlindnessType,
310
+ image: ImageInput,
311
+ type: ColorBlindnessType,
1136
312
  options?: WcagOptions
1137
313
  ): 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
- */
314
+ export function analyzeWcagAllCvd(image: ImageInput, options?: WcagOptions): Promise<CvdWcagReport>;
315
+ export function analyzeWcagAllCvdSync(image: ImageInput, options?: WcagOptions): CvdWcagReport;
1218
316
  export function getColorBlindnessTypes(): ColorBlindnessTypeInfo[];
1219
317
 
1220
- // ============================================================================
1221
- // Diff Fingerprint API
1222
- // ============================================================================
1223
-
1224
- /**
1225
- * Magnitude of a diff, bucketed into categories
1226
- */
1227
318
  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
319
  export interface DiffFingerprint {
1234
- /** Number of clusters (distinct change regions) */
1235
- clusterCount: number;
1236
-
1237
- /** Normalized positions of cluster centers (0.0-1.0 relative to image dimensions) */
1238
- clusterPositions: [number, number][];
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
- */
320
+ regionCount: number;
321
+ regionPositions: [number, number][];
322
+ regionSizes: number[];
323
+ meanDeltaE: number | null;
324
+ averageDensity: number;
1253
325
  zoneMask: number;
1254
-
1255
- /** Overall diff percentage bucketed into categories */
1256
326
  diffMagnitude: DiffMagnitude;
1257
-
1258
- /** Pre-computed coarse hash for fast grouping (hex string) */
1259
327
  hash: string;
1260
328
  }
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
- */
329
+ export function computeFingerprintSync(analysis: DiffAnalysis): DiffFingerprint | null;
330
+ export function fingerprintSimilaritySync(left: DiffFingerprint, right: DiffFingerprint): number;
1346
331
  export function fingerprintHashSync(fingerprint: DiffFingerprint): string;