imatcher 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/.rubocop.yml +8 -0
- data/.travis.yml +5 -4
- data/Gemfile +0 -2
- data/README.md +66 -9
- data/bin/setup +0 -2
- data/imatcher.gemspec +3 -1
- data/lib/imatcher.rb +10 -0
- data/lib/imatcher/color_methods.rb +11 -0
- data/lib/imatcher/image.rb +47 -0
- data/lib/imatcher/matcher.rb +33 -0
- data/lib/imatcher/modes.rb +7 -0
- data/lib/imatcher/modes/base.rb +52 -0
- data/lib/imatcher/modes/delta.rb +50 -0
- data/lib/imatcher/modes/grayscale.rb +48 -0
- data/lib/imatcher/modes/rgb.rb +33 -0
- data/lib/imatcher/result.rb +23 -0
- data/lib/imatcher/version.rb +1 -1
- data/spec/fixtures/a.png +0 -0
- data/spec/fixtures/b.png +0 -0
- data/spec/fixtures/darker.png +0 -0
- data/spec/fixtures/delta_diff.png +0 -0
- data/spec/fixtures/grayscale_diff.png +0 -0
- data/spec/fixtures/rgb_diff.png +0 -0
- data/spec/fixtures/small.png +0 -0
- data/spec/fixtures/very_small.png +0 -0
- data/spec/imatcher_spec.rb +0 -4
- data/spec/integrations/delta_spec.rb +43 -0
- data/spec/integrations/grayscale_spec.rb +71 -0
- data/spec/integrations/rgb_spec.rb +26 -0
- data/spec/matcher_spec.rb +45 -0
- data/spec/spec_helper.rb +11 -0
- metadata +41 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2ad15eab2eb3ec4656cafb23117199966625e18d
|
4
|
+
data.tar.gz: 3d15c721ce46ed7cbca79a0d9d4e310972ebcfc2
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b098cf87274c56a0089e795e686d6299ea91339d8bff948486ca25ea5a75e993fb1eb50d533416e2eddd18e035d1dc1330eb055e8a23ddb74e49d54c87f6b406
|
7
|
+
data.tar.gz: 11c76b5365728cfb495a764e15241d1aea897b8d1cf0642a0dceb870479ce2fc4f3bec87efd1c80ce59b12b8fc28d817cfda79013ec384cba7ed76bf7000635b
|
data/.gitignore
CHANGED
data/.rubocop.yml
CHANGED
@@ -32,9 +32,17 @@ Style/BlockDelimiters:
|
|
32
32
|
Exclude:
|
33
33
|
- 'spec/**/*.rb'
|
34
34
|
|
35
|
+
Style/ParallelAssignment:
|
36
|
+
Enabled: false
|
37
|
+
|
35
38
|
Lint/AmbiguousRegexpLiteral:
|
36
39
|
Enabled: false
|
37
40
|
|
38
41
|
Metrics/MethodLength:
|
39
42
|
Exclude:
|
40
43
|
- 'spec/**/*.rb'
|
44
|
+
|
45
|
+
Metrics/LineLength:
|
46
|
+
max: 100
|
47
|
+
Exclude:
|
48
|
+
- 'spec/**/*.rb'
|
data/.travis.yml
CHANGED
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -1,8 +1,9 @@
|
|
1
|
-
|
1
|
+
[](https://travis-ci.org/teachbase/imatcher)
|
2
2
|
|
3
|
-
|
3
|
+
# Imatcher
|
4
4
|
|
5
|
-
|
5
|
+
Compare PNG images in pure Ruby (uses [ChunkyPNG](https://github.com/wvanbergen/chunky_png)) using different algorithms.
|
6
|
+
This is an utility library for image regression testing.
|
6
7
|
|
7
8
|
## Installation
|
8
9
|
|
@@ -20,20 +21,76 @@ Or install it yourself as:
|
|
20
21
|
|
21
22
|
$ gem install imatcher
|
22
23
|
|
24
|
+
## Modes
|
25
|
+
|
26
|
+
Imatcher supports different ways (_modes_) of comparing images.
|
27
|
+
|
28
|
+
Source images used in examples:
|
29
|
+
|
30
|
+
<img src="https://raw.githubusercontent.com/teachbase/imatcher/master/spec/fixtures/a.png" width="300" />
|
31
|
+
<img src="https://raw.githubusercontent.com/teachbase/imatcher/master/spec/fixtures/b.png" width="300" />
|
32
|
+
|
33
|
+
### Base (RGB) mode
|
34
|
+
|
35
|
+
Compare pixels by values, resulting score is a ratio of unequal pixels.
|
36
|
+
Resulting diff represents per-channel difference.
|
37
|
+
|
38
|
+
<img src="https://raw.githubusercontent.com/teachbase/imatcher/master/spec/fixtures/rgb_diff.png" width="300" />
|
39
|
+
|
40
|
+
### Grayscale mode
|
41
|
+
|
42
|
+
Compare pixels as grayscale (by brightness and alpha), resulting score is a ratio of unequal pixels (with respect to provided tolerance).
|
43
|
+
|
44
|
+
Resulting diff contains grayscale version of the first image with different pixels highlighted in red and red bounding box.
|
45
|
+
|
46
|
+
<img src="https://raw.githubusercontent.com/teachbase/imatcher/master/spec/fixtures/grayscale_diff.png" width="300" />
|
47
|
+
|
48
|
+
### Delta
|
49
|
+
|
50
|
+
Compare pixels using [Delta E](https://en.wikipedia.org/wiki/Color_difference) distance.
|
51
|
+
Resulting diff contains grayscale version of the first image with different pixels highlighted in red (with respect to diff score).
|
52
|
+
|
53
|
+
<img src="https://raw.githubusercontent.com/teachbase/imatcher/master/spec/fixtures/delta_diff.png" width="300" />
|
54
|
+
|
23
55
|
## Usage
|
24
56
|
|
25
|
-
|
57
|
+
```ruby
|
58
|
+
# create new matcher with default threshold equals to 0
|
59
|
+
# and base (RGB) mode
|
60
|
+
cmp = Imatcher::Matcher.new
|
61
|
+
cmp.mode #=> Imatcher::Modes::RGB
|
26
62
|
|
27
|
-
|
63
|
+
# create matcher with specific threshold
|
64
|
+
cmp = Imatcher::Matcher.new threshold: 0.05
|
65
|
+
cmp.threshold #=> 0.05
|
28
66
|
|
29
|
-
|
67
|
+
# create zero-tolerance grayscale matcher
|
68
|
+
cmp = Imatcher::Matcher.new mode: :grayscale, tolerance: 0
|
69
|
+
cmp.mode #=> Imatcher::Modes::Grayscale
|
30
70
|
|
31
|
-
|
71
|
+
res = cmp.compare(path_1, path_2)
|
72
|
+
res #=> Imatcher::Result
|
32
73
|
|
33
|
-
|
74
|
+
res.match? #=> true
|
75
|
+
|
76
|
+
res.score #=> 0.0
|
77
|
+
|
78
|
+
# Return diff image object
|
79
|
+
res.difference_image #=> Imatcher::Image
|
34
80
|
|
35
|
-
|
81
|
+
res.difference_image.save(new_path)
|
82
|
+
|
83
|
+
# without explicit matcher
|
84
|
+
res = Imatcher.compare(path_1, path_2, options)
|
85
|
+
|
86
|
+
# equals to
|
87
|
+
res = Imatcher::Matcher.new(options).compare(path_1, path_2)
|
88
|
+
|
89
|
+
```
|
90
|
+
|
91
|
+
## Contributing
|
36
92
|
|
93
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/teachbase/imatcher.
|
37
94
|
|
38
95
|
## License
|
39
96
|
|
data/bin/setup
CHANGED
data/imatcher.gemspec
CHANGED
@@ -15,7 +15,9 @@ Gem::Specification.new do |spec|
|
|
15
15
|
spec.files = `git ls-files`.split($/)
|
16
16
|
spec.require_paths = ["lib"]
|
17
17
|
|
18
|
-
spec.
|
18
|
+
spec.add_dependency "chunky_png", "~> 1.3.5"
|
19
|
+
|
20
|
+
spec.add_development_dependency "simplecov", ">= 0.3.8"
|
19
21
|
spec.add_development_dependency "rake", "~> 10.0"
|
20
22
|
spec.add_development_dependency 'pry-byebug'
|
21
23
|
spec.add_development_dependency "rspec", "~> 3.0"
|
data/lib/imatcher.rb
CHANGED
@@ -1,4 +1,14 @@
|
|
1
1
|
require "imatcher/version"
|
2
2
|
|
3
|
+
# Compare PNG images using different algorithms
|
3
4
|
module Imatcher
|
5
|
+
class SizesMismatchError < StandardError
|
6
|
+
end
|
7
|
+
|
8
|
+
require 'imatcher/matcher'
|
9
|
+
require 'imatcher/color_methods'
|
10
|
+
|
11
|
+
def self.compare(path_a, path_b, options = {})
|
12
|
+
Matcher.new(options).compare(path_a, path_b)
|
13
|
+
end
|
4
14
|
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
require 'imatcher/color_methods'
|
2
|
+
|
3
|
+
module Imatcher
|
4
|
+
# Extend ChunkyPNG::Image with some methods.
|
5
|
+
class Image < ChunkyPNG::Image
|
6
|
+
include ColorMethods
|
7
|
+
|
8
|
+
def each_pixel
|
9
|
+
height.times do |y|
|
10
|
+
row(y).each_with_index do |pixel, x|
|
11
|
+
yield(pixel, x, y)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def compare_each_pixel(image)
|
17
|
+
height.times do |y|
|
18
|
+
next if image.row(y) == row(y)
|
19
|
+
row(y).each_with_index do |pixel, x|
|
20
|
+
yield(pixel, image[x, y], x, y)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def to_grayscale
|
26
|
+
each_pixel do |pixel, x, y|
|
27
|
+
self[x, y] = grayscale(brightness(pixel).round)
|
28
|
+
end
|
29
|
+
self
|
30
|
+
end
|
31
|
+
|
32
|
+
def with_alpha(value)
|
33
|
+
each_pixel do |pixel, x, y|
|
34
|
+
self[x, y] = rgba(r(pixel), g(pixel), b(pixel), value)
|
35
|
+
end
|
36
|
+
self
|
37
|
+
end
|
38
|
+
|
39
|
+
def sizes_match?(image)
|
40
|
+
[width, height] == [image.width, image.height]
|
41
|
+
end
|
42
|
+
|
43
|
+
def render_bounds(left, bot, right, top)
|
44
|
+
rect(left, bot, right, top, rgb(255, 0, 0))
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module Imatcher
|
2
|
+
# Matcher contains information about compare mode
|
3
|
+
class Matcher
|
4
|
+
require 'imatcher/image'
|
5
|
+
require 'imatcher/result'
|
6
|
+
require 'imatcher/modes'
|
7
|
+
|
8
|
+
MODES = {
|
9
|
+
rgb: 'RGB',
|
10
|
+
delta: 'Delta',
|
11
|
+
grayscale: 'Grayscale'
|
12
|
+
}.freeze
|
13
|
+
|
14
|
+
attr_reader :threshold, :mode
|
15
|
+
|
16
|
+
def initialize(options = {})
|
17
|
+
mode_type = options.delete(:mode) || :rgb
|
18
|
+
@mode = Modes.const_get(MODES[mode_type]).new(options)
|
19
|
+
end
|
20
|
+
|
21
|
+
def compare(path_1, path_2)
|
22
|
+
a = Image.from_file(path_1)
|
23
|
+
b = Image.from_file(path_2)
|
24
|
+
raise SizesMismatchError,
|
25
|
+
"Size mismatch: first image size: " \
|
26
|
+
"#{a.width}x#{a.height}, " \
|
27
|
+
"second image size: " \
|
28
|
+
"#{b.width}x#{b.height}" unless a.sizes_match?(b)
|
29
|
+
|
30
|
+
mode.compare(a, b)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
module Imatcher
|
2
|
+
module Modes
|
3
|
+
class Base # :nodoc:
|
4
|
+
include ColorMethods
|
5
|
+
|
6
|
+
attr_reader :result, :threshold
|
7
|
+
|
8
|
+
def initialize(threshold: 0.0)
|
9
|
+
@threshold = threshold
|
10
|
+
@result = Result.new(self, threshold)
|
11
|
+
end
|
12
|
+
|
13
|
+
def compare(a, b)
|
14
|
+
result.image = a
|
15
|
+
|
16
|
+
b.compare_each_pixel(a) do |b_pixel, a_pixel, x, y|
|
17
|
+
next if pixels_equal?(b_pixel, a_pixel)
|
18
|
+
update_result(b_pixel, a_pixel, x, y)
|
19
|
+
end
|
20
|
+
|
21
|
+
result.score = score
|
22
|
+
result
|
23
|
+
end
|
24
|
+
|
25
|
+
def diff(bg, diff)
|
26
|
+
diff_image = background(bg)
|
27
|
+
diff_image.render_bounds(*calculate_bounds(diff))
|
28
|
+
diff.each do |pixels_pair|
|
29
|
+
pixels_diff(diff_image, *pixels_pair)
|
30
|
+
end
|
31
|
+
create_diff_image(bg, diff_image)
|
32
|
+
end
|
33
|
+
|
34
|
+
def score
|
35
|
+
@result.diff.length.to_f / @result.image.pixels.length
|
36
|
+
end
|
37
|
+
|
38
|
+
# rubocop:disable Metrics/AbcSize
|
39
|
+
def calculate_bounds(diff)
|
40
|
+
xmin, xmax, ymin, ymax = result.image.width, 0, result.image.height, 0
|
41
|
+
diff.each do |pixels_pair|
|
42
|
+
xmin = pixels_pair[2] if pixels_pair[2] < xmin
|
43
|
+
xmax = pixels_pair[2] if pixels_pair[2] > xmax
|
44
|
+
ymin = pixels_pair[3] if pixels_pair[3] < ymin
|
45
|
+
ymax = pixels_pair[3] if pixels_pair[3] > ymax
|
46
|
+
end
|
47
|
+
|
48
|
+
[xmin - 1, ymin - 1, xmax + 1, ymax + 1]
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
module Imatcher
|
2
|
+
module Modes # :nodoc:
|
3
|
+
require 'imatcher/modes/base'
|
4
|
+
|
5
|
+
# Compare pixels using Delta E distance.
|
6
|
+
class Delta < Base
|
7
|
+
def initialize(threshold: 0.0)
|
8
|
+
@threshold = threshold
|
9
|
+
@result = Result.new(self, threshold)
|
10
|
+
@delta_score = 0.0
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
def pixels_equal?(a, b)
|
16
|
+
a == b
|
17
|
+
end
|
18
|
+
|
19
|
+
def update_result(a, b, x, y)
|
20
|
+
d = euclid(a, b) / (MAX * Math.sqrt(3))
|
21
|
+
@result.diff << [a, b, x, y, d]
|
22
|
+
@delta_score += d
|
23
|
+
end
|
24
|
+
|
25
|
+
def background(bg)
|
26
|
+
Image.new(bg.width, bg.height, WHITE).with_alpha(0)
|
27
|
+
end
|
28
|
+
|
29
|
+
def euclid(a, b)
|
30
|
+
Math.sqrt(
|
31
|
+
(r(a) - r(b))**2 +
|
32
|
+
(g(a) - g(b))**2 +
|
33
|
+
(b(a) - b(b))**2
|
34
|
+
)
|
35
|
+
end
|
36
|
+
|
37
|
+
def create_diff_image(bg, diff_image)
|
38
|
+
bg.to_grayscale.compose!(diff_image, 0, 0)
|
39
|
+
end
|
40
|
+
|
41
|
+
def pixels_diff(d, *_args, x, y, a)
|
42
|
+
d[x, y] = rgba(MAX, 0, 0, (a * MAX).round)
|
43
|
+
end
|
44
|
+
|
45
|
+
def score
|
46
|
+
@delta_score / result.image.pixels.length
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module Imatcher
|
2
|
+
module Modes # :nodoc:
|
3
|
+
require 'imatcher/modes/base'
|
4
|
+
|
5
|
+
# Compare pixels by alpha and brightness.
|
6
|
+
#
|
7
|
+
# Options:
|
8
|
+
# - tolerance - defines the maximum allowed difference for alpha/brightness
|
9
|
+
# (default value is 16)
|
10
|
+
class Grayscale < Base
|
11
|
+
DEFAULT_TOLERANCE = 16
|
12
|
+
|
13
|
+
attr_reader :tolerance
|
14
|
+
|
15
|
+
def initialize(options)
|
16
|
+
@tolerance = options.delete(:tolerance) || DEFAULT_TOLERANCE
|
17
|
+
super(options)
|
18
|
+
end
|
19
|
+
|
20
|
+
def pixels_equal?(a, b)
|
21
|
+
alpha = color_similar?(a(a), a(b))
|
22
|
+
brightness = color_similar?(brightness(a), brightness(b))
|
23
|
+
brightness && alpha
|
24
|
+
end
|
25
|
+
|
26
|
+
def update_result(a, b, x, y)
|
27
|
+
@result.diff << [a, b, x, y]
|
28
|
+
end
|
29
|
+
|
30
|
+
def background(bg)
|
31
|
+
bg.to_grayscale
|
32
|
+
end
|
33
|
+
|
34
|
+
def pixels_diff(d, _a, _b, x, y)
|
35
|
+
d[x, y] = rgb(255, 0, 0)
|
36
|
+
end
|
37
|
+
|
38
|
+
def create_diff_image(_bg, diff_image)
|
39
|
+
diff_image
|
40
|
+
end
|
41
|
+
|
42
|
+
def color_similar?(a, b)
|
43
|
+
d = (a - b).abs
|
44
|
+
d <= tolerance
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module Imatcher
|
2
|
+
module Modes # :nodoc:
|
3
|
+
require 'imatcher/modes/base'
|
4
|
+
|
5
|
+
# Compare pixels by values.
|
6
|
+
# Resulting image contains per-channel differences.
|
7
|
+
class RGB < Base
|
8
|
+
def pixels_equal?(a, b)
|
9
|
+
a == b
|
10
|
+
end
|
11
|
+
|
12
|
+
def update_result(a, b, x, y)
|
13
|
+
@result.diff << [a, b, x, y]
|
14
|
+
end
|
15
|
+
|
16
|
+
def background(bg)
|
17
|
+
Image.new(bg.width, bg.height, BLACK)
|
18
|
+
end
|
19
|
+
|
20
|
+
def create_diff_image(bg, diff_image)
|
21
|
+
bg.to_grayscale.compose!(diff_image, 0, 0)
|
22
|
+
end
|
23
|
+
|
24
|
+
def pixels_diff(d, a, b, x, y)
|
25
|
+
d[x, y] = rgb(
|
26
|
+
(r(a) - r(b)).abs,
|
27
|
+
(g(a) - g(b)).abs,
|
28
|
+
(b(a) - b(b)).abs
|
29
|
+
)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Imatcher
|
2
|
+
# Object containing comparison score and diff image
|
3
|
+
class Result
|
4
|
+
attr_accessor :score, :image
|
5
|
+
attr_reader :diff, :mode, :threshold
|
6
|
+
|
7
|
+
def initialize(mode, threshold)
|
8
|
+
@score = 0.0
|
9
|
+
@diff = []
|
10
|
+
@threshold = threshold
|
11
|
+
@mode = mode
|
12
|
+
end
|
13
|
+
|
14
|
+
def difference_image
|
15
|
+
@diff_image ||= mode.diff(image, diff)
|
16
|
+
end
|
17
|
+
|
18
|
+
# Returns true iff score less or equals to threshold
|
19
|
+
def match?
|
20
|
+
score <= threshold
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
data/lib/imatcher/version.rb
CHANGED
data/spec/fixtures/a.png
ADDED
Binary file
|
data/spec/fixtures/b.png
ADDED
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
data/spec/imatcher_spec.rb
CHANGED
@@ -0,0 +1,43 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Imatcher::Modes::Delta do
|
4
|
+
let(:path_1) { image_path "a" }
|
5
|
+
let(:path_2) { image_path "darker" }
|
6
|
+
subject { Imatcher.compare(path_1, path_2, options) }
|
7
|
+
|
8
|
+
let(:options) { { mode: :delta } }
|
9
|
+
|
10
|
+
context "with similar images" do
|
11
|
+
it "score around 0.094" do
|
12
|
+
expect(subject.score).to be_within(0.0005).of(0.094)
|
13
|
+
end
|
14
|
+
|
15
|
+
context "with custom threshold" do
|
16
|
+
subject { Imatcher.compare(path_1, path_2, options).match? }
|
17
|
+
|
18
|
+
context "below score" do
|
19
|
+
let(:options) { { mode: :delta, threshold: 0.01 } }
|
20
|
+
|
21
|
+
it { expect(subject).to eq false }
|
22
|
+
end
|
23
|
+
|
24
|
+
context "above score" do
|
25
|
+
let(:options) { { mode: :delta, threshold: 0.1 } }
|
26
|
+
|
27
|
+
it { expect(subject).to eq true }
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
context "with different images" do
|
33
|
+
let(:path_2) { image_path "b" }
|
34
|
+
|
35
|
+
it "score around 0.0195" do
|
36
|
+
expect(subject.score).to be_within(0.0005).of(0.0195)
|
37
|
+
end
|
38
|
+
|
39
|
+
it "creates correct difference image" do
|
40
|
+
expect(subject.difference_image).to eq(Imatcher::Image.from_file(image_path("delta_diff")))
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Imatcher::Modes::Grayscale do
|
4
|
+
let(:path_1) { image_path "a" }
|
5
|
+
let(:path_2) { image_path "darker" }
|
6
|
+
subject { Imatcher.compare(path_1, path_2, options) }
|
7
|
+
|
8
|
+
let(:options) { { mode: :grayscale } }
|
9
|
+
|
10
|
+
context "darker image" do
|
11
|
+
it "score around 0.7" do
|
12
|
+
expect(subject.score).to be_within(0.05).of(0.75)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
context "different images" do
|
17
|
+
let(:path_2) { image_path "b" }
|
18
|
+
|
19
|
+
it "score around 0.02" do
|
20
|
+
expect(subject.score).to be_within(0.0005).of(0.009)
|
21
|
+
end
|
22
|
+
|
23
|
+
it "creates correct difference image" do
|
24
|
+
expect(subject.difference_image).to eq(Imatcher::Image.from_file(image_path("grayscale_diff")))
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
context "with zero tolerance" do
|
29
|
+
let(:options) { { mode: :grayscale, tolerance: 0 } }
|
30
|
+
|
31
|
+
context "darker image" do
|
32
|
+
it "score equals to 1" do
|
33
|
+
expect(subject.score).to eq 1.0
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
context "different image" do
|
38
|
+
let(:path_2) { image_path "b" }
|
39
|
+
|
40
|
+
it "score around 0.97" do
|
41
|
+
expect(subject.score).to be_within(0.005).of(0.97)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
context "equal image" do
|
46
|
+
let(:path_2) { image_path "a" }
|
47
|
+
|
48
|
+
it "score equals to 0" do
|
49
|
+
expect(subject.score).to eq 0
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
context "with small tolerance" do
|
55
|
+
let(:options) { { mode: :grayscale, tolerance: 8 } }
|
56
|
+
|
57
|
+
context "darker image" do
|
58
|
+
it "score around 0.9" do
|
59
|
+
expect(subject.score).to be_within(0.005).of(0.9)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
context "different image" do
|
64
|
+
let(:path_2) { image_path "b" }
|
65
|
+
|
66
|
+
it "score around 0.027" do
|
67
|
+
expect(subject.score).to be_within(0.0005).of(0.027)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Imatcher::Modes::RGB do
|
4
|
+
let(:path_1) { image_path "a" }
|
5
|
+
let(:path_2) { image_path "darker" }
|
6
|
+
subject { Imatcher.compare(path_1, path_2, options) }
|
7
|
+
let(:options) { {} }
|
8
|
+
|
9
|
+
context "with similar images" do
|
10
|
+
it "score equals to 1" do
|
11
|
+
expect(subject.score).to eq 1
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
context "with different images" do
|
16
|
+
let(:path_2) { image_path "b" }
|
17
|
+
|
18
|
+
it "score around 0.97" do
|
19
|
+
expect(subject.score).to be_within(0.005).of(0.97)
|
20
|
+
end
|
21
|
+
|
22
|
+
it "creates correct difference image" do
|
23
|
+
expect(subject.difference_image).to eq(Imatcher::Image.from_file(image_path("rgb_diff")))
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Imatcher::Matcher do
|
4
|
+
describe "new" do
|
5
|
+
subject { Imatcher::Matcher.new(options) }
|
6
|
+
|
7
|
+
context "without options" do
|
8
|
+
let(:options) { {} }
|
9
|
+
|
10
|
+
it { expect(subject.mode.threshold).to eq 0 }
|
11
|
+
it { expect(subject.mode).to be_a Imatcher::Modes::RGB }
|
12
|
+
end
|
13
|
+
|
14
|
+
context "with custom threshold" do
|
15
|
+
let(:options) { { threshold: 0.1 } }
|
16
|
+
|
17
|
+
it { expect(subject.mode.threshold).to eq 0.1 }
|
18
|
+
end
|
19
|
+
|
20
|
+
context "with custom options" do
|
21
|
+
let(:options) { { mode: :grayscale, tolerance: 0 } }
|
22
|
+
|
23
|
+
it { expect(subject.mode.tolerance).to eq 0 }
|
24
|
+
end
|
25
|
+
|
26
|
+
context "with custom mode" do
|
27
|
+
let(:options) { { mode: :delta } }
|
28
|
+
|
29
|
+
it { expect(subject.mode).to be_a Imatcher::Modes::Delta }
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
describe "compare" do
|
34
|
+
let(:path_1) { image_path "very_small" }
|
35
|
+
let(:path_2) { image_path "very_small" }
|
36
|
+
subject { Imatcher::Matcher.new.compare(path_1, path_2) }
|
37
|
+
|
38
|
+
it { expect(subject).to be_a Imatcher::Result }
|
39
|
+
|
40
|
+
context "when sizes mismatch" do
|
41
|
+
let(:path_2) { image_path "small" }
|
42
|
+
it { expect { subject }.to raise_error(Imatcher::SizesMismatchError) }
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
data/spec/spec_helper.rb
CHANGED
@@ -2,6 +2,13 @@ $LOAD_PATH.unshift File.expand_path('../../lib', __FILE__)
|
|
2
2
|
|
3
3
|
require 'rspec'
|
4
4
|
require 'pry-byebug'
|
5
|
+
|
6
|
+
if ENV['COVER']
|
7
|
+
require 'simplecov'
|
8
|
+
SimpleCov.root File.join(File.dirname(__FILE__), '..')
|
9
|
+
SimpleCov.start
|
10
|
+
end
|
11
|
+
|
5
12
|
require 'imatcher'
|
6
13
|
|
7
14
|
Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each { |f| require f }
|
@@ -9,3 +16,7 @@ Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each { |f| require f }
|
|
9
16
|
RSpec.configure do |config|
|
10
17
|
config.mock_with :rspec
|
11
18
|
end
|
19
|
+
|
20
|
+
def image_path(name)
|
21
|
+
"#{File.dirname(__FILE__)}/fixtures/#{name}.png"
|
22
|
+
end
|
metadata
CHANGED
@@ -1,29 +1,43 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: imatcher
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- palkan
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-02-
|
11
|
+
date: 2016-02-17 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
|
-
name:
|
14
|
+
name: chunky_png
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
17
|
- - "~>"
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version:
|
20
|
-
type: :
|
19
|
+
version: 1.3.5
|
20
|
+
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version:
|
26
|
+
version: 1.3.5
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: simplecov
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 0.3.8
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 0.3.8
|
27
41
|
- !ruby/object:Gem::Dependency
|
28
42
|
name: rake
|
29
43
|
requirement: !ruby/object:Gem::Requirement
|
@@ -85,8 +99,29 @@ files:
|
|
85
99
|
- bin/setup
|
86
100
|
- imatcher.gemspec
|
87
101
|
- lib/imatcher.rb
|
102
|
+
- lib/imatcher/color_methods.rb
|
103
|
+
- lib/imatcher/image.rb
|
104
|
+
- lib/imatcher/matcher.rb
|
105
|
+
- lib/imatcher/modes.rb
|
106
|
+
- lib/imatcher/modes/base.rb
|
107
|
+
- lib/imatcher/modes/delta.rb
|
108
|
+
- lib/imatcher/modes/grayscale.rb
|
109
|
+
- lib/imatcher/modes/rgb.rb
|
110
|
+
- lib/imatcher/result.rb
|
88
111
|
- lib/imatcher/version.rb
|
112
|
+
- spec/fixtures/a.png
|
113
|
+
- spec/fixtures/b.png
|
114
|
+
- spec/fixtures/darker.png
|
115
|
+
- spec/fixtures/delta_diff.png
|
116
|
+
- spec/fixtures/grayscale_diff.png
|
117
|
+
- spec/fixtures/rgb_diff.png
|
118
|
+
- spec/fixtures/small.png
|
119
|
+
- spec/fixtures/very_small.png
|
89
120
|
- spec/imatcher_spec.rb
|
121
|
+
- spec/integrations/delta_spec.rb
|
122
|
+
- spec/integrations/grayscale_spec.rb
|
123
|
+
- spec/integrations/rgb_spec.rb
|
124
|
+
- spec/matcher_spec.rb
|
90
125
|
- spec/spec_helper.rb
|
91
126
|
homepage: http://github.com/teachbase/imatcher
|
92
127
|
licenses:
|