Recipe 15.4. Creating Resized Thumbnails with RMagick


Problem

You want to create resized thumbnails as you upload images to your application.

Solution

Use the RMagick image library to process thumbnails as each image is uploaded and saved to your application. This solution extends Section 15.2," by adding a "thumb" field to the photo table for storing image thumbnails:

db/migrate/001_build_db.rb:

class BuildDb < ActiveRecord::Migration   def self.up     create_table :items do |t|       t.column :name,         :string       t.column :description,  :text     end     create_table :photos do |t|       t.column :item_id,      :integer       t.column :name,         :string       t.column :content_type, :string       t.column :data,         :binary       t.column :thumb,        :binary     end   end   def self.down     drop_table :photos     drop_table :items   end end

It also adds image-processing code to the photo method of the Photo model definition:

app/models/photo.rb:

require 'RMagick'  # or, this line can go in environment.rb include Magick class Photo < ActiveRecord::Base   belongs_to :item   def photo=(image_field)     self.name = base_part_of(image_field.original_filename)     self.content_type = image_field.content_type.chomp     img = Magick::Image::read_inline(Base64.b64encode(image_field.read)).first     img_tn = img     img.change_geometry!('600x600') do |cols, rows, image|       if cols < img.columns  or rows < img.rows then         image.resize!(cols, rows)       end          end          self.data = img.to_blob     img_tn.change_geometry!('100x100') do |cols, rows, image|       if cols < img.columns  or rows < img.rows then         image.resize!(cols, rows)       end          end          self.thumb = img_tn.to_blob     # Envoke RMagick Garbage Collection:     GC.start   end   def base_part_of(file_name)     name = File.basename(file_name)     name.gsub(/[^\w._-]/, '')   end end

The Photos Controller gets an additional method, show_thumb, to fetch and display thumbnail images:

app/controllers/photos_controller.rb:

class PhotosController < ApplicationController   def show     @photo = Photo.find(params[:id])     send_data(@photo.data,               :filename => @photo.name,               :type => @photo.content_type,               :disposition => "inline")   end   def show_thumb     @photo = Photo.find(params[:id])     send_data(@photo.thumb,               :filename => @photo.name,               :type => @photo.content_type,               :disposition => "inline")   end end

Discussion

To get a better feel of what's going on behind the scenes when you upload a file you can set a breakpoint in the photo method and inspect the properties of the incoming image_field parameter using the breakpointer.

To learn more about using the Rails breakpoint facility, see Chapter 10.


The class method tells us that we are dealing with a object of the StringIO class:

irb(#<Photo:0x40a7dd10>):001:0> image_field.class => StringIO

The first thing we extract from this object is the name of the uploaded file. The solution uses the base_part_of method to perform some cleanup on the filename by removing spaces and any unusual characters. The result is saved in the "name" attribute of the Photo object:

irb(#<Photo:0x40a7dd10>):002:0> image_field.original_filename => "logo.gif" 

Next, we can examine the content_type of the image. The content type method of the StringIO class returns the file type with a carriage return appended to the end. The solution removes this character with chomp and saves the result.

irb(#<Photo:0x40a7dd10>):003:0> image_field.content_type => "image/gif\r" 

The solution attempts two resize operations for each uploaded image. This is usually what you want to avoid storing arbitrarily large image files in your database. Each call to RMagick's change_geometry! method attempts to resize its own copy of the Magick::Image object if the size of that object is larger than the dimensions passed to change_geometry!. If the uploaded image is smaller than the minimum requirements for your primary or thumbnail images fields, then skip resizing it.

RMagick's change_geometry! is passed a geometry string (e.g., '600x600'), which specifies the height and width constraints of the resize operation. Note that the aspect ratio of the image remains the same. The method then yields to a block that we define based on our specific requirements. In the body of our blocks, we check that the image's height and width are both smaller than the corresponding values we're constraining to. If so, the call does nothing, and the image data is save to the database, otherwise the resizing is performed.

After a resize attempt, each image object is converted to a blob type and saved in either the data or thumb fields of the photos table.

As in Section 15.3," we display these images with methods that use send_data in our Photos controller.

See Also

  • Section 15.1"

  • Section 15.2"




Rails Cookbook
Rails Cookbook (Cookbooks (OReilly))
ISBN: 0596527314
EAN: 2147483647
Year: 2007
Pages: 250
Authors: Rob Orsini

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net