Paperclip: Adding an annotation to an uploaded image
This is a little late-night hack for my late-night project I’m doing for a friend. I’m using Paperclip for uploading image attachments, and one of the requirements was to add some text (watermark or annotation or whatever you call it) to an image to indicate the website as the source of the image.
Update (2008.07.08): If you implement this solution, please be so kind as to replace “watermark” everywhere with “annotation”. It’s just more correct. Also, if you can recommend a nice way to quote/highlight code in WordPress, I’d love to hear from you.
Update: What the hey, I just changed it.
Firstly, a quick thanks to Andy Stewart and the help provided on his blog. Off we go:
We are adding two options: annotation_text, which is the text that will appear on the image, and annotation_for, which indicates the styles for which the text is applicable. For this, we need to modify a few files in the paperclip plugin. (Sorry, I don’t have a nice way yet to show code snippets on my blog like other sites do, but I will get there one day).
In attachment.rb:
   def self.default_options      @default_options ||= {        :url          => "/:attachment/:id/:style/:basename.:extension",        :path         => ":rails_root/public/:attachment/:id/:style/:basename.:extension",        :styles       => {},        :default_url  => "/:attachment/:style/missing.png",        :default_style => :original,        :validations  => [],        :storage      => :filesystem,        :annotation_for => [] #<-- Insert      }    end
We also change the initialize method to store the values of the options as instance variables:
   def initialize name, instance, options = {}      @name             = name      @instance         = instance      options = self.class.default_options.merge(options)      @url              = options[:url]      @path             = options[:path]      @styles           = options[:styles]      @default_url      = options[:default_url]      @validations      = options[:validations]      @default_style    = options[:default_style]      @storage          = options[:storage]      @whiny_thumbnails = options[:whiny_thumbnails]      @annotation_text   = options[:annotation_text] #<--Insert      @annotation_for    = options[:annotation_for] #<--Insert      @options          = options      @queued_for_delete = []      @queued_for_write = {}      @errors           = []      @validation_errors = nil      @dirty            = false
Our next stop is the post_process method:
   def post_process #:nodoc:      return if @queued_for_write[:original].nil?      @styles.each do |name, args|        begin          @annotation_for.include?(name) ? annotation_text = @annotation_text : annotation_text = nil #<-- Insert          dimensions, format = args          @queued_for_write[name] = Thumbnail.make(@queued_for_write[:original],                                                   dimensions,                                                   format,                                                   @whiny_thumnails, annotation_text) #<-- Insert        rescue PaperclipError => e          @errors << e.message if @whiny_thumbnails        end      end    end
Now, in thumbnail.rb, we make the following changes:
   def initialize file, target_geometry, format = nil, whiny_thumbnails = true, annotation_text = false      @file            = file      @crop            = target_geometry[-1,1] == '#'      @target_geometry = Geometry.parse target_geometry      @current_geometry = Geometry.from_file file      @whiny_thumbnails = whiny_thumbnails      @current_format  = File.extname(@file.path)      @basename        = File.basename(@file.path, @current_format)      @format = format      @annotation_text = annotation_text #<-- Insert    end    # Creates a thumbnail, as specified in +initialize+, +make+s it, and returns the    # resulting Tempfile.    def self.make file, dimensions, format = nil, whiny_thumbnails = true, annotation_text = false #<-- Change      new(file, dimensions, format, whiny_thumbnails, annotation_text).make #<-- Change   end
Finally, we change the transformation_command method to add the text to the image if applicable:
   def transformation_command      scale, crop = @current_geometry.transformation_to(@target_geometry, crop?)      trans = "-scale \"#{scale}\""      trans << " -crop \"#{crop}\" +repage" if crop      if @annotation_text #<-- Insert        # Code for annotating from http://www.imagemagick.org/Usage/annotating/        trans << " -gravity south" #<-- Insert        trans << " -stroke '#000C' -strokewidth 2 -annotate 0 '#{@annotation_text}'" #<-- Insert        trans << " -stroke none  -fill white   -annotate 0 '#{@annotation_text}'" #<-- Insert      end      trans    end  end
Now we can use the options in our model as follows:
class Puzzle < ActiveRecord::Base  has_attached_file :image, :styles => { :large => "800x600", :medium => "600x480>", :small => "400x300", :thumb => "100x100>" },    :annotation_text => "WANTED - AT YOUR EARLIEST CONVENIENCE",    :annotation_for => [:large, :medium] end
That’s it. I hope I remembered to include all the changes. The result:
Update (2008.07.08): For extra credit, it would be cool to be able to specify a column whose text to use for the annotation. Whether this has any practical value is questionable, I guess, and you’d have to make some more changes to paperclip to update the images from the original each time the column changes. Maybe some day when the need arises…
Leave a Reply