Cropped Thumbnails in attachment_fu using ImageScience // writing
There’s now an evil twin plugin version of this method for ImageScience and miniMagick.
Upon discovering attachment_fu by the awesome Rick Olsen and the (not as scary as RMagick) ImageScience I believed all my image uploading and resizing problems were solved! Mike Clark’s excellent tutorial led me down the garden path toward geting exactly what I needed.
However the precise functionality for the thumbnailing I required was missing. I needed to create proportionally (but non-square) cropped thumbnails of my images. For example you pass in a ‘portrait’ image but the thumbnail has to fit in a ‘landscape’ space on the page but without any horrid squashed pixels.
Like so.

First thing I found when digging through the source to attachment_fu was that “!”, aspect ratio flag, wasn’t enabled. So I reenabled it. (changes marked with +)
FLAGS = ['', '%', '<', '>', '!']#, '@']
Then the to_s method of the geometry class wasn’t passing out the flags correctly.
def to_s
str = ''
str << "%g" % @width if @width > 0
str << 'x' if (@width > 0 || @height > 0)
str << "%g" % @height if @height > 0
str << "%+d%+d" % [@x, @y] if (@x != 0 || @y != 0)
+ str << RFLAGS.index(@flag)
end
And we needed to add a case to fix the heights when passed an !-suffixed geometry string in the new_dimensions_for method.
when :aspect
new_width = @width unless @width.nil?
new_height = @height unless @height.nil?
With this fixed all it remained to do was to modify the resize_image function in the image_science_processor.rb.
def resize_image(img, size)
# create a dummy temp file to write to
self.temp_path = write_to_temp_file(filename)
grab_dimensions = lambda do |img|
self.width = img.width if respond_to?(:width)
self.height = img.height if respond_to?(:height)
img.save temp_path
self.size = File.size(self.temp_path)
callback_with_args :after_resize, img
end
size = size.first if size.is_a?(Array) && size.length == 1
if size.is_a?(Fixnum) || (size.is_a?(Array) && size.first.is_a?(Fixnum))
if size.is_a?(Fixnum)
img.thumbnail(size, &grab_dimensions)
else
img.resize(size[0], size[1], &grab_dimensions)
end
else
n_size = [img.width, img.height] / size.to_s
+ if size.ends_with? "!"
+ aspect = n_size[0].to_f / n_size[1].to_f
+ ih, iw = img.height, img.width
+ w, h = (ih * aspect), (iw / aspect)
+ w = [iw, w].min.to_i
+ h = [ih, h].min.to_i
+ img.with_crop( (iw-w)/2, (ih-h)/2, (iw+w)/2, (ih+h)/2) {
+ |crop| crop.resize(n_size[0], n_size[1], &grab_dimensions )
+ }
+ else
img.resize(n_size[0], n_size[1], &grab_dimensions)
end
end
end
I got stuck for hours until I received some kind help from Ramon who helped me with the final piece of the puzzle and the guys from the comments on toolmantim.com on whose metaphorical shoulders I stood.
This method could easily be extended to the RMagick and miniMagick processors (with less fiddling as they have the proportional resize and crop built in) and I may do that very thing over the next couple of days as I humbly lay this patch before the original author.
Minimagick
Ian Drysdale has done a version of this for minimagick, why not check it out if ImageScience is not your bag.
Comments
Andy Croll
Dorian, could you be a bit more specific? Perhaps email me your problem.
‘Doesn’t work’ can cover a multitude of sins!
dorian mcfarland
oops… “doesn’t work� in this instance meant: “I forgot to restart mongrel�!
Although there seemed to be a typo in your code above which was giving me an error:
pre. n_size<sup class="footnote">0</sup>, n_size<sup class="footnote">1</sup>
I think should be:
pre. new_size<sup class="footnote">0</sup>, new_size<sup class="footnote">1</sup>
I changed it to that anyway and it’s working a treat now. Thanks very much!
dorian mcfarland
Andy, glad you’re doing this as I was happily using file_column which had cropping baked in, but for some reason, all my pictures just end up being resized rather than cropped. I’m using “100×100!� to try and get a centered 100 pixel square crop and it’s just not working :(
Wade
Andy,
Great work – I’ve run into just this very situation (needing to do a proportional resize), so seeing your solution was great. I’m also going to do a centered crop.
I’m also using MiniMagick, so could you drop your solution in my email as well? It’ll keep me from reinventing the wheel.
Andy Croll
I only needed it to do it with the resultant image centred, keeping as much of the subject of the photo centred – like Flickr does. However all you’d have to do is change the values on the img.with_crop line to 0, 0, w, h to crop from the top left corner.
Hope that helps
PS I’ve converted but not tested to mini_magick I’ll drop a copy in your email.
Luis Lavena
Andy, you assumed a ‘center’ gravity for your cropping.
What about anything beside it? (i’m trying to convert it to mini_magick).
Thanks.
Tim Lucas
Haven’t had a chance to play with ImageScience, and was wondering whether it had the proportional resize baked in. Nice snippet! One q: what geometry string did you end up using?
p.s. I really enjoy your writing style, and design.
Andy Croll
I use the Geometry string “120×40!” to get a proportionally-sized thumbnail. Not sure if that’s the correct interpretation of the “!” flag but it works for me!
PS Thanks!
OmarVelous
Andy… I seem to be getting a “invalid geometry format”. Mind you, i’m not using ImageScience, but instead was following the mini-magick tutorial.
The errors i’m experience seem to stem from the geometry file, which I believe has been updated since your original tutorial.
Would you be willing to paste/share the contents of your geometry file?
I think the lines in particular would be the following:
line 33ish:
if m = RE.match(str)
new(m[1].to_i, m[2].to_i, m[3].to_i, m[4].to_i, RFLAGS[m[5]])
else
raise ArgumentError, “invalid geometry format”
end
line 41ish:
def to_s
str = ‘’
str << “%g” % @width if @width > 0
str << ‘x’ if (@width > 0 || @height > 0)
str << “%g” % @height if @height > 0
str << “%+d%+d” % [@x, @y] if (@x != 0 || @y != 0)
str << FLAGS[@flag.to_i]
end
NOTE: “str << FLAGS[@flag.to_i]” - do we replace this with your “str << RFLAGS.index(@flag)”? I’ve tried to no avail.
BTW, i’m using the Feb 26th 2008 release.
Thank you!