@vizzly-testing/honeydiff 0.3.1 → 0.4.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 CHANGED
@@ -257,7 +257,7 @@ let dims = await getDimensions(buffer);
257
257
 
258
258
  ## Accessibility Features
259
259
 
260
- Built-in WCAG color contrast analysis to catch accessibility violations in screenshots and UI designs.
260
+ Built-in WCAG color contrast analysis and color blindness simulation to catch accessibility violations in screenshots and UI designs.
261
261
 
262
262
  ### WCAG Contrast Analysis
263
263
 
@@ -308,6 +308,92 @@ let analysis = await analyzeWcagContrast('screenshot.png', {
308
308
  console.log(`AA pass rate: ${analysis.aaNormalPassPercentage.toFixed(1)}%`);
309
309
  ```
310
310
 
311
+ ### Color Blindness Simulation
312
+
313
+ Simulate how your UI appears to users with color vision deficiencies. Uses the scientifically accurate Brettel, Viénot & Mollon 1997 algorithm.
314
+
315
+ **Supported CVD types:**
316
+ - `protanopia` - Red-blind (~1% of males)
317
+ - `deuteranopia` - Green-blind (~1% of males, most common)
318
+ - `tritanopia` - Blue-blind (~0.003% of population)
319
+ - `achromatopsia` - Complete color blindness (grayscale only)
320
+
321
+ ```javascript
322
+ const {
323
+ simulateColorBlindness,
324
+ saveColorBlindnessSimulation,
325
+ saveAllColorBlindnessSimulations,
326
+ getColorBlindnessTypes
327
+ } = require('@vizzly-testing/honeydiff');
328
+
329
+ // Get CVD type information
330
+ let types = getColorBlindnessTypes();
331
+ types.forEach(cvd => {
332
+ console.log(`${cvd.name}: ${cvd.description} (${cvd.prevalence})`);
333
+ });
334
+
335
+ // Simulate and get buffer (useful for in-memory operations)
336
+ let simulated = await simulateColorBlindness('chart.png', 'deuteranopia');
337
+ fs.writeFileSync('chart-deuteranopia.png', simulated);
338
+
339
+ // Or save directly to file
340
+ await saveColorBlindnessSimulation('ui.png', 'protanopia', 'ui-protanopia.png');
341
+
342
+ // Generate all simulations at once
343
+ // Creates: dashboard_protanopia.png, dashboard_deuteranopia.png,
344
+ // dashboard_tritanopia.png, dashboard_achromatopsia.png
345
+ await saveAllColorBlindnessSimulations('dashboard.png', 'dashboard', 'png');
346
+ ```
347
+
348
+ ### CVD-Aware WCAG Analysis
349
+
350
+ Analyze contrast accessibility specifically for colorblind users. This catches issues that only appear when colors are perceived differently.
351
+
352
+ ```javascript
353
+ const { analyzeWcagForCvd, analyzeWcagAllCvd } = require('@vizzly-testing/honeydiff');
354
+
355
+ // Analyze for a specific CVD type
356
+ let analysis = await analyzeWcagForCvd('ui.png', 'deuteranopia', {
357
+ checkAA: true,
358
+ checkAAA: false
359
+ });
360
+
361
+ if (analysis.violations.length > 0) {
362
+ console.log(`Found ${analysis.violations.length} contrast issues for red-green colorblind users`);
363
+ }
364
+
365
+ // Comprehensive report for all CVD types
366
+ let report = await analyzeWcagAllCvd('dashboard.png', {
367
+ checkAA: true,
368
+ checkAAA: true
369
+ });
370
+
371
+ console.log('Accessibility Report:');
372
+ console.log(`Normal vision: ${report.normalVision.violations.length} violations`);
373
+ console.log(`Protanopia: ${report.protanopia.violations.length} violations`);
374
+ console.log(`Deuteranopia: ${report.deuteranopia.violations.length} violations`);
375
+ console.log(`Tritanopia: ${report.tritanopia.violations.length} violations`);
376
+ console.log(`Total: ${report.totalViolations} violations`);
377
+ console.log(`CVD-only issues: ~${report.cvdOnlyViolationCount}`);
378
+
379
+ if (report.hasAnyViolations) {
380
+ console.log('Accessibility issues detected!');
381
+ }
382
+ ```
383
+
384
+ **Test integration:**
385
+ ```javascript
386
+ test('UI colors work for colorblind users', async () => {
387
+ let screenshot = await page.screenshot();
388
+
389
+ // Comprehensive CVD accessibility check
390
+ let report = await analyzeWcagAllCvd(screenshot, { checkAA: true });
391
+
392
+ expect(report.hasAnyViolations).toBe(false);
393
+ expect(report.deuteranopia.aaNormalPassPercentage).toBeGreaterThan(95);
394
+ });
395
+ ```
396
+
311
397
  ## Complete API Reference
312
398
 
313
399
  ### Async Functions (Recommended)
@@ -507,10 +593,11 @@ if (result.diffPercentage > 5.0) {
507
593
  ## Examples
508
594
 
509
595
  See [examples/](./examples/) directory:
510
- - [basic.js](./examples/basic.js) - Core features and accessibility
596
+ - [basic.js](./examples/basic.js) - Core features and comparison options
511
597
  - [buffers.js](./examples/buffers.js) - Working with in-memory images
512
598
  - [typescript.ts](./examples/typescript.ts) - TypeScript integration
513
- - [accessibility.js](./examples/accessibility.js) - WCAG and color blindness
599
+ - [wcag.js](./examples/wcag.js) - WCAG contrast analysis
600
+ - [color-blindness.js](./examples/color-blindness.js) - CVD simulation and accessibility
514
601
 
515
602
  ## License
516
603
 
package/index.d.ts CHANGED
@@ -222,10 +222,7 @@ export function compare(
222
222
  * }
223
223
  * ```
224
224
  */
225
- export function quickCompare(
226
- img1: ImageInput,
227
- img2: ImageInput
228
- ): Promise<boolean>;
225
+ export function quickCompare(img1: ImageInput, img2: ImageInput): Promise<boolean>;
229
226
 
230
227
  /**
231
228
  * Compare two images synchronously (blocks event loop)
@@ -270,10 +267,7 @@ export function compareSync(
270
267
  * }
271
268
  * ```
272
269
  */
273
- export function quickCompareSync(
274
- img1: ImageInput,
275
- img2: ImageInput
276
- ): boolean;
270
+ export function quickCompareSync(img1: ImageInput, img2: ImageInput): boolean;
277
271
 
278
272
  // ============================================================================
279
273
  // Dimensions API
@@ -478,10 +472,7 @@ export interface WcagAnalysis {
478
472
  * }
479
473
  * ```
480
474
  */
481
- export function analyzeWcagContrast(
482
- img: ImageInput,
483
- options?: WcagOptions
484
- ): Promise<WcagAnalysis>;
475
+ export function analyzeWcagContrast(img: ImageInput, options?: WcagOptions): Promise<WcagAnalysis>;
485
476
 
486
477
  /**
487
478
  * Analyze WCAG color contrast accessibility synchronously (blocks event loop)
@@ -504,10 +495,7 @@ export function analyzeWcagContrast(
504
495
  * }
505
496
  * ```
506
497
  */
507
- export function analyzeWcagContrastSync(
508
- img: ImageInput,
509
- options?: WcagOptions
510
- ): WcagAnalysis;
498
+ export function analyzeWcagContrastSync(img: ImageInput, options?: WcagOptions): WcagAnalysis;
511
499
 
512
500
  /**
513
501
  * Options for saving WCAG overlay images
@@ -582,3 +570,353 @@ export function saveWcagOverlaySync(
582
570
  outputPath: string,
583
571
  options?: WcagOutputOptions
584
572
  ): void;
573
+
574
+ // ============================================================================
575
+ // Color Vision Deficiency (CVD) Simulation API
576
+ // ============================================================================
577
+
578
+ /**
579
+ * Types of color vision deficiency (color blindness) that can be simulated.
580
+ *
581
+ * - `protanopia` - Red-blind (~1% of males). Reds appear dark/muddy, red-green confusion.
582
+ * - `deuteranopia` - Green-blind (~1% of males, most common). Greens shift toward red/brown.
583
+ * - `tritanopia` - Blue-blind (~0.003% of population). Blues appear greenish, yellow-purple confusion.
584
+ * - `achromatopsia` - Complete color blindness (very rare). Only luminance is perceived.
585
+ *
586
+ * Short aliases are also accepted: `protan`, `deutan`, `tritan`, `achroma`, `monochromacy`
587
+ */
588
+ export type ColorBlindnessType =
589
+ | 'protanopia'
590
+ | 'deuteranopia'
591
+ | 'tritanopia'
592
+ | 'achromatopsia'
593
+ | 'protan'
594
+ | 'deutan'
595
+ | 'tritan'
596
+ | 'achroma'
597
+ | 'monochromacy';
598
+
599
+ /**
600
+ * Information about a color blindness type
601
+ */
602
+ export interface ColorBlindnessTypeInfo {
603
+ /** The canonical type identifier (e.g., "protanopia") */
604
+ type: string;
605
+ /** Human-readable name (e.g., "Protanopia") */
606
+ name: string;
607
+ /** Description of what colors are affected */
608
+ description: string;
609
+ /** Approximate prevalence in the population */
610
+ prevalence: string;
611
+ }
612
+
613
+ /**
614
+ * Comprehensive WCAG analysis report for all color vision deficiency types.
615
+ *
616
+ * Contains WCAG contrast analysis for normal vision and all three main types
617
+ * of dichromatic color blindness (protanopia, deuteranopia, tritanopia).
618
+ */
619
+ export interface CvdWcagReport {
620
+ /** WCAG analysis for normal (typical) color vision */
621
+ normalVision: WcagAnalysis;
622
+
623
+ /** WCAG analysis simulating protanopia (red-blind) */
624
+ protanopia: WcagAnalysis;
625
+
626
+ /** WCAG analysis simulating deuteranopia (green-blind) */
627
+ deuteranopia: WcagAnalysis;
628
+
629
+ /** WCAG analysis simulating tritanopia (blue-blind) */
630
+ tritanopia: WcagAnalysis;
631
+
632
+ /** Whether any CVD type has violations */
633
+ hasAnyViolations: boolean;
634
+
635
+ /** Total number of violations across all CVD types */
636
+ totalViolations: number;
637
+
638
+ /**
639
+ * Estimated count of violations unique to colorblind users.
640
+ * These are violations present in CVD analysis but not in normal vision.
641
+ */
642
+ cvdOnlyViolationCount: number;
643
+ }
644
+
645
+ /**
646
+ * Simulate color blindness for an image asynchronously (recommended)
647
+ *
648
+ * Transforms the input image to show how it appears to someone with the
649
+ * specified type of color vision deficiency. Uses the Brettel, Viénot & Mollon
650
+ * 1997 algorithm, which is considered the gold standard for CVD simulation.
651
+ *
652
+ * @param img - Image to transform (file path or Buffer)
653
+ * @param cvdType - Type of color blindness to simulate
654
+ * @returns Promise resolving to a Buffer containing the simulated PNG image
655
+ *
656
+ * @example
657
+ * ```typescript
658
+ * import { simulateColorBlindness } from '@vizzly-testing/honeydiff';
659
+ * import fs from 'fs';
660
+ *
661
+ * // Simulate how an image appears to someone with red-green color blindness
662
+ * const simulated = await simulateColorBlindness('chart.png', 'deuteranopia');
663
+ *
664
+ * // Save the result
665
+ * fs.writeFileSync('chart-deuteranopia.png', simulated);
666
+ *
667
+ * // Or use directly with image comparison
668
+ * const result = await compare('chart.png', simulated);
669
+ * ```
670
+ */
671
+ export function simulateColorBlindness(
672
+ img: ImageInput,
673
+ cvdType: ColorBlindnessType
674
+ ): Promise<Buffer>;
675
+
676
+ /**
677
+ * Simulate color blindness for an image synchronously (blocks event loop)
678
+ *
679
+ * Use this only when you need blocking behavior.
680
+ * For most cases, prefer the async simulateColorBlindness() function.
681
+ *
682
+ * @param img - Image to transform (file path or Buffer)
683
+ * @param cvdType - Type of color blindness to simulate
684
+ * @returns Buffer containing the simulated PNG image
685
+ *
686
+ * @example
687
+ * ```typescript
688
+ * const simulated = simulateColorBlindnessSync('button.png', 'protanopia');
689
+ * fs.writeFileSync('button-protanopia.png', simulated);
690
+ * ```
691
+ */
692
+ export function simulateColorBlindnessSync(img: ImageInput, cvdType: ColorBlindnessType): Buffer;
693
+
694
+ /**
695
+ * Simulate and save color blindness image to file asynchronously (recommended)
696
+ *
697
+ * Convenience function that simulates CVD and saves the output in one step.
698
+ *
699
+ * @param img - Image to transform (file path or Buffer)
700
+ * @param cvdType - Type of color blindness to simulate
701
+ * @param outputPath - Path where the simulated image should be saved
702
+ * @returns Promise that resolves when the file is saved
703
+ *
704
+ * @example
705
+ * ```typescript
706
+ * // Save a single simulation
707
+ * await saveColorBlindnessSimulation('ui.png', 'deuteranopia', 'ui-deutan.png');
708
+ *
709
+ * // Generate simulations for all types you care about
710
+ * for (const type of ['protanopia', 'deuteranopia', 'tritanopia']) {
711
+ * await saveColorBlindnessSimulation('ui.png', type, `ui-${type}.png`);
712
+ * }
713
+ * ```
714
+ */
715
+ export function saveColorBlindnessSimulation(
716
+ img: ImageInput,
717
+ cvdType: ColorBlindnessType,
718
+ outputPath: string
719
+ ): Promise<void>;
720
+
721
+ /**
722
+ * Simulate and save color blindness image to file synchronously (blocks event loop)
723
+ *
724
+ * Use this only when you need blocking behavior.
725
+ *
726
+ * @param img - Image to transform (file path or Buffer)
727
+ * @param cvdType - Type of color blindness to simulate
728
+ * @param outputPath - Path where the simulated image should be saved
729
+ *
730
+ * @example
731
+ * ```typescript
732
+ * saveColorBlindnessSimulationSync('chart.png', 'tritanopia', 'chart-tritan.png');
733
+ * ```
734
+ */
735
+ export function saveColorBlindnessSimulationSync(
736
+ img: ImageInput,
737
+ cvdType: ColorBlindnessType,
738
+ outputPath: string
739
+ ): void;
740
+
741
+ /**
742
+ * Generate and save all CVD simulations to files asynchronously (recommended)
743
+ *
744
+ * Generates simulated images for all CVD types (protanopia, deuteranopia,
745
+ * tritanopia, achromatopsia), saving each with a suffix indicating the type.
746
+ *
747
+ * @param img - Image to transform (file path or Buffer)
748
+ * @param outputPrefix - Base path for output files (without extension)
749
+ * @param extension - File extension to use (default: "png")
750
+ * @returns Promise that resolves when all files are saved
751
+ *
752
+ * @example
753
+ * ```typescript
754
+ * // Creates: dashboard_protanopia.png, dashboard_deuteranopia.png,
755
+ * // dashboard_tritanopia.png, dashboard_achromatopsia.png
756
+ * await saveAllColorBlindnessSimulations('dashboard.png', 'dashboard', 'png');
757
+ *
758
+ * // With different extension
759
+ * await saveAllColorBlindnessSimulations('ui.png', './artifacts/ui', 'jpg');
760
+ * ```
761
+ */
762
+ export function saveAllColorBlindnessSimulations(
763
+ img: ImageInput,
764
+ outputPrefix: string,
765
+ extension?: string
766
+ ): Promise<void>;
767
+
768
+ /**
769
+ * Generate and save all CVD simulations to files synchronously (blocks event loop)
770
+ *
771
+ * Use this only when you need blocking behavior.
772
+ *
773
+ * @param img - Image to transform (file path or Buffer)
774
+ * @param outputPrefix - Base path for output files (without extension)
775
+ * @param extension - File extension to use (default: "png")
776
+ *
777
+ * @example
778
+ * ```typescript
779
+ * saveAllColorBlindnessSimulationsSync('button.png', 'button');
780
+ * ```
781
+ */
782
+ export function saveAllColorBlindnessSimulationsSync(
783
+ img: ImageInput,
784
+ outputPrefix: string,
785
+ extension?: string
786
+ ): void;
787
+
788
+ /**
789
+ * Analyze WCAG contrast for a specific color blindness type asynchronously (recommended)
790
+ *
791
+ * Simulates the image as seen by someone with the specified color vision
792
+ * deficiency, then runs WCAG contrast analysis. This finds contrast violations
793
+ * that only appear for colorblind users - critical for inclusive design.
794
+ *
795
+ * @param img - Image to analyze (file path or Buffer)
796
+ * @param cvdType - Type of color blindness to simulate
797
+ * @param options - WCAG analysis options
798
+ * @returns Promise resolving to WCAG analysis results for the simulated image
799
+ *
800
+ * @example
801
+ * ```typescript
802
+ * // Check if colors are distinguishable for deuteranopia users
803
+ * const analysis = await analyzeWcagForCvd('ui.png', 'deuteranopia', {
804
+ * checkAA: true,
805
+ * checkAAA: false
806
+ * });
807
+ *
808
+ * if (analysis.violations.length > 0) {
809
+ * console.log(`Found ${analysis.violations.length} contrast issues`);
810
+ * console.log('for users with red-green color blindness');
811
+ * }
812
+ * ```
813
+ */
814
+ export function analyzeWcagForCvd(
815
+ img: ImageInput,
816
+ cvdType: ColorBlindnessType,
817
+ options?: WcagOptions
818
+ ): Promise<WcagAnalysis>;
819
+
820
+ /**
821
+ * Analyze WCAG contrast for a specific color blindness type synchronously (blocks event loop)
822
+ *
823
+ * Use this only when you need blocking behavior.
824
+ *
825
+ * @param img - Image to analyze (file path or Buffer)
826
+ * @param cvdType - Type of color blindness to simulate
827
+ * @param options - WCAG analysis options
828
+ * @returns WCAG analysis results for the simulated image
829
+ *
830
+ * @example
831
+ * ```typescript
832
+ * const analysis = analyzeWcagForCvdSync('chart.png', 'protanopia');
833
+ * console.log(`Pass rate: ${analysis.aaNormalPassPercentage.toFixed(1)}%`);
834
+ * ```
835
+ */
836
+ export function analyzeWcagForCvdSync(
837
+ img: ImageInput,
838
+ cvdType: ColorBlindnessType,
839
+ options?: WcagOptions
840
+ ): WcagAnalysis;
841
+
842
+ /**
843
+ * Analyze WCAG contrast for all CVD types asynchronously (recommended)
844
+ *
845
+ * Runs WCAG contrast analysis for normal vision and all three main types of
846
+ * dichromatic color blindness. This provides a comprehensive accessibility
847
+ * report covering the majority of color vision deficiencies.
848
+ *
849
+ * @param img - Image to analyze (file path or Buffer)
850
+ * @param options - WCAG analysis options (same options used for all analyses)
851
+ * @returns Promise resolving to a report containing WCAG analysis for each vision type
852
+ *
853
+ * @example
854
+ * ```typescript
855
+ * const report = await analyzeWcagAllCvd('dashboard.png', {
856
+ * checkAA: true,
857
+ * checkAAA: true
858
+ * });
859
+ *
860
+ * console.log('Accessibility Report:');
861
+ * console.log(`Normal vision violations: ${report.normalVision.violations.length}`);
862
+ * console.log(`Protanopia violations: ${report.protanopia.violations.length}`);
863
+ * console.log(`Deuteranopia violations: ${report.deuteranopia.violations.length}`);
864
+ * console.log(`Tritanopia violations: ${report.tritanopia.violations.length}`);
865
+ *
866
+ * if (report.hasAnyViolations) {
867
+ * console.log(`\nTotal issues found: ${report.totalViolations}`);
868
+ * console.log(`Issues only affecting colorblind users: ~${report.cvdOnlyViolationCount}`);
869
+ * }
870
+ * ```
871
+ */
872
+ export function analyzeWcagAllCvd(img: ImageInput, options?: WcagOptions): Promise<CvdWcagReport>;
873
+
874
+ /**
875
+ * Analyze WCAG contrast for all CVD types synchronously (blocks event loop)
876
+ *
877
+ * Use this only when you need blocking behavior.
878
+ *
879
+ * @param img - Image to analyze (file path or Buffer)
880
+ * @param options - WCAG analysis options
881
+ * @returns Report containing WCAG analysis for each vision type
882
+ *
883
+ * @example
884
+ * ```typescript
885
+ * const report = analyzeWcagAllCvdSync('ui.png');
886
+ *
887
+ * if (report.hasAnyViolations) {
888
+ * console.log('Accessibility issues detected!');
889
+ * }
890
+ * ```
891
+ */
892
+ export function analyzeWcagAllCvdSync(img: ImageInput, options?: WcagOptions): CvdWcagReport;
893
+
894
+ /**
895
+ * Get metadata about all supported color blindness types
896
+ *
897
+ * Returns information about each CVD type including name, description,
898
+ * and prevalence. Useful for building UI selectors or generating reports.
899
+ *
900
+ * @returns Array of color blindness type information
901
+ *
902
+ * @example
903
+ * ```typescript
904
+ * const types = getColorBlindnessTypes();
905
+ *
906
+ * for (const cvd of types) {
907
+ * console.log(`${cvd.name} (${cvd.type})`);
908
+ * console.log(` ${cvd.description}`);
909
+ * console.log(` Affects: ${cvd.prevalence}`);
910
+ * }
911
+ *
912
+ * // Output:
913
+ * // Protanopia (protanopia)
914
+ * // Red-blind: reds appear dark, red-green confusion
915
+ * // Affects: ~1% of males
916
+ * // Deuteranopia (deuteranopia)
917
+ * // Green-blind: greens shift to brown, red-green confusion
918
+ * // Affects: ~1% of males
919
+ * // ...
920
+ * ```
921
+ */
922
+ export function getColorBlindnessTypes(): ColorBlindnessTypeInfo[];
package/package.json CHANGED
@@ -1,7 +1,8 @@
1
1
  {
2
2
  "name": "@vizzly-testing/honeydiff",
3
- "version": "0.3.1",
3
+ "version": "0.4.0",
4
4
  "description": "High-performance image diffing for Node.js - Native bindings to Honeydiff Rust library",
5
+ "type": "module",
5
6
  "main": "index.js",
6
7
  "types": "index.d.ts",
7
8
  "neon": {
@@ -15,7 +16,14 @@
15
16
  "scripts": {
16
17
  "build": "cargo build --release",
17
18
  "build-debug": "cargo build",
18
- "test": "node examples/basic.js",
19
+ "test": "node --test --experimental-strip-types tests/*.test.ts",
20
+ "test:types": "tsc --noEmit",
21
+ "test:examples": "node examples/basic.js",
22
+ "check": "biome check .",
23
+ "check:fix": "biome check --write .",
24
+ "lint": "biome lint .",
25
+ "format": "biome format --write .",
26
+ "format:check": "biome format .",
19
27
  "clean": "cargo clean && rm -f index.node"
20
28
  },
21
29
  "repository": {
@@ -40,39 +48,6 @@
40
48
  "index.js",
41
49
  "index.d.ts",
42
50
  "index.node",
43
- "README.md",
44
- "index.js",
45
- "index.d.ts",
46
- "load-platform.js",
47
- "platforms/*.node",
48
- "README.md",
49
- "index.js",
50
- "index.d.ts",
51
- "load-platform.js",
52
- "platforms/*.node",
53
- "README.md",
54
- "index.js",
55
- "index.d.ts",
56
- "load-platform.js",
57
- "platforms/*.node",
58
- "README.md",
59
- "index.js",
60
- "index.d.ts",
61
- "load-platform.js",
62
- "platforms/*.node",
63
- "README.md",
64
- "index.js",
65
- "index.d.ts",
66
- "load-platform.js",
67
- "platforms/*.node",
68
- "README.md",
69
- "index.js",
70
- "index.d.ts",
71
- "load-platform.js",
72
- "platforms/*.node",
73
- "README.md",
74
- "index.js",
75
- "index.d.ts",
76
51
  "load-platform.js",
77
52
  "platforms/*.node",
78
53
  "README.md",
@@ -84,5 +59,10 @@
84
59
  ],
85
60
  "engines": {
86
61
  "node": ">=22.0.0"
62
+ },
63
+ "devDependencies": {
64
+ "@biomejs/biome": "2.3.8",
65
+ "@types/node": "^24.10.1",
66
+ "typescript": "^5.9.3"
87
67
  }
88
68
  }