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…

Tags: , , ,

  • This is fantastic. Thank you sir.

    PS: Ubuntu + Firefox + Firebug = hot

  • Marshall

    I need to know how to do the column thing you mention as extra credit! I can’t figure out how to edit Paperclip to access another column on the original model!

  • admin

    Hi Marshall,

    I currently don’t have my development environment available, but I’d like to think that you ought to be able to do this by changing #{@annotation_text} in thumbnail.rb (as per the changes above) to #{@attributes[‘image_text’]} (where image_text would be your column name). I might be very wrong. As soon as my environment is back up and running, I would like to confirm this for you.

    Martin

  • Hi Martin,

    I figured it out. I created an option for paperclip called “caption_column”. Then, within attachment.rb, I created a @caption_text variable that called instance[options[:caption_column]]. instance, as you may know, passes the original object, so calling instance[options[:caption_column]] returns the contents of that column, which I then pass to Thumbnail.make in post_process. If anyone has questions, they can e-mail me (marshallsontag aaattt gmail dot com).

    How receptive is the paperclip author to patches that add custom imagemagick functionality? i am aware of the new convert_options option, but who wants to flood their models with imagemagick code? Guess I should find out…

  • Pingback: Recent Links Tagged With "paperclip" - JabberTags()

  • Andrew

    How to do this in a new paperclip?

    Paperclip:
    http://github.com/thoughtbot/paperclip/tree/master

  • Martin, I do recommend WPSyntax plugin for syntax highlighting:

    “WP-Syntax provides clean syntax highlighting using GeSHi — supporting a wide range of popular languages. It supports highlighting with or without line numbers and maintains formatting while copying snippets of code from the browser.”

    http://wordpress.org/extend/plugins/wp-syntax/

  • admin

    Hi Vitlalie, thanks very much for the suggestion. I have now installed WP-Syntax and used it for my last post. Works wonders.