Sorry this page looks weird. It was automatically migrated from my old blog, which had a different layout and different CSS.

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.

Comments

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

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

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, attachment_fu still has the content_type 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.temp_path = write_to_temp_file(img.to_blob { self.quality = 50; self.format = ‘JPEG’ }) end

Jamie • 8 July 2007

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

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

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

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

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

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

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

Martin • 1 July 2008

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 • 7 July 2008

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

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

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

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

Thanks for posting valuable points…

Thiyagarajan Veluchamy • 13 September 2008

Okay I’ve been trying for hours to change the file type from tiff to jpg.

Andy Stewart your code will change the file extension but doesn’t actually convert the file to jpg.

Has any one been able to do this?

Jonathan Spooner • 10 May 2009

Jonathan: hmm, that’s weird. It certainly used to work. Perhaps RMagick has changed since I wrote this article.

My recommendation now would be to use Paperclip instead of attachment_fu. Paperclip will handle format conversion for you if you pass an array containing the geometry string and a format; see the Post Processing section of the README.

Another advantage of Paperclip is that it uses ImageMagick directly so you don’t need RMagick anymore.

Andy Stewart • 10 May 2009

Andrew Stewart • 27 June 2007 • Rails
You can reach me by email or on Twitter.