Ruby on Rails and Rmagick Crop, Resize, Rotate, Thumbnail and Upload Images

Cropping Images With Rmagick

This post is a healthy mix of both ruby and rails methods for rmagick!

def submit_crop_image
   require 'RMagick'

   @user = curr_user

   tmpfile = "#{RAILS_ROOT}/public/images/users/tmp/#{@user.id}.jpg"
   img001 = "#{RAILS_ROOT}/public/images/users/#{@user.id}_001.jpg"
   img002 = "#{RAILS_ROOT}/public/images/users/#{@user.id}_002.jpg"
   img003 = "#{RAILS_ROOT}/public/images/users/#{@user.id}_003.jpg"
size001 = 100s
   size002 = 50
   size003 = 16

   top    = params['top'].to_i
   left   = params['left'].to_i
   width  = params['width'].to_i
   height = params['height'].to_i

   begin
     img = Magick::Image::read(tmpfile).first
     unless width == 0 or height == 0
       img.crop!(left,top,width,height)
     end
     img.resize! size001, size001
     img.write img001
     img.resize! size002, size002
     img.write img002
     img.resize! size003, size003
     img.write img003
     File.delete tmpfile
   rescue Exception => err
     @upload_image_error = 'Could not process your image file.  Please
try again.' + err
     render :action => 'edit_image'
   else
     @user.image_flag = 'Y'
     @user.save
     redirect_to :action => "profile", :username => @user.username
   end
 end

Thumbnail Images With Rmagick:

require 'RMagick'
 
 class PhotoController < ApplicationController
 
 [...snip...]
 
     def render_resized_image
                 @photo=Photo.find(@params["id"])
 
                 maxw = @params["width"] != nil ? @params["width"].to_i : 90
                 maxh = @params["height"] != nil ? @params["height"].to_i : 90
                 aspectratio = maxw.to_f / maxh.to_f
 
 
                 pic = Magick::Image.from_blob(@photo.image)[0]
 
 
                 picw = pic.columns
                 pich = pic.rows
                 picratio = picw.to_f / pich.to_f
 
                 if picratio > aspectratio then
                         scaleratio = maxw.to_f / picw
                 else
                         scaleratio = maxh.to_f / pich
                 end
 
                 #breakpoint
 
                 thumb = pic.resize(scaleratio)
 
                 @response.headers["Content-type"]=@photo.mime
     end
 end

Thumbnailer Number Two With Rmagick!

require 'RMagick'

maxwidth = 120
maxheight = 160
aspectratio = maxwidth.to_f / maxheight.to_f
imgfile = 'world'

pic = Magick::Image.read(imgfile + '.jpg').first
imgwidth = pic.columns
imgheight = pic.rows
imgratio = imgwidth.to_f / imgheight.to_f
imgratio > aspectratio ? scaleratio = maxwidth.to_f / imgwidth : scaleratio = maxheight.to_f / imgheight
thumb = pic.resize(scaleratio)

white_bg = Magick::Image.new(maxwidth, thumb.height)
pic = white_bg.composite(thumb, Magick::CenterGravity, Magick::OverCompositeOp)
pic.write(imgfile + '.thumb.jpg')

Database storage of uploaded images

class DbFile < ActiveRecord::Base
  IMAGE_TYPES = ['image/jpeg', 'image/pjpeg', 'image/gif', 'image/png', 'image/x-png']
  before_validation     :sanitize_filename
  validates_presence_of :size, :filename, :content_type

  class << self
    def new_file(file_data)
      content_type = file_data.content_type.strip
      (IMAGE_TYPES.include?(content_type) ? DbImage : DbFile).new \
        :data         => file_data.read,
        :filename     => file_data.original_filename,
        :size         => file_data.size,
        :content_type => content_type
    end
  end

  protected
  def sanitize_filename
      # NOTE: File.basename doesn't work right with Windows paths on Unix
      # get only the filename, not the whole path
      filename.gsub! /^.*(\\|\/)/, ''

      # Finally, replace all non alphanumeric, underscore or periods with underscore
      filename.gsub! /[^\w\.\-]/, '_'
  end
end

require 'rmagick'
require 'base64'
class DbImage < DbFile
  def data=(file_data)
    with_image(file_data, true) do |img|
      self.width  = img.columns
      self.height = img.rows
    end
  end

  def with_image(file_data = nil, save_image = false, &block)
    img = Magick::Image::read_inline(Base64.b64encode(file_data || self.data)).first
    block.call(img)
    write_attribute('data', img.to_blob) if save_image
    img = nil
    GC.start
  end
end

Controller Usage:

# returns DbImage if content_type matches
db_file = DbFile.new_file(params[:file][:data])
db_file.save

Model Usage:

# raw binary image data
File.open('my_file', 'w') { |f| f.write(db_file.data) }

# Image resizing with rmagick
# automatically creates RMagick::Image and 
# invokes GC.start
db_file.with_image do |img|
  img.scale(.25)
  img.write('thumb.jpg')
end

Rotating Images With RMagick and Ruby on Rails

This is archived from:Here

Photo model is fairly simple:

class Photo < ActiveRecord::Base
  file_column :file,
              :magick => {
                :size => ‘440×330',
                :crop => ‘4:3',
                :versions => {
                    :square => {
                                :crop => ‘1:1',
                                :size => ‘100×100'}
                              }
                }
end

Basically, this crops the uploaded photo so that it is 4:3 in ratio with a max width of 440 and a max height of 330. It also creates a square thumbnail version that is 100px on each side. That was simple and easy and after a tweak, uploading and resizing was working great. I figured that rotating an image would be simple, so I browsed the RMagick docs until I found rotate. Yep, it is simple.

Add photos controller named rotate like so:

def rotate
    photo   = Photo.find(params[:id])
    degrees = if params[:direction] == 'left' then -90 else 90 end

    #main photo
    image   = Magick::ImageList.new(photo.file)
    image   = image.rotate(degrees)
    image.write(photo.file)

    # thumb
    thumb   =  RAILS_ROOT + "/public/photo/file/#{photo.id}/square/#{File.basename(photo.file)}"
    image   = Magick::ImageList.new(thumb)
    image   = image.rotate(degrees)
    image.write(thumb)

    redirect_to :action => 'list'
end

Rotating the main image

The first line finds the photo based on the id passed. I also pass a direction (either left or right). Based on the direction, I either rotate the photo 90 degrees (clockwise) or -90 degrees (counter clockwise). photo.file is the path to the image I want to resize. After having determined the degrees, I created a new RMagick image object and rotated it by degrees. image.write(photo.file) simply saves the rotated file. One photo down, but one more rotation to go.

Rotating the square thumbnail

The square thumbnail was trickier. File column by default stores files in public/model_name/column_name/id/name_of_image.jpg. Different versions of the image are then stored inside a folder named the same as the version name. For example, because my model is ‘photo’ and the column name is ‘file’, an image with an id of 24 would be stored in public/photo/file/24/name_of_image.jpg and a version of that photo named square would be stored in public/photo/file/24/square/name_of_image.jpg. Knowing this, I just made the thumbnail file path the RAILS_ROOT plus the known path and file name. I then performed the same rotation on the square version as the original and saved the changes. Nothing to complex, but I was feeling pretty good about the result. All that was left to do was add some fancy little arrows (as you saw above) and my rotation addition was complete. For those that are curious below is the code to show the arrow images and make them link to the rotation action is shown below. Happy rotating!

<%= link_to image_tag('admin/arrow_rotate_anticlockwise.gif', {:alt => 'Rotate Clockwise'}), :action => 'rotate', :id => photo.id, :direction => 'left' %>


<%= link_to image_tag('admin/arrow_rotate_clockwise.gif', {:alt => 'Rotate Clockwise'}), :action => 'rotate', :id => photo.id, :direction => 'right' %>

Rotating has an negative effect on the quality of the image. In order to rectify this, you have to add a block after each image.write call like so:

image.write(photo.file) {self.quality = 100}