Thu 21 Feb 2008
Quick and Dirty cropping images with attachment_fu
Posted by Spike under Active Record, Ruby on Rails
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) &&
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!
February 28th, 2008 at 7:02 pm
Wow this is the first example I’ve seen that really works for cropping attachment_fu uploads with Rmagick. Been trying to get this working for hours, thank you so much!!!
One bug I encountered (its probably something to do with how the code is being displayed by wordpress)….on line 7 of your resize_image method:
/^crop: (d*)x(d*)/ishould be changed to
/^crop: (\d*)x(\d*)/iFebruary 28th, 2008 at 8:57 pm
You’re welcome! The backslash thing was a Wordpress issue, and I’ve corrected it above. Thanks for pointing it out.
April 5th, 2008 at 11:49 am
Thanks for this, Spike. Works perfectly for me, and seems cleaner than the other suggestions for patching the rmagick_processor.
April 6th, 2008 at 4:08 am
Do you have any idea how we could use this function to resize images that have already been stored.
e.g If the user has the option to select the size of her avatar/thumbnail after uploading the original image.
April 9th, 2008 at 2:16 pm
Shan, I haven’t tried it in production, but the following should work:
i = User.image
i.create_or_update_thumbnail(i.create_temp_file,:thumb, 'crop: 100x100')
It will use the overridden resize method to crop the image, and update the size of the thumbnail in the database.
May 3rd, 2008 at 12:19 pm
Thanks a lot for this. Works a treat.
I also added in the line:
img.strip!before the if statement. This just strips out the colour profile information and makes images a lot smaller