Class: LookbookVisualTester::ImageComparator

Inherits:
Object
  • Object
show all
Defined in:
lib/lookbook_visual_tester/services/image_comparator.rb

Constant Summary collapse

DIFF_COLOR =

Neon Red for differences

ChunkyPNG::Color.from_hex('#FF073A')

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(baseline_path, current_path, diff_path) ⇒ ImageComparator

Returns a new instance of ImageComparator.



11
12
13
14
15
# File 'lib/lookbook_visual_tester/services/image_comparator.rb', line 11

def initialize(baseline_path, current_path, diff_path)
  @baseline_path = baseline_path
  @current_path = current_path
  @diff_path = diff_path
end

Instance Attribute Details

#baseline_pathObject (readonly)

Returns the value of attribute baseline_path.



6
7
8
# File 'lib/lookbook_visual_tester/services/image_comparator.rb', line 6

def baseline_path
  @baseline_path
end

#current_pathObject (readonly)

Returns the value of attribute current_path.



6
7
8
# File 'lib/lookbook_visual_tester/services/image_comparator.rb', line 6

def current_path
  @current_path
end

#diff_pathObject (readonly)

Returns the value of attribute diff_path.



6
7
8
# File 'lib/lookbook_visual_tester/services/image_comparator.rb', line 6

def diff_path
  @diff_path
end

Instance Method Details

#callObject



17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
# File 'lib/lookbook_visual_tester/services/image_comparator.rb', line 17

def call
  unless File.exist?(baseline_path)
    return { diff_path: nil, mismatch: 0.0, error: 'Baseline not found' }
  end

  baseline = ChunkyPNG::Image.from_file(baseline_path)
  current = ChunkyPNG::Image.from_file(current_path)

  max_width = [baseline.width, current.width].max
  max_height = [baseline.height, current.height].max

  diff_pixels_count = 0
  diff_image = ChunkyPNG::Image.new(max_width, max_height, ChunkyPNG::Color::WHITE)

  max_height.times do |y|
    max_width.times do |x|
      in_baseline = x < baseline.width && y < baseline.height
      in_current = x < current.width && y < current.height

      if in_baseline && in_current
        pixel1 = baseline[x, y]
        pixel2 = current[x, y]

        score = 0.0
        if pixel1 != pixel2
          score = ChunkyPNG::Color.euclidean_distance_rgba(pixel1, pixel2) / 510.0
        end

        if score <= LookbookVisualTester.config.tolerance
          # Match (including exact match where score is 0.0)
          # Blue context for unchanged pixels to make it easier for humans
          gray_val = ChunkyPNG::Color.r(ChunkyPNG::Color.grayscale_teint(pixel1))
          # Keep intensity in R/G but push blue to make it the dominant tint
          diff_image[x, y] = ChunkyPNG::Color.rgba(gray_val, gray_val, 255, 50)
        else
          diff_image[x, y] = DIFF_COLOR
          diff_pixels_count += 1
        end
      elsif in_baseline || in_current
        # Pixel exists in one but not the other -> Mismatch
        diff_image[x, y] = DIFF_COLOR
        diff_pixels_count += 1
      end
    end
  end

  mismatch_percentage = (diff_pixels_count.to_f / (max_width * max_height)) * 100.0

  if diff_pixels_count > 0
    FileUtils.mkdir_p(File.dirname(diff_path))
    diff_image.save(diff_path)
    { diff_path: diff_path, mismatch: mismatch_percentage, error: nil }
  else
    { diff_path: nil, mismatch: 0.0, error: nil }
  end
rescue StandardError => e
  { diff_path: nil, mismatch: 0.0, error: e.message }
end