
image-comparison
Contributed an internal engine refactor to romankh3/image-comparison (PR #255), an open-source visual QA testing library on Maven Central. Decoupled comparison state into a local DifferenceMatrix, replaced recursive region traversal with an iterative ArrayDeque to avoid stack overflow risks, and added primitive coordinate checks to avoid per-pixel Point allocations.
The Problem
The comparison engine had maintainability and stack safety concerns: temporary difference matrix and counter state were stored directly on the ImageComparison instance, region grouping used deep recursion that risked StackOverflowError on large connected diffs, and excluded-area checks instantiated a new java.awt.Point object for every pixel checked.
Technical Approach
Encapsulated comparison-specific state into a private DifferenceMatrix class. Refactored recursive flood-fill into an iterative ArrayDeque traversal using index arithmetic (y * width + x), added primitive int coordinate overloads to ExcludedAreas and Rectangle, replaced magic numbers with named constants, and introduced regression unit tests.
PR #255: Recursive Flood-Fill vs Iterative ArrayDeque
// ITERATIVE ARRAYDEQUE (PR #255)
ArrayDeque<Integer> positions = new ArrayDeque<>();
addToRegion(matrix, regionMarker, positions, x, y);
while (!positions.isEmpty()) {
int pos = positions.removeLast();
int curX = pos % matrix[0].length;
int curY = pos / matrix[0].length;
// Flat stack; index arithmetic y * w + x
}Key Highlights & Architecture
- Decoupled comparison state from ImageComparison instance fields into an isolated DifferenceMatrix record
- Replaced recursive region grouping with an iterative ArrayDeque traversal storing flattened pixel indices (y * width + x) to prevent stack overflow on deep difference regions
- Added primitive containsPoint(int x, int y) overloads on Rectangle and ExcludedAreas, eliminating per-pixel Point allocations
- Added regression unit tests for large 20,000-pixel difference regions and primitive coordinate checks, passing all 66 unit tests
Engineering Challenges
- Ensuring the iterative traversal produced pixel-for-pixel identical difference bounding boxes as the original recursive approach without regressions.
- Adding regression tests for large diff regions without introducing long test execution times in CI.
Key Engineering Decisions
- Iterative ArrayDeque Traversal: Replaced recursive calls with an in-memory deque processing neighbor offsets, eliminating recursion depth limits.
- Primitive Coordinate Overloads: Added containsPoint(int x, int y) to evaluate coordinates directly, avoiding new Point(x, y) allocations during pixel loops.
- Encapsulated DifferenceMatrix: Moved matrix and counters out of instance fields, making the comparison flow easier to maintain and reason about.
Trade-Off Analysis & Architectural Rationale
Iterative ArrayDeque Region Traversal
Recursive joinToRegion Method
Prevents JVM StackOverflowError when difference clusters span tens of thousands of connected pixels.
Requires managing an in-memory deque and index math (y * width + x) instead of implicit call-stack frames.
Primitive Coordinate Method Overloads
Allocating java.awt.Point per pixel check
Checks coordinates directly with primitive ints in ExcludedAreas, avoiding heap allocations inside the hot pixel loop.
Adds overloaded methods to Rectangle and ExcludedAreas.
Result & Outcome
Submitted upstream as Pull Request #255, passing all 66 unit tests and PMD check, resolving maintainability and recursion limits as part of a university Software Engineering assignment.