David Eisinger


Elsewhere > Manual Cropping with Paperclip

Posted 2012-05-31 on viget.com

It’s relatively straightforward to add basic manual (browser-based) cropping support to your Paperclip image attachments. See RJCrop for one valid approach. What’s not so straightforward, though, is adding manual cropping while preserving Paperclip’s built-in thumbnailing capabilities. Here’s how.

Just so we’re on the same page, when we’re talking about “thumbnailing,” we’re talking about the ability to set a size of 50x50#, which means “scale and crop the image into a 50 by 50 pixel square.” If the original image is 200x100, it would first be scaled down to 100x50, and then 25 pixels trimmed from both sides to arrive at the final dimensions. This is not a native capability of ImageMagick, but rather the result of some decently complex code in Paperclip.

Our goal is to allow a user to select a portion of an image and then create a thumbnail of just that selected portion, ideally taking advantage of Paperclip’s existing cropping/scaling logic.

Any time you’re dealing with custom Paperclip image processing, you’re talking about creating a custom Processor. In this case, we’ll be subclassing the default Thumbnail processor and making a few small tweaks. We’ll imagine you have a model with the fields crop_x, crop_y, crop_width, and crop_height. How those get set is left as an exercise for the reader (though I recommend JCrop). Some code, then:

module Paperclip
  class ManualCropper < Thumbnail
    def initialize(file, options = {}, attachment = nil)
      super
      @current_geometry.width = target.crop_width
      @current_geometry.height = target.crop_height
    end

    def target
      @attachment.instance
    end

    def transformation_command
      crop_command = [
        "-crop",
        "#{target.crop_width}x"
        "#{target.crop_height}+"
        "#{target.crop_x}+"
        "#{target.crop_y}",
        "+repage"
      ]

      crop_command + super
    end
  end
end

In our initialize method, we call super, which sets a whole host of instance variables, include @current_geometry, which is responsible for creating the geometry string that will crop and scale our image. We then set its width and height to be the dimensions of our cropped image.

We also override the transformation_command method, prepending our manual crop to the instructions provided by @current_geometry. The end result is a geometry string which crops the image, repages it, then scales the image and crops it a second time. Simple, but not certainly not intuitive, at least not to me.

From here, you can include this cropper using the :processors directive in your has_attached_file declaration, and you should be good to go. This simple approach assumes that the crop dimensions will always be set, so tweak accordingly if that’s not the case.