@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 +79 -0
- package/index.d.ts +107 -0
- package/package.json +11 -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
|
@@ -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.
|
|
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": {
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|