master
  1class Image
  2  attr_reader :path
  3
  4  def initialize(path, exif = ExifParser.new)
  5    @path = path
  6    @exif = exif
  7    ensure_in_whitelist!(@path)
  8  end
  9
 10  def filename
 11    @filename ||= sanitize(@path)
 12  end
 13
 14  def content_type
 15    @content_type ||= ::MIME::Types.type_for(filename).first.to_s
 16  end
 17
 18  def geolocation
 19    @exif.parse_geolocation_from(@path)
 20  end
 21
 22  def sha256
 23    @sha256 ||= Digest::SHA256.file(@path).to_s
 24  end
 25
 26  def resize_to_fit(width:, height:)
 27    manipulate! do |image|
 28      image.resize "#{width}x#{height}"
 29      image = yield(image) if block_given?
 30      image
 31    end
 32  end
 33
 34  def resize_to_fill(width:, height:, gravity: 'Center')
 35    manipulate! do |image|
 36      columns, rows = image[:dimensions]
 37      image.combine_options do |cmd|
 38        if width != columns || height != rows
 39          scale_x = width/columns.to_f
 40          scale_y = height/rows.to_f
 41          if scale_x >= scale_y
 42            columns = (scale_x * (columns + 0.5)).round
 43            rows = (scale_x * (rows + 0.5)).round
 44            cmd.resize "#{columns}"
 45          else
 46            columns = (scale_y * (columns + 0.5)).round
 47            rows = (scale_y * (rows + 0.5)).round
 48            cmd.resize "x#{rows}"
 49          end
 50        end
 51        cmd.gravity gravity
 52        cmd.background "rgba(255,255,255,0.0)"
 53        cmd.extent "#{width}x#{height}" if columns != width || rows != height
 54      end
 55      image = yield(image) if block_given?
 56      image
 57    end
 58  end
 59
 60  def watermark(message)
 61    manipulate! do |image|
 62      image.combine_options do |c|
 63        c.gravity 'Southeast'
 64        c.draw "text 10,10 \"#{message} on CakeSide.com\""
 65        c.font 'helvetica'
 66        c.fill("#FFFFFF")
 67        c.pointsize 28
 68      end
 69      image
 70    end
 71  end
 72
 73  private
 74
 75  def manipulate!
 76    image = ::MiniMagick::Image.open(path)
 77    image = yield(image)
 78    image.write(path)
 79    image.run_command("identify", path)
 80  rescue StandardError => error
 81    Rails.logger.error("#{error.message} #{error.backtrace.join(', ')}")
 82  ensure
 83    image.try(:destroy!)
 84  end
 85
 86  def sanitize(name)
 87    name = name.gsub("\\", "/")
 88    name = File.basename(name)
 89    name = name.gsub(sanitize_regexp,"_")
 90    name = "_#{name}" if name =~ /\A\.+\z/
 91    name = "unnamed" if name.size == 0
 92    return name.mb_chars.to_s
 93  end
 94
 95  def sanitize_regexp
 96    /[^a-zA-Z0-9\.\-\+_]/
 97  end
 98
 99  def ensure_in_whitelist!(path)
100    unless %w(.jpg .jpeg .gif .png .bmp .tif).include?(File.extname(path).downcase)
101      raise StandardError.new("This file is not in the whitelist. #{path}")
102    end
103  end
104end