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 = "
"
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 :
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 ?