class ImageComparator

Constants

DEFAULT_FILE1_NAME
DEFAULT_FILE2_NAME
DEFAULT_TEMP_FOLDER
LOG_FILE
LOG_ROTATE_PERIOCITY

Public Class Methods

new() click to toggle source

Class constructor. It just initializes the logger.

# File lib/image_comparator.rb, line 19
def initialize()
  @logger = Logger.new(LOG_FILE, LOG_ROTATE_PERIOCITY)
  @logger.debug("in: initialize()")
  @areas = nil
end

Public Instance Methods

calculate_diff_image(path_res='./diff.png') click to toggle source

This method generates a png with the image differences and returns a fingerprint of the returned image.

If areas are defined, they will be used on the comparison. See {ImageComparator#set_areas}

@param path_res [String] (Optional, by default ./diff.png) It defines where the png file with the differences will

be placed.

@return [String] It generates a png with the image differences and returns a fingerprint of the returned image

# File lib/image_comparator.rb, line 53
def calculate_diff_image(path_res='./diff.png')
  @logger.debug("in: calculate_diff_image (#{path_res})")
  begin
    p_image1 = @path_image1
    p_image2 = @path_image2

    unless @areas.nil?
      prepare_areas_on_files
      p_image1 = "#{DEFAULT_TEMP_FOLDER}/#{DEFAULT_FILE1_NAME}"
      p_image2 = "#{DEFAULT_TEMP_FOLDER}/#{DEFAULT_FILE2_NAME}"
    end

    images = [
        ChunkyPNG::Image.from_file(p_image1),
        ChunkyPNG::Image.from_file(p_image2)
    ]

    images.first.height.times do |y|
      images.first.row(y).each_with_index do |pixel, x|

        images.last[x,y] = rgb(
            r(pixel) + r(images.last[x,y]) - 2 * [r(pixel), r(images.last[x,y])].min,
            g(pixel) + g(images.last[x,y]) - 2 * [g(pixel), g(images.last[x,y])].min,
            b(pixel) + b(images.last[x,y]) - 2 * [b(pixel), b(images.last[x,y])].min
        )
      end
    end
    images.last.save(path_res)
    diff_fingerprint = Phashion::Image.new(path_res).fingerprint
    @logger.debug("out: calculate_diff_image (#{path_res}) - ret: #{diff_fingerprint}")
    diff_fingerprint
  rescue StandardError
    @logger.error 'There was a problem'
    raise
  ensure
    delete_temp_images
  end
end
compare() click to toggle source

This method return a boolean to indicate if 2 png images are compare from a pixel point of view.

If areas are defined, they will be used on the comparison. See {ImageComparator#set_areas}

@return [Boolean] True if the 2 images are compare, otherwise false.

# File lib/image_comparator.rb, line 41
def compare()
  compare_pixel_perfect()
end
compare_and_generate_diff(path_res='./diff.png') click to toggle source

This method returns a boolean to indicate if the 2 images are compare, based on the threshold, and in case they are not, a png with the differences is generated.

If areas are defined, they will be used on the comparison. See {ImageComparator#set_areas}

@param path_res [String] (Optional, by default ./diff.png) It defines where the png file with the differences will

be placed.

@return [Boolean] True if the 2 images are compare, otherwise false. In case of being false a png image with the

differences is generated.
# File lib/image_comparator.rb, line 102
def compare_and_generate_diff(path_res='./diff.png')
  @logger.debug("in:compare_and_generate_diff(#{path_res}")
  images_compares = compare()
  calculate_diff_image(path_res) unless images_compares
  @logger.debug("out:compare_and_generate_diff(#{path_res} - ret: #{images_compares}")
  images_compares
end
define_images_to_compare(path_image1, path_image2) click to toggle source

It defines the images to be compared.

@param path_image1 [String] The path to the first image to compare. @param path_image2 [String] The path to the second image to compare.

# File lib/image_comparator.rb, line 30
def define_images_to_compare(path_image1, path_image2)
  @path_image1 = path_image1
  @path_image2 = path_image2
end
set_areas(areas) click to toggle source

This method defines the areas that will be used on the comparison methods, ie: compare, calculate_diff_image and compare_and_generate_diff methods.

The comparison methods will only focus the comparison on the areas flagged as included and will ignore the ones marked as exclude.

If the areas are defined, the comparison methods first create a temporal image for each image to compare with only the areas that will be included (if there is any) and then will remove from this image the excluded images (if any). These temporal images will be used on the comparison.

@param areas [Array<Symbol>] This is an array of areas to include or exclude on the comparison methods. It has the

following format:

areas = [area1, ..., areaN]

areaX = {'origin':point, 'end': point, 'exclude': boolean}

point = {'x': integer, 'y': integer}

@example Here you can see an example of areas.

origin_coord = {'x':828, 'y':293}
end_coord = {'x':1173, 'y':688}
area1 = {'origin': origin_coord, 'end': end_coord, 'exclude': false}
origin_coord2 = {'x':0, 'y':0}
end_coord2 = {'x':828, 'y':293}
area2 = {'origin': origin_coord2, 'end': end_coord2, 'exclude': true}
areas = [area1, area2]
# File lib/image_comparator.rb, line 138
def set_areas(areas)
  @areas=areas
end

Private Instance Methods

add_areas(path_image) click to toggle source
# File lib/image_comparator.rb, line 181
def add_areas(path_image)
  @logger.debug("in: add_areas(#{path_image})")
  include_areas = get_include_areas(@areas)
  original_image = Magick::Image.read(path_image)[0]
  ret = get_file(include_areas, original_image)
  @logger.debug("out: add_areas(#{path_image}) - ret: #{ret}")
  ret
end
compare_md5() click to toggle source
# File lib/image_comparator.rb, line 249
def compare_md5
  @logger.debug ('in: compare')
  p_image1 = @path_image1
  p_image2 = @path_image2

  unless @areas.nil?
    prepare_areas_on_files
    p_image1 = "#{DEFAULT_TEMP_FOLDER}/#{DEFAULT_FILE1_NAME}"
    p_image2 = "#{DEFAULT_TEMP_FOLDER}/#{DEFAULT_FILE2_NAME}"
  end

  img1 = Magick::Image.read(p_image1).first
  img2 = Magick::Image.read(p_image2).first
  md5_1 = Digest::MD5.hexdigest img1.export_pixels.join
  md5_2 = Digest::MD5.hexdigest img2.export_pixels.join
  res = md5_1 == md5_2
  @logger.debug ('in: compare - ret: #{res} - md5_1: #{md5_1} - md5_2: #{md5_2}')
  res
end
compare_phassion(distance_threshold=0) click to toggle source
# File lib/image_comparator.rb, line 269
def compare_phassion(distance_threshold=0)
  @logger.debug("in: compare (#{distance_threshold})")
  begin
    p_image1 = @path_image1
    p_image2 = @path_image2

    unless @areas.nil?
      prepare_areas_on_files
      p_image1 = "#{DEFAULT_TEMP_FOLDER}/#{DEFAULT_FILE1_NAME}"
      p_image2 = "#{DEFAULT_TEMP_FOLDER}/#{DEFAULT_FILE2_NAME}"
    end

    image1 = Phashion::Image.new p_image1
    image2 = Phashion::Image.new p_image2

    res = image1.duplicate?(image2, :threshold => distance_threshold)
    @logger.debug("out: compare (#{distance_threshold}) - res: #{res} - distance: #{image1.distance_from(image2)}")
    res
  rescue StandardError
    @logger.error 'There was a problem'
    raise
  ensure
    delete_temp_images
  end
end
compare_pixel_perfect() click to toggle source
# File lib/image_comparator.rb, line 295
def compare_pixel_perfect
  p_image1 = @path_image1
  p_image2 = @path_image2

  unless @areas.nil?
    prepare_areas_on_files
    p_image1 = "#{DEFAULT_TEMP_FOLDER}/#{DEFAULT_FILE1_NAME}"
    p_image2 = "#{DEFAULT_TEMP_FOLDER}/#{DEFAULT_FILE2_NAME}"
  end
  images = [
      ChunkyPNG::Image.from_file(p_image1),
      ChunkyPNG::Image.from_file(p_image2)
  ]

  images.first.height.times do |y|
    images.first.row(y).each_with_index do |pixel, x|
      return false unless pixel == images.last[x,y]
    end
  end
  true
end
create_tmp_folder() click to toggle source
# File lib/image_comparator.rb, line 151
def create_tmp_folder
  @logger.debug('in: create_tmp_folder')
  FileUtils::mkdir_p DEFAULT_TEMP_FOLDER unless File.directory?(DEFAULT_TEMP_FOLDER)
  @logger.debug('out: create_tmp_folder')
end
delete_temp_images() click to toggle source
# File lib/image_comparator.rb, line 144
def delete_temp_images
  @logger.debug('in: delete_temp_images')
  FileUtils.rm_rf Dir.glob("#{DEFAULT_TEMP_FOLDER}/#{DEFAULT_FILE1_NAME}") if File.exist?("#{DEFAULT_TEMP_FOLDER}/#{DEFAULT_FILE1_NAME}")
  FileUtils.rm_rf Dir.glob("#{DEFAULT_TEMP_FOLDER}/#{DEFAULT_FILE2_NAME}") if File.exist?("#{DEFAULT_TEMP_FOLDER}/#{DEFAULT_FILE2_NAME}")
  @logger.debug('out: delete_temp_images')
end
get_exclude_areas(areas) click to toggle source
# File lib/image_comparator.rb, line 190
def get_exclude_areas(areas)
  @logger.debug("in: get_exclude_areas(#{areas})")
  exclude = true
  ret = get_hash_condition(areas, exclude)
  @logger.debug("out: get_exclude_areas(#{areas}) - ret: #{ret}")
  ret
end
get_file(areas, input_image) click to toggle source
# File lib/image_comparator.rb, line 216
def get_file(areas, input_image)
  @logger.debug("in: get_file(#{areas}, #{input_image})")
  original_image = input_image
  output_image = input_image
  operation = nil

  areas.each_with_index { |area, index|
    x = area[:origin][:x]
    y = area[:origin][:y]
    width = area[:end][:x]-area[:origin][:x]
    height = area[:end][:y]-area[:origin][:y]
    exclude = area[:exclude]

    area_from_image = Magick::Image.constitute(width, height, 'RGB', input_image.dispatch(x, y, width, height, 'RGB'))

    if index==0
      operation = Magick::OverCompositeOp
      output_image = Magick::Image.new(original_image.columns, original_image.rows) do |c|
        c.background_color= "black"
      end

      if exclude
        output_image = original_image
        operation = Magick::SubtractCompositeOp
      end
    end

    output_image.composite!(area_from_image, x, y, operation)
  }
  @logger.debug("out: get_file(#{areas}, #{input_image}) - ret: #{output_image}")
  output_image
end
get_hash_condition(areas, condition) click to toggle source
# File lib/image_comparator.rb, line 206
def get_hash_condition(areas, condition)
  @logger.debug("in: get_hash_condition(#{areas}, #{condition})")
  res = Array.new
  areas.each{|area|
    res << area if area[:exclude]==condition
  }
  @logger.debug("out: get_hash_condition(#{areas}, #{condition}) - ret: #{res}")
  res
end
get_include_areas(areas) click to toggle source
# File lib/image_comparator.rb, line 198
def get_include_areas(areas)
  @logger.debug("in: get_include_areas(#{areas})")
  exclude = false
  ret = get_hash_condition(areas, exclude)
  @logger.debug("out: get_include_areas(#{areas}) - ret: #{ret}")
  ret
end
normalize_file(path_image, path_output_file) click to toggle source
# File lib/image_comparator.rb, line 165
def normalize_file(path_image, path_output_file)
  @logger.debug("in: normalize_file(#{path_image}, #{path_output_file})")
  output_image = add_areas(path_image)
  output_image = remove_areas(output_image)
  output_image.write(path_output_file)
  @logger.debug("out: normalize_file(#{path_image}, #{path_output_file})")
end
prepare_areas_on_files() click to toggle source
# File lib/image_comparator.rb, line 157
def prepare_areas_on_files
  @logger.debug('in: prepare_areas_on_files')
  create_tmp_folder
  normalize_file(@path_image1, "#{DEFAULT_TEMP_FOLDER}/#{DEFAULT_FILE1_NAME}")
  normalize_file(@path_image2, "#{DEFAULT_TEMP_FOLDER}/#{DEFAULT_FILE2_NAME}")
  @logger.debug('out: prepare_areas_on_files')
end
remove_areas(input_image) click to toggle source
# File lib/image_comparator.rb, line 173
def remove_areas(input_image)
  @logger.debug("in: remove_areas(#{input_image})")
  exclude_areas = get_exclude_areas(@areas)
  ret = get_file(exclude_areas, input_image)
  @logger.debug("out: remove_areas(#{input_image}) - ret: #{ret}")
  ret
end