@vizzly-testing/honeydiff 0.9.0 → 0.10.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/CHANGELOG.md CHANGED
@@ -5,6 +5,37 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [0.10.0] - 2026-02-03
9
+
10
+ ## What's Changed
11
+
12
+ ### Added
13
+ - **GMSD (Gradient Magnitude Similarity Deviation)** - New `includeGMSD` option for fast, edge-sensitive perceptual comparison
14
+ - Returns `gmsdScore` in results (0.0 = identical, higher = more different)
15
+ - Very fast compared to SSIM, ideal for detecting border/outline changes
16
+ - Based on Xue et al. 2014 research paper
17
+ - **Note:** GMSD requires images with identical dimensions. For variable-height comparisons, `gmsdScore` will be `null`
18
+ - **Cluster Merging** - New `clusterMerge` option to consolidate fragmented text regions
19
+ - Simple API: `clusterMerge: true` enables with sensible defaults
20
+ - Advanced API: Pass an object with `horizontalDistance`, `yBandTolerance`, `maxHeightRatio`, `maxWidthRatio`
21
+ - Solves the "59 clusters for one date string" problem by intelligently merging nearby character-level changes into logical text regions
22
+ - Uses SWT-inspired heuristics (Epshtein et al. 2010) for horizontal-biased text detection
23
+
24
+ **Full Changelog**: https://github.com/vizzly-testing/honeydiff/compare/v0.9.0...v0.10.0
25
+
26
+ ## [Unreleased]
27
+
28
+ ### Added
29
+ - **GMSD (Gradient Magnitude Similarity Deviation)** - New `includeGMSD` option for fast, edge-sensitive perceptual comparison
30
+ - Returns `gmsdScore` in results (0.0 = identical, higher = more different)
31
+ - Very fast compared to SSIM, ideal for detecting border/outline changes
32
+ - Based on Xue et al. 2014 research paper
33
+ - **Cluster Merging** - New `clusterMerge` option to consolidate fragmented text regions
34
+ - Simple API: `clusterMerge: true` enables with sensible defaults
35
+ - Advanced API: Pass an object with `horizontalDistance`, `yBandTolerance`, `maxHeightRatio`, `maxWidthRatio`
36
+ - Solves the "59 clusters for one date string" problem
37
+ - Uses SWT-inspired heuristics (Epshtein et al. 2010)
38
+
8
39
  ## [0.9.0] - 2026-01-26
9
40
 
10
41
  ## What's Changed
package/README.md CHANGED
@@ -188,10 +188,39 @@ if (result.diffClusters) {
188
188
  | 2 | Default - filters single isolated pixels as rendering noise |
189
189
  | 3+ | More permissive - only larger clusters detected |
190
190
 
191
- ### 6. Perceptual Similarity (SSIM)
191
+ **Cluster Merging for Text Regions:**
192
+
193
+ Text changes often fragment into many small clusters (one per character). Enable cluster merging to consolidate them into logical regions:
194
+
195
+ ```javascript
196
+ // Simple: enable with sensible defaults
197
+ const result = await compare('img1.png', 'img2.png', {
198
+ includeClusters: true,
199
+ clusterMerge: true // Merge nearby clusters (great for text)
200
+ });
201
+
202
+ // Advanced: tune the merging behavior
203
+ const result = await compare('img1.png', 'img2.png', {
204
+ includeClusters: true,
205
+ clusterMerge: {
206
+ horizontalDistance: 15, // Max gap between clusters to merge (pixels)
207
+ yBandTolerance: 5, // Vertical tolerance for "same line"
208
+ maxHeightRatio: 2.0, // Prevent merging very different sized clusters
209
+ maxWidthRatio: 3.0
210
+ }
211
+ });
212
+
213
+ // Before: "2024-01-01" detected as 59 clusters (one per character/gap)
214
+ // After: "2024-01-01" detected as 1-2 logical regions
215
+ ```
216
+
217
+ Uses SWT-inspired heuristics from text detection research (Epshtein et al. 2010).
218
+
219
+ ### 6. Perceptual Similarity (SSIM & GMSD)
192
220
 
193
221
  Beyond pixel counting - measure structural similarity from a human perception perspective.
194
222
 
223
+ **SSIM (Structural Similarity Index)** - Overall perceptual similarity:
195
224
  ```javascript
196
225
  const result = await compare('img1.png', 'img2.png', {
197
226
  includeSSIM: true // Note: Can be slow on large images
@@ -200,13 +229,33 @@ const result = await compare('img1.png', 'img2.png', {
200
229
  if (result.perceptualScore !== null) {
201
230
  console.log(`SSIM: ${result.perceptualScore.toFixed(3)}`);
202
231
  // Output: SSIM: 0.923 (0.0 = different, 1.0 = identical)
232
+ }
233
+ ```
234
+
235
+ **GMSD (Gradient Magnitude Similarity Deviation)** - Fast edge-sensitive metric:
236
+ ```javascript
237
+ const result = await compare('img1.png', 'img2.png', {
238
+ includeGMSD: true // Very fast, great for detecting structural changes
239
+ });
240
+
241
+ if (result.gmsdScore !== null) {
242
+ console.log(`GMSD: ${result.gmsdScore.toFixed(4)}`);
243
+ // Output: GMSD: 0.0234 (0.0 = identical, higher = more different)
203
244
 
204
- if (result.perceptualScore > 0.95) {
205
- console.log('Images are perceptually very similar');
245
+ if (result.gmsdScore < 0.05) {
246
+ console.log('Edges are very similar');
206
247
  }
207
248
  }
208
249
  ```
209
250
 
251
+ GMSD is ideal for catching:
252
+ - Border thickness changes
253
+ - Font weight shifts
254
+ - Icon updates
255
+ - Any edge/outline regressions
256
+
257
+ **Reference:** Xue et al. 2014 - "Gradient Magnitude Similarity Deviation: A Highly Efficient Perceptual Image Quality Index"
258
+
210
259
  ### 7. Tolerance & Color Spaces
211
260
 
212
261
  **RGB Mode (default)** - Exact matching with pixel tolerance:
@@ -558,22 +607,22 @@ All functions accept:
558
607
  ```typescript
559
608
  interface CompareOptions {
560
609
  // Basic options
561
- pixelTolerance?: number; // 0-255, ignore diffs below threshold (default: 0)
562
- colorThreshold?: number; // 0.0-1.0, YIQ mode threshold (default: 0.0 = RGB)
610
+ threshold?: number; // CIEDE2000 Delta E threshold (default: 2.0)
563
611
  antialiasing?: boolean; // Ignore AA artifacts (default: true)
564
- ignoreColors?: boolean; // Brightness only (default: false)
565
612
  maxDiffs?: number; // Stop after N diffs (default: unlimited)
566
613
 
567
614
  // Analysis options
568
615
  includeDiffPixels?: boolean; // List all diff pixels (memory intensive, default: false)
569
616
  includeClusters?: boolean; // Spatial clustering (default: false)
570
617
  includeSSIM?: boolean; // SSIM perceptual score (slow, default: false)
618
+ includeGMSD?: boolean; // GMSD edge similarity (fast, default: false)
571
619
  minClusterSize?: number; // Filter clusters smaller than this (default: 2)
572
-
573
- // Accessibility options
574
- includeAccessibilityData?: boolean; // RGB, luminance, WCAG (default: false)
575
- checkColorBlindness?: boolean; // Color blindness simulation (default: false)
576
- colorBlindnessThreshold?: number; // Visibility threshold 0-255 (default: 30.0)
620
+ clusterMerge?: boolean | { // Merge nearby clusters (default: false)
621
+ horizontalDistance?: number; // Max horizontal gap to merge (default: 15)
622
+ yBandTolerance?: number; // Vertical "same line" tolerance (default: 5)
623
+ maxHeightRatio?: number; // Max height ratio to merge (default: 2.0)
624
+ maxWidthRatio?: number; // Max width ratio to merge (default: 3.0)
625
+ };
577
626
 
578
627
  // Output options
579
628
  diffPath?: string; // Save diff image path
@@ -606,7 +655,8 @@ interface DiffResult {
606
655
  diffPixelsList: DiffPixel[] | null; // Null unless includeDiffPixels enabled
607
656
  diffClusters: DiffCluster[] | null; // Null unless includeClusters enabled
608
657
  intensityStats: IntensityStats | null; // Null unless includeDiffPixels enabled
609
- perceptualScore: number | null; // 0.0-1.0, null unless includeSSIM enabled
658
+ perceptualScore: number | null; // SSIM 0.0-1.0, null unless includeSSIM enabled
659
+ gmsdScore: number | null; // GMSD 0.0+, null unless includeGMSD enabled
610
660
  }
611
661
  ```
612
662
 
package/index.d.ts CHANGED
@@ -91,12 +91,67 @@ export interface DiffResult {
91
91
  intensityStats: IntensityStats | null;
92
92
  /** SSIM (Structural Similarity Index) perceptual score 0.0-1.0 (null unless includeSSIM is enabled) */
93
93
  perceptualScore: number | null;
94
+ /**
95
+ * GMSD (Gradient Magnitude Similarity Deviation) score (null unless includeGMSD is enabled)
96
+ * Lower values (closer to 0.0) indicate more similar images
97
+ * Typical range: 0.0 to ~0.3 for natural images
98
+ */
99
+ gmsdScore: number | null;
94
100
  }
95
101
 
96
102
  // ============================================================================
97
103
  // Options
98
104
  // ============================================================================
99
105
 
106
+ /**
107
+ * Options for merging nearby clusters
108
+ *
109
+ * These heuristics are designed to merge fragmented text regions (like individual
110
+ * characters in a date string) into logical regions while avoiding merging
111
+ * unrelated visual changes.
112
+ *
113
+ * Inspired by the Stroke Width Transform (SWT) text detection algorithm:
114
+ * Epshtein, B., Ofek, E., & Wexler, Y. (2010). "Detecting Text in Natural Scenes
115
+ * with Stroke Width Transform." CVPR 2010.
116
+ */
117
+ export interface ClusterMergeOptions {
118
+ /**
119
+ * Maximum horizontal distance to merge clusters in same Y-band (pixels)
120
+ *
121
+ * Clusters within this horizontal distance and overlapping Y-ranges
122
+ * will be merged. Suitable for merging characters in text.
123
+ * @default 15
124
+ */
125
+ horizontalDistance?: number;
126
+
127
+ /**
128
+ * Maximum vertical tolerance for "same Y-band" (pixels)
129
+ *
130
+ * Clusters with Y-ranges within this tolerance of each other are
131
+ * considered to be on the same line.
132
+ * @default 5
133
+ */
134
+ yBandTolerance?: number;
135
+
136
+ /**
137
+ * Maximum height ratio between clusters to allow merging
138
+ *
139
+ * Prevents merging clusters of very different sizes (e.g., a word
140
+ * with a large image). Based on SWT heuristic.
141
+ * @default 2.0
142
+ */
143
+ maxHeightRatio?: number;
144
+
145
+ /**
146
+ * Maximum width ratio between clusters to allow merging
147
+ *
148
+ * Additional SWT-inspired heuristic to prevent merging dissimilar
149
+ * regions.
150
+ * @default 3.0
151
+ */
152
+ maxWidthRatio?: number;
153
+ }
154
+
100
155
  export interface CompareOptions {
101
156
  /**
102
157
  * Perceptual color difference threshold using CIEDE2000 (Delta E units)
@@ -145,6 +200,23 @@ export interface CompareOptions {
145
200
  */
146
201
  includeSSIM?: boolean;
147
202
 
203
+ /**
204
+ * Calculate GMSD (Gradient Magnitude Similarity Deviation) score
205
+ *
206
+ * GMSD is very fast and highly sensitive to edge/structural changes.
207
+ * Useful for detecting border thickness changes, font weight shifts,
208
+ * and icon updates.
209
+ *
210
+ * **Note:** GMSD requires images with identical dimensions. For variable-height
211
+ * comparisons, `gmsdScore` will be `null`. Use SSIM for variable-height images.
212
+ *
213
+ * Reference: Xue et al. 2014 - "Gradient Magnitude Similarity Deviation:
214
+ * A Highly Efficient Perceptual Image Quality Index"
215
+ *
216
+ * @default false
217
+ */
218
+ includeGMSD?: boolean;
219
+
148
220
  /**
149
221
  * Minimum cluster size to count as a real difference
150
222
  *
@@ -161,6 +233,28 @@ export interface CompareOptions {
161
233
  */
162
234
  minClusterSize?: number;
163
235
 
236
+ /**
237
+ * Merge nearby clusters into logical regions
238
+ *
239
+ * When enabled, nearby clusters are merged using horizontal-biased heuristics
240
+ * that work well for text regions. This helps consolidate fragmented text
241
+ * changes (e.g., "2024-01-01" showing as 59 clusters) into logical regions.
242
+ *
243
+ * Automatically enables clustering when set.
244
+ *
245
+ * @default undefined (no merging)
246
+ *
247
+ * @example
248
+ * ```typescript
249
+ * // Simple: enable merging with sensible defaults
250
+ * { clusterMerge: true }
251
+ *
252
+ * // Advanced: tune the merging behavior
253
+ * { clusterMerge: { horizontalDistance: 20, yBandTolerance: 10 } }
254
+ * ```
255
+ */
256
+ clusterMerge?: boolean | ClusterMergeOptions;
257
+
164
258
  /**
165
259
  * Path to save the diff image (highlighted differences)
166
260
  * @default undefined
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vizzly-testing/honeydiff",
3
- "version": "0.9.0",
3
+ "version": "0.10.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",