@vizzly-testing/honeydiff 0.15.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.js CHANGED
@@ -37,12 +37,13 @@ if (existsSync(platformLoaderPath)) {
37
37
 
38
38
  // Core comparison API
39
39
  export const compare = addon.compare;
40
- export const quickCompare = addon.quickCompare;
41
40
  export const compareSync = addon.compareSync;
42
- export const quickCompareSync = addon.quickCompareSync;
41
+ export const imagesDiffer = addon.imagesDiffer;
42
+ export const imagesDifferSync = addon.imagesDifferSync;
43
+ export const analyze = addon.analyze;
44
+ export const analyzeSync = addon.analyzeSync;
43
45
  export const getDimensions = addon.getDimensions;
44
46
  export const getDimensionsSync = addon.getDimensionsSync;
45
- export const maskSemanticsVersion = addon.maskSemanticsVersion;
46
47
  export const version = packageMetadata.version;
47
48
 
48
49
  // Image Metadata API
@@ -70,7 +71,89 @@ export const analyzeWcagAllCvd = addon.analyzeWcagAllCvd;
70
71
  export const analyzeWcagAllCvdSync = addon.analyzeWcagAllCvdSync;
71
72
  export const getColorBlindnessTypes = addon.getColorBlindnessTypes;
72
73
 
73
- // Diff Fingerprint API
74
- export const computeFingerprintSync = addon.computeFingerprintSync;
75
- export const fingerprintSimilaritySync = addon.fingerprintSimilaritySync;
76
- export const fingerprintHashSync = addon.fingerprintHashSync;
74
+ // Diff fingerprints are pure derivatives of the returned analysis.
75
+ export function computeFingerprintSync(analysis) {
76
+ let regions = analysis.difference.regions;
77
+ if (regions.length === 0) return null;
78
+
79
+ let width = analysis.images.canvas.width;
80
+ let height = analysis.images.canvas.height;
81
+ let canvasArea = width * height;
82
+ let regionPositions = regions.map(({ spatial }) => [
83
+ spatial.centroid.x / width,
84
+ spatial.centroid.y / height,
85
+ ]);
86
+ let regionSizes = regions.map(
87
+ ({ spatial }) => (spatial.bounds.width * spatial.bounds.height) / canvasArea
88
+ );
89
+ let averageDensity =
90
+ regions.reduce((total, region) => total + region.spatial.density, 0) / regions.length;
91
+ let zoneMask = regions.reduce((mask, region) => {
92
+ let zoneX = Math.min(3, Math.floor((region.spatial.centroid.x / width) * 4));
93
+ let zoneY = Math.min(3, Math.floor((region.spatial.centroid.y / height) * 4));
94
+ return mask | (1 << (zoneY * 4 + zoneX));
95
+ }, 0);
96
+ let fraction = analysis.difference.fraction;
97
+ let diffMagnitude =
98
+ fraction < 0.001
99
+ ? 'tiny'
100
+ : fraction < 0.01
101
+ ? 'small'
102
+ : fraction < 0.05
103
+ ? 'medium'
104
+ : fraction < 0.2
105
+ ? 'large'
106
+ : 'massive';
107
+ let fingerprint = {
108
+ regionCount: regions.length,
109
+ regionPositions,
110
+ regionSizes,
111
+ meanDeltaE: analysis.difference.appearance?.mean ?? null,
112
+ averageDensity,
113
+ zoneMask,
114
+ diffMagnitude,
115
+ hash: '',
116
+ };
117
+ fingerprint.hash = fingerprintHashSync(fingerprint);
118
+ return fingerprint;
119
+ }
120
+
121
+ export function fingerprintSimilaritySync(left, right) {
122
+ let intersection = left.zoneMask & right.zoneMask;
123
+ let union = left.zoneMask | right.zoneMask;
124
+ let bitCount = (value) => {
125
+ let count = 0;
126
+ for (let remaining = value; remaining !== 0; remaining >>>= 1) count += remaining & 1;
127
+ return count;
128
+ };
129
+ let weighted = (bitCount(intersection) / Math.max(1, bitCount(union))) * 40;
130
+ let weights = 40;
131
+ weighted += (1 / (1 + Math.abs(left.regionCount - right.regionCount) * 0.5)) * 20;
132
+ weights += 20;
133
+ weighted += (left.diffMagnitude === right.diffMagnitude ? 1 : 0) * 15;
134
+ weights += 15;
135
+ weighted += (1 / (1 + Math.abs(left.averageDensity - right.averageDensity) * 5)) * 15;
136
+ weights += 15;
137
+ if (left.meanDeltaE !== null && right.meanDeltaE !== null) {
138
+ weighted += (1 / (1 + Math.abs(left.meanDeltaE - right.meanDeltaE) / 10)) * 10;
139
+ weights += 10;
140
+ }
141
+ return weighted / weights;
142
+ }
143
+
144
+ export function fingerprintHashSync(fingerprint) {
145
+ let magnitude = ['tiny', 'small', 'medium', 'large', 'massive'].indexOf(
146
+ fingerprint.diffMagnitude
147
+ );
148
+ if (magnitude === -1) throw new TypeError('Invalid diffMagnitude');
149
+ let countBucket =
150
+ fingerprint.regionCount === 1
151
+ ? 0
152
+ : fingerprint.regionCount <= 3
153
+ ? 1
154
+ : fingerprint.regionCount <= 10
155
+ ? 2
156
+ : 3;
157
+ let value = fingerprint.zoneMask * 32 + magnitude * 4 + countBucket;
158
+ return value.toString(16).padStart(16, '0');
159
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vizzly-testing/honeydiff",
3
- "version": "0.15.0",
3
+ "version": "0.16.0",
4
4
  "description": "High-performance image diffing for Node.js - Native bindings to Honeydiff Rust library",
5
5
  "type": "module",
6
6
  "main": "index.js",
@@ -18,7 +18,7 @@
18
18
  "build-debug": "cargo build",
19
19
  "test": "node --test --experimental-strip-types tests/*.test.ts",
20
20
  "test:types": "tsc --noEmit",
21
- "test:examples": "node examples/basic.js",
21
+ "test:examples": "node examples/basic.js ../../tests/fixtures/screenshots/vizzly-baseline.png ../../tests/fixtures/screenshots/vizzly-with-diff.png",
22
22
  "check": "biome check .",
23
23
  "check:fix": "biome check --write .",
24
24
  "lint": "biome lint .",
@@ -51,7 +51,8 @@
51
51
  "assets/",
52
52
  "examples/",
53
53
  "README.md",
54
- "CHANGELOG.md"
54
+ "CHANGELOG.md",
55
+ "LICENSE"
55
56
  ],
56
57
  "engines": {
57
58
  "node": ">=22.0.0"