A powerfull yet simple on the fly thumbnail generator for Ruby on Rails


One of the first thing I need to deal with when I start a project are images, and thumbnail generation is -in my developper point of view- one the key feature of a framework/application. Nonetheless, I found it pretty hard to get a really good thumbnailer as I starded using Rails, so I wrote my own with full caching, img tag attributes and JS events support.

Edit : I finally turned my little helper into a plugin, you should now install it from Github like this :

script/plugin install git@github.com:guillaumedelyon/thumbnail_tag.git

...but anyway, if you want to read about how it works, you can continue reading.

Wanna try it ? Put this code snippet inside one of your helpers (I choosed the main "application_helper")

  def thumbnail_tag(args)
    #On the fly image resizer with cache managment    
    #synopsis = thumbnail_tag(:img=>'path_to_image', :size=>'100xauto' [, :class=>'your_css_class_here', :id=>'id_of_your_img_tag', :title=>'title_of_your_image', :alt=>'alt_attribute', :onclick=>"alert('Hi !')"...])
    
    #Sanity check
    raise 'Thumbnailer error ! Undefined :img parameter in thumbnail_tag helper' if (!args[:img])
    raise 'Thumbnailer error ! Undefined :size parameter in thumbnail_tag helper' if (!args[:size])

    #Get attributes
    options = ""    
    if args[:title] then options << " title='#{args[:title]}'" end
    if args[:id] then options << " id='#{args[:id]}'" end
    if args[:class] then options << " class='#{args[:class]}'" end
    #The alt attribute is automatically picked from the image file name if not explicitly given as an argument
    args[:alt] ? options << " alt='#{args[:alt]}'" : options << " alt='#{args[:img].split('/').last}'"
            
    #Get all other attributes as JS events
    attributes_array = ['img','alt','title','class','id','size']
    args.each do |arg|
      if !attributes_array.include?(arg[0].to_s)  
        options << " #{arg[0].to_s}=\"#{arg[1].to_s}\""
      end
    end
      
    #Create cache dirs if needed
    Dir.mkdir("#{RAILS_ROOT}/public/cache/#{request.host.split('www.').last}") if !File.directory?("#{RAILS_ROOT}/public/cache/#{request.host.split('www.').last}")
    Dir.mkdir("#{RAILS_ROOT}/public/cache/#{request.host.split('www.').last}/images") if !File.directory?("#{RAILS_ROOT}/public/cache/#{request.host.split('www.').last}/images")
    Dir.mkdir("#{RAILS_ROOT}/public/cache/#{request.host.split('www.').last}/images/#{args[:size]}") if !File.directory?("#{RAILS_ROOT}/public/cache/#{request.host.split('www.').last}/images/#{args[:size]}")
    Dir.mkdir("#{RAILS_ROOT}/public/cache/#{request.host.split('www.').last}/images/#{args[:size]}/#{args[:img].split('/')[0]}") if !File.directory?("#{RAILS_ROOT}/public/cache/#{request.host.split('www.').last}/images/#{args[:size]}/#{args[:img].split('/')[0]}")

    #Fallback in case something goes wrong later...
    response = "no pic"

    if (resize_array[0] == 'auto' && resize_array[1] == 'auto')
      #Dry picture requested => send back direct link to image, no cache
      response = ""
    end

    if (FileTest.exists?("#{RAILS_ROOT}/public/cache/#{request.host.split('www.').last}/images/#{args[:size]}/#{args[:img]}"))
      #We've got it in cache
      response = ""
    end
    
    if (!FileTest.exists?("#{RAILS_ROOT}/public/cache/#{request.host.split('www.').last}/images/#{args[:size]}/#{args[:img]}")) && !(resize_array[0] == 'auto' && resize_array[1] == 'auto')
      #File doesn't exists in cache and have to be resized
      require 'rubygems'
      require 'RMagick'
   
      #Image not already in cache ! => resize and write to cache 
      begin
        img = Magick::Image.read("#{RAILS_ROOT}/public/sites/#{request.host.split('www.').last}/medias/images/#{args[:img]}").first
      rescue
        #Ooops, image may have been deleted ?! => fall back on an empty image
        img = Magick::Image.read("#{RAILS_ROOT}/public/images/no_pic.jpg").first
      end
      
      #get requested size
      resize_array = args[:size].split('x')
      width = resize_array[0].to_i if resize_array[0] != 'auto'
      height = resize_array[1].to_i if resize_array[1] != 'auto'
    
      if resize_array[0] == 'auto' 
        #Width is set to 'auto' => compute corresponding X scale
        if img.rows.to_i > height.to_i 
          width = img.columns/(img.rows.to_f/height.to_f)
        else
          width,height = img.columns,img.rows
        end
      end
      
      if resize_array[1] == 'auto'
        #height is set to 'auto' => compute corresponding Y scale
        if img.columns.to_i > width.to_i 
          height = img.rows/(img.columns.to_f/width.to_f)
        else
          width,height = img.columns,img.rows
        end
      end  
    
      #resize
      thumb = img.resize(width,height)
      
      #write it to cache
      thumb.write("#{RAILS_ROOT}/public/cache/#{request.host.split('www.').last}/images/#{args[:size]}/#{args[:img]}")

      #send the image tag pointing on the cached picture
      response = ""
    end
      
    response
  end

Now you can use it like this in your views :

<%= thumbnail_tag(:img=>'/path_to_image/image_name.jpg', :size=>'100xauto') %>

This will generate thumbnail, cache it, then output this to your view :

image_name

Want a height of 200px, with a css class and an onclick event handler ? Simple as pie :

 
<%= thumbnail_tag(:img=>'path_to_image', :size=>'autox200', :class=>'landscape', :onclick=>"alert('hi !')") %>

Full synopsis looks like :

thumbnail_tag(:img=>'path_to_image', :size=>'100xauto' [, :class=>'your_css_class_here', :id=>'id_of_your_img_tag', :title=>'title_of_your_image', :alt=>'alt_attribute', :onclick=>"alert('Hi !')"...])

 

Now, please be carefull about these too things :

  • FIRST OF ALL : WATCH FOR PATH NAMES, as I use a pretty custom file tree in my apps, this surelly won't fit into your own (look about your cache dir !)
  • I actually use ImageMagick to manipulate images, ensure to have it working on your PC

Comments are welcome, but I do not garantee any support.


Envie de donner votre avis ?

Votre Nom*

Adresse Mail (ne sera pas publiée)*

Site web

Votre commentaire*


Cochez cette case si vous n'êtes pas un robot spammeur