Compressing Images With attachment_fu And RMagick

Right now I’m building a content management system for a fashion photographer, darling. Attachment_fu is resizing the images and it was all going swimmingly until we noticed that the thumbnails’ file sizes were much larger than expected.

Malingering Metadata

After some flapping of arms and scratching of heads I realised that my photographer’s images contain lots of metadata including colour profiles, EXIF information and TIFF information. We don’t need any of that in the thumbnails so I looked at the rather good RMagick documentation to find out how to strip it out. Here’s how:

img.strip!

(Thanks to Sebastien Grosjean for alerting me to the strip! method. I originally had delete_profile '*' which works but isn’t as neat.)

You add it to your model, let’s say Photo, like this:

class Photo < ActiveRecord::Base
  has_attachment :content_type => :image,
                 :storage      => :file_system,
                 # etc

  def resize_image(img, size)
    # Get rid of all colour profiles.  They take up a lot of space.
    img.strip!
    super
  end
end

Colour profiles help individual users see colours as they are intended to be seen — provided the user has set up their profiles correctly. But if you are using Flash as a front-end, which I am here, they’re irrelevant because Flash simply ignores them and uses an RGB profile instead.

Throwing away the colour profiles made all the difference: my 96x64 pixel thumbnail went from 32kB down to 3kB.

Flashy Interlude

While we’re talking about Flash, you should read this excellent post which tells you clearly and concisely how to embed SWFs in your Rails views. I hadn’t used Flash before but that post got me up and running first time.

Compression Up, Quality Down

Along the way I learned that RMagick lets you alter the trade-off between compression and quality. My photographer’s images are JPEGs so this works rather well; the JPEG format was designed with exactly this kind of trade-off in mind.

To do it set the quality to a value between approximately 10 (huge compression, awful quality) and 95 (tiny compression, best quality) in attachment_fu’s RMagick processor’s resize_image method:

def resize_image(img, size)
  # ...
  self.temp_path = write_to_temp_file(img.to_blob { self.quality = 50 })
end

The default is 75. See the RMagick documentation and the JPEG Image Compression FAQ for more details.

Filter Finesse

If you’re still struggling to get the compression characteristics you need, it might be worth trying a different filter. RMagick uses the Lanczos filter by default but there are 15 or so other filters which may suit your images better.

Just pass the one you want into the image#resize method.

Conclusion

Thumbnails don’t need image metadata. Throw it away and banish the bloat.

Andy Stewart, 27 June 2007

Posted in Rails


15 Comments

Jump to comment form
  1. Another superb post Mr. S! Keep them coming :-)

    I've heard that RMagick can have memory leak issues. Have you experienced this on this project, or is it a smear campaign by ImageScience?

    Lylolly
    27 June 2007
  2. Why thank you!

    I've heard the same about RMagick but so far it's behaved impeccably. My hope-for-the-best approach seems to be working....

    Andy Stewart
    27 June 2007
  3. That's great. The only thing I am struggling with when it comes to setting the quality/format is that say you upload a png and convert it to a jpeg, attachmentfu still has the contenttype and filename with png in it. Any ideas how to get around this as I want to allow uploading of png's but have them automatically converted to jpegs??

    I currently have the following:

    def resize_image(img, size) self.temppath = writetotempfile(img.to_blob { self.quality = 50; self.format = 'JPEG' }) end

    Jamie
    08 July 2007
  4. Jamie, you want the after_resize callback. Add this to your model:

    after_resize do |record, img|
      record.content_type = 'image/jpeg'
      record.filename = record.filename.sub(/[.]png$/, '.jpg')
    end
    

    The attributes will then be set correctly in the database.

    By the way, the resize_image method you've got above won't actually resize the image. Have a look at the original implementation to see what you're missing.

    Andy Stewart
    10 July 2007
  5. Andy: that works great, although attachment_fu doesn't actually reference the thumbnails in the public_filename method. This means that if you leave your original upload as a png and convert the thumbnails to jpegs, public_filename(:a_thumbnail) will have a .png extension instead of .jpg.

    I have gotten around this by overriding the thumbnail_name_for method:

    # Gets the thumbnail filename from db so be sure to :include => :thumbnails
      def thumbnail_name_for(thumbnail = nil)
      return filename if thumbnail.blank?
      basename = filename.sub(/\.\w+$/, '')
      obj = self.thumbnails.select { |t| t.thumbnail == thumbnail.to_s }.first
      ext = (obj.nil? ? filename : obj.filename).match(/\.\w+$/)[0]
      "\#{basename}_\#{thumbnail}\#{ext}"
    end
    Jamie
    11 July 2007
  6. Hi,

    Nice article, I'll check out how to make this as a plugin (maybe even submit a patch), this would be much easier and more DRY over more models.

    By the way why to use

    
    img.delete_profile "*"
    

    when you can directly use

    
    img.strip!
    
    Sebastien Grosjean - Zencocoon
    23 September 2007
  7. Hi Sebastien,

    I'd be delighted if you were to convert this into a plugin. Thanks for the offer.

    And thanks for pointing out img.strip! --- I didn't know about that and it's neater than the way I was doing it.

    Andy Stewart
    24 September 2007
  8. This is great. I have exactly the same problem with bloated thumbnails due to metadata, but I'm using the paperclip plugin by thoughtbot. I was thinking of changing to attachment_fu based on your neat solution, but that means changing my whole project (argh)! Guess I'll have to go do some digging to see if I can do something similar to what you did. Thanks!

    Martin
    29 June 2008
  9. Martin, one way or another you'll want to augment Paperclip's transformation command method, adding a line like this:

    trans << " -strip"
    

    This will do the trick.

    I've just started using Paperclip, and I must say that I prefer it to attachment_fu.

    Andy Stewart
    30 June 2008
  10. Hi Andy, Your advice was spot-on! Thanks very much. That did indeed do the trick.

    Martin
    01 July 2008
  11. Hi Andy, building on the knowledge I acquired from you, I have made a little hack to Paperclip to give the developer the ability to add a "watermark" or "annotation" to the image. I've published it at http://martinceronio.net/?p=59 if you're interested in suggesting improvements.

    Martin
    07 July 2008
  12. This trick only works if you are resizing the image.

    What I did was to move the self.temp_path = line to the process_attachment_with_processing...

    
    def process_attachment_with_processing
       ...
       resize_image_or_thumbnail! img
       self.temp_path = write_to_temp_file(img.to_blob {self.quality = 50 })
       ...
    end
    

    Then just remove that line from the resize_image method. The resize image method is only called if you specify :resize_to => ... in has_attachment in the model.

    I might fork attachment_fu and write a :quality option for it... it seems to be something very useful. In fact, there are a lot of useful things missing that are easily implementable, like watermarks.

    Brian Cardarella
    14 August 2008
  13. Brian, thanks for pointing that out.

    I think lots of people use attachment_fu, so if you were to fork it and add features, you would be much appreciated.

    Andy Stewart
    14 August 2008
  14. There seems to be an issue with the unit tests with Rails 2.1 After looking into it further I've discovered that a completely rewritten Attachment_fu plugin is in the works:

    http://github.com/technoweenie/attachment_fu/tree/rewrite

    I think I'm going to wait and see what changes are made with this new version. (although progress on the plugin seems to be going pretty slow)

    Brian Cardarella
    23 August 2008
  15. Brian, I've had great success recently with Paperclip. It's being actively developed, so you'll probably be able to make more progress adding watermarks and so on.

    Andy Stewart
    27 August 2008

Have your say

You can use Markdown in your comments. If you want to post code, do this:

<pre><code class="ruby|javascript|css|html">your code here</code></pre>

Thanks!