Rotating Paperclip Image Attachments in Rails

Rotating Paperclip Image Attachments in Rails

October 22nd, 2011 // 3:30 pm @

With users uploading personal photos, especially ones coming their phones that capture landscape photos in portrait mode and vice versa, one of the things I wanted to integrate into Black Book Singles is the ability to rotate photos. Since I’m using the Paperclip gem, this should be relatively easy to achieve through a custom attachment processor.

Doing a precursory search resulted in a bit of helpful code to get me started. Thanks to tekn0t for sharing his example in this gist.

Unfortunately, I came to the conclusion that this example has three main issues:

  1. It doesn’t follow Paperclip convention of passing a set of option key/value pairs into the Processor class to enable the processing.
  2. It assumes any class that implements it also implements both rotating? and rotation methods or attributes, with no flexibility on the naming.
  3. The base class of the implemented Rotator class is Thumbnail. Because Thumbnail is included by default when the geometry option is used, this means the geometry commands will unnecessarily be executed twice.

With these things in mind, I set out to rewrite the example code into a more robust implementation.

First, we’re going to need an attribute on our model that keeps track of the current angle of rotation. This is necessary so that future rotations will be based upon the current angle in degrees. To do this, create a migration that adds an integer column named rotation to whichever Paperclip model you’re working with.

class AddRotationToPhotos < ActiveRecord::Migration
  def self.up
    add_column :photos, :rotation, :integer, :null => false, :default => 0
  end
 
  def self.down
    remove_column :photos, :rotation
  end
end

Next, we’ll want a simple way to update the rotation value on a model. Since Paperclip needs to be explicitly told when to reprocess an image, we’ll need to tell it to do so whenever this value changes. Also, keep in mind that basic math tells us that the only degrees we should worry about are between 0-360, meaning we can perform a simple modulus operation on our rotation to keep it in this range.

class Photo < ActiveRecord::Base
  before_save :adjust_rotation
  before_update :reprocess_image
 
protected
 
  def adjust_rotation
    self.rotation = self.rotation.to_i
    self.rotation = self.rotation % 360 if (self.rotation >= 360 || self.rotation <= -360)
  end
 
  def reprocess_image
    self.image.reprocess! if self.rotation_changed?
  end
end

Now we can set the rotation value when creating a record (defaulting to 0) or when updating an existing record.

# new record example to rotate 90 degrees clockwise
photo = Photo.new(params[:photo])
photo.rotation = 90
photo.save
 
# update record example to rotate 90 degrees counter-clockwise
photo = Photo.find(params[:id])
photo.update_attribute(:rotation, -90)

So far, our code has’t done anything to tell Paperclip what to do. The first step in notifying Paperclip is to update the hash passed into has_attached_file. Notably, we’ll want to include :rotator (which will be the name of our processor class) into the array of processors. Also, we’ll need to pass a :rotation key/value pair in with each attachment style we wish to process as such. Since this call is being made at the model level, we can’t simply use self.rotation to access the value of the record’s rotation angle. Fortunately, Paperclip allows us to use a lambda function to return a styles hash, which includes the Attachment object. Knowing this, we can access this current record’s rotation via attachment.instance.rotation.

class Photo < ActiveRecord::Base
  has_attached_file :image,
    :processors => [:rotator],
    :styles => lambda { |a| {
      :thumb => {
        :geometry => '50x50#',
        :rotation => a.instance.rotation,
      },
      :full => {
        :geometry => '640x640>',
        :rotation => a.instance.rotation,
      },
    } }
end

Finally, now that we’re passing the rotation value to Paperclip, we need to create the processor to handle it. Much of the basis of this code is taken from the Thumbnail processor included with Paperclip, which I won’t be delving into. The main thing to pay attention to is any code pertaining to the rotation attribute being set and used, most notably in the transformation_command method.

module Paperclip
  class Rotator < Processor
    attr_accessor :file, :rotation, :source_file_options, :convert_options, :whiny, :current_format, :basename
 
    def initialize(file, options = {}, attachment = nil)
      super
      @file                = file
      @rotation            = options[:rotation].to_i
      @source_file_options = options[:source_file_options]
      @convert_options     = options[:convert_options]
      @whiny               = options[:whiny].nil? ? true : options[:whiny]
      @current_format      = File.extname(@file.path)
      @basename            = File.basename(@file.path, @current_format)
    end
 
    def make
      if @rotation != 0
        dst = Tempfile.new([@basename, @format ? ".#{@format}" : ''])
        dst.binmode
 
        begin
          parameters = []
          parameters << source_file_options
          parameters << ":source"
          parameters << transformation_command
          parameters << convert_options
          parameters << ":dest"
 
          parameters = parameters.flatten.compact.join(" ").strip.squeeze(" ")
          success = Paperclip.run("convert", parameters, :source => File.expand_path(@file.path), :dest => File.expand_path(dst.path))
        rescue Cocaine::ExitStatusError => e
          raise PaperclipError, "There was an error processing the image rotation for #{@basename}" if @whiny
        rescue Cocaine::CommandNotFoundError => e
          raise Paperclip::CommandNotFoundError.new("Could not run the `convert` command. Please install ImageMagick.")
        end
 
        dst
      else
        @file
      end
    end
 
  private
 
    def transformation_command
      "-rotate #{@rotation}"
    end
  end
end

And there you have it! An easy-to-use rotation processor for use with your Paperclip enabled model in Rails.


Category : Blog &Featured

2 Comments → “Rotating Paperclip Image Attachments in Rails”


  1. Zubin

    1 month ago

    This works very well, thanks for posting this Matt!

    Reply

    • babu

      3 weeks ago

      do u know how to use paperclip

      Reply

Leave a Reply

Testimonials

"You're one of the best programmers I've worked with, and a nice guy to boot."

By Warren B., USA

Subscribe Now