Cropped thumbnails in attachment_fu using mini_magick

I’m in the process of building a ruby on rails application that allows people to upload images. The application then takes these images and creates a series of thumbnails that are used throughout the site.

Having opted to use attachment_fu and mini_magick as my respective file upload plugin and image processor, I found an excellent post by Khamsouk Souvanlasy explaining how to create the upload form using AJAX.

The difficultly came when I wanted to make one of the custom thumbnails measure 100px x 100px, and crop the source image appropriately:

Example of ratio cropping

Thankfully Andy Croll had written how he patched attachment_fu to enable it to do just that, only using ImageScience as it’s image processor.

Therefore, I’ve extended this patch to allow you to use a ! flag to create an aspect ratio cropped image, using mini_magick as your processor.

Step 1

Modify attachment_fu/lib/geometry.rb, as suggested by Andy in cropped thumbnails in attachment_fu using imagescience. This ensures the ! flag is interpretted correctly in attachment_fu.

Step 2

Modify the resize_image function of attachment_fu/processors/mini_magick_processor.rb:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
def resize_image(img, size)
  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)
      size = [size, size]
      img.resize(size.join('x'))
    else
      img.resize(size.join('x') + '!')
    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
      if ih > h
        shave_off =  ((ih - h) / 2).round
        img.shave("0x#{shave_off}")
      end
      if iw > w
        shave_off = ((iw - w ) / 2).round
        img.shave("#{shave_off}x0")
      end
      img.resize(size.to_s)
    else
      img.resize(size.to_s)
    end
    self.temp_path = img
  end
end

Step 3

Modify your model, using the new ! flag to force a cropped aspect ratio:

has_attachment  :storage => :file_system,
                :max_size => 2.megabytes,
                :thumbnails => { :thumb => '100x100!', :medium => '470x470' },
                :processor => :MiniMagick

I think this is an excellent solution. Thanks to everyone I’ve link to here, they’ve done the real work!

17 Responses to “Cropped thumbnails in attachment_fu using mini_magick”

  1. Added a link to you on the main article at my place. Nice work!

  2. Very nice!
    I am still fairly new to this whole magick enterprise and i have to say you saved me a great deal of headache.

    I was wondering how i would do the same without forcefully centering the crop?
    (in other words, if it would be possible to use your solution with offsets and dimensions rather than a shave)

  3. Ian- Nice work! Would the crop command with a “center” gravity also work in this case?

  4. This is so sweet! Excellent work Ian and Andy.

  5. Shane, nguma,

    I’m answering both your posts here. Firstly, yes, you can use the crop command, and secondly, by doing this you could offset which part of the picture you show.

    Replace the if statement that starts on line 12 above with the following:

    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    
    if size.ends_with? "!"
      aspect = n_size[0].to_f / n_size[1].to_f
      ih, iw = img[:height], img[:width]
      h, w = (ih * aspect), (iw / aspect)
      w = [iw, w].min.to_i
      h = [ih, h].min.to_i
      offset = 0.5
      if ih > h
        img.gravity 'center'
        img.crop("#{h}x#{h}+0+#{offset * (ih-h)}")
      end
      if iw > w
        img.gravity 'center'
        img.crop("#{w}x#{w}+#{offset * (iw-w)}+0")
      end
      img.resize(size.to_s)
    else
      img.resize(size.to_s)
    end

    You can see I’ve hardcoded an offset value = 0.5 - This means it’ll effectively centre the crop. However, if the image was landscape, and you wanted a hard right, set that to 0. Hard left = 1. It’s a percentage placement.

    See how you get on…

  6. Hi Ian,
    Thanks for the great tutorial. I think there is one error on line 15 of the latest comment, w and h have to be switched round for the crops to work properly.

    Cheers,
    Jason

  7. Just wanted to say a big thank you to you and Andy for this - saved me a major headache.

  8. Thanks a lot! I was using acts_as_attachment + rmagick and it was a pain to get it working.

    Now with attachment_fu and minimagick it crops, works and rocks! :D

  9. Thanks for this!

    I was still getting scaled images if the image was landscape and being resized to a landscape aspect ratio. This fixed that for me, hope it helps:

    ih, iw = img[:height], img[:width]
    if aspect > 1 and iw > ih
    w, h = (ih / aspect), (iw * aspect)
    else
    w, h = (ih * aspect), (iw / aspect)
    end

  10. i had a problem with cropping/handling gif’s, in the doc of imagemagick i found that resize is called before crop. I changed it and now it is working ….
    #h, w = (ih * aspect), (iw / aspect)
    w = [iw, w].min.to_i
    h = [ih, h].min.to_i
    offset = 0
    img.resize(size.to_s)
    if ih > h
    img.gravity ‘center’
    img.crop(”#{h}x#{h}+0+#{offset * (ih-h)}”)
    end
    if iw > w
    img.gravity ‘center’
    img.crop(”#{w}x#{w}+#{offset * (iw-w)}+0″)
    end
    else
    img.resize(size.to_s)
    end

  11. Thanks for the writeup, this works great. But instead of hacking inside the lib directory in the plugin, you can use the “evil twins” method suggested by Chris (http://errtheblog.com/posts/67-evil-twin-plugin):

    - create RAILS_ROOT/plugins/attachment_fu_hack
    - in it, add an init.rb
    - and put something like this in it: http://pastie.caboo.se/129911

  12. […] necesitas recortar las fotos, echa un vistazo a este tutorial de Ian Drysdale para saber cómo has de parchear el attachment_fu. Para tu comodidad, adjunto los dos ficheros […]

  13. Hi…i got following error stack while uploading image by attachment_fu with mini_magick processor

    ImageMagick command (identify C:/DOCUME~1/ADMINI~1/LOCALS~1/Temp/minimagic4892-0 ) failed: Error Given 256

    #{RAILS_ROOT}/vendor/plugins/mini_magick/lib/mini_magick.rb:98:in `run_command’
    #{RAILS_ROOT}/vendor/plugins/mini_magick/lib/mini_magick.rb:55:in `initialize’
    #{RAILS_ROOT}/vendor/plugins/mini_magick/lib/mini_magick.rb:37:in `new’
    #{RAILS_ROOT}/vendor/plugins/mini_magick/lib/mini_magick.rb:37:in `from_blob’
    #{RAILS_ROOT}/vendor/plugins/mini_magick/lib/mini_magick.rb:43:in `from_file’
    #{RAILS_ROOT}/vendor/plugins/mini_magick/lib/mini_magick.rb:42:in `open’
    #{RAILS_ROOT}/vendor/plugins/mini_magick/lib/mini_magick.rb:42:in `from_file’
    #{RAILS_ROOT}/vendor/plugins/attachment_fu/lib/technoweenie/attachment_fu/processors/mini_magick_processor.rb:15:in `with_image’
    #{RAILS_ROOT}/vendor/plugins/attachment_fu/lib/technoweenie/attachment_fu.rb:322:in `with_image’
    #{RAILS_ROOT}/vendor/plugins/attachment_fu/lib/technoweenie/attachment_fu/processors/mini_magick_processor.rb:30:in `process_attachment’
    C:/InstantRails/ruby/lib/ruby/gems/1.8/gems/activerecord-1.15.3/lib/active_record/callbacks.rb:333:in `send’
    C:/InstantRails/ruby/lib/ruby/gems/1.8/gems/activerecord-1.15.3/lib/active_record/callbacks.rb:333:in `callback’
    C:/InstantRails/ruby/lib/ruby/gems/1.8/gems/activerecord-1.15.3/lib/active_record/callbacks.rb:330:in `each’
    C:/InstantRails/ruby/lib/ruby/gems/1.8/gems/activerecord-1.15.3/lib/active_record/callbacks.rb:330:in `callback’
    C:/InstantRails/ruby/lib/ruby/gems/1.8/gems/activerecord-1.15.3/lib/active_record/callbacks.rb:301:in `valid?’
    C:/InstantRails/ruby/lib/ruby/gems/1.8/gems/activerecord-1.15.3/lib/active_record/validations.rb:751:in `save_without_transactions’
    C:/InstantRails/ruby/lib/ruby/gems/1.8/gems/activerecord-1.15.3/lib/active_record/transactions.rb:129:in `save’
    C:/InstantRails/ruby/lib/ruby/gems/1.8/gems/activerecord-1.15.3/lib/active_record/connection_adapters/abstract/database_statements.rb:59:in `transaction’
    C:/InstantRails/ruby/lib/ruby/gems/1.8/gems/activerecord-1.15.3/lib/active_record/transactions.rb:95:in `transaction’
    C:/InstantRails/ruby/lib/ruby/gems/1.8/gems/activerecord-1.15.3/lib/active_record/transactions.rb:121:in `transaction’
    C:/InstantRails/ruby/lib/ruby/gems/1.8/gems/activerecord-1.15.3/lib/active_record/transactions.rb:129:in `save’
    #{RAILS_ROOT}/app/controllers/test_upload_images_controller.rb:30:in `create’

    can u tell me what will be reason behind this?? thax

  14. I’m using the attachment_fu_hack with the suggestions in the comments and ur tutorial, but for some reason it’s resizing, but not re-copping…

    This is the resize code i’m using…

    def resize_image(img, size)
    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)
    size = [size, size]
    img.resize(size.join(’x'))
    else
    img.resize(size.join(’x') + ‘!’)
    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]
    if aspect > 1 and iw > ih
    w, h = (ih / aspect), (iw * aspect)
    else
    w, h = (ih * aspect), (iw / aspect)
    end
    w = [iw, w].min.to_i
    h = [ih, h].min.to_i
    offset = 0.5
    if ih > h
    img.gravity ‘center’
    img.crop(”#{h}x#{h}+0+#{offset * (ih-h)}”)
    end
    if iw > w
    img.gravity ‘center’
    img.crop(”#{w}x#{w}+#{offset * (iw-w)}+0″)
    end
    img.resize(size.to_s)
    else
    img.resize(size.to_s)
    end
    self.temp_path = img
    end
    end

  15. […] with the way mini_magick handled geometry strings and a quick bit of Google_fu led us to this wonderful blog post by Ian Drysdale who told us how to patch into attachment_fu and change the resize […]

  16. Thanks for writing this up. Works just like it should.

    It’d be nice to have it integrate with this great javascript-based image cropper for better control of where the thumbs crop. http://kropper.captchr.com/

    The best implementation would probably crop the thumbnails using shave() the way you have it now upon initial upload But then it would use crop() on a 2nd action that would crop the original image down to the desired coordinates only in the thumbnail. Perhaps the resize_image method could just be rewritten to look for an optional set of coordinates passed from somewhere and use crop() instead of shave() if they are present.

    You up to the challenge of putting it together?

  17. Worked like a charm for me. Thanks!

Discussion Area - Leave a Comment