imatcher 0.1.3 → 0.1.4

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 7b023655f4f68f37b2d4a48c89dc11b367b2fa77
4
- data.tar.gz: 542127918bee9dbc4a0b849f852f36eb138d6a8f
3
+ metadata.gz: c95e7ff691b3bd8a4c0225764cd6352e0b013cc3
4
+ data.tar.gz: 2ad7efe2e068f910d686d18b3f84ff35832354d2
5
5
  SHA512:
6
- metadata.gz: 1bb1b4da86c334eaf59c7a33c5e95b85f1304b6af8859e5f6928af4c0dabdf4388cf8b8c1b7a2e79b1ab5e8ea7a2fb2661f74b1c4fd8e459ee44619ba1057207
7
- data.tar.gz: 7ba78053279d58196c07a29700caf8f1fcdc3366780a997e53f7100bb36438a71e0e39995a2cb279dbd0ce1ef44abe151aed6ecc9f441bd40007c1dbeaf22381
6
+ metadata.gz: 14b97bfa6fc938c673aa126a56604bd51c1766b596e26302dd08416299219e058d8e294260f06c72099066e77df322ec393010541b2b57f3d46515eb96ece271
7
+ data.tar.gz: 0ccb28a3281c111a3a552f1a42c657113e868e8e2bc6cd6eab106ce281c1aba92a49f688ffa453b3d6fa141170839e9bf01758d570e3447f25bc15b62e30e689
data/README.md CHANGED
@@ -88,6 +88,22 @@ res = Imatcher::Matcher.new(options).compare(path_1, path_2)
88
88
 
89
89
  ```
90
90
 
91
+ ## Excluding area
92
+
93
+ <img src="https://raw.githubusercontent.com/teachbase/imatcher/master/spec/fixtures/a.png" width="300" />
94
+ <img src="https://raw.githubusercontent.com/teachbase/imatcher/master/spec/fixtures/a1.png" width="300" />
95
+
96
+ You can exclude area from comparing by passing `:exclude_area` to `compare`.
97
+ E.g., if `path_1` and `path_2` contain images above
98
+ ```ruby
99
+ Imatcher.compare(path_1, path_2, exclude_area: [200, 150, 275, 200]).match? # => true
100
+ ```
101
+ `[200, 150, 275, 200]` is array of two vertices of rectangle -- (200, 150) is left-top vertex and (275, 200) is right-bottom.
102
+
103
+ ## Including area
104
+
105
+ You can set bounds of comparing by passing `:include_area` to `compare` with array similar to previous example
106
+
91
107
  ## Contributing
92
108
 
93
109
  Bug reports and pull requests are welcome on GitHub at https://github.com/teachbase/imatcher.
@@ -6,7 +6,6 @@ Gem::Specification.new do |spec|
6
6
  spec.version = Imatcher::VERSION
7
7
  spec.authors = ["palkan"]
8
8
  spec.email = ["dementiev.vm@gmail.com"]
9
-
10
9
  spec.summary = "Image comparison lib"
11
10
  spec.description = "Image comparison lib built on top of ChunkyPNG"
12
11
  spec.homepage = "http://github.com/teachbase/imatcher"
@@ -11,5 +11,17 @@ module Imatcher
11
11
  def brightness(a)
12
12
  0.3 * r(a) + 0.59 * g(a) + 0.11 * b(a)
13
13
  end
14
+
15
+ def red
16
+ rgb(255, 0, 0)
17
+ end
18
+
19
+ def green
20
+ rgb(0, 255, 0)
21
+ end
22
+
23
+ def blue
24
+ rgb(0, 0, 255)
25
+ end
14
26
  end
15
27
  end
@@ -13,11 +13,13 @@ module Imatcher
13
13
  end
14
14
  end
15
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)
16
+ def compare_each_pixel(image, area: nil)
17
+ area = bounding_rect if area.nil?
18
+ (area.top..area.bot).each do |y|
19
+ range = (area.left..area.right)
20
+ next if image.row(y).slice(range) == row(y).slice(range)
21
+ (area.left..area.right).each do |x|
22
+ yield(self[x, y], image[x, y], x, y)
21
23
  end
22
24
  end
23
25
  end
@@ -40,12 +42,19 @@ module Imatcher
40
42
  [width, height] == [image.width, image.height]
41
43
  end
42
44
 
43
- def render_bounds(left, bot, right, top)
44
- rect(left, bot, right, top, rgb(255, 0, 0))
45
- end
46
-
47
45
  def inspect
48
46
  "Image:#{object_id}<#{width}x#{height}>"
49
47
  end
48
+
49
+ def highlight_rectangle(rect, color = :red)
50
+ fail ArgumentError, "Undefined color: #{color}" unless respond_to?(color)
51
+ return self if rect.nil?
52
+ rect(*rect.bounds, send(color))
53
+ self
54
+ end
55
+
56
+ def bounding_rect
57
+ Rectangle.new(0, 0, width - 1, height - 1)
58
+ end
50
59
  end
51
60
  end
@@ -15,17 +15,35 @@ module Imatcher
15
15
 
16
16
  def initialize(options = {})
17
17
  mode_type = options.delete(:mode) || :rgb
18
+ fail ArgumentError, "Undefined mode: #{ mode_type }" unless MODES.keys.include?(mode_type)
18
19
  @mode = Modes.const_get(MODES[mode_type]).new(options)
19
20
  end
20
21
 
21
22
  def compare(a, b)
22
23
  a = Image.from_file(a) unless a.is_a?(Image)
23
24
  b = Image.from_file(b) unless b.is_a?(Image)
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)
25
+
26
+ fail SizesMismatchError,
27
+ "Size mismatch: first image size: " \
28
+ "#{a.width}x#{a.height}, " \
29
+ "second image size: " \
30
+ "#{b.width}x#{b.height}" unless a.sizes_match?(b)
31
+
32
+ image_area = Rectangle.new(0, 0, a.width - 1, a.height - 1)
33
+
34
+ unless mode.exclude_rect.nil?
35
+ fail ArgumentError,
36
+ "Bounds must be in image" unless image_area.contains?(mode.exclude_rect)
37
+ end
38
+
39
+ unless mode.include_rect.nil?
40
+ fail ArgumentError,
41
+ "Bounds must be in image" unless image_area.contains?(mode.include_rect)
42
+ unless mode.exclude_rect.nil?
43
+ fail ArgumentError,
44
+ "Included area must contain excluded" unless mode.include_rect.contains?(mode.exclude_rect)
45
+ end
46
+ end
29
47
 
30
48
  mode.compare(a, b)
31
49
  end
@@ -1,21 +1,26 @@
1
1
  module Imatcher
2
2
  module Modes
3
3
  class Base # :nodoc:
4
+ require 'imatcher/rectangle'
4
5
  include ColorMethods
5
6
 
6
- attr_reader :result, :threshold, :bounds
7
+ attr_reader :result, :threshold, :bounds, :exclude_rect, :include_rect
7
8
 
8
- def initialize(threshold: 0.0)
9
+ def initialize(threshold: 0.0, exclude_rect: nil, include_rect: nil)
10
+ @include_rect = Rectangle.new(*include_rect) unless include_rect.nil?
11
+ @exclude_rect = Rectangle.new(*exclude_rect) unless exclude_rect.nil?
9
12
  @threshold = threshold
10
13
  @result = Result.new(self, threshold)
11
14
  end
12
15
 
13
16
  def compare(a, b)
14
17
  result.image = a
15
- @bounds = [0, 0, result.image.width - 1, result.image.height - 1]
18
+ @include_rect ||= a.bounding_rect
19
+ @bounds = Rectangle.new(*include_rect.bounds)
16
20
 
17
- b.compare_each_pixel(a) do |b_pixel, a_pixel, x, y|
21
+ b.compare_each_pixel(a, area: include_rect) do |b_pixel, a_pixel, x, y|
18
22
  next if pixels_equal?(b_pixel, a_pixel)
23
+ next if !exclude_rect.nil? && exclude_rect.contains_point?(x, y)
19
24
  update_result(b_pixel, a_pixel, x, y)
20
25
  end
21
26
 
@@ -24,15 +29,17 @@ module Imatcher
24
29
  end
25
30
 
26
31
  def diff(bg, diff)
27
- diff_image = background(bg)
32
+ diff_image = background(bg).highlight_rectangle(exclude_rect, :blue)
28
33
  diff.each do |pixels_pair|
29
34
  pixels_diff(diff_image, *pixels_pair)
30
35
  end
31
- create_diff_image(bg, diff_image).render_bounds(*bounds)
36
+ create_diff_image(bg, diff_image).
37
+ highlight_rectangle(bounds).
38
+ highlight_rectangle(include_rect, :green)
32
39
  end
33
40
 
34
41
  def score
35
- @result.diff.length.to_f / @result.image.pixels.length
42
+ result.diff.length.to_f / area
36
43
  end
37
44
 
38
45
  def update_result(*_args, x, y)
@@ -40,10 +47,16 @@ module Imatcher
40
47
  end
41
48
 
42
49
  def update_bounds(x, y)
43
- bounds[0] = [x, bounds[0]].max
44
- bounds[1] = [y, bounds[1]].max
45
- bounds[2] = [x, bounds[2]].min
46
- bounds[3] = [y, bounds[3]].min
50
+ bounds.left = [x, bounds.left].max
51
+ bounds.top = [y, bounds.top].max
52
+ bounds.right = [x, bounds.right].min
53
+ bounds.bot = [y, bounds.bot].min
54
+ end
55
+
56
+ def area
57
+ area = include_rect.area
58
+ return area if exclude_rect.nil?
59
+ area - exclude_rect.area
47
60
  end
48
61
  end
49
62
  end
@@ -47,7 +47,7 @@ module Imatcher
47
47
  end
48
48
 
49
49
  def score
50
- @delta_score / result.image.pixels.length
50
+ @delta_score / area
51
51
  end
52
52
  end
53
53
  end
@@ -18,7 +18,7 @@ module Imatcher
18
18
  Image.new(bg.width, bg.height, BLACK)
19
19
  end
20
20
 
21
- def create_diff_image(bg, diff_image)
21
+ def create_diff_image(_bg, diff_image)
22
22
  diff_image
23
23
  end
24
24
 
@@ -0,0 +1,31 @@
1
+ module Imatcher
2
+ class Rectangle
3
+ attr_accessor :left, :top, :right, :bot
4
+
5
+ def initialize(l, t, r, b)
6
+ @left = l
7
+ @top = t
8
+ @right = r
9
+ @bot = b
10
+ end
11
+
12
+ def area
13
+ (right - left + 1) * (bot - top + 1)
14
+ end
15
+
16
+ def contains?(rect)
17
+ (left <= rect.left) &&
18
+ (right >= rect.right) &&
19
+ (top <= rect.top) &&
20
+ (bot >= rect.bot)
21
+ end
22
+
23
+ def bounds
24
+ [left, top, right, bot]
25
+ end
26
+
27
+ def contains_point?(x, y)
28
+ x.between?(left, right) && y.between?(top, bot)
29
+ end
30
+ end
31
+ end
@@ -1,3 +1,3 @@
1
1
  module Imatcher # :nodoc:
2
- VERSION = "0.1.3".freeze
2
+ VERSION = "0.1.4".freeze
3
3
  end
Binary file
Binary file
Binary file
Binary file
Binary file
@@ -0,0 +1,11 @@
1
+ require 'spec_helper'
2
+
3
+ describe Imatcher::Image do
4
+ describe "highlight_rectangle" do
5
+ let(:image) { described_class.new(10, 10, described_class::BLACK) }
6
+ let(:rect) { Imatcher::Rectangle.new(0, 0, 1, 1) }
7
+ subject { image.highlight_rectangle(rect, :deep_purple) }
8
+
9
+ it { expect { subject }.to raise_error(ArgumentError) }
10
+ end
11
+ end
@@ -18,13 +18,13 @@ describe Imatcher::Modes::Delta do
18
18
  context "below score" do
19
19
  let(:options) { { mode: :delta, threshold: 0.01 } }
20
20
 
21
- it { expect(subject).to eq false }
21
+ it { expect(subject).to be_falsey }
22
22
  end
23
23
 
24
24
  context "above score" do
25
25
  let(:options) { { mode: :delta, threshold: 0.1 } }
26
26
 
27
- it { expect(subject).to eq true }
27
+ it { expect(subject).to be_truthy }
28
28
  end
29
29
  end
30
30
  end
@@ -23,4 +23,29 @@ describe Imatcher::Modes::RGB do
23
23
  expect(subject.difference_image).to eq(Imatcher::Image.from_file(image_path("rgb_diff")))
24
24
  end
25
25
  end
26
+
27
+ context "exclude rect" do
28
+ let(:options) { { exclude_rect: [200, 150, 275, 200] } }
29
+ let(:path_2) { image_path "a1" }
30
+ it { expect(subject.difference_image).to eq Imatcher::Image.from_file(image_path("exclude")) }
31
+ it { expect(subject.score).to eq 0 }
32
+
33
+ context "calculates score correctly" do
34
+ let(:path_2) { image_path "darker" }
35
+
36
+ it { expect(subject.score).to eq 1 }
37
+ end
38
+ end
39
+
40
+ context "include rect" do
41
+ let(:options) { { include_rect: [0, 0, 100, 100] } }
42
+ let(:path_2) { image_path "a1" }
43
+ it { expect(subject.difference_image).to eq Imatcher::Image.from_file(image_path("include")) }
44
+ it { expect(subject.score).to eq 0 }
45
+
46
+ context "calculates score correctly" do
47
+ let(:path_2) { image_path "darker" }
48
+ it { expect(subject.score).to eq 1 }
49
+ end
50
+ end
26
51
  end
@@ -28,18 +28,50 @@ describe Imatcher::Matcher do
28
28
 
29
29
  it { expect(subject.mode).to be_a Imatcher::Modes::Delta }
30
30
  end
31
+
32
+ context "with undefined mode" do
33
+ let(:options) { { mode: :gamma } }
34
+
35
+ it { expect { subject }.to raise_error(ArgumentError) }
36
+ end
31
37
  end
32
38
 
33
39
  describe "compare" do
34
40
  let(:path_1) { image_path "very_small" }
35
41
  let(:path_2) { image_path "very_small" }
36
- subject { Imatcher::Matcher.new.compare(path_1, path_2) }
42
+ let(:options) { {} }
43
+ subject { Imatcher.compare(path_1, path_2, options) }
37
44
 
38
45
  it { expect(subject).to be_a Imatcher::Result }
39
46
 
40
47
  context "when sizes mismatch" do
41
48
  let(:path_2) { image_path "small" }
42
- it { expect { subject }.to raise_error(Imatcher::SizesMismatchError) }
49
+ it { expect { subject }.to raise_error Imatcher::SizesMismatchError }
50
+ end
51
+
52
+ context "with negative exclude rect bounds" do
53
+ let(:options) { { exclude_rect: [-1, -1, -1, -1] } }
54
+ it { expect { subject }.to raise_error ArgumentError }
55
+ end
56
+
57
+ context "with big exclude rect bounds" do
58
+ let(:options) { { exclude_rect: [100, 100, 100, 100] } }
59
+ it { expect { subject }.to raise_error ArgumentError }
60
+ end
61
+
62
+ context "with negative include rect bounds" do
63
+ let(:options) { { include_rect: [-1, -1, -1, -1] } }
64
+ it { expect { subject }.to raise_error ArgumentError }
65
+ end
66
+
67
+ context "with big include rect bounds" do
68
+ let(:options) { { include_rect: [100, 100, 100, 100] } }
69
+ it { expect { subject }.to raise_error ArgumentError }
70
+ end
71
+
72
+ context "with wrong include and exclude rects combination" do
73
+ let(:options) { { include_rect: [1, 1, 2, 2], exclude_rect: [0, 0, 1, 1] } }
74
+ it { expect { subject }.to raise_error ArgumentError }
43
75
  end
44
76
  end
45
77
  end
@@ -0,0 +1,37 @@
1
+ require 'spec_helper'
2
+
3
+ describe Imatcher::Rectangle do
4
+ let(:rect) { described_class.new(0, 0, 9, 9) }
5
+
6
+ describe 'area' do
7
+ subject { rect.area }
8
+
9
+ it { expect(subject).to eq 100 }
10
+ end
11
+
12
+ describe "contains?" do
13
+ let(:rect2) { described_class.new(1, 1, 8, 8) }
14
+ subject { rect.contains?(rect2) }
15
+
16
+ it { expect(subject).to be_truthy }
17
+
18
+ context "when does not contain" do
19
+ let(:rect2) { described_class.new(2, 2, 10, 10) }
20
+
21
+ it { expect(subject).to be_falsey }
22
+ end
23
+ end
24
+
25
+ describe "contains_point?" do
26
+ let(:point) { [5, 5] }
27
+ subject { rect.contains_point?(*point) }
28
+
29
+ it { expect(subject).to be_truthy }
30
+
31
+ context "when does not contain" do
32
+ let(:point) { [10, 10] }
33
+
34
+ it { expect(subject).to be_falsey }
35
+ end
36
+ end
37
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: imatcher
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.3
4
+ version: 0.1.4
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-18 00:00:00.000000000 Z
11
+ date: 2016-03-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: oily_png
@@ -94,21 +94,27 @@ files:
94
94
  - lib/imatcher/modes/delta.rb
95
95
  - lib/imatcher/modes/grayscale.rb
96
96
  - lib/imatcher/modes/rgb.rb
97
+ - lib/imatcher/rectangle.rb
97
98
  - lib/imatcher/result.rb
98
99
  - lib/imatcher/version.rb
99
100
  - spec/fixtures/a.png
101
+ - spec/fixtures/a1.png
100
102
  - spec/fixtures/b.png
101
103
  - spec/fixtures/darker.png
102
104
  - spec/fixtures/delta_diff.png
105
+ - spec/fixtures/exclude.png
103
106
  - spec/fixtures/grayscale_diff.png
107
+ - spec/fixtures/include.png
104
108
  - spec/fixtures/rgb_diff.png
105
109
  - spec/fixtures/small.png
106
110
  - spec/fixtures/very_small.png
111
+ - spec/image_spec.rb
107
112
  - spec/imatcher_spec.rb
108
113
  - spec/integrations/delta_spec.rb
109
114
  - spec/integrations/grayscale_spec.rb
110
115
  - spec/integrations/rgb_spec.rb
111
116
  - spec/matcher_spec.rb
117
+ - spec/rectangle_spec.rb
112
118
  - spec/spec_helper.rb
113
119
  homepage: http://github.com/teachbase/imatcher
114
120
  licenses:
@@ -135,3 +141,4 @@ signing_key:
135
141
  specification_version: 4
136
142
  summary: Image comparison lib
137
143
  test_files: []
144
+ has_rdoc: