Categories: Rails

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:

Most Wanted

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…

Article info




Leave a Reply

Your email address will not be published. Required fields are marked *