class ImageComparator
Constants
- DEFAULT_FILE1_NAME
- DEFAULT_FILE2_NAME
- DEFAULT_TEMP_FOLDER
- LOG_FILE
- LOG_ROTATE_PERIOCITY
Public Class Methods
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
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
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
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
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
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
# 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
# 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
# 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
# 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
# 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
# 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
# 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
# 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
# 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
# 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
# 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
# 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
# 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