lib/capybara/screenshot/diff/image_compare.rb in capybara-screenshot-diff-1.8.3 vs lib/capybara/screenshot/diff/image_compare.rb in capybara-screenshot-diff-1.9.0
- old
+ new
@@ -1,7 +1,9 @@
# frozen_string_literal: true
+require "capybara/screenshot/diff/comparison"
+
module Capybara
module Screenshot
module Diff
LOADED_DRIVERS = {}
@@ -9,220 +11,184 @@
# range considering color values and difference area size.
class ImageCompare
TOLERABLE_OPTIONS = [:tolerance, :color_distance_limit, :shift_distance_limit, :area_size_limit].freeze
attr_reader :driver, :driver_options
+ attr_reader :image_path, :base_image_path
+ attr_reader :difference, :error_message
- attr_reader :annotated_image_path, :annotated_base_image_path,
- :image_path, :base_image_path,
- :new_file_name, :old_file_name
-
def initialize(image_path, base_image_path, options = {})
@image_path = Pathname.new(image_path)
-
- @new_file_name = @image_path.to_s
- @annotated_image_path = @image_path.sub_ext(".diff.png")
-
@base_image_path = Pathname.new(base_image_path)
- @old_file_name = @base_image_path.to_s
- @annotated_base_image_path = @base_image_path.sub_ext(".diff.png")
-
@driver_options = options.dup
@driver = Drivers.for(@driver_options)
end
# Compare the two image files and return `true` or `false` as quickly as possible.
# Return falsely if the old file does not exist or the image dimensions do not match.
def quick_equal?
- @error_message = nil
- return false unless image_files_exist?
- # TODO: Confirm this change. There are screenshots with the same size, but there is a big difference
+ require_images_exists!
+
+ # NOTE: This is very fuzzy logic, but so far it's helps to support current performance.
return true if new_file_size == old_file_size
comparison = load_and_process_images
unless driver.same_dimension?(comparison)
- @error_message = build_error_for_different_dimensions(comparison)
+ self.difference = build_failed_difference(comparison, {different_dimensions: true})
return false
end
- return true if driver.same_pixels?(comparison)
+ if driver.same_pixels?(comparison)
+ self.difference = build_no_difference(comparison)
+ return true
+ end
- # Could not make any difference to be tolerable, so skip and return as not equal
+ # NOTE: Could not make any difference to be tolerable, so skip and return as not equal.
return false if without_tolerable_options?
- @difference = driver.find_difference_region(comparison)
- return true unless @difference.different?
+ self.difference = driver.find_difference_region(comparison)
- @error_message = @difference.inspect
- false
+ !difference.different?
end
# Compare the two image referenced by this object, and return `true` if they are different,
# and `false` if they are the same.
def different?
- @error_message = nil
+ processed.difference.different?
+ end
- @error_message = _different?
+ def reporter
+ @reporter ||= begin
+ current_difference = difference || build_no_difference(nil)
+ Capybara::Screenshot::Diff::Reporters::Default.new(current_difference)
+ end
+ end
- clean_tmp_files unless @error_message
+ def processed?
+ !!difference
+ end
- !@error_message.nil?
+ def processed
+ self.difference = find_difference unless processed?
+ @error_message ||= reporter.generate
+ self
end
- def build_error_for_different_dimensions(comparison)
- change_msg = [comparison.base_image, comparison.new_image]
- .map { |i| driver.dimension(i).join("x") }
- .join(" => ")
+ private
- "Screenshot dimension has been changed for #{@new_file_name}: #{change_msg}"
+ def find_difference
+ require_images_exists!
+
+ comparison = load_and_process_images
+
+ unless driver.same_dimension?(comparison)
+ return build_failed_difference(comparison, {different_dimensions: true})
+ end
+
+ if driver.same_pixels?(comparison)
+ build_no_difference(comparison)
+ else
+ driver.find_difference_region(comparison)
+ end
end
- def clean_tmp_files
- @annotated_base_image_path.unlink if @annotated_base_image_path.exist?
- @annotated_image_path.unlink if @annotated_image_path.exist?
+ def require_images_exists!
+ raise ArgumentError, "There is no original (base) screenshot version to compare, located: #{base_image_path}" unless base_image_path.exist?
+ raise ArgumentError, "There is no new screenshot version to compare, located: #{image_path}" unless image_path.exist?
end
- def save(image, image_path)
- driver.save_image_to(image, image_path.to_s)
+ def difference=(new_difference)
+ @error_message = nil
+ @reporter = nil
+ @difference = new_difference
end
def image_files_exist?
@base_image_path.exist? && @image_path.exist?
end
- NEW_LINE = "\n"
-
- attr_reader :error_message
-
- private
-
def without_tolerable_options?
(@driver_options.keys & TOLERABLE_OPTIONS).empty?
end
- def _different?
- raise "There is no original (base) screenshot version to compare, located: #{@base_image_path}" unless @base_image_path.exist?
- raise "There is no new screenshot version to compare, located: #{@image_path}" unless @image_path.exist?
-
- comparison = load_and_process_images
-
- unless driver.same_dimension?(comparison)
- return build_error_for_different_dimensions(comparison)
- end
-
- return not_different if driver.same_pixels?(comparison)
-
- @difference = driver.find_difference_region(comparison)
- return not_different unless @difference.different?
-
- different(@difference)
+ def build_failed_difference(comparison, failed_by)
+ Difference.new(
+ nil,
+ {difference_level: nil, max_color_distance: 0},
+ comparison,
+ failed_by
+ )
end
def load_and_process_images
- images = driver.load_images(old_file_name, new_file_name)
+ images = driver.load_images(base_image_path, image_path)
base_image, new_image = preprocess_images(images)
- Comparison.new(new_image, base_image, @driver_options)
+ Comparison.new(new_image, base_image, @driver_options, driver, image_path, base_image_path)
end
- def build_error_message(difference)
- [
- "(#{difference.inspect})",
- new_file_name,
- annotated_base_image_path.to_path,
- annotated_image_path.to_path
- ].join(NEW_LINE)
- end
-
def skip_area
@driver_options[:skip_area]
end
def median_filter_window_size
@driver_options[:median_filter_window_size]
end
- def dimensions
- @driver_options[:dimensions]
- end
-
- def different(difference)
- annotate_and_save_images(difference)
- build_error_message(difference)
- end
-
def preprocess_images(images)
images.map { |image| preprocess_image(image) }
end
def preprocess_image(image)
result = image
- # FIXME: How can we access to this method from public interface? Is this not documented feature?
- if dimensions && driver.inscribed?(dimensions, result)
- result = driver.crop(dimensions, result)
- end
-
if skip_area
result = ignore_skipped_area(result)
end
if median_filter_window_size
- result = blur_image_by(image, median_filter_window_size)
+ if driver.is_a?(Drivers::VipsDriver)
+ result = blur_image_by(image, median_filter_window_size)
+ else
+ warn(
+ "[capybara-screenshot-diff] Median filter has been skipped for #{image_path} " \
+ "because it is not supported by #{driver.class.name}"
+ )
+ end
end
result
end
def blur_image_by(image, size)
driver.filter_image_with_median(image, size)
end
def ignore_skipped_area(image)
- skip_area.reduce(image) { |memo, region| driver.add_black_box(memo, region) }
+ skip_area&.reduce(image) { |memo, region| driver.add_black_box(memo, region) }
end
def old_file_size
- @old_file_size ||= image_files_exist? && File.size(@old_file_name)
+ base_image_path.size
end
def new_file_size
- File.size(@new_file_name)
+ image_path.size
end
- def not_different
- nil
+ def build_no_difference(comparison = nil)
+ Difference.new(
+ nil,
+ {difference_level: nil, max_color_distance: 0},
+ comparison || build_comparison
+ ).freeze
end
- def annotate_and_save_images(difference)
- annotate_and_save_image(difference, difference.comparison.new_image, @annotated_image_path)
- annotate_and_save_image(difference, difference.comparison.base_image, @annotated_base_image_path)
+ def build_comparison
+ Capybara::Screenshot::Diff::Comparison.new(nil, nil, driver_options, driver, image_path, base_image_path).freeze
end
-
- def annotate_and_save_image(difference, image, image_path)
- image = annotate_difference(image, difference.region)
- image = annotate_skip_areas(image, difference.skip_area) if difference.skip_area
- save(image, image_path.to_path)
- end
-
- DIFF_COLOR = [255, 0, 0, 255].freeze
-
- def annotate_difference(image, region)
- driver.draw_rectangles(Array[image], region, DIFF_COLOR, offset: 1).first
- end
-
- SKIP_COLOR = [255, 192, 0, 255].freeze
-
- def annotate_skip_areas(image, skip_areas)
- skip_areas.reduce(image) do |memo, region|
- driver.draw_rectangles(Array[memo], region, SKIP_COLOR).first
- end
- end
- end
-
- class Comparison < Struct.new(:new_image, :base_image, :options)
end
end
end
end