Class: SnapDiff::Comparison

Inherits:
Object
  • Object
show all
Defined in:
lib/snap_diff/comparison.rb

Overview

Handles comparison of two images with a focus on performance and accuracy.

This class implements a multi-layered optimization strategy for image comparison:

  1. Early File-based Checks (Fastest):

    • Verifies both images exist (raises ArgumentError if not)
    • Compares file sizes (different sizes → different images)
    • Performs byte-by-byte comparison for identical files (exact match)
  2. Quick Comparison (Fast):

    • Compares image dimensions (different dimensions → different images)
    • Performs pixel-by-pixel comparison if dimensions match
  3. Detailed Analysis (Slower):

    • Only performed if quick comparison finds differences
    • Handles anti-aliasing, color tolerance, and shift detection
    • Respects skip_area and other comparison parameters

This layered approach ensures optimal performance by:

  • Using the fastest possible method for early rejection
  • Only performing expensive operations when absolutely necessary
  • Maintaining high accuracy for complex comparisons

Defined Under Namespace

Classes: Images

Constant Summary collapse

TOLERABLE_OPTIONS =
[:tolerance, :color_distance_limit, :shift_distance_limit, :area_size_limit].freeze
KNOWN_OPTIONS =

Every key anything downstream of here actually reads -- capture options included, because ScreenshotMatcher hands the same hash to the screenshoter and only carves :crop / :stability_time_limit / :wait out of the copy it passes on.

Written out rather than derived from Config#default_options: that hash is what the gem merges in, so deriving from it would make the check agree with itself and validate nothing about the keys a USER adds.

%i[
  area_size_limit
  capybara_screenshot_options
  color_distance_limit
  crop
  delayed
  driver
  median_filter_window_size
  perceptual_threshold
  screenshot_format
  shift_distance_limit
  skip_area
  stability_time_limit
  tolerance
  wait
].freeze

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(image_path, base_image_path, options = {}) ⇒ Comparison

Returns a new instance of Comparison.



75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
# File 'lib/snap_diff/comparison.rb', line 75

def initialize(image_path, base_image_path, options = {})
  @image_path = Pathname.new(image_path)
  @base_image_path = Pathname.new(base_image_path)

  ensure_files_exist!

  @driver_options = options.freeze
  # THE no-silent-no-op check (ADR-010). Every option hash in the gem
  # reaches this constructor, so a key nothing reads is caught here
  # whichever entry point produced it. Frozen-but-unvalidated is how a
  # misspelt `tolerence:` bought a green suite that compared nothing.
  (options.keys - KNOWN_OPTIONS).each do |key|
    Removal.warn_once(:"unknown_option_#{key}", Removal.unknown_option(key))
  end
  # The per-comparison half of the shift_distance_limit removal (the
  # global half is Config#shift_distance_limit=). Presence is not enough:
  # config.default_options carries the key on EVERY comparison, nil for
  # everyone who never set it.
  if options[:shift_distance_limit]
    Removal.warn_once(:shift_distance_limit, Removal::SHIFT_DISTANCE_LIMIT_REMOVED)
  end
  @driver = Drivers.for(@driver_options)
  @without_tolerable_options = (driver_options.keys & TOLERABLE_OPTIONS).empty?
end

Instance Attribute Details

#base_image_pathObject (readonly)

Returns the value of attribute base_image_path.



72
73
74
# File 'lib/snap_diff/comparison.rb', line 72

def base_image_path
  @base_image_path
end

#differenceObject

Returns the value of attribute difference.



73
74
75
# File 'lib/snap_diff/comparison.rb', line 73

def difference
  @difference
end

#driverObject (readonly)

Returns the value of attribute driver.



71
72
73
# File 'lib/snap_diff/comparison.rb', line 71

def driver
  @driver
end

#driver_optionsObject (readonly)

Returns the value of attribute driver_options.



71
72
73
# File 'lib/snap_diff/comparison.rb', line 71

def driver_options
  @driver_options
end

#error_messageObject (readonly)

Returns the value of attribute error_message.



73
74
75
# File 'lib/snap_diff/comparison.rb', line 73

def error_message
  @error_message
end

#image_pathObject (readonly)

Returns the value of attribute image_path.



72
73
74
# File 'lib/snap_diff/comparison.rb', line 72

def image_path
  @image_path
end

Instance Method Details

#different?Boolean

Determines if the images are different according to the comparison rules.

This method performs a full comparison if not already done, including any configured tolerances for color differences and shift distances.

Returns:

  • (Boolean)
    • true if the images are different beyond configured tolerances
    • false if the images are considered identical

See Also:



134
135
136
# File 'lib/snap_diff/comparison.rb', line 134

def different?
  processed.difference.different?
end

#dimensions_changed?Boolean

Returns:

  • (Boolean)


138
139
140
# File 'lib/snap_diff/comparison.rb', line 138

def dimensions_changed?
  difference.failed_by&.[](:different_dimensions)
end

#ensure_files_exist!Object

Raises:

  • (ArgumentError)


118
119
120
121
# File 'lib/snap_diff/comparison.rb', line 118

def ensure_files_exist!
  raise ArgumentError, "There is no original (base) screenshot located at #{@base_image_path}" unless @base_image_path.exist?
  raise ArgumentError, "There is no new screenshot located at #{@image_path}" unless @image_path.exist?
end

#processedObject



150
151
152
153
154
# File 'lib/snap_diff/comparison.rb', line 150

def processed
  self.difference = find_difference(quick_mode: false) unless processed?
  @error_message ||= reporter.generate
  self
end

#processed?Boolean

Returns:

  • (Boolean)


146
147
148
# File 'lib/snap_diff/comparison.rb', line 146

def processed?
  !!difference
end

#quick_equal?Boolean

Note:

This method will raise ArgumentError if either image file is missing.

Performs a quick comparison of two image files.

This method is optimized for speed and will return as soon as a difference is found. It's used for fast rejection before performing more expensive comparisons.

Returns:

  • (Boolean)
    • true if images are exactly identical (byte-for-byte match)
    • false if images are different or if a quick difference is detected


110
111
112
113
114
115
116
# File 'lib/snap_diff/comparison.rb', line 110

def quick_equal?
  return true if identical_files?

  result, difference = find_difference(quick_mode: true)
  self.difference = difference
  result
end

#reporterObject



142
143
144
# File 'lib/snap_diff/comparison.rb', line 142

def reporter
  @reporter ||= build_reporter
end