Quick and Dirty cropping images with attachment_fu

2 minute read

Rick Olson’s attachment_fu is a great plugin for attaching files documents to Rails models. It’s a rewrite of his acts_as_attachment plugin. While it can handle any kind of file data, most commonly, it is used for attaching images; as a result attachment_fu handles automatic resizing of images, and creation of thumbnails using RMagick, minimagick, or ImageScience.

For example:

class ProductImage  < ActiveRecord::Base
  belongs_to :product
  has_attachment :content_type => :image,
                 :storage => :file_system,
                 :path_prefix => '/public/images/products/',
                 :resize_to => '300',
                 :thumbnails => {:thumb => '75x75' }

  validates_as_attachment
end

The above will take an image, resize it to 300 pixels wide (automatically adjusting the height to preserve the original images aspect ratio), and to 75 by 75 pixels for a thumbnail, and save resulting images. Combined with a Product model that has_one :image, or has_many :images, and the right form, you can easily manage your product images.

However, an image with both a fixed width, and fixed height, like our thumbnail, can be a problem. If the original, and resized image do not have the same aspect ratio the resized image will be distorted. In this case, if the original is not square, our thumbnail will be look squished in which ever dimension was longer originally. This is not a problem for the main image because we let the height be calculated automatically.

Fortunately, there is a simple trick that allows us to override the method attachment_fu uses to resize image and manipulate it ourselves. Add the following to the ProductImage model:

  protected

  # Override image resizing method
  def resize_image(img, size)
    # resize_image take size in a number of formats, we just want
    # Strings in the form of "crop: WxH"
    if (size.is_a?(String) && size =~ /^crop: (\\d*)x(\\d*)/i) ||
        (size.is_a?(Array) && size.first.is_a?(String) &amp;&amp;
          size.first =~ /^crop: (\\d*)x(\\d*)/i)
      img.crop_resized!($1.to_i, $2.to_i)
      # We need to save the resized image in the same way the
      # orignal does.
      self.temp_path = write_to_temp_file(img.to_blob)
    else
      super # Otherwise let attachment_fu handle it
    end
  end

and change the thumbnail size to:

:thumbnails => {:thumb => 'crop: 75x75' }

Now, if the image size starts with ‘crop: ‘, the image will be resized and then cropped to fit. Otherwise, it’s passed on to attachment_fu and handed normally. I’m using the RMagic crop_resized! method, which resize the image using the smaller dimension and then crops the large one to fit. If you are using minimagick, or ImageScience you may need to fiddle a bit with the code. Obviously, you can extend this approach to manipulate the image anyway you see fit. For example you could automatically put a border on the images:

  def resize_image(img, size)
    # Add a 2x2 red border and pass the image to attachment_fu
    img.border!(2,2,'red')
    super
  end

Or blur them:

  def resize_image(img, size)
    img = img.blur_image
    super # Pass the blured image to attachment_fu
  end

Or any other weirdness your heart desires. Have fun!

Comments