Class: Capybara::Screenshot::Diff::Drivers::ChunkyPNGDriver::DifferenceRegionFinder

Inherits:
Object
  • Object
show all
Defined in:
lib/capybara/screenshot/diff/drivers/chunky_png_driver.rb

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(comparison, driver = nil) ⇒ DifferenceRegionFinder

Returns a new instance of DifferenceRegionFinder.



84
85
86
87
88
89
90
91
# File 'lib/capybara/screenshot/diff/drivers/chunky_png_driver.rb', line 84

def initialize(comparison, driver = nil)
  @comparison = comparison
  @driver = driver

  @color_distance_limit = comparison.options[:color_distance_limit]
  @shift_distance_limit = comparison.options[:shift_distance_limit]
  @skip_area = comparison.options[:skip_area]
end

Instance Attribute Details

#color_distance_limitObject

Returns the value of attribute color_distance_limit.



82
83
84
# File 'lib/capybara/screenshot/diff/drivers/chunky_png_driver.rb', line 82

def color_distance_limit
  @color_distance_limit
end

#shift_distance_limitObject

Returns the value of attribute shift_distance_limit.



82
83
84
# File 'lib/capybara/screenshot/diff/drivers/chunky_png_driver.rb', line 82

def shift_distance_limit
  @shift_distance_limit
end

#skip_areaObject

Returns the value of attribute skip_area.



82
83
84
# File 'lib/capybara/screenshot/diff/drivers/chunky_png_driver.rb', line 82

def skip_area
  @skip_area
end

Instance Method Details

#color_distance_at(new_img, old_img, x, y, shift_distance_limit:) ⇒ Object



218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
# File 'lib/capybara/screenshot/diff/drivers/chunky_png_driver.rb', line 218

def color_distance_at(new_img, old_img, x, y, shift_distance_limit:)
  org_color = old_img[x, y]
  if shift_distance_limit
    start_x = [0, x - shift_distance_limit].max
    end_x = [x + shift_distance_limit, new_img.width - 1].min
    xs = (start_x..end_x).to_a
    start_y = [0, y - shift_distance_limit].max
    end_y = [y + shift_distance_limit, new_img.height - 1].min
    ys = (start_y..end_y).to_a
    new_pixels = xs.product(ys)

    distances = new_pixels.map do |dx, dy|
      ChunkyPNG::Color.euclidean_distance_rgba(org_color, new_img[dx, dy])
    end
    distances.min
  else
    ChunkyPNG::Color.euclidean_distance_rgba(org_color, new_img[x, y])
  end
end

#color_matches(new_img, org_color, x, y, color_distance_limit) ⇒ Object



291
292
293
294
295
296
297
# File 'lib/capybara/screenshot/diff/drivers/chunky_png_driver.rb', line 291

def color_matches(new_img, org_color, x, y, color_distance_limit)
  new_color = new_img[x, y]
  return new_color == org_color unless color_distance_limit

  color_distance = ChunkyPNG::Color.euclidean_distance_rgba(org_color, new_color)
  color_distance <= color_distance_limit
end

#difference_level(_diff_mask, base_image, region) ⇒ Object



124
125
126
127
128
129
# File 'lib/capybara/screenshot/diff/drivers/chunky_png_driver.rb', line 124

def difference_level(_diff_mask, base_image, region)
  image_area_size = @driver.image_area_size(base_image)
  return nil if image_area_size.zero?

  region.size.to_f / image_area_size
end

#find_bottom(old_img, new_img, left, right, bottom, cache:) ⇒ Object



176
177
178
179
180
181
182
183
184
185
186
# File 'lib/capybara/screenshot/diff/drivers/chunky_png_driver.rb', line 176

def find_bottom(old_img, new_img, left, right, bottom, cache:)
  if bottom
    (old_img.height - 1).step(bottom + 1, -1).find do |y|
      (left..right).find do |x|
        bottom = y unless same_color?(old_img, new_img, x, y, cache: cache)
      end
    end
  end

  bottom
end

#find_diff_rectangle(org_img, new_img, area_coordinates, cache:) ⇒ Object



131
132
133
134
135
136
# File 'lib/capybara/screenshot/diff/drivers/chunky_png_driver.rb', line 131

def find_diff_rectangle(org_img, new_img, area_coordinates, cache:)
  left, top, right, bottom = find_left_right_and_top(org_img, new_img, area_coordinates, cache: cache)
  bottom = find_bottom(org_img, new_img, left, right, bottom, cache: cache)

  Region.from_edge_coordinates(left, top, right, bottom)
end

#find_difference_region(comparison) ⇒ Object



97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
# File 'lib/capybara/screenshot/diff/drivers/chunky_png_driver.rb', line 97

def find_difference_region(comparison)
  new_image, base_image, = comparison.new_image, comparison.base_image

  meta = {}
  meta[:max_color_distance] = 0
  meta[:max_shift_distance] = 0 if shift_distance_limit

  region = find_top(base_image, new_image, cache: meta)
  region = if region.nil? || region[1].nil?
    nil
  else
    find_diff_rectangle(base_image, new_image, region, cache: meta)
  end

  result = Difference.new(region, meta, comparison)

  unless result.blank?
    meta[:max_color_distance] = meta[:max_color_distance].ceil(1) if meta[:max_color_distance]

    if comparison.options[:tolerance]
      meta[:difference_level] = difference_level(nil, base_image, region)
    end
  end

  result
end

#find_left_right_and_top(old_img, new_img, region, cache:) ⇒ Object



147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
# File 'lib/capybara/screenshot/diff/drivers/chunky_png_driver.rb', line 147

def find_left_right_and_top(old_img, new_img, region, cache:)
  region = region.is_a?(Region) ? region.to_edge_coordinates : region

  left = region[0] || old_img.width - 1
  top = region[1]
  right = region[2] || 0
  bottom = region[3]

  old_img.height.times do |y|
    (0...left).find do |x|
      next if same_color?(old_img, new_img, x, y, cache: cache)

      top ||= y
      bottom = y
      left = x
      right = x if x > right
      x
    end
    (old_img.width - 1).step(right + 1, -1).find do |x|
      unless same_color?(old_img, new_img, x, y, cache: cache)
        bottom = y
        right = x
      end
    end
  end

  [left, top, right, bottom]
end

#find_top(old_img, new_img, cache:) ⇒ Object



138
139
140
141
142
143
144
145
# File 'lib/capybara/screenshot/diff/drivers/chunky_png_driver.rb', line 138

def find_top(old_img, new_img, cache:)
  old_img.height.times do |y|
    old_img.width.times do |x|
      return [x, y, x, y] unless same_color?(old_img, new_img, x, y, cache: cache)
    end
  end
  nil
end

#performObject



93
94
95
# File 'lib/capybara/screenshot/diff/drivers/chunky_png_driver.rb', line 93

def perform
  find_difference_region(@comparison)
end

#same_color?(old_img, new_img, x, y, cache:) ⇒ Boolean

Returns:

  • (Boolean)


188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
# File 'lib/capybara/screenshot/diff/drivers/chunky_png_driver.rb', line 188

def same_color?(old_img, new_img, x, y, cache:)
  return true if skipped_region?(x, y)

  color_distance =
    color_distance_at(new_img, old_img, x, y, shift_distance_limit: @shift_distance_limit)

  if color_distance > cache[:max_color_distance]
    cache[:max_color_distance] = color_distance
  end

  color_matches = color_distance == 0 ||
    (!!@color_distance_limit && @color_distance_limit > 0 && color_distance <= @color_distance_limit)

  return color_matches if !@shift_distance_limit || cache[:max_shift_distance] == Float::INFINITY

  shift_distance = (color_matches && 0) ||
    shift_distance_at(new_img, old_img, x, y, color_distance_limit: @color_distance_limit)
  if shift_distance && (cache[:max_shift_distance].nil? || shift_distance > cache[:max_shift_distance])
    cache[:max_shift_distance] = shift_distance
  end

  color_matches
end

#shift_distance_at(new_img, old_img, x, y, color_distance_limit:) ⇒ Object



238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
# File 'lib/capybara/screenshot/diff/drivers/chunky_png_driver.rb', line 238

def shift_distance_at(new_img, old_img, x, y, color_distance_limit:)
  org_color = old_img[x, y]
  shift_distance = 0
  loop do
    bounds_breached = 0
    top_row = y - shift_distance
    if top_row >= 0 # top
      ([0, x - shift_distance].max..[x + shift_distance, new_img.width - 1].min).each do |dx|
        if color_matches(new_img, org_color, dx, top_row, color_distance_limit)
          return shift_distance
        end
      end
    else
      bounds_breached += 1
    end
    if shift_distance > 0
      if (x - shift_distance) >= 0 # left
        ([0, top_row + 1].max..[y + shift_distance, new_img.height - 2].min)
          .each do |dy|
          if color_matches(new_img, org_color, x - shift_distance, dy, color_distance_limit)
            return shift_distance
          end
        end
      else
        bounds_breached += 1
      end
      if (y + shift_distance) < new_img.height # bottom
        ([0, x - shift_distance].max..[x + shift_distance, new_img.width - 1].min).each do |dx|
          if color_matches(new_img, org_color, dx, y + shift_distance, color_distance_limit)
            return shift_distance
          end
        end
      else
        bounds_breached += 1
      end
      if (x + shift_distance) < new_img.width # right
        ([0, top_row + 1].max..[y + shift_distance, new_img.height - 2].min)
          .each do |dy|
          if color_matches(new_img, org_color, x + shift_distance, dy, color_distance_limit)
            return shift_distance
          end
        end
      else
        bounds_breached += 1
      end
    end
    break if bounds_breached == 4

    shift_distance += 1
  end
  Float::INFINITY
end

#skipped_region?(x, y) ⇒ Boolean

Returns:

  • (Boolean)


212
213
214
215
216
# File 'lib/capybara/screenshot/diff/drivers/chunky_png_driver.rb', line 212

def skipped_region?(x, y)
  return false unless @skip_area

  @skip_area.any? { |region| region.cover?(x, y) }
end