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