[Rails] Mask an image using ImageMagick, Paperclip and S3
A project I’m currently working on requires image masking. I had already developed the site with plans to deploy to Heroku using Paperclip for image attachments using Amazon S3 for storage (you can read how I set that up here). I scoured the web for existing tutorials and documentation but found little that was relevant to my situation. My first inclination was to write a post-process method – grab the image, mask it and write it back to S3 which proved to be a dead-end (you can see the problem I ran into here – granted it’s possible you could still go that route). After a bit of Googling I ended up using a processor. Here’s what I did (I must confess the “boilerplate” code for the processor was some I found, unfortunately I did not keep the link – if you recognize it please let me know!):
In my model:
has_attached_file :image, :styles =>{ :main_feature => {:geometry => "1020x470", :processors => [:masker] }, :large => "1020x470", :event_page => "460x212", :top_feature => "345x159", :smallest => "229x131"#, }, :storage => :s3, :s3_credentials => "#{Rails.root}/config/s3.yml", :path => ":attachment/:id/:style.:extension", :url => "/:id/:style/:basename.:extension", :bucket => "yo-bucket-name"
Note line 3, where I’ve added a processor called “masker”. I created a folder called ‘paperclip_processors’ inside my lib directory and created masker.rb. In that same folder I included the png of my mask (mine is simply called mask.png). I’m using an alpha mask. In masker.rb I placed the following code:
module Paperclip class Masker < Processor def initialize file, options = {}, attachment = nil super @format = File.extname(@file.path) @basename = File.basename(@file.path, @format) end def make src = @file dst = Tempfile.new([@basename, @format]) dst.binmode begin parameters = [] parameters << ':source' parameters << ':mask' parameters << '-alpha' parameters << 'on' parameters << '-compose' parameters << 'CopyOpacity' parameters << '-composite' parameters << ':dest' parameters = parameters.flatten.compact.join(" ").strip.squeeze(" ") mask_path = File.expand_path('lib/paperclip_processors/mask.png') success = Paperclip.run("convert", parameters, :source => "#{File.expand_path(src.path)}[0]", :mask => "#{mask_path}[0]", :dest => File.expand_path(dst.path)) rescue PaperclipCommandLineError => e raise PaperclipError, "There was an error during the mask for #{@basename}" if @whiny end dst end end end
Lines 18-25 show the arguments we’ll be using to interface with ImageMagick (the documentation for this is found here). Basically we’re inputting the source and mask, turning the alpha flag on, using the compose method with CopyOpacity to copy the opacity of the mask to the final composite (masked) image and finally the destination. Now when an image is added to my model, a new image is sized and created – best of all, it works on Heroku!