capybara-screenshot-diff 1.7.1 → 1.8.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/Rakefile +29 -0
- data/capybara-screenshot-diff.gemspec +6 -3
- data/gems.rb +8 -2
- data/lib/capybara/screenshot/diff/browser_helpers.rb +29 -28
- data/lib/capybara/screenshot/diff/cucumber.rb +11 -0
- data/lib/capybara/screenshot/diff/difference.rb +63 -0
- data/lib/capybara/screenshot/diff/drivers/base_driver.rb +42 -0
- data/lib/capybara/screenshot/diff/drivers/chunky_png_driver.rb +188 -260
- data/lib/capybara/screenshot/diff/drivers/utils.rb +18 -0
- data/lib/capybara/screenshot/diff/drivers/vips_driver.rb +54 -104
- data/lib/capybara/screenshot/diff/drivers.rb +16 -0
- data/lib/capybara/screenshot/diff/image_compare.rb +125 -154
- data/lib/capybara/screenshot/diff/os.rb +1 -1
- data/lib/capybara/screenshot/diff/screenshot_matcher.rb +128 -0
- data/lib/capybara/screenshot/diff/screenshoter.rb +136 -0
- data/lib/capybara/screenshot/diff/stabilization.rb +0 -184
- data/lib/capybara/screenshot/diff/stable_screenshoter.rb +106 -0
- data/lib/capybara/screenshot/diff/test_methods.rb +51 -90
- data/lib/capybara/screenshot/diff/vcs.rb +44 -22
- data/lib/capybara/screenshot/diff/version.rb +1 -1
- data/lib/capybara/screenshot/diff.rb +13 -17
- data/sig/capybara/screenshot/diff/diff.rbs +28 -0
- data/sig/capybara/screenshot/diff/difference.rbs +33 -0
- data/sig/capybara/screenshot/diff/drivers/base_driver.rbs +63 -0
- data/sig/capybara/screenshot/diff/drivers/browser_helpers.rbs +36 -0
- data/sig/capybara/screenshot/diff/drivers/chunky_png_driver.rbs +89 -0
- data/sig/capybara/screenshot/diff/drivers/utils.rbs +13 -0
- data/sig/capybara/screenshot/diff/drivers/vips_driver.rbs +25 -0
- data/sig/capybara/screenshot/diff/image_compare.rbs +93 -0
- data/sig/capybara/screenshot/diff/os.rbs +11 -0
- data/sig/capybara/screenshot/diff/region.rbs +43 -0
- data/sig/capybara/screenshot/diff/screenshot_matcher.rbs +60 -0
- data/sig/capybara/screenshot/diff/screenshoter.rbs +48 -0
- data/sig/capybara/screenshot/diff/stable_screenshoter.rbs +29 -0
- data/sig/capybara/screenshot/diff/test_methods.rbs +39 -0
- data/sig/capybara/screenshot/diff/vcs.rbs +17 -0
- metadata +28 -25
- data/.gitattributes +0 -4
- data/.github/dependabot.yml +0 -8
- data/.github/workflows/lint.yml +0 -25
- data/.github/workflows/test.yml +0 -138
- data/.gitignore +0 -14
- data/.standard.yml +0 -12
- data/CONTRIBUTING.md +0 -24
- data/Dockerfile +0 -59
- data/README.md +0 -567
- data/bin/bundle +0 -114
- data/bin/console +0 -15
- data/bin/install-vips +0 -11
- data/bin/rake +0 -27
- data/bin/setup +0 -8
- data/bin/standardrb +0 -29
- data/gemfiles/rails60_gems.rb +0 -8
- data/gemfiles/rails61_gems.rb +0 -7
- data/gemfiles/rails70_gems.rb +0 -7
- data/tmp/.keep +0 -0
@@ -2,38 +2,17 @@
|
|
2
2
|
|
3
3
|
require "chunky_png"
|
4
4
|
|
5
|
+
require "capybara/screenshot/diff/drivers/base_driver"
|
6
|
+
|
5
7
|
module Capybara
|
6
8
|
module Screenshot
|
7
9
|
module Diff
|
8
10
|
# Compare two images and determine if they are equal, different, or within some comparison
|
9
11
|
# range considering color values and difference area size.
|
10
12
|
module Drivers
|
11
|
-
class ChunkyPNGDriver
|
13
|
+
class ChunkyPNGDriver < BaseDriver
|
12
14
|
include ChunkyPNG::Color
|
13
15
|
|
14
|
-
attr_reader :new_file_name, :old_file_name
|
15
|
-
attr_accessor :skip_area, :color_distance_limit, :shift_distance_limit
|
16
|
-
|
17
|
-
def initialize(new_file_name, old_file_name = nil, options = {})
|
18
|
-
options = old_file_name if old_file_name.is_a?(Hash)
|
19
|
-
|
20
|
-
@new_file_name = new_file_name
|
21
|
-
@old_file_name = old_file_name || "#{new_file_name}#{ImageCompare::TMP_FILE_SUFFIX}"
|
22
|
-
|
23
|
-
@color_distance_limit = options[:color_distance_limit]
|
24
|
-
@shift_distance_limit = options[:shift_distance_limit]
|
25
|
-
@skip_area = options[:skip_area]
|
26
|
-
|
27
|
-
reset
|
28
|
-
end
|
29
|
-
|
30
|
-
# Resets the calculated data about the comparison with regard to the "new_image".
|
31
|
-
# Data about the original image is kept.
|
32
|
-
def reset
|
33
|
-
@max_color_distance = @color_distance_limit ? 0 : nil
|
34
|
-
@max_shift_distance = @shift_distance_limit ? 0 : nil
|
35
|
-
end
|
36
|
-
|
37
16
|
def load_images(old_file_name, new_file_name)
|
38
17
|
old_bytes, new_bytes = load_image_files(old_file_name, new_file_name)
|
39
18
|
|
@@ -48,65 +27,8 @@ module Capybara
|
|
48
27
|
image
|
49
28
|
end
|
50
29
|
|
51
|
-
def
|
52
|
-
|
53
|
-
end
|
54
|
-
|
55
|
-
def image_area_size(old_img)
|
56
|
-
width_for(old_img) * height_for(old_img)
|
57
|
-
end
|
58
|
-
|
59
|
-
def shift_distance_equal?
|
60
|
-
# Stub
|
61
|
-
false
|
62
|
-
end
|
63
|
-
|
64
|
-
def shift_distance_different?
|
65
|
-
# Stub
|
66
|
-
true
|
67
|
-
end
|
68
|
-
|
69
|
-
def find_difference_region(new_image, old_image, color_distance_limit, shift_distance_limit, area_size_limit, fast_fail: false)
|
70
|
-
return nil, nil if new_image.pixels == old_image.pixels
|
71
|
-
|
72
|
-
if fast_fail && !(color_distance_limit || shift_distance_limit || area_size_limit)
|
73
|
-
return build_region_for_whole_image(new_image), nil
|
74
|
-
end
|
75
|
-
|
76
|
-
region = find_top(old_image, new_image)
|
77
|
-
region = if region.nil? || region[1].nil?
|
78
|
-
nil
|
79
|
-
else
|
80
|
-
find_diff_rectangle(old_image, new_image, region)
|
81
|
-
end
|
82
|
-
|
83
|
-
[region, nil]
|
84
|
-
end
|
85
|
-
|
86
|
-
def height_for(image)
|
87
|
-
image.height
|
88
|
-
end
|
89
|
-
|
90
|
-
def width_for(image)
|
91
|
-
image.width
|
92
|
-
end
|
93
|
-
|
94
|
-
def max_color_distance
|
95
|
-
calculate_metrics unless @max_color_distance
|
96
|
-
@max_color_distance
|
97
|
-
end
|
98
|
-
|
99
|
-
def max_shift_distance
|
100
|
-
calculate_metrics unless @max_shift_distance || !@shift_distance_limit
|
101
|
-
@max_shift_distance
|
102
|
-
end
|
103
|
-
|
104
|
-
def adds_error_details_to(log)
|
105
|
-
max_color_distance = self.max_color_distance.ceil(1)
|
106
|
-
max_shift_distance = self.max_shift_distance
|
107
|
-
|
108
|
-
log[:max_color_distance] = max_color_distance
|
109
|
-
log.merge!(max_shift_distance: max_shift_distance) if max_shift_distance
|
30
|
+
def find_difference_region(comparison)
|
31
|
+
DifferenceRegionFinder.new(comparison, self).perform
|
110
32
|
end
|
111
33
|
|
112
34
|
def crop(region, i)
|
@@ -114,46 +36,7 @@ module Capybara
|
|
114
36
|
end
|
115
37
|
|
116
38
|
def from_file(filename)
|
117
|
-
ChunkyPNG::Image.from_file(filename)
|
118
|
-
end
|
119
|
-
|
120
|
-
# private
|
121
|
-
|
122
|
-
def calculate_metrics
|
123
|
-
old_file, new_file = load_image_files(@old_file_name, @new_file_name)
|
124
|
-
|
125
|
-
if old_file == new_file
|
126
|
-
@max_color_distance = 0
|
127
|
-
@max_shift_distance = 0
|
128
|
-
return
|
129
|
-
end
|
130
|
-
|
131
|
-
old_image, new_image = _load_images(old_file, new_file)
|
132
|
-
calculate_max_color_distance(new_image, old_image)
|
133
|
-
calculate_max_shift_limit(new_image, old_image) if @shift_distance_limit
|
134
|
-
end
|
135
|
-
|
136
|
-
def calculate_max_color_distance(new_image, old_image)
|
137
|
-
pixel_pairs = old_image.pixels.zip(new_image.pixels)
|
138
|
-
@max_color_distance = pixel_pairs.inject(0) { |max, (p1, p2)|
|
139
|
-
next max unless p1 && p2
|
140
|
-
|
141
|
-
d = ChunkyPNG::Color.euclidean_distance_rgba(p1, p2)
|
142
|
-
[max, d].max
|
143
|
-
}
|
144
|
-
end
|
145
|
-
|
146
|
-
def calculate_max_shift_limit(new_img, old_img)
|
147
|
-
(0...new_img.width).each do |x|
|
148
|
-
(0...new_img.height).each do |y|
|
149
|
-
shift_distance =
|
150
|
-
shift_distance_at(new_img, old_img, x, y, color_distance_limit: @color_distance_limit)
|
151
|
-
if shift_distance && (@max_shift_distance.nil? || shift_distance > @max_shift_distance)
|
152
|
-
@max_shift_distance = shift_distance
|
153
|
-
return if @max_shift_distance == Float::INFINITY # rubocop: disable Lint/NonLocalExitFromIterator
|
154
|
-
end
|
155
|
-
end
|
156
|
-
end
|
39
|
+
ChunkyPNG::Image.from_file(filename.to_s)
|
157
40
|
end
|
158
41
|
|
159
42
|
def save_image_to(image, filename)
|
@@ -165,203 +48,248 @@ module Capybara
|
|
165
48
|
end
|
166
49
|
|
167
50
|
def load_image_files(old_file_name, file_name)
|
168
|
-
|
169
|
-
new_file = File.binread(file_name)
|
170
|
-
[old_file, new_file]
|
171
|
-
end
|
172
|
-
|
173
|
-
def dimension_changed?(old_image, new_image)
|
174
|
-
return unless old_image.dimension != new_image.dimension
|
175
|
-
|
176
|
-
change_msg = [old_image, new_image].map { |i| "#{i.width}x#{i.height}" }.join(" => ")
|
177
|
-
warn "Image size has changed for #{@new_file_name}: #{change_msg}"
|
178
|
-
true
|
51
|
+
[File.binread(old_file_name), File.binread(file_name)]
|
179
52
|
end
|
180
53
|
|
181
|
-
def draw_rectangles(images, region, (r, g, b))
|
54
|
+
def draw_rectangles(images, region, (r, g, b), offset: 0)
|
182
55
|
border_color = ChunkyPNG::Color.rgb(r, g, b)
|
183
56
|
border_shadow = ChunkyPNG::Color.rgba(r, g, b, 100)
|
184
57
|
|
185
58
|
images.map do |image|
|
186
59
|
new_img = image.dup
|
187
|
-
new_img.rect(region.left -
|
60
|
+
new_img.rect(region.left - offset, region.top - offset, region.right + offset, region.bottom + offset, border_color)
|
188
61
|
new_img.rect(region.left, region.top, region.right, region.bottom, border_shadow)
|
189
62
|
new_img
|
190
63
|
end
|
191
64
|
end
|
192
65
|
|
66
|
+
def same_pixels?(comparison)
|
67
|
+
comparison.new_image == comparison.base_image
|
68
|
+
end
|
69
|
+
|
193
70
|
private
|
194
71
|
|
195
|
-
def
|
196
|
-
|
72
|
+
def _load_images(old_file, new_file)
|
73
|
+
[ChunkyPNG::Image.from_blob(old_file), ChunkyPNG::Image.from_blob(new_file)]
|
197
74
|
end
|
198
75
|
|
199
|
-
|
200
|
-
|
201
|
-
bottom = find_bottom(org_img, new_img, left, right, bottom)
|
76
|
+
class DifferenceRegionFinder
|
77
|
+
attr_accessor :skip_area, :color_distance_limit, :shift_distance_limit
|
202
78
|
|
203
|
-
|
204
|
-
|
79
|
+
def initialize(comparison, driver = nil)
|
80
|
+
@comparison = comparison
|
81
|
+
@driver = driver
|
205
82
|
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
return [x, y, x, y] unless same_color?(old_img, new_img, x, y)
|
210
|
-
end
|
83
|
+
@color_distance_limit = comparison.options[:color_distance_limit]
|
84
|
+
@shift_distance_limit = comparison.options[:shift_distance_limit]
|
85
|
+
@skip_area = comparison.options[:skip_area]
|
211
86
|
end
|
212
|
-
nil
|
213
|
-
end
|
214
87
|
|
215
|
-
|
216
|
-
|
88
|
+
def perform
|
89
|
+
find_difference_region(@comparison)
|
90
|
+
end
|
217
91
|
|
218
|
-
|
219
|
-
|
220
|
-
right = region[2] || 0
|
221
|
-
bottom = region[3]
|
92
|
+
def find_difference_region(comparison)
|
93
|
+
new_image, base_image, = comparison.new_image, comparison.base_image
|
222
94
|
|
223
|
-
|
224
|
-
|
225
|
-
|
95
|
+
meta = {}
|
96
|
+
meta[:max_color_distance] = 0
|
97
|
+
meta[:max_shift_distance] = 0 if shift_distance_limit
|
226
98
|
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
99
|
+
region = find_top(base_image, new_image, cache: meta)
|
100
|
+
region = if region.nil? || region[1].nil?
|
101
|
+
nil
|
102
|
+
else
|
103
|
+
find_diff_rectangle(base_image, new_image, region, cache: meta)
|
232
104
|
end
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
105
|
+
|
106
|
+
result = Difference.new(region, meta, comparison)
|
107
|
+
|
108
|
+
unless result.blank?
|
109
|
+
meta[:max_color_distance] = meta[:max_color_distance].ceil(1) if meta[:max_color_distance]
|
110
|
+
|
111
|
+
if comparison.options[:tolerance]
|
112
|
+
meta[:difference_level] = difference_level(nil, base_image, region)
|
237
113
|
end
|
238
114
|
end
|
115
|
+
|
116
|
+
result
|
239
117
|
end
|
240
118
|
|
241
|
-
|
242
|
-
|
119
|
+
def difference_level(_diff_mask, base_image, region)
|
120
|
+
image_area_size = @driver.image_area_size(base_image)
|
121
|
+
return nil if image_area_size.zero?
|
122
|
+
|
123
|
+
region.size.to_f / image_area_size
|
124
|
+
end
|
243
125
|
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
126
|
+
def find_diff_rectangle(org_img, new_img, area_coordinates, cache:)
|
127
|
+
left, top, right, bottom = find_left_right_and_top(org_img, new_img, area_coordinates, cache: cache)
|
128
|
+
bottom = find_bottom(org_img, new_img, left, right, bottom, cache: cache)
|
129
|
+
|
130
|
+
Region.from_edge_coordinates(left, top, right, bottom)
|
131
|
+
end
|
132
|
+
|
133
|
+
def find_top(old_img, new_img, cache:)
|
134
|
+
old_img.height.times do |y|
|
135
|
+
old_img.width.times do |x|
|
136
|
+
return [x, y, x, y] unless same_color?(old_img, new_img, x, y, cache: cache)
|
249
137
|
end
|
250
138
|
end
|
139
|
+
nil
|
251
140
|
end
|
252
141
|
|
253
|
-
|
254
|
-
|
142
|
+
def find_left_right_and_top(old_img, new_img, region, cache:)
|
143
|
+
region = region.is_a?(Region) ? region.to_edge_coordinates : region
|
144
|
+
|
145
|
+
left = region[0] || old_img.width - 1
|
146
|
+
top = region[1]
|
147
|
+
right = region[2] || 0
|
148
|
+
bottom = region[3]
|
149
|
+
|
150
|
+
old_img.height.times do |y|
|
151
|
+
(0...left).find do |x|
|
152
|
+
next if same_color?(old_img, new_img, x, y, cache: cache)
|
255
153
|
|
256
|
-
|
257
|
-
|
154
|
+
top ||= y
|
155
|
+
bottom = y
|
156
|
+
left = x
|
157
|
+
right = x if x > right
|
158
|
+
x
|
159
|
+
end
|
160
|
+
(old_img.width - 1).step(right + 1, -1).find do |x|
|
161
|
+
unless same_color?(old_img, new_img, x, y, cache: cache)
|
162
|
+
bottom = y
|
163
|
+
right = x
|
164
|
+
end
|
165
|
+
end
|
166
|
+
end
|
258
167
|
|
259
|
-
|
260
|
-
color_distance_at(new_img, old_img, x, y, shift_distance_limit: @shift_distance_limit)
|
261
|
-
if !@max_color_distance || color_distance > @max_color_distance
|
262
|
-
@max_color_distance = color_distance
|
168
|
+
[left, top, right, bottom]
|
263
169
|
end
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
170
|
+
|
171
|
+
def find_bottom(old_img, new_img, left, right, bottom, cache:)
|
172
|
+
if bottom
|
173
|
+
(old_img.height - 1).step(bottom + 1, -1).find do |y|
|
174
|
+
(left..right).find do |x|
|
175
|
+
bottom = y unless same_color?(old_img, new_img, x, y, cache: cache)
|
176
|
+
end
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
bottom
|
272
181
|
end
|
273
|
-
color_matches
|
274
|
-
end
|
275
182
|
|
276
|
-
|
277
|
-
|
183
|
+
def same_color?(old_img, new_img, x, y, cache:)
|
184
|
+
return true if skipped_region?(x, y)
|
278
185
|
|
279
|
-
|
280
|
-
|
186
|
+
color_distance =
|
187
|
+
color_distance_at(new_img, old_img, x, y, shift_distance_limit: @shift_distance_limit)
|
188
|
+
|
189
|
+
if color_distance > cache[:max_color_distance]
|
190
|
+
cache[:max_color_distance] = color_distance
|
191
|
+
end
|
192
|
+
|
193
|
+
color_matches = color_distance == 0 ||
|
194
|
+
(!!@color_distance_limit && @color_distance_limit > 0 && color_distance <= @color_distance_limit)
|
281
195
|
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
new_pixels = xs.product(ys)
|
292
|
-
distances = new_pixels.map { |dx, dy|
|
293
|
-
new_color = new_img[dx, dy]
|
294
|
-
ChunkyPNG::Color.euclidean_distance_rgba(org_color, new_color)
|
295
|
-
}
|
296
|
-
distances.min
|
297
|
-
else
|
298
|
-
ChunkyPNG::Color.euclidean_distance_rgba(org_color, new_img[x, y])
|
196
|
+
return color_matches if !@shift_distance_limit || cache[:max_shift_distance] == Float::INFINITY
|
197
|
+
|
198
|
+
shift_distance = (color_matches && 0) ||
|
199
|
+
shift_distance_at(new_img, old_img, x, y, color_distance_limit: @color_distance_limit)
|
200
|
+
if shift_distance && (cache[:max_shift_distance].nil? || shift_distance > cache[:max_shift_distance])
|
201
|
+
cache[:max_shift_distance] = shift_distance
|
202
|
+
end
|
203
|
+
|
204
|
+
color_matches
|
299
205
|
end
|
300
|
-
end
|
301
206
|
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
207
|
+
def skipped_region?(x, y)
|
208
|
+
return false unless @skip_area
|
209
|
+
|
210
|
+
@skip_area.any? { |region| region.cover?(x, y) }
|
211
|
+
end
|
212
|
+
|
213
|
+
def color_distance_at(new_img, old_img, x, y, shift_distance_limit:)
|
214
|
+
org_color = old_img[x, y]
|
215
|
+
if shift_distance_limit
|
216
|
+
start_x = [0, x - shift_distance_limit].max
|
217
|
+
end_x = [x + shift_distance_limit, new_img.width - 1].min
|
218
|
+
xs = (start_x..end_x).to_a
|
219
|
+
start_y = [0, y - shift_distance_limit].max
|
220
|
+
end_y = [y + shift_distance_limit, new_img.height - 1].min
|
221
|
+
ys = (start_y..end_y).to_a
|
222
|
+
new_pixels = xs.product(ys)
|
223
|
+
|
224
|
+
distances = new_pixels.map do |dx, dy|
|
225
|
+
ChunkyPNG::Color.euclidean_distance_rgba(org_color, new_img[dx, dy])
|
313
226
|
end
|
227
|
+
distances.min
|
314
228
|
else
|
315
|
-
|
229
|
+
ChunkyPNG::Color.euclidean_distance_rgba(org_color, new_img[x, y])
|
316
230
|
end
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
231
|
+
end
|
232
|
+
|
233
|
+
def shift_distance_at(new_img, old_img, x, y, color_distance_limit:)
|
234
|
+
org_color = old_img[x, y]
|
235
|
+
shift_distance = 0
|
236
|
+
loop do
|
237
|
+
bounds_breached = 0
|
238
|
+
top_row = y - shift_distance
|
239
|
+
if top_row >= 0 # top
|
240
|
+
([0, x - shift_distance].max..[x + shift_distance, new_img.width - 1].min).each do |dx|
|
241
|
+
if color_matches(new_img, org_color, dx, top_row, color_distance_limit)
|
322
242
|
return shift_distance
|
323
243
|
end
|
324
244
|
end
|
325
245
|
else
|
326
246
|
bounds_breached += 1
|
327
247
|
end
|
328
|
-
if
|
329
|
-
(
|
330
|
-
|
331
|
-
|
248
|
+
if shift_distance > 0
|
249
|
+
if (x - shift_distance) >= 0 # left
|
250
|
+
([0, top_row + 1].max..[y + shift_distance, new_img.height - 2].min)
|
251
|
+
.each do |dy|
|
252
|
+
if color_matches(new_img, org_color, x - shift_distance, dy, color_distance_limit)
|
253
|
+
return shift_distance
|
254
|
+
end
|
332
255
|
end
|
256
|
+
else
|
257
|
+
bounds_breached += 1
|
333
258
|
end
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
.each do |dy|
|
340
|
-
if color_matches(new_img, org_color, x + shift_distance, dy, color_distance_limit)
|
341
|
-
return shift_distance
|
259
|
+
if (y + shift_distance) < new_img.height # bottom
|
260
|
+
([0, x - shift_distance].max..[x + shift_distance, new_img.width - 1].min).each do |dx|
|
261
|
+
if color_matches(new_img, org_color, dx, y + shift_distance, color_distance_limit)
|
262
|
+
return shift_distance
|
263
|
+
end
|
342
264
|
end
|
265
|
+
else
|
266
|
+
bounds_breached += 1
|
267
|
+
end
|
268
|
+
if (x + shift_distance) < new_img.width # right
|
269
|
+
([0, top_row + 1].max..[y + shift_distance, new_img.height - 2].min)
|
270
|
+
.each do |dy|
|
271
|
+
if color_matches(new_img, org_color, x + shift_distance, dy, color_distance_limit)
|
272
|
+
return shift_distance
|
273
|
+
end
|
274
|
+
end
|
275
|
+
else
|
276
|
+
bounds_breached += 1
|
343
277
|
end
|
344
|
-
else
|
345
|
-
bounds_breached += 1
|
346
278
|
end
|
347
|
-
|
348
|
-
break if bounds_breached == 4
|
279
|
+
break if bounds_breached == 4
|
349
280
|
|
350
|
-
|
281
|
+
shift_distance += 1
|
282
|
+
end
|
283
|
+
Float::INFINITY
|
351
284
|
end
|
352
|
-
Float::INFINITY
|
353
|
-
end
|
354
285
|
|
355
|
-
|
356
|
-
|
357
|
-
|
358
|
-
|
359
|
-
color_distance = ChunkyPNG::Color.euclidean_distance_rgba(org_color, new_color)
|
360
|
-
color_distance <= color_distance_limit
|
361
|
-
end
|
286
|
+
def color_matches(new_img, org_color, x, y, color_distance_limit)
|
287
|
+
new_color = new_img[x, y]
|
288
|
+
return new_color == org_color unless color_distance_limit
|
362
289
|
|
363
|
-
|
364
|
-
|
290
|
+
color_distance = ChunkyPNG::Color.euclidean_distance_rgba(org_color, new_color)
|
291
|
+
color_distance <= color_distance_limit
|
292
|
+
end
|
365
293
|
end
|
366
294
|
end
|
367
295
|
end
|
@@ -10,15 +10,33 @@ module Capybara
|
|
10
10
|
result << :vips if defined?(Vips) || require("vips")
|
11
11
|
rescue LoadError
|
12
12
|
# vips not present
|
13
|
+
Object.send(:remove_const, :Vips) if defined?(Vips)
|
13
14
|
end
|
14
15
|
begin
|
15
16
|
result << :chunky_png if defined?(ChunkyPNG) || require("chunky_png")
|
16
17
|
rescue LoadError
|
17
18
|
# chunky_png not present
|
19
|
+
Object.send(:remove_const, :ChunkyPNG) if defined?(ChunkyPNG)
|
18
20
|
end
|
19
21
|
result
|
20
22
|
end
|
21
23
|
|
24
|
+
def self.find_driver_class_for(driver)
|
25
|
+
driver = AVAILABLE_DRIVERS.first if driver == :auto
|
26
|
+
|
27
|
+
LOADED_DRIVERS[driver] ||=
|
28
|
+
case driver
|
29
|
+
when :chunky_png
|
30
|
+
require "capybara/screenshot/diff/drivers/chunky_png_driver"
|
31
|
+
Drivers::ChunkyPNGDriver
|
32
|
+
when :vips
|
33
|
+
require "capybara/screenshot/diff/drivers/vips_driver"
|
34
|
+
Drivers::VipsDriver
|
35
|
+
else
|
36
|
+
fail "Wrong adapter #{driver.inspect}. Available adapters: #{AVAILABLE_DRIVERS.inspect}"
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
22
40
|
def self.detect_test_framework_assert
|
23
41
|
require "minitest"
|
24
42
|
::Minitest::Assertion
|