lib/image_processing/mini_magick.rb



require "image_processing/version"
require "mini_magick"
require "tempfile"

if MiniMagick.version < Gem::Version.new("4.3.5")
  raise "image_processing requires mini_magick version >= 4.3.5"
end

module ImageProcessing
  module MiniMagick
    def self.nondestructive_alias(name, original)
      define_method(name) do |image, *args, &block|
        send(original, _copy_to_tempfile(image), *args, &block)
      end
    end

    module_function

    # Changes the image encoding format to the given format
    #
    # @see http://www.imagemagick.org/script/command-line-options.php#format
    # @param [MiniMagick::Image] image    the image to convert
    # @param [String] format              the format to convert to
    # @yield [MiniMagick::Tool::Mogrify, MiniMagick::Tool::Convert]
    # @return [File, Tempfile]
    def convert!(image, format, &block)
      with_minimagick(image) do |img|
        img.format(format.downcase, nil, &block)
      end
    end
    nondestructive_alias :convert, :convert!

    # Adjusts the image so that its orientation is suitable for viewing.
    #
    # @see http://www.imagemagick.org/script/command-line-options.php#auto-orient
    # @param [MiniMagick::Image] image    the image to convert
    # @yield [MiniMagick::Tool::Mogrify, MiniMagick::Tool::Convert]
    # @return [File, Tempfile]
    def auto_orient!(image)
      with_minimagick(image) do |img|
        img.combine_options do |cmd|
          yield cmd if block_given?
          cmd.auto_orient
        end
      end
    end
    nondestructive_alias :auto_orient, :auto_orient!

    # Resize the image to fit within the specified dimensions while retaining
    # the original aspect ratio. Will only resize the image if it is larger
    # than the specified dimensions. The resulting image may be shorter or
    # narrower than specified in either dimension but will not be larger than
    # the specified values.
    #
    # @param [MiniMagick::Image] image    the image to convert
    # @param [#to_s] width                the maximum width
    # @param [#to_s] height               the maximum height
    # @yield [MiniMagick::Tool::Mogrify, MiniMagick::Tool::Convert]
    # @return [File, Tempfile]
    def resize_to_limit!(image, width, height)
      with_minimagick(image) do |img|
        img.combine_options do |cmd|
          yield cmd if block_given?
          cmd.resize "#{width}x#{height}>"
        end
      end
    end
    nondestructive_alias :resize_to_limit, :resize_to_limit!

    # Resize the image to fit within the specified dimensions while retaining
    # the original aspect ratio. The image may be shorter or narrower than
    # specified in the smaller dimension but will not be larger than the
    # specified values.
    #
    # @param [MiniMagick::Image] image    the image to convert
    # @param [#to_s] width                the width to fit into
    # @param [#to_s] height               the height to fit into
    # @yield [MiniMagick::Tool::Mogrify, MiniMagick::Tool::Convert]
    # @return [File, Tempfile]
    def resize_to_fit!(image, width, height)
      with_minimagick(image) do |img|
        img.combine_options do |cmd|
          yield cmd if block_given?
          cmd.resize "#{width}x#{height}"
        end
      end
    end
    nondestructive_alias :resize_to_fit, :resize_to_fit!

    # Resize the image so that it is at least as large in both dimensions as
    # specified, then crops any excess outside the specified dimensions.
    #
    # The resulting image will always be exactly as large as the specified
    # dimensions.
    #
    # By default, the center part of the image is kept, and the remainder
    # cropped off, but this can be changed via the `gravity` option.
    #
    # @param [MiniMagick::Image] image    the image to convert
    # @param [#to_s] width                the width to fill out
    # @param [#to_s] height               the height to fill out
    # @param [String] gravity             which part of the image to focus on
    # @yield [MiniMagick::Tool::Mogrify, MiniMagick::Tool::Convert]
    # @return [File, Tempfile]
    # @see http://www.imagemagick.org/script/command-line-options.php#gravity
    def resize_to_fill!(image, width, height, gravity: "Center")
      with_minimagick(image) do |img|
        img.combine_options do |cmd|
          yield cmd if block_given?
          cmd.resize "#{width}x#{height}^"
          cmd.gravity gravity
          cmd.extent "#{width}x#{height}"
        end
      end
    end
    nondestructive_alias :resize_to_fill, :resize_to_fill!

    # Resize the image to fit within the specified dimensions while retaining
    # the original aspect ratio in the same way as {#fill}. Unlike {#fill} it
    # will, if necessary, pad the remaining area with the given color, which
    # defaults to transparent where supported by the image format and white
    # otherwise.
    #
    # The resulting image will always be exactly as large as the specified
    # dimensions.
    #
    # By default, the image will be placed in the center but this can be
    # changed via the `gravity` option.
    #
    # @param [MiniMagick::image] image    the image to convert
    # @param [#to_s] width                the width to fill out
    # @param [#to_s] height               the height to fill out
    # @param [string] background          the color to use as a background
    # @param [string] gravity             which part of the image to focus on
    # @yield [MiniMagick::Tool::Mogrify, MiniMagick::Tool::Convert]
    # @return [File, Tempfile]
    # @see http://www.imagemagick.org/script/color.php
    # @see http://www.imagemagick.org/script/command-line-options.php#gravity
    def resize_and_pad!(image, width, height, background: "transparent", gravity: "Center")
      with_minimagick(image) do |img|
        img.combine_options do |cmd|
          yield cmd if block_given?
          cmd.resize "#{width}x#{height}"
          if background == "transparent"
            cmd.background "rgba(255, 255, 255, 0.0)"
          else
            cmd.background background
          end
          cmd.gravity gravity
          cmd.extent "#{width}x#{height}"
        end
      end
    end
    nondestructive_alias :resize_and_pad, :resize_and_pad!

    # Resample the image to fit within the specified resolution while retaining
    # the original image size.
    #
    # The resulting image will always be the same pixel size as the source with
    # an adjusted resolution dimensions.
    #
    # @param [MiniMagick::Image] img      the image to convert
    # @param [#to_s] width                the dpi width
    # @param [#to_s] height               the dpi height
    # @yield [MiniMagick::Tool::Mogrify, MiniMagick::Tool::Convert]
    # @return [File, Tempfile]
    # @see http://www.imagemagick.org/script/command-line-options.php#resample
    def resample!(image, width, height)
      with_minimagick(image) do |img|
        img.combine_options do |cmd|
          yield cmd if block_given?
          cmd.resample "#{width}x#{height}"
        end
      end
    end
    nondestructive_alias :resample, :resample!

    # Convert an image into a MiniMagick::Image for the duration of the block,
    # and at the end return a File object.
    def with_minimagick(image)
      image = ::MiniMagick::Image.new(image.path, image)
      yield image
      image.instance_variable_get("@tempfile")
    end

    # Creates a copy of the file and stores it into a Tempfile. Works for any
    # IO object that responds to `#read(length = nil, outbuf = nil)`.
    def _copy_to_tempfile(file)
      args = [File.basename(file.path, ".*"), File.extname(file.path)] if file.respond_to?(:path)
      tempfile = Tempfile.new(args || "image", binmode: true)
      IO.copy_stream(file, tempfile.path)
      tempfile
    end
  end
end