Rotating Paperclip Image Attachments in Rails
October 22nd, 2011 // 3:30 pm @ matt
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:
- It doesn’t follow Paperclip convention of passing a set of option key/value pairs into the Processor class to enable the processing.
- It assumes any class that implements it also implements both
rotating?androtationmethods or attributes, with no flexibility on the naming. - The base class of the implemented
Rotatorclass isThumbnail. BecauseThumbnailis 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.
Zubin
1 month ago
This works very well, thanks for posting this Matt!
babu
3 weeks ago
do u know how to use paperclip