@vizzly-testing/honeydiff 0.2.1 → 0.3.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/README.md +37 -98
- package/index.d.ts +261 -60
- package/package.json +6 -1
- package/platforms/index-aarch64-apple-darwin.node +0 -0
- package/platforms/index-aarch64-unknown-linux-gnu.node +0 -0
- package/platforms/index-x86_64-pc-windows-msvc.node +0 -0
- package/platforms/index-x86_64-unknown-linux-gnu.node +0 -0
package/README.md
CHANGED
|
@@ -16,7 +16,7 @@ Requires Node.js 22+. Pre-built binaries included for macOS (ARM64), Linux (x64/
|
|
|
16
16
|
|
|
17
17
|
- **6x faster than odiff** - Processes 230M+ pixels/sec with multi-core parallelization
|
|
18
18
|
- **Smart anti-aliasing** - Filters out rendering artifacts, not actual changes
|
|
19
|
-
- **Accessibility built-in** - WCAG contrast
|
|
19
|
+
- **Accessibility built-in** - WCAG contrast analysis for catching accessibility violations
|
|
20
20
|
- **Flexible inputs** - Works with file paths or Buffers
|
|
21
21
|
- **Full TypeScript support** - Complete type definitions included
|
|
22
22
|
- **Advanced analytics** - SSIM perceptual scoring, spatial clustering, intensity stats
|
|
@@ -257,116 +257,55 @@ let dims = await getDimensions(buffer);
|
|
|
257
257
|
|
|
258
258
|
## Accessibility Features
|
|
259
259
|
|
|
260
|
-
Built-in
|
|
260
|
+
Built-in WCAG color contrast analysis to catch accessibility violations in screenshots and UI designs.
|
|
261
261
|
|
|
262
|
-
### WCAG Contrast
|
|
262
|
+
### WCAG Contrast Analysis
|
|
263
263
|
|
|
264
|
-
|
|
264
|
+
Analyze entire images for WCAG color contrast violations - perfect for catching accessibility issues in screenshots and UI designs.
|
|
265
265
|
|
|
266
266
|
```javascript
|
|
267
|
-
const
|
|
268
|
-
|
|
269
|
-
|
|
267
|
+
const { analyzeWcagContrast, saveWcagOverlay } = require('@vizzly-testing/honeydiff');
|
|
268
|
+
|
|
269
|
+
// Analyze a screenshot for accessibility violations
|
|
270
|
+
let analysis = await analyzeWcagContrast('screenshot.png', {
|
|
271
|
+
edgeThreshold: 60, // Edge detection sensitivity (0-255)
|
|
272
|
+
minRegionSize: 50, // Minimum violation region size (pixels)
|
|
273
|
+
checkAA: true, // Check WCAG AA (4.5:1 normal, 3.0:1 large)
|
|
274
|
+
checkAAA: true // Check WCAG AAA (7.0:1 normal, 4.5:1 large)
|
|
270
275
|
});
|
|
271
276
|
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
console.log('⚠️ Below WCAG AA large text (3.0:1)');
|
|
286
|
-
}
|
|
287
|
-
if (meta.minContrastRatio >= 7.0) {
|
|
288
|
-
console.log('✓ Meets WCAG AAA (7.0:1)');
|
|
289
|
-
}
|
|
290
|
-
}
|
|
291
|
-
});
|
|
292
|
-
}
|
|
293
|
-
```
|
|
294
|
-
|
|
295
|
-
### RGB Channel Analysis
|
|
296
|
-
|
|
297
|
-
Track which color channels changed - useful for debugging design changes.
|
|
298
|
-
|
|
299
|
-
```javascript
|
|
300
|
-
const result = await compare('baseline.png', 'current.png', {
|
|
301
|
-
includeClusters: true,
|
|
302
|
-
includeAccessibilityData: true
|
|
277
|
+
console.log(`Total edges analyzed: ${analysis.totalEdges}`);
|
|
278
|
+
console.log(`AA normal text pass rate: ${analysis.aaNormalPassPercentage.toFixed(1)}%`);
|
|
279
|
+
console.log(`Found ${analysis.violations.length} violations`);
|
|
280
|
+
|
|
281
|
+
// Examine violations
|
|
282
|
+
analysis.violations.forEach((violation, i) => {
|
|
283
|
+
console.log(`\nViolation ${i + 1}:`);
|
|
284
|
+
console.log(` Location: (${violation.boundingBox.x}, ${violation.boundingBox.y})`);
|
|
285
|
+
console.log(` Contrast ratio: ${violation.contrastRatio.toFixed(2)}:1`);
|
|
286
|
+
console.log(` Foreground: rgb(${violation.foregroundColor.slice(0,3).join(', ')})`);
|
|
287
|
+
console.log(` Background: rgb(${violation.backgroundColor.slice(0,3).join(', ')})`);
|
|
288
|
+
console.log(` Fails AA normal: ${violation.failsAaNormal}`);
|
|
289
|
+
console.log(` Fails AAA normal: ${violation.failsAaaNormal}`);
|
|
303
290
|
});
|
|
304
291
|
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
let meta = cluster.accessibilityMetadata;
|
|
309
|
-
|
|
310
|
-
console.log(`RGB diffs: R=${meta.avgRDiff}, G=${meta.avgGDiff}, B=${meta.avgBDiff}`);
|
|
311
|
-
console.log(`Dominant channel: ${meta.dominantChannel}`);
|
|
312
|
-
console.log(`Luminance: baseline=${meta.avgBaselineLuminance.toFixed(3)}, current=${meta.avgCurrentLuminance.toFixed(3)}`);
|
|
313
|
-
}
|
|
314
|
-
});
|
|
315
|
-
}
|
|
316
|
-
// Output:
|
|
317
|
-
// RGB diffs: R=45.2, G=12.3, B=8.7
|
|
318
|
-
// Dominant channel: R
|
|
319
|
-
// Luminance: baseline=0.234, current=0.567
|
|
320
|
-
```
|
|
321
|
-
|
|
322
|
-
### Color Blindness Simulation
|
|
323
|
-
|
|
324
|
-
Test if visual changes are visible to users with color vision deficiencies.
|
|
325
|
-
|
|
326
|
-
```javascript
|
|
327
|
-
const result = await compare('baseline.png', 'current.png', {
|
|
328
|
-
includeClusters: true,
|
|
329
|
-
checkColorBlindness: true,
|
|
330
|
-
colorBlindnessThreshold: 30.0 // Sensitivity threshold (0-255)
|
|
292
|
+
// Generate visual overlay showing violations
|
|
293
|
+
await saveWcagOverlay('screenshot.png', analysis, 'violations.png', {
|
|
294
|
+
highlightColor: [255, 0, 0, 180] // Semi-transparent red
|
|
331
295
|
});
|
|
332
|
-
|
|
333
|
-
if (result.diffClusters) {
|
|
334
|
-
result.diffClusters.forEach(cluster => {
|
|
335
|
-
if (cluster.accessibilityMetadata?.colorBlindnessImpact) {
|
|
336
|
-
let impact = cluster.accessibilityMetadata.colorBlindnessImpact;
|
|
337
|
-
|
|
338
|
-
console.log(`Visibility score: ${(impact.visibilityScore * 100).toFixed(0)}%`);
|
|
339
|
-
console.log(`Deuteranopia (red-green, ~1% of population): ${impact.deuteranopiaVisible ? '✓' : '✗'}`);
|
|
340
|
-
console.log(`Protanopia (red-weak, ~0.5%): ${impact.protanopiaVisible ? '✓' : '✗'}`);
|
|
341
|
-
console.log(`Tritanopia (blue-yellow, rare): ${impact.tritanopiaVisible ? '✓' : '✗'}`);
|
|
342
|
-
console.log(`Achromatopsia (grayscale, very rare): ${impact.achromatopsiaVisible ? '✓' : '✗'}`);
|
|
343
|
-
|
|
344
|
-
if (impact.visibilityScore < 0.5) {
|
|
345
|
-
console.log('⚠️ May not be visible to colorblind users');
|
|
346
|
-
}
|
|
347
|
-
}
|
|
348
|
-
});
|
|
349
|
-
}
|
|
350
296
|
```
|
|
351
297
|
|
|
352
|
-
**
|
|
353
|
-
|
|
298
|
+
**Advanced filtering:**
|
|
354
299
|
```javascript
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
// Ensure button hover is visible to colorblind users
|
|
362
|
-
let lowestVisibility = Math.min(
|
|
363
|
-
...result.diffClusters.map(c =>
|
|
364
|
-
c.accessibilityMetadata?.colorBlindnessImpact?.visibilityScore ?? 1
|
|
365
|
-
)
|
|
366
|
-
);
|
|
367
|
-
|
|
368
|
-
expect(lowestVisibility).toBeGreaterThan(0.7);
|
|
300
|
+
let analysis = await analyzeWcagContrast('screenshot.png', {
|
|
301
|
+
edgeThreshold: 80, // Higher = fewer false positives
|
|
302
|
+
minRegionSize: 100, // Ignore tiny regions
|
|
303
|
+
maxContrastThreshold: 3.5, // Only flag severe violations
|
|
304
|
+
checkAA: true,
|
|
305
|
+
checkAAA: false
|
|
369
306
|
});
|
|
307
|
+
|
|
308
|
+
console.log(`AA pass rate: ${analysis.aaNormalPassPercentage.toFixed(1)}%`);
|
|
370
309
|
```
|
|
371
310
|
|
|
372
311
|
## Complete API Reference
|
package/index.d.ts
CHANGED
|
@@ -49,42 +49,6 @@ export interface IntensityStats {
|
|
|
49
49
|
stdDev: number;
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
-
export interface ColorBlindnessImpact {
|
|
53
|
-
/** Whether the difference is visible to people with deuteranopia (red-green color blindness) */
|
|
54
|
-
deuteranopiaVisible: boolean;
|
|
55
|
-
/** Whether the difference is visible to people with protanopia (red-weak color blindness) */
|
|
56
|
-
protanopiaVisible: boolean;
|
|
57
|
-
/** Whether the difference is visible to people with tritanopia (blue-yellow color blindness) */
|
|
58
|
-
tritanopiaVisible: boolean;
|
|
59
|
-
/** Whether the difference is visible to people with achromatopsia (complete color blindness) */
|
|
60
|
-
achromatopsiaVisible: boolean;
|
|
61
|
-
/** Overall visibility score (0.0-1.0, higher = more visible) */
|
|
62
|
-
visibilityScore: number;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
export interface AccessibilityMetadata {
|
|
66
|
-
/** Average difference in red channel (0-255) */
|
|
67
|
-
avgRDiff: number;
|
|
68
|
-
/** Average difference in green channel (0-255) */
|
|
69
|
-
avgGDiff: number;
|
|
70
|
-
/** Average difference in blue channel (0-255) */
|
|
71
|
-
avgBDiff: number;
|
|
72
|
-
/** Dominant color channel ('R', 'G', or 'B') */
|
|
73
|
-
dominantChannel: string;
|
|
74
|
-
/** Average relative luminance of baseline pixels (0.0-1.0, sRGB) */
|
|
75
|
-
avgBaselineLuminance: number;
|
|
76
|
-
/** Average relative luminance of current pixels (0.0-1.0, sRGB) */
|
|
77
|
-
avgCurrentLuminance: number;
|
|
78
|
-
/** WCAG contrast ratio between average colors (1.0-21.0, values below 4.5 may fail WCAG AA) */
|
|
79
|
-
contrastRatio: number;
|
|
80
|
-
/** Minimum WCAG contrast ratio in this cluster (worst case) */
|
|
81
|
-
minContrastRatio: number;
|
|
82
|
-
/** Maximum WCAG contrast ratio in this cluster (best case) */
|
|
83
|
-
maxContrastRatio: number;
|
|
84
|
-
/** Color blindness visibility analysis (null unless checkColorBlindness is enabled) */
|
|
85
|
-
colorBlindnessImpact: ColorBlindnessImpact | null;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
52
|
export interface DiffCluster {
|
|
89
53
|
/** Number of pixels in this cluster */
|
|
90
54
|
pixelCount: number;
|
|
@@ -94,8 +58,6 @@ export interface DiffCluster {
|
|
|
94
58
|
avgIntensity: number;
|
|
95
59
|
/** Bounding box containing this cluster */
|
|
96
60
|
boundingBox: BoundingBox;
|
|
97
|
-
/** Accessibility metadata (null unless includeAccessibilityData is enabled) */
|
|
98
|
-
accessibilityMetadata: AccessibilityMetadata | null;
|
|
99
61
|
}
|
|
100
62
|
|
|
101
63
|
export interface DiffResult {
|
|
@@ -208,28 +170,6 @@ export interface CompareOptions {
|
|
|
208
170
|
* @default false
|
|
209
171
|
*/
|
|
210
172
|
overwrite?: boolean;
|
|
211
|
-
|
|
212
|
-
/**
|
|
213
|
-
* Include accessibility metadata (RGB channels, luminance, WCAG contrast)
|
|
214
|
-
* When enabled, each cluster will include detailed color and brightness analysis
|
|
215
|
-
* @default false
|
|
216
|
-
*/
|
|
217
|
-
includeAccessibilityData?: boolean;
|
|
218
|
-
|
|
219
|
-
/**
|
|
220
|
-
* Check if differences are visible to color blind users
|
|
221
|
-
* Automatically enables includeAccessibilityData if not already enabled
|
|
222
|
-
* Tests visibility for deuteranopia, protanopia, tritanopia, and achromatopsia
|
|
223
|
-
* @default false
|
|
224
|
-
*/
|
|
225
|
-
checkColorBlindness?: boolean;
|
|
226
|
-
|
|
227
|
-
/**
|
|
228
|
-
* Threshold for color blindness visibility detection (0-255)
|
|
229
|
-
* Differences below this threshold after color blindness simulation are considered invisible
|
|
230
|
-
* @default 30.0
|
|
231
|
-
*/
|
|
232
|
-
colorBlindnessThreshold?: number;
|
|
233
173
|
}
|
|
234
174
|
|
|
235
175
|
// ============================================================================
|
|
@@ -381,3 +321,264 @@ export function getDimensions(img: ImageInput): Promise<ImageDimensions>;
|
|
|
381
321
|
* ```
|
|
382
322
|
*/
|
|
383
323
|
export function getDimensionsSync(img: ImageInput): ImageDimensions;
|
|
324
|
+
|
|
325
|
+
// ============================================================================
|
|
326
|
+
// WCAG Accessibility API
|
|
327
|
+
// ============================================================================
|
|
328
|
+
|
|
329
|
+
/**
|
|
330
|
+
* Options for WCAG color contrast analysis
|
|
331
|
+
*/
|
|
332
|
+
export interface WcagOptions {
|
|
333
|
+
/**
|
|
334
|
+
* Edge detection threshold (0-255)
|
|
335
|
+
* Differences below this threshold are not considered edges
|
|
336
|
+
* @default 60
|
|
337
|
+
*/
|
|
338
|
+
edgeThreshold?: number;
|
|
339
|
+
|
|
340
|
+
/**
|
|
341
|
+
* Minimum region size (in pixels)
|
|
342
|
+
* Regions smaller than this are filtered out to reduce noise
|
|
343
|
+
* @default 50
|
|
344
|
+
*/
|
|
345
|
+
minRegionSize?: number;
|
|
346
|
+
|
|
347
|
+
/**
|
|
348
|
+
* Maximum contrast threshold
|
|
349
|
+
* Regions with contrast above this are excluded (gradient filtering)
|
|
350
|
+
* @default 3.5
|
|
351
|
+
*/
|
|
352
|
+
maxContrastThreshold?: number;
|
|
353
|
+
|
|
354
|
+
/**
|
|
355
|
+
* Check WCAG AA compliance (4.5:1 for normal text, 3.0:1 for large text)
|
|
356
|
+
* @default true
|
|
357
|
+
*/
|
|
358
|
+
checkAA?: boolean;
|
|
359
|
+
|
|
360
|
+
/**
|
|
361
|
+
* Check WCAG AAA compliance (7.0:1 for normal text, 4.5:1 for large text)
|
|
362
|
+
* @default false
|
|
363
|
+
*/
|
|
364
|
+
checkAAA?: boolean;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
/**
|
|
368
|
+
* A single WCAG color contrast violation region
|
|
369
|
+
*/
|
|
370
|
+
export interface ContrastViolation {
|
|
371
|
+
/** Bounding box containing this violation region */
|
|
372
|
+
boundingBox: BoundingBox;
|
|
373
|
+
|
|
374
|
+
/** List of pixel coordinates [x, y] in this violation */
|
|
375
|
+
pixels: [number, number][];
|
|
376
|
+
|
|
377
|
+
/** Center of mass [x, y] of the violation region */
|
|
378
|
+
centerOfMass: [number, number];
|
|
379
|
+
|
|
380
|
+
/** Number of pixels in this violation */
|
|
381
|
+
pixelCount: number;
|
|
382
|
+
|
|
383
|
+
/** Foreground color [r, g, b, a] (0-255) */
|
|
384
|
+
foregroundColor: [number, number, number, number];
|
|
385
|
+
|
|
386
|
+
/** Background color [r, g, b, a] (0-255) */
|
|
387
|
+
backgroundColor: [number, number, number, number];
|
|
388
|
+
|
|
389
|
+
/** Foreground relative luminance (0.0-1.0, sRGB) */
|
|
390
|
+
foregroundLuminance: number;
|
|
391
|
+
|
|
392
|
+
/** Background relative luminance (0.0-1.0, sRGB) */
|
|
393
|
+
backgroundLuminance: number;
|
|
394
|
+
|
|
395
|
+
/** Average contrast ratio for this region (1.0-21.0) */
|
|
396
|
+
contrastRatio: number;
|
|
397
|
+
|
|
398
|
+
/** Minimum contrast ratio in this region (worst case) */
|
|
399
|
+
minContrastRatio: number;
|
|
400
|
+
|
|
401
|
+
/** Maximum contrast ratio in this region (best case) */
|
|
402
|
+
maxContrastRatio: number;
|
|
403
|
+
|
|
404
|
+
/** Whether this region fails WCAG AA for normal text (< 4.5:1) */
|
|
405
|
+
failsAaNormal: boolean;
|
|
406
|
+
|
|
407
|
+
/** Whether this region fails WCAG AA for large text (< 3.0:1) */
|
|
408
|
+
failsAaLarge: boolean;
|
|
409
|
+
|
|
410
|
+
/** Whether this region fails WCAG AAA for normal text (< 7.0:1) */
|
|
411
|
+
failsAaaNormal: boolean;
|
|
412
|
+
|
|
413
|
+
/** Whether this region fails WCAG AAA for large text (< 4.5:1) */
|
|
414
|
+
failsAaaLarge: boolean;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
/**
|
|
418
|
+
* Complete WCAG accessibility analysis result
|
|
419
|
+
*/
|
|
420
|
+
export interface WcagAnalysis {
|
|
421
|
+
/** Total number of edges analyzed */
|
|
422
|
+
totalEdges: number;
|
|
423
|
+
|
|
424
|
+
/** Number of edges passing WCAG AA for normal text */
|
|
425
|
+
aaNormalPass: number;
|
|
426
|
+
|
|
427
|
+
/** Number of edges passing WCAG AA for large text */
|
|
428
|
+
aaLargePass: number;
|
|
429
|
+
|
|
430
|
+
/** Number of edges passing WCAG AAA for normal text */
|
|
431
|
+
aaaNormalPass: number;
|
|
432
|
+
|
|
433
|
+
/** Number of edges passing WCAG AAA for large text */
|
|
434
|
+
aaaLargePass: number;
|
|
435
|
+
|
|
436
|
+
/** List of contrast violations found */
|
|
437
|
+
violations: ContrastViolation[];
|
|
438
|
+
|
|
439
|
+
/** Percentage of edges passing WCAG AA for normal text (0.0-100.0) */
|
|
440
|
+
aaNormalPassPercentage: number;
|
|
441
|
+
|
|
442
|
+
/** Percentage of edges passing WCAG AA for large text (0.0-100.0) */
|
|
443
|
+
aaLargePassPercentage: number;
|
|
444
|
+
|
|
445
|
+
/** Percentage of edges passing WCAG AAA for normal text (0.0-100.0) */
|
|
446
|
+
aaaNormalPassPercentage: number;
|
|
447
|
+
|
|
448
|
+
/** Percentage of edges passing WCAG AAA for large text (0.0-100.0) */
|
|
449
|
+
aaaLargePassPercentage: number;
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
/**
|
|
453
|
+
* Analyze WCAG color contrast accessibility asynchronously (recommended)
|
|
454
|
+
*
|
|
455
|
+
* Detects text/content edges in an image and checks if they meet WCAG contrast requirements.
|
|
456
|
+
* This is useful for catching accessibility issues in screenshots and UI designs.
|
|
457
|
+
*
|
|
458
|
+
* @param img - Image to analyze (file path or Buffer)
|
|
459
|
+
* @param options - WCAG analysis options
|
|
460
|
+
* @returns Promise resolving to detailed accessibility analysis
|
|
461
|
+
*
|
|
462
|
+
* @example
|
|
463
|
+
* ```typescript
|
|
464
|
+
* const analysis = await analyzeWcagContrast('screenshot.png', {
|
|
465
|
+
* edgeThreshold: 60,
|
|
466
|
+
* checkAA: true,
|
|
467
|
+
* checkAAA: true
|
|
468
|
+
* });
|
|
469
|
+
*
|
|
470
|
+
* console.log(`Total edges: ${analysis.totalEdges}`);
|
|
471
|
+
* console.log(`AA pass rate: ${analysis.aaNormalPassPercentage.toFixed(1)}%`);
|
|
472
|
+
* console.log(`Found ${analysis.violations.length} violations`);
|
|
473
|
+
*
|
|
474
|
+
* for (let violation of analysis.violations.slice(0, 5)) {
|
|
475
|
+
* console.log(`Region at (${violation.boundingBox.x}, ${violation.boundingBox.y})`);
|
|
476
|
+
* console.log(` Contrast: ${violation.contrastRatio.toFixed(2)}:1`);
|
|
477
|
+
* console.log(` Fails AA: ${violation.failsAaNormal}`);
|
|
478
|
+
* }
|
|
479
|
+
* ```
|
|
480
|
+
*/
|
|
481
|
+
export function analyzeWcagContrast(
|
|
482
|
+
img: ImageInput,
|
|
483
|
+
options?: WcagOptions
|
|
484
|
+
): Promise<WcagAnalysis>;
|
|
485
|
+
|
|
486
|
+
/**
|
|
487
|
+
* Analyze WCAG color contrast accessibility synchronously (blocks event loop)
|
|
488
|
+
*
|
|
489
|
+
* Use this only when you need blocking behavior.
|
|
490
|
+
* For most cases, prefer the async analyzeWcagContrast() function.
|
|
491
|
+
*
|
|
492
|
+
* @param img - Image to analyze (file path or Buffer)
|
|
493
|
+
* @param options - WCAG analysis options
|
|
494
|
+
* @returns Detailed accessibility analysis
|
|
495
|
+
*
|
|
496
|
+
* @example
|
|
497
|
+
* ```typescript
|
|
498
|
+
* const analysis = analyzeWcagContrastSync('screenshot.png', {
|
|
499
|
+
* minRegionSize: 50
|
|
500
|
+
* });
|
|
501
|
+
*
|
|
502
|
+
* if (analysis.violations.length > 0) {
|
|
503
|
+
* console.log('Accessibility issues found!');
|
|
504
|
+
* }
|
|
505
|
+
* ```
|
|
506
|
+
*/
|
|
507
|
+
export function analyzeWcagContrastSync(
|
|
508
|
+
img: ImageInput,
|
|
509
|
+
options?: WcagOptions
|
|
510
|
+
): WcagAnalysis;
|
|
511
|
+
|
|
512
|
+
/**
|
|
513
|
+
* Options for saving WCAG overlay images
|
|
514
|
+
*/
|
|
515
|
+
export interface WcagOutputOptions {
|
|
516
|
+
/**
|
|
517
|
+
* Highlight color for violations [r, g, b, a] (0-255)
|
|
518
|
+
* @default [255, 0, 0, 180] (semi-transparent red)
|
|
519
|
+
*/
|
|
520
|
+
highlightColor?: [number, number, number, number];
|
|
521
|
+
|
|
522
|
+
/**
|
|
523
|
+
* Whether to overwrite existing output file
|
|
524
|
+
* @default false
|
|
525
|
+
*/
|
|
526
|
+
overwrite?: boolean;
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
/**
|
|
530
|
+
* Save WCAG violation overlay image asynchronously (recommended)
|
|
531
|
+
*
|
|
532
|
+
* Generates an image with contrast violations highlighted in red (or custom color).
|
|
533
|
+
* Useful for visual debugging of accessibility issues.
|
|
534
|
+
*
|
|
535
|
+
* @param img - Original image (file path or Buffer)
|
|
536
|
+
* @param analysis - WCAG analysis result from analyzeWcagContrast()
|
|
537
|
+
* @param outputPath - Path to save the overlay image
|
|
538
|
+
* @param options - Output options
|
|
539
|
+
* @returns Promise that resolves when the file is saved
|
|
540
|
+
*
|
|
541
|
+
* @example
|
|
542
|
+
* ```typescript
|
|
543
|
+
* const analysis = await analyzeWcagContrast('screenshot.png');
|
|
544
|
+
*
|
|
545
|
+
* await saveWcagOverlay(
|
|
546
|
+
* 'screenshot.png',
|
|
547
|
+
* analysis,
|
|
548
|
+
* 'violations-overlay.png',
|
|
549
|
+
* { highlightColor: [255, 0, 0, 180] }
|
|
550
|
+
* );
|
|
551
|
+
*
|
|
552
|
+
* console.log('Overlay saved to violations-overlay.png');
|
|
553
|
+
* ```
|
|
554
|
+
*/
|
|
555
|
+
export function saveWcagOverlay(
|
|
556
|
+
img: ImageInput,
|
|
557
|
+
analysis: WcagAnalysis,
|
|
558
|
+
outputPath: string,
|
|
559
|
+
options?: WcagOutputOptions
|
|
560
|
+
): Promise<void>;
|
|
561
|
+
|
|
562
|
+
/**
|
|
563
|
+
* Save WCAG violation overlay image synchronously (blocks event loop)
|
|
564
|
+
*
|
|
565
|
+
* Use this only when you need blocking behavior.
|
|
566
|
+
* For most cases, prefer the async saveWcagOverlay() function.
|
|
567
|
+
*
|
|
568
|
+
* @param img - Original image (file path or Buffer)
|
|
569
|
+
* @param analysis - WCAG analysis result from analyzeWcagContrastSync()
|
|
570
|
+
* @param outputPath - Path to save the overlay image
|
|
571
|
+
* @param options - Output options
|
|
572
|
+
*
|
|
573
|
+
* @example
|
|
574
|
+
* ```typescript
|
|
575
|
+
* const analysis = analyzeWcagContrastSync('screenshot.png');
|
|
576
|
+
* saveWcagOverlaySync('screenshot.png', analysis, 'violations.png');
|
|
577
|
+
* ```
|
|
578
|
+
*/
|
|
579
|
+
export function saveWcagOverlaySync(
|
|
580
|
+
img: ImageInput,
|
|
581
|
+
analysis: WcagAnalysis,
|
|
582
|
+
outputPath: string,
|
|
583
|
+
options?: WcagOutputOptions
|
|
584
|
+
): void;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vizzly-testing/honeydiff",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "High-performance image diffing for Node.js - Native bindings to Honeydiff Rust library",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"types": "index.d.ts",
|
|
@@ -70,6 +70,11 @@
|
|
|
70
70
|
"index.d.ts",
|
|
71
71
|
"load-platform.js",
|
|
72
72
|
"platforms/*.node",
|
|
73
|
+
"README.md",
|
|
74
|
+
"index.js",
|
|
75
|
+
"index.d.ts",
|
|
76
|
+
"load-platform.js",
|
|
77
|
+
"platforms/*.node",
|
|
73
78
|
"README.md"
|
|
74
79
|
],
|
|
75
80
|
"engines": {
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|