This is deepcalm.com.

Cropped Thumbnails in attachment_fu using ImageScience // writing

12Apr2007

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.

Proportional Cropping

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

12Apr2007

Andy Croll

Dorian, could you be a bit more specific? Perhaps email me your problem.

‘Doesn’t work’ can cover a multitude of sins!

12Apr2007

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!

12Apr2007

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 :(

12Apr2007

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.

12Apr2007

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.

12Apr2007

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.

12Apr2007

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.

12Apr2007

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!

25Mar2008

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!

AndyCroll

Address

17A The Frabella 1
109 Rada Street
Makati City 1229 Philippines

Download my vCard

Colophon