Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Multiple areas #6

Merged
merged 29 commits into from
Mar 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
c0a6f68
code update for better discrimination of points of difference, separa…
Feb 6, 2024
b11af26
some code improvements
Feb 6, 2024
65c1cbf
updates related to multiple ares for all the modes
Feb 7, 2024
1c119d2
add new gemini api test with new gemini_api_helper, feature and steps
Feb 9, 2024
6b7958d
add new gemini api test with new gemini_api_helper, feature and steps
Feb 9, 2024
d971fa4
add new gemini api test with new gemini_api_helper, feature and steps
Feb 9, 2024
0a50e33
add new gemini api test with new gemini_api_helper, feature and steps
Feb 9, 2024
e371675
add new gemini api test with new gemini_api_helper, feature and steps
Feb 9, 2024
dc6539f
add new gemini api test with new gemini_api_helper, feature and steps
Feb 9, 2024
0cef0c3
add new gemini api test with new gemini_api_helper, feature and steps
Feb 9, 2024
76fd474
add new gemini api test with new gemini_api_helper, feature and steps
Feb 9, 2024
6c51579
add new gemini api test with new gemini_api_helper, feature and steps
Feb 9, 2024
f1a6f04
added multiple ares for exclude ract.
Feb 15, 2024
e4f9c4e
added multiple ares for exclude ract.
Feb 15, 2024
b1723b9
added multiple ares for exclude ract.
Feb 15, 2024
ad4ac80
added multiple ares for exclude ract.
Feb 15, 2024
9f963ef
improve the performance the base.rb
Feb 29, 2024
18042d5
improve the performance the base.rb
Feb 29, 2024
72403f1
improve the performance the base.rb
Feb 29, 2024
ae7b850
some fixes
Mar 16, 2024
e4dd40c
some fixes
Mar 18, 2024
7b51e53
some fixes
Mar 18, 2024
545f4f1
some fixes
Mar 18, 2024
ed56d63
some fixes
Mar 18, 2024
9da7d5b
some fixes
Mar 18, 2024
6c91f0a
some fixes
Mar 18, 2024
249b977
Merge branch 'main' into multiple_areas
cristianofmc Mar 18, 2024
980a0d6
some fixes
Mar 18, 2024
c658fca
Merge remote-tracking branch 'origin/multiple_areas' into multiple_areas
Mar 18, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 8 additions & 28 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<img alt="Gem total downloads" src="https://shields.io/gem/dt/image_compare" />

Compare PNG images in pure Ruby (uses [ChunkyPNG](https://github.com/wvanbergen/chunky_png)) using different algorithms.
This is an utility library for image regression testing.
This is a utility library for image regression testing.

## Installation

Expand Down Expand Up @@ -53,28 +53,6 @@ Resulting diff contains version of the first image with different pixels highlig

<img alt="color_diff" src="spec/fixtures/color_diff.png" />

### RGB mode (a.png X b.png)

Compare pixels by values, resulting score is a ratio of unequal pixels.
Resulting diff represents per-channel difference.

<img alt="rgb_diff.png" src="spec/fixtures/rgb_diff.png" />

### Grayscale mode (a.png X a1.png)

Compare pixels as grayscale (by brightness and alpha), resulting score is a ratio of unequal pixels (with respect to provided tolerance).

Resulting diff contains grayscale version of the first image with different pixels highlighted in red and red bounding box.

<img alt="grayscale_diff.png" src="spec/fixtures/grayscale_diff.png" />

### Delta (a.png X a1.png)

Compare pixels using [Delta E](https://en.wikipedia.org/wiki/Color_difference) distance.
Resulting diff contains grayscale version of the first image with different pixels highlighted in red (with respect to diff score).

<img alt="delta_diff.png" src="spec/fixtures/delta_diff.png" />

## Usage

```ruby
Expand Down Expand Up @@ -122,16 +100,16 @@ res.score #=> 0.0

## Excluding rectangle (a.png X a1.png)

<img alt="a1.png" src="spec/fixtures/rgb_exclude_rect.png" />
<img alt="a1.png" src="spec/fixtures/multiple_exclude_rects.png" />

You can exclude rectangle from comparing by passing `:exclude_rect` to `compare`.
You can exclude rectangle from comparing by passing `:exclude_rects` to `compare`.
E.g., if `path_1` and `path_2` contain images above
```ruby
ImageCompare.compare("path/image1.png", "path/image2.png", mode: :rgb, exclude_rect: [200, 150, 275, 200]).match? # => true
ImageCompare.compare("path/image1.png", "path/image2.png", mode: :rgb, exclude_rects: [[170, 221, 188, 246], [289, 221, 307, 246]]).match? # => true

# or

cmp = ImageCompare::Matcher.new mode: :rgb, exclude_rect: [200, 150, 275, 200]
cmp = ImageCompare::Matcher.new mode: :rgb, exclude_rect: [[170, 221, 188, 246], [289, 221, 307, 246]]
res = cmp.compare("path/image1.png", "path/image2.png")
res #=> ImageCompare::Result
res.match? #=> true
Expand All @@ -141,7 +119,9 @@ res.score #=> 0.0
res.difference_image #=> ImageCompare::Image
res.difference_image.save("path/diff.png")
```
`[200, 150, 275, 200]` is array of two vertices of rectangle -- (200, 150) is left-top vertex and (275, 200) is right-bottom.
`[[170, 221, 188, 246],[289, 221, 307, 246]]` is a set of multiple areas, containing area not to be considered in comparison, each area is an array of two vertices of rectangle -- (170, 121) is left-top vertex and (288, 246) is right-bottom.



### Cucumber + Capybara example
`support/env.rb`:
Expand Down
4 changes: 2 additions & 2 deletions lib/image_compare.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ class SizesMismatchError < StandardError
require "image_compare/color_methods"
require "image_compare/matcher"

def self.compare(path_a, path_b, **options)
Matcher.new(**options).compare(path_a, path_b)
def self.compare(path_a, path_b, **)
Matcher.new(**).compare(path_a, path_b)
end
end
20 changes: 13 additions & 7 deletions lib/image_compare/image.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,20 @@ def each_pixel
end
end

def compare_each_pixel(image, area: nil)
def find_different_pixel(image, area: nil, order: :top_to_bottom)
area = bounding_rect if area.nil?
(area.top..area.bot).each do |y|
current_row = row(y) || []
range = (area.left..area.right)
next if image.row(y).slice(range) == current_row.slice(range)
(area.left..area.right).each do |x|
yield(self[x, y], image[x, y], x, y)

rows = (area.top..area.bot).to_a
rows.reverse! if order == :bottom_to_top

cols = (area.left..area.right).to_a

rows.each do |y|
cols.each do |x|
pixel_self = self[x, y]
pixel_image = image[x, y]
next if pixel_self == pixel_image
yield(pixel_self, pixel_image, x, y)
end
end
end
Expand Down
5 changes: 3 additions & 2 deletions lib/image_compare/matcher.rb
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,9 @@ def compare(a, b)
unless image_area.contains?(mode.include_rect)
raise ArgumentError, "Bounds must be in image"
end
unless mode.exclude_rect.nil?
unless mode.include_rect.contains?(mode.exclude_rect)

mode.exclude_rects&.each do |exclude_rect|
unless mode.include_rect.contains?(exclude_rect)
raise ArgumentError, "Included area must contain excluded"
end
end
Expand Down
3 changes: 0 additions & 3 deletions lib/image_compare/modes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,5 @@
module ImageCompare
module Modes
require "image_compare/modes/color"
require "image_compare/modes/delta"
require "image_compare/modes/grayscale"
require "image_compare/modes/rgb"
end
end
82 changes: 59 additions & 23 deletions lib/image_compare/modes/base.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,41 +6,48 @@ class Base
require "image_compare/rectangle"
include ColorMethods

attr_reader :result, :threshold, :lower_threshold, :bounds, :exclude_rect, :include_rect
attr_reader :result, :threshold, :lower_threshold, :different_areas, :exclude_rects, :include_rect

def initialize(threshold: 0.0, lower_threshold: 0.0, exclude_rect: nil, include_rect: nil)
def initialize(threshold: 0.0, lower_threshold: 0.0, exclude_rects: [], include_rect: nil)
@include_rect = Rectangle.new(*include_rect) unless include_rect.nil?
@exclude_rect = Rectangle.new(*exclude_rect) unless exclude_rect.nil?
@exclude_rects = exclude_rects.empty? ? Set.new : Set.new(exclude_rects.map { |rect| Rectangle.new(*rect) })
@threshold = threshold
@lower_threshold = lower_threshold
@result = Result.new(self, threshold: threshold, lower_threshold: lower_threshold)
@different_areas = Set.new
end

def create_sections(session = [])
@sections.append(session)
end

def compare(a, b)
result.image = a
@include_rect ||= a.bounding_rect
@bounds = Rectangle.new(*include_rect.bounds)
@comparison_area = @include_rect

b.compare_each_pixel(a, area: include_rect) do |b_pixel, a_pixel, x, y|
next if !exclude_rect.nil? && exclude_rect.contains_point?(x, y)
excluded_points = Hash.new { |h, k| h[k] = Hash.new(false) }
@exclude_rects.each do |rect|
(rect.top..rect.bot).each do |y|
(rect.left..rect.right).each do |x|
if @comparison_area.contains_point?(x, y)
excluded_points[x][y] = true
end
end
end
end

cristianofmc marked this conversation as resolved.
Show resolved Hide resolved
b.find_different_pixel(a, area: @comparison_area) do |b_pixel, a_pixel, x, y|
next if excluded_points[x][y]
next if pixels_equal?(b_pixel, a_pixel)

update_result(b_pixel, a_pixel, x, y)
end

result.score = score
result
end

def diff(bg, diff)
diff_image = background(bg).highlight_rectangle(exclude_rect, :blue)
diff.each do |pixels_pair|
pixels_diff(diff_image, *pixels_pair)
end
create_diff_image(bg, diff_image)
.highlight_rectangle(bounds)
.highlight_rectangle(include_rect, :green)
end

def score
result.diff.length.to_f / area
end
Expand All @@ -49,17 +56,46 @@ def update_result(*_args, x, y)
update_bounds(x, y)
end

def connected_area_index(x, y)
@different_areas.each_with_index do |area, index|
if area.close_to_the_area?(x, y)
return index
end
end
nil
end

def areas_connected?(origin_area)
connected_areas, disconnected_areas = @different_areas.partition { |area| origin_area.rect_close_to_the_area?(area) }
merged_area = connected_areas.reduce(origin_area, :merge)
@different_areas = disconnected_areas.to_set
@different_areas.add(merged_area)
end

def create_area(x, y)
@different_areas.add(Rectangle.new(x, y, x, y))
end

def update_bounds(x, y)
bounds.left = [x, bounds.left].max
bounds.top = [y, bounds.top].max
bounds.right = [x, bounds.right].min
bounds.bot = [y, bounds.bot].min
current_area = @different_areas.find { |area| area.close_to_the_area?(x, y) }
if current_area.nil?
create_area(x, y)
current_area = @different_areas.to_a.last
else
current_area.left = [x, current_area.left].min
current_area.top = [y, current_area.top].min
current_area.right = [x, current_area.right].max
current_area.bot = [y, current_area.bot].max
end

areas_connected?(current_area)
current_area
end

def area
area = include_rect.area
return area if exclude_rect.nil?
area - exclude_rect.area
total_area = @include_rect.area
total_exclude_area = @exclude_rects.sum(&:area) || 0
total_area - total_exclude_area
end
end
end
Expand Down
45 changes: 26 additions & 19 deletions lib/image_compare/modes/color.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,36 +17,43 @@ def initialize(**options)
end

def diff(bg, _diff)
diff_image = bg.highlight_rectangle(exclude_rect, :blue)
diff_image = bg
@exclude_rects.each do |rect|
diff_image = diff_image.highlight_rectangle(rect, :blue)
end

unless result.match? || area_in_exclude_rect?
return diff_image.highlight_rectangle(bounds, :red)
@different_areas.each do |area|
unless result.match? || area_in_exclude_rect?(area)
diff_image = diff_image.highlight_rectangle(area, :red)
end
end

diff_image
end

def area_in_exclude_rect?
return false if exclude_rect.nil?
def area_in_exclude_rect?(bound)
return false if exclude_rects.nil?

diff_area = {
left: bounds.bounds[0],
top: bounds.bounds[1],
right: bounds.bounds[2],
bot: bounds.bounds[3]
left: bound.bounds[0],
top: bound.bounds[1],
right: bound.bounds[2],
bot: bound.bounds[3]
}

exclude_area = {
left: exclude_rect.bounds[0],
top: exclude_rect.bounds[1],
right: exclude_rect.bounds[2],
bot: exclude_rect.bounds[3]
}
exclude_rects.any? do |exclude_rect|
exclude_area = {
left: exclude_rect.bounds[0],
top: exclude_rect.bounds[1],
right: exclude_rect.bounds[2],
bot: exclude_rect.bounds[3]
}

diff_area[:left] <= exclude_area[:left] &&
diff_area[:top] <= exclude_area[:top] &&
diff_area[:right] >= exclude_area[:right] &&
diff_area[:bot] >= exclude_area[:bot]
diff_area[:left] <= exclude_area[:left] &&
diff_area[:top] <= exclude_area[:top] &&
diff_area[:right] >= exclude_area[:right] &&
diff_area[:bot] >= exclude_area[:bot]
end
end

def pixels_equal?(a, b)
Expand Down
55 changes: 0 additions & 55 deletions lib/image_compare/modes/delta.rb

This file was deleted.

Loading
Loading