@vizzly-testing/honeydiff 0.1.0 → 0.2.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
@@ -10,6 +10,7 @@ validation, and detecting visual changes.
10
10
  **Why Honeydiff?**
11
11
  - **Fast:** Native performance processing 230M+ pixels/sec
12
12
  - **Smart:** Intelligent anti-aliasing detection filters rendering artifacts
13
+ - **Accessible:** Built-in WCAG contrast checking and color blindness simulation
13
14
  - **Flexible:** File paths or Buffers, async or sync APIs
14
15
  - **Complete:** TypeScript types, pre-built binaries for all platforms
15
16
  - **Advanced:** SSIM perceptual scoring, spatial clustering, variable-height screenshots
@@ -68,10 +69,12 @@ if (result.diffClusters) {
68
69
  **Async (recommended)**
69
70
  - `compare(img1, img2, options?): Promise<DiffResult>` - Full comparison
70
71
  - `quickCompare(img1, img2): Promise<boolean>` - Quick check
72
+ - `getDimensions(img): Promise<{ width: number, height: number }>` - Get image dimensions
71
73
 
72
74
  **Sync (blocks event loop)**
73
75
  - `compareSync(img1, img2, options?): DiffResult`
74
76
  - `quickCompareSync(img1, img2): boolean`
77
+ - `getDimensionsSync(img): { width: number, height: number }` - Get image dimensions
75
78
 
76
79
  Inputs: `string` (file path) or `Buffer`
77
80
 
@@ -90,6 +93,9 @@ interface CompareOptions {
90
93
  includeDiffPixels?: boolean; // Detailed pixel list (default: false)
91
94
  includeClusters?: boolean; // Spatial clusters (default: false)
92
95
  includeSSIM?: boolean; // SSIM score (default: false)
96
+ includeAccessibilityData?: boolean; // RGB, luminance, WCAG (default: false)
97
+ checkColorBlindness?: boolean; // Color blindness simulation (default: false)
98
+ colorBlindnessThreshold?: number; // Visibility threshold 0-255 (default: 30)
93
99
  diffPath?: string; // Save diff image
94
100
  maskPath?: string; // Save mask image
95
101
  overlayPath?: string; // Save overlay image
@@ -121,6 +127,24 @@ interface DiffResult {
121
127
 
122
128
  ## Examples
123
129
 
130
+ **Get Image Dimensions**
131
+ ```javascript
132
+ const { getDimensions, getDimensionsSync } = require('@vizzly-testing/honeydiff');
133
+
134
+ // Async
135
+ let dims = await getDimensions('screenshot.png');
136
+ console.log(`Image is ${dims.width}x${dims.height} pixels`);
137
+ // Output: Image is 1920x1080 pixels
138
+
139
+ // Sync
140
+ let dims = getDimensionsSync('screenshot.png');
141
+ console.log(`Image is ${dims.width}x${dims.height} pixels`);
142
+
143
+ // Works with buffers too
144
+ let buffer = fs.readFileSync('image.png');
145
+ let dims = await getDimensions(buffer);
146
+ ```
147
+
124
148
  **Buffers**
125
149
  ```javascript
126
150
  const fs = require('fs');
@@ -174,6 +198,53 @@ if (result.intensityStats) {
174
198
  // Output: Range: 12-255, Mean: 87.3
175
199
  ```
176
200
 
201
+ **Accessibility Features**
202
+
203
+ ```javascript
204
+ // WCAG contrast checking and RGB analysis
205
+ let result = await compare('baseline.png', 'current.png', {
206
+ includeClusters: true,
207
+ includeAccessibilityData: true
208
+ });
209
+
210
+ if (result.diffClusters) {
211
+ result.diffClusters.forEach(cluster => {
212
+ if (cluster.accessibilityMetadata) {
213
+ let meta = cluster.accessibilityMetadata;
214
+ console.log(`RGB diffs: R=${meta.avgRDiff}, G=${meta.avgGDiff}, B=${meta.avgBDiff}`);
215
+ console.log(`Dominant channel: ${meta.dominantChannel}`);
216
+ console.log(`WCAG contrast: ${meta.contrastRatio.toFixed(2)}:1`);
217
+
218
+ if (meta.contrastRatio < 4.5) {
219
+ console.log('⚠️ Below WCAG AA threshold');
220
+ }
221
+ }
222
+ });
223
+ }
224
+
225
+ // Color blindness simulation
226
+ let result = await compare('baseline.png', 'current.png', {
227
+ includeClusters: true,
228
+ checkColorBlindness: true
229
+ });
230
+
231
+ if (result.diffClusters) {
232
+ result.diffClusters.forEach(cluster => {
233
+ if (cluster.accessibilityMetadata?.colorBlindnessImpact) {
234
+ let impact = cluster.accessibilityMetadata.colorBlindnessImpact;
235
+ console.log(`Visibility score: ${(impact.visibilityScore * 100).toFixed(0)}%`);
236
+ console.log(`Deuteranopia (red-green): ${impact.deuteranopiaVisible ? '✓' : '✗'}`);
237
+ console.log(`Protanopia (red-weak): ${impact.protanopiaVisible ? '✓' : '✗'}`);
238
+ console.log(`Tritanopia (blue-yellow): ${impact.tritanopiaVisible ? '✓' : '✗'}`);
239
+
240
+ if (impact.visibilityScore < 0.5) {
241
+ console.log('⚠️ May not be visible to colorblind users');
242
+ }
243
+ }
244
+ });
245
+ }
246
+ ```
247
+
177
248
  **Complete Result Example**
178
249
  ```javascript
179
250
  let result = await compare('baseline.png', 'current.png', {
@@ -234,6 +305,7 @@ console.log(JSON.stringify(result, null, 2));
234
305
 
235
306
  - **Visual Regression Testing:** Detect unintended UI changes in your test suite
236
307
  - **Screenshot Validation:** Compare generated screenshots against baselines
308
+ - **Accessibility Testing:** Validate WCAG contrast ratios and color blindness visibility
237
309
  - **Design QA:** Validate UI implementations match designs
238
310
  - **Cross-browser Testing:** Detect rendering differences across browsers
239
311
  - **Scrollable Content:** Compare full-page screenshots with variable heights
@@ -253,6 +325,13 @@ Tested on real-world tall screenshots (750 x 24,162 pixels = 18.3M pixels):
253
325
 
254
326
  ## Advanced Features
255
327
 
328
+ **Accessibility Testing**
329
+ - **RGB Channel Analysis:** Track which color channels changed (R/G/B)
330
+ - **Luminance Tracking:** Measure perceived brightness using sRGB relative luminance
331
+ - **WCAG Contrast Ratios:** Validate contrast meets AA (4.5:1) and AAA (7:1) standards
332
+ - **Color Blindness Simulation:** Test visibility for deuteranopia, protanopia, tritanopia, and achromatopsia
333
+ - **Visibility Scoring:** 0-100% score indicating how visible changes are to colorblind users
334
+
256
335
  **Variable Height Screenshots**
257
336
  Compare scrollable content with different heights. Overlapping regions are compared pixel-by-pixel,
258
337
  extra rows counted as differences.
package/index.d.ts CHANGED
@@ -49,6 +49,42 @@ 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
+
52
88
  export interface DiffCluster {
53
89
  /** Number of pixels in this cluster */
54
90
  pixelCount: number;
@@ -58,6 +94,8 @@ export interface DiffCluster {
58
94
  avgIntensity: number;
59
95
  /** Bounding box containing this cluster */
60
96
  boundingBox: BoundingBox;
97
+ /** Accessibility metadata (null unless includeAccessibilityData is enabled) */
98
+ accessibilityMetadata: AccessibilityMetadata | null;
61
99
  }
62
100
 
63
101
  export interface DiffResult {
@@ -170,6 +208,28 @@ export interface CompareOptions {
170
208
  * @default false
171
209
  */
172
210
  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;
173
233
  }
174
234
 
175
235
  // ============================================================================
@@ -274,3 +334,50 @@ export function quickCompareSync(
274
334
  img1: ImageInput,
275
335
  img2: ImageInput
276
336
  ): boolean;
337
+
338
+ // ============================================================================
339
+ // Dimensions API
340
+ // ============================================================================
341
+
342
+ export interface ImageDimensions {
343
+ /** Image width in pixels */
344
+ width: number;
345
+ /** Image height in pixels */
346
+ height: number;
347
+ }
348
+
349
+ /**
350
+ * Get image dimensions asynchronously (recommended)
351
+ *
352
+ * Fast utility to retrieve image width and height without loading the full image data.
353
+ * Useful for pre-flight checks before comparison.
354
+ *
355
+ * @param img - Image (file path or Buffer)
356
+ * @returns Promise resolving to { width, height } in pixels
357
+ *
358
+ * @example
359
+ * ```typescript
360
+ * const dims = await getDimensions('screenshot.png');
361
+ * console.log(`Image is ${dims.width}x${dims.height} pixels`);
362
+ * // Output: Image is 1920x1080 pixels
363
+ * ```
364
+ */
365
+ export function getDimensions(img: ImageInput): Promise<ImageDimensions>;
366
+
367
+ /**
368
+ * Get image dimensions synchronously (blocks event loop)
369
+ *
370
+ * Fast utility to retrieve image width and height without loading the full image data.
371
+ * Use this only when you need blocking behavior.
372
+ *
373
+ * @param img - Image (file path or Buffer)
374
+ * @returns { width, height } in pixels
375
+ *
376
+ * @example
377
+ * ```typescript
378
+ * const dims = getDimensionsSync('screenshot.png');
379
+ * console.log(`Image is ${dims.width}x${dims.height} pixels`);
380
+ * // Output: Image is 1920x1080 pixels
381
+ * ```
382
+ */
383
+ export function getDimensionsSync(img: ImageInput): ImageDimensions;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vizzly-testing/honeydiff",
3
- "version": "0.1.0",
3
+ "version": "0.2.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",
@@ -55,6 +55,16 @@
55
55
  "index.d.ts",
56
56
  "load-platform.js",
57
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",
58
68
  "README.md"
59
69
  ],
60
70
  "engines": {