Skip to content

Commit 973b255

Browse files
committed
🔖 v0.10.1
1 parent d9bbf8d commit 973b255

4 files changed

Lines changed: 197 additions & 13 deletions

File tree

CHANGELOG.md

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,46 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## [0.10.1] - 2026-03-11
9+
10+
## What's Changed
11+
12+
### Fixed
13+
- Variable-height image comparisons now correctly return diff clusters for the height-difference region. Previously, when a baseline and current screenshot had different heights, `diffClusters` was empty even though `isDifferent` was `true` and `diffPixels` was non-zero — meaning callers had no bounding box to highlight. A synthetic cluster covering the full-width region at the height boundary is now included in `diffClusters`.
14+
15+
**Full Changelog**: https://github.com/vizzly-testing/honeydiff/compare/v0.10.0...v0.10.1
16+
17+
## [0.10.0] - 2026-02-03
18+
19+
## What's Changed
20+
21+
### Added
22+
- **GMSD (Gradient Magnitude Similarity Deviation)** - New `includeGMSD` option for fast, edge-sensitive perceptual comparison
23+
- Returns `gmsdScore` in results (0.0 = identical, higher = more different)
24+
- Very fast compared to SSIM, ideal for detecting border/outline changes
25+
- Based on Xue et al. 2014 research paper
26+
- **Note:** GMSD requires images with identical dimensions. For variable-height comparisons, `gmsdScore` will be `null`
27+
- **Cluster Merging** - New `clusterMerge` option to consolidate fragmented text regions
28+
- Simple API: `clusterMerge: true` enables with sensible defaults
29+
- Advanced API: Pass an object with `horizontalDistance`, `yBandTolerance`, `maxHeightRatio`, `maxWidthRatio`
30+
- Solves the "59 clusters for one date string" problem by intelligently merging nearby character-level changes into logical text regions
31+
- Uses SWT-inspired heuristics (Epshtein et al. 2010) for horizontal-biased text detection
32+
33+
**Full Changelog**: https://github.com/vizzly-testing/honeydiff/compare/v0.9.0...v0.10.0
34+
35+
## [Unreleased]
36+
37+
### Added
38+
- **GMSD (Gradient Magnitude Similarity Deviation)** - New `includeGMSD` option for fast, edge-sensitive perceptual comparison
39+
- Returns `gmsdScore` in results (0.0 = identical, higher = more different)
40+
- Very fast compared to SSIM, ideal for detecting border/outline changes
41+
- Based on Xue et al. 2014 research paper
42+
- **Cluster Merging** - New `clusterMerge` option to consolidate fragmented text regions
43+
- Simple API: `clusterMerge: true` enables with sensible defaults
44+
- Advanced API: Pass an object with `horizontalDistance`, `yBandTolerance`, `maxHeightRatio`, `maxWidthRatio`
45+
- Solves the "59 clusters for one date string" problem
46+
- Uses SWT-inspired heuristics (Epshtein et al. 2010)
47+
848
## [0.9.0] - 2026-01-26
949

1050
## What's Changed

README.md

Lines changed: 62 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -188,10 +188,39 @@ if (result.diffClusters) {
188188
| 2 | Default - filters single isolated pixels as rendering noise |
189189
| 3+ | More permissive - only larger clusters detected |
190190

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)
192220

193221
Beyond pixel counting - measure structural similarity from a human perception perspective.
194222

223+
**SSIM (Structural Similarity Index)** - Overall perceptual similarity:
195224
```javascript
196225
const result = await compare('img1.png', 'img2.png', {
197226
includeSSIM: true // Note: Can be slow on large images
@@ -200,13 +229,33 @@ const result = await compare('img1.png', 'img2.png', {
200229
if (result.perceptualScore !== null) {
201230
console.log(`SSIM: ${result.perceptualScore.toFixed(3)}`);
202231
// 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)
203244

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');
206247
}
207248
}
208249
```
209250

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+
210259
### 7. Tolerance & Color Spaces
211260

212261
**RGB Mode (default)** - Exact matching with pixel tolerance:
@@ -558,22 +607,22 @@ All functions accept:
558607
```typescript
559608
interface CompareOptions {
560609
// 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)
563611
antialiasing?: boolean; // Ignore AA artifacts (default: true)
564-
ignoreColors?: boolean; // Brightness only (default: false)
565612
maxDiffs?: number; // Stop after N diffs (default: unlimited)
566613

567614
// Analysis options
568615
includeDiffPixels?: boolean; // List all diff pixels (memory intensive, default: false)
569616
includeClusters?: boolean; // Spatial clustering (default: false)
570617
includeSSIM?: boolean; // SSIM perceptual score (slow, default: false)
618+
includeGMSD?: boolean; // GMSD edge similarity (fast, default: false)
571619
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+
};
577626

578627
// Output options
579628
diffPath?: string; // Save diff image path
@@ -606,7 +655,8 @@ interface DiffResult {
606655
diffPixelsList: DiffPixel[] | null; // Null unless includeDiffPixels enabled
607656
diffClusters: DiffCluster[] | null; // Null unless includeClusters enabled
608657
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
610660
}
611661
```
612662

index.d.ts

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,12 +91,67 @@ export interface DiffResult {
9191
intensityStats: IntensityStats | null;
9292
/** SSIM (Structural Similarity Index) perceptual score 0.0-1.0 (null unless includeSSIM is enabled) */
9393
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;
94100
}
95101

96102
// ============================================================================
97103
// Options
98104
// ============================================================================
99105

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+
100155
export interface CompareOptions {
101156
/**
102157
* Perceptual color difference threshold using CIEDE2000 (Delta E units)
@@ -145,6 +200,23 @@ export interface CompareOptions {
145200
*/
146201
includeSSIM?: boolean;
147202

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+
148220
/**
149221
* Minimum cluster size to count as a real difference
150222
*
@@ -161,6 +233,28 @@ export interface CompareOptions {
161233
*/
162234
minClusterSize?: number;
163235

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+
164258
/**
165259
* Path to save the diff image (highlighted differences)
166260
* @default undefined

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@vizzly-testing/honeydiff",
3-
"version": "0.9.0",
3+
"version": "0.10.1",
44
"description": "High-performance image diffing for Node.js - Native bindings to Honeydiff Rust library",
55
"type": "module",
66
"main": "index.js",

0 commit comments

Comments
 (0)