Search code examples
ruby-on-railsruby-on-rails-4carrierwavermagick

Exact dimensions through imageMagic and Carrierwave


So I know how both imageMagik and carrierwave work but the question is can they do exact resize of image. I tried with process resize_to_fit: [100, 100] and uploaded a 800*200 image which resulted in 100*27 instead of 100*100. resize_to_fill does it but crops the image which should not be. Is there any way to resize to exact width and height in Ruby on Rails using the carrierwave .


Solution

  • Summary

    Carrierwave passes a block with resize_to_fit and resize_to_limit to the manipulate! method to crop the image.

    By changing this block, you can change the cropping/resizing. Carrierwave calls on instance Magick::Image the method change_geometry at this line.

    The change_geometry method supports resizing a method by specifying constraints. For example, you can specify that the image should be resized such that the aspect ratio should be retained but the resulting image should be no larger than 640 pixels wide and 480 pixels tall.

    The argument may be either a geometry string or a Geometry object. Change_geometry yields to the block, passing new width and height values based on the argument with respect to self. The return value is the return value of the block.

    You need to pass a block to manipulate! similar to the below example:

    def resize_no_crop(width, height)
      width = dimension_from width
      height = dimension_from height
      manipulate! do |img|
          # change the below geometry object to achieve your effect
          # geometry = Magick::Geometry.new(width, height, 0, 0,    Magick::GreaterGeometry)
          new_img = img.change_geometry(geometry) do |new_width, new_height|
          img.resize(new_width, new_height)
        end
        destroy_image(img)
        new_img = yield(new_img) if block_given?
        new_img
      end
    end
    

    Set the geometry object as per your desires

    geometry = Magick::Geometry.new(width, height, 0, 0, !)
    

    as I read in the docs, the last parameters ! has the following effect

    ! Use this flag when you want to force the new image to have exactly the size specified by the the width and height attributes.

    ImageMagick command line tool and Rmagick api docs provide more information.

    Explanation

    Carrierwave uses miniMagick to perform the resizing process.

    The ImageProcessing gem uses MiniMagick, a mini replacement for RMagick that uses ImageMagick command-line tools, to build a "convert" command that performs the processing.

    This is how the resize_to_limit method works, it may help you building your own method using the Rmagick and ImageMagick command line tools.

    module MiniMagick
      extend ActiveSupport::Concern
    
      included do
        require "image_processing/mini_magick"
      end
    
      module ClassMethods
        def convert(format)
          process :convert => format
        end
    
        def resize_to_limit(width, height)
          process :resize_to_limit => [width, height]
        end
      end
    end
    

    The method manipulates the images using the ImageMagick command line tool and Rmagick. You can visit the above websites to read their documentation.

    As you can read below resize_to_limit manipulates the image by creating a new instance of Magick::Geometry and passing a width,height, x,y and flag

    def resize_to_limit(width, height)
      width = dimension_from width
      height = dimension_from height
      manipulate! do |img|
         # Read The Explanation below
      end
    end
    

    Carrierwave created their own manipulate! method which will iterate with the yield statement and crop/resize the image

      image.each_with_index do |frame, index|
        frame = yield(*[frame, index, options].take(block.arity)) if block_given?
        frames << frame if frame
      end
    

    The method each_with_index will iterate between manipulate! do ... end with the yield statement.

    *[frame, index, options].take(block.arity), .take(block.arity) will pass only frame as it is called with only one parameter.

    The frame variable will equal the new_image returned.

    geometry = Magick::Geometry.new(width, height, 0, 0, Magick::GreaterGeometry)
    new_img = img.change_geometry(geometry) do |new_width, new_height|
      img.resize(new_width, new_height)
    end
    destroy_image(img)
    new_img = yield(new_img) if block_given?
    new_img
    

    You can read more about Image::Magick class and on rmagick.github.io to better understand how the process of frame selection works.