Picture uploading / validating / resizing

Hello,

I’m researching various options to handle picture
uploading/validating/resizing in Rails.

I plan to have one table “pictures” (model Picture) which will store all
pictures. These pictures can then be assigned to other models through
associations.

Here is the process:

  1. User browses the picture and uploads it.
  2. Picture’s content-type and size are validated (e.g. max 1MB size)
  3. Picture is autmatically resized to a given size (e.g. 700x500) and
    converted to JPG. This resulting JPG is stored in the file system as
    “original”. The path pointing to the picture should be
    “/public/pictures/<picture.id>/original.jpg”. Temp folder should be used
    for form redisplays in some situations.
  4. Views can request original jpg or some modified version (using
    rmagick modifications) - e.g. view can request 200x100 version of the
    original. These modified versions are cached in the file system.

I’ve started to play with file_column and ImageMagick for Rails.

Can this scenario be accomplished using these 2 extensions? Or do you
know about some other helpful resources?

Thanks for any help :wink:

I’m doing something very similar (I do thumbnailing for images that
represent users), but I’m not using file_column, I’m just using a BLOB
column. I haven’t done any scale testing yet, so I might have to switch
to
some file caching scheme in the future.

I made some small additions to the Picture class from the Agile book.
Here’s the code:

#—

Excerpted from “Agile Web D. with Rails”

We make no guarantees that this code is fit for any purpose.

Visit http://www.pragmaticprogrammer.com for more book information.

#—
class Picture < ActiveRecord::Base

validates_format_of :content_type, :with => /^image/,
:message => “— you can only upload pictures”

#MES- TODO: We may want to delay loading the data column- loading the
data

just to get info like the extension might get expensive.

DEFAULT_THUMBNAIL_HEIGHT = 50.0

MAX_IMAGE_SIZE = 1 * 1024 * 1024

def picture=(picture_field)
self.name = base_part_of(picture_field.original_filename)
self.extension = File.extname(self.name)
self.content_type = picture_field.content_type.chomp
self.data = picture_field.read
end

def check_valid
#MES- Check that the data isn’t too big
if MAX_IMAGE_SIZE < data.length
raise “Image too large”
end

#MES- Check that the blob we contain is an image
return false if !load_image_lib
begin
  img = Magick::Image.from_blob(data)
  return true
rescue
  #MES- If we get an exception converting it to an image, then it's 

not
good
raise “Image format not recognized”
end

end

def base_part_of(file_name)
name = File.basename(file_name)
name.gsub(/[^\w._-]/, ‘’)
end

def create_thumbnail(height = DEFAULT_THUMBNAIL_HEIGHT)
#MES- Only do thumbnailing if the Image Magick library can be
loaded.
# This is to make setup easier for other developers- they are not
# required to have Image Magick.
# More information on Image Magick is available at
# http://studio.imagemagick.org/RMagick/doc/usage.html
if load_image_lib
#MES- Turn the blob into an ImageMagick object
img = Magick::Image.from_blob(data).first
if img.nil?
logger.info “Failed to make thumbnail for image #{self.name}-
unable
to create RMagick wrapper for image”
return nil
end
#MES- Shrink the image
thumbnail = img.thumbnail(height/img.rows.to_f)
#MES- Store it into a new Picture object and return it
return Picture.create(:name => self.name, :extension =>
self.extension,
:content_type => self.content_type, :data => thumbnail.to_blob)
else
return nil
end
end

def load_image_lib
begin
#MES- Note that “require” caches values, so calling it multiple
times
should NOT cause the
# library to be loaded multiple times.
require ‘RMagick’
return true
rescue MissingSourceFile
logger.info ‘In Picture::load_image_lib. The RMagick library
could
not be loaded, so image manipulations will not be performed.’
return false
end
end
end

and here’s the schema I’m using:

CREATE TABLE pictures (
id <%= @pk %>,
name varchar(255) default NULL,
extension varchar(255) default NULL,
content_type varchar(255) default NULL,
data blob,
created_at <%= @datetime %>,
updated_at <%= @datetime %>
) <%= @options %>;

To display the image, I put methods into my controller, like this:

def image
@user = User.find(params[:id])
send_image(@user.image)
end

def thumbnail
@user = User.find(params[:id])
send_image(@user.thumbnail)
end

def send_image(img)
send_data(img.data, :filename => img.name, :type =>
img.content_type,
:disposition => ‘inline’)
end

and I generate URLs like this:

def build_user_thumbnail_url(user)
if !user.thumbnail.nil?
return image_tag(url_for(:controller => ‘user’, :action =>
‘thumbnail’, :id => “#{user.id}#{user.thumbnail.extension}”))
else
return “
end
end

Finally, I add to my routes.rb to handle various image extensions:

#MES- Map any file extension for user ‘image’ or ‘thumbnail’ to the
action
map.connect ‘user/image/:id’, :controller => ‘user’, :action =>
‘image’,
:id => /^...$/
map.connect ‘user/thumbnail/:id’, :controller => ‘user’, :action =>
‘thumbnail’, :id => /^...$/

I hope that helps