#182
Oct 05, 2009

Cropping Images

Present a slick user interface for cropping image attachments using Jcrop and Paperclip.
Tags: plugins
Download (46.3 MB, 14:07)
alternative download for iPod & Apple TV (22.4 MB, 14:07)

Resources

# models/user.rb
class User < ActiveRecord::Base
  has_attached_file :avatar, :styles => { :small => "100x100#", :large => "500x500>" }, :processors => [:cropper]
  attr_accessor :crop_x, :crop_y, :crop_w, :crop_h
  after_update :reprocess_avatar, :if => :cropping?
  
  def cropping?
    !crop_x.blank? && !crop_y.blank? && !crop_w.blank? && !crop_h.blank?
  end
  
  def avatar_geometry(style = :original)
    @geometry ||= {}
    @geometry[style] ||= Paperclip::Geometry.from_file(avatar.path(style))
  end
  
  private
  
  def reprocess_avatar
    avatar.reprocess!
  end
end

# users_controller.rb
def create
  @user = User.new(params[:user])
  if @user.save
    if params[:user][:avatar].blank?
      flash[:notice] = "Successfully created user."
      redirect_to @user
    else
      render :action => "crop"
    end
  else
    render :action => 'new'
  end
end

def update
  @user = User.find(params[:id])
  if @user.update_attributes(params[:user])
    if params[:user][:avatar].blank?
      flash[:notice] = "Successfully updated user."
      redirect_to @user
    else
      render :action => "crop"
    end
  else
    render :action => 'edit'
  end
end

# lib/paperclip_processors/cropper.rb
module Paperclip
  class Cropper < Thumbnail
    def transformation_command
      if crop_command
        crop_command + super.sub(/ -crop \S+/, '')
      else
        super
      end
    end
    
    def crop_command
      target = @attachment.instance
      if target.cropping?
        " -crop '#{target.crop_w.to_i}x#{target.crop_h.to_i}+#{target.crop_x.to_i}+#{target.crop_y.to_i}'"
      end
    end
  end
end
<!-- views/users/crop.html.erb -->
<% title "Crop avatar" %>
<% content_for(:head) do %>
<%= stylesheet_link_tag "jquery.Jcrop" %>
<%= javascript_include_tag "jquery.Jcrop.min" %>
<script type="text/javascript" charset="utf-8">
$(function() {
  $('#cropbox').Jcrop({
    onChange: update_crop,
    onSelect: update_crop,
    setSelect: [0, 0, 500, 500],
    aspectRatio: 1
  });
});

function update_crop(coords) {
  var rx = 100/coords.w;
  var ry = 100/coords.h;
  $('#preview').css({
    width: Math.round(rx * <%= @user.avatar_geometry(:large).width %>) + 'px',
    height: Math.round(ry * <%= @user.avatar_geometry(:large).height %>) + 'px',
    marginLeft: '-' + Math.round(rx * coords.x) + 'px',
    marginTop: '-' + Math.round(ry * coords.y) + 'px'
  });
  var ratio = <%= @user.avatar_geometry(:original).width %> / <%= @user.avatar_geometry(:large).width %>;
  $("#crop_x").val(Math.round(coords.x * ratio));
  $("#crop_y").val(Math.round(coords.y * ratio));
  $("#crop_w").val(Math.round(coords.w * ratio));
  $("#crop_h").val(Math.round(coords.h * ratio));
}
</script>
<% end %>

<%= image_tag @user.avatar.url(:large), :id => "cropbox" %>

<h4>Preview:</h4>
<div style="width:100px; height:100px; overflow:hidden">
  <%= image_tag @user.avatar.url(:large), :id => "preview" %>
</div>

<% form_for @user do |f| %>
  <% for attribute in [:crop_x, :crop_y, :crop_w, :crop_h] %>
    <%= f.hidden_field attribute, :id => attribute %>
  <% end %>
  <p><%= f.submit "Crop" %></p>
<% end %>

RSS Feed for Episode Comments 55 comments

1. Fredrik Oct 05, 2009 at 15:20

You keep surpassing my expectations every monday. Exactly what I was looking for in my on-going project, many thanks for the time you spend at creating these screencasts.


2. dotemacs Oct 05, 2009 at 16:00

Ryan, thank you for another great cast. Even though it was a bit late, it was well worth the wait. Also thanks to jschwindt for creating a great tool.


3. cascadeclimbers Oct 05, 2009 at 16:22

Thanks again, Ryan. Anybody know of a similar plugin or jslib for drawing on an image?


4. laserlemon Oct 05, 2009 at 17:26

Great cast! I didn't know that digging into the Paperclip processors was that accessible. I'd love to figure out how to rock some rounded corners or other such coolness.


5. Kris Oct 05, 2009 at 18:49

This....is cool...

Great timing too; I was just thinking over the weekend that this would be a great feature for one of my projects.


6. iblue Oct 05, 2009 at 18:55

Ryan, I'm not sure, but what happens if some evil attacker modifies the crop_* parameter to something like `dd if=/dev/zero of=/dev/hda` or any other evil stuff?

Wouldn't that lead to a remote command injection vulerability in cropper.rb, line 14?


7. Junio Vitorino Oct 05, 2009 at 19:23

Amazing very useful too. Thanks Ryan once again.


8. Lin He Oct 05, 2009 at 20:16

Ryan, u r so awesome!!!!! seriously, I was looking for ways to do this last week and now you've done an episode for us.


9. Ryan Bates Oct 05, 2009 at 21:07

@iblue, good point. I think a quick "to_i" in the processor should solve this problem. I'll update the code.


10. Juan Schwindt Oct 05, 2009 at 22:01

Hi, Ryan.

I couldn't believe my eyes when I saw my little rjcrop example in your screencast, it's a great honor. I learn so much from you every week that contributing back feels very good.

Greetings from Buenos Aires, Argentina.
 


11. Nithin Bekal Oct 06, 2009 at 02:38

Thanks for the screencast. I've been storing images directly in the databases in my current application, and have been looking for a good way to crop the images before saving.

But will Jcrop cause problems with prototype? I always seem to run into trouble when I use jquery plugins, although jquery works fine in noconflict mode.


12. luciano Oct 06, 2009 at 05:50

Ryan,
One more time you made an awesome screencast!
Thanks a lot!


13. Brian Armstrong Oct 06, 2009 at 06:01

Wow...cropping gets into some more heavyweight stuff, this is great. Thanks Ryan!


14. Eric Berry Oct 06, 2009 at 07:05

Wow!

I am extremely impressed on the depth of this screencast. This is probably my new favorite. Please keep the more advanced screencasts coming. Outstanding job!


15. nemo Oct 06, 2009 at 11:22

Hey Ryan,
Excellent screencast....really helpful and concise.

Also, why dont you add recaptcha to your comments section to avoid all the spammars!

Thanks again and great job.


16. Florian Witteler Oct 06, 2009 at 12:25

Hi Ryan!

This is an amazing screencast. I didn't know that something like that is possible in such a cool ruby-on-rails way.
It is definitely a very useful feature and absolutely an eyecatcher on a website.


17. Dave Spurr Oct 06, 2009 at 12:47

Ryan,

An excellent screencast as usual, which was actually quite timely as I was just wondering how to pass cropping parameters to Paperclip.

Also thanks for mentioning my Prototype & script.aculo.us cropper. It was very good timing as I was actually in the process of releasing a new version, which is now live.

@Nithin Bekal - if you don't want to use jQuery and prototype together then look into my cropper (mentioned in the screencast & linked to in the show notes) that is Prototype & script.aculo.us based.


18. Nelson Rojas Oct 06, 2009 at 14:33

Hi Ryan! that good news was to see the new railscast appear.
I was wondering do you have something happened to the idol of Monday?
I also take your time to thank and congratulate the videos of "Everyday ActiveRecord" and "Mastering Rails Forms" are excellent.
Thanks and best regards from Chile!
Sincerely, Nelson.


19. Jaime Iniesta Oct 07, 2009 at 01:52

Thanks Ryan! Great stuff.

I use the prototype alternative for this, I'm not a jQuery guy :)


20. Larry Sprock Oct 07, 2009 at 06:15

Hi Ryan,
Another useful screencast! I would like to personally volunteer to weed out the spam and moderate the comments. I would suggest shutting down comments after a given time period. The amount of spam is crazy!


21. Karl Oct 07, 2009 at 08:40

Awesome RailsCast as always!

Ryan, you might want to check your mail, you've got 138 new messages! :)


22. Andreas Lyngstad Oct 07, 2009 at 12:28

Thanks! I check in every monday.


23. Emerson Oct 08, 2009 at 17:58

Hey Ryan,

Thanks for this. I am in the middle of developing an application that requires users to upload images and this is exactly the screen-cast I needed.

Also in the middle of learning how to use that GeoKit plugin... it's amazing.


24. -jul- Oct 08, 2009 at 18:07

It's simple, it's elegant, and it's *awesome*! Best screencast ever! I'll recommend this episode to my PHP friends as well, I don't think they would mind :)

However, you should update your commenting system, because of the spam it collects.

I once had a simple commenting / voting system which was abandoned for a couple of years, and... umm... well, when I had a look on it, I had to remove about 20000 spam messages. And, the commenting feature.


25. Matthew Savage Oct 09, 2009 at 09:22

For anyone who might be interested I have created a similar processor to handle rotation.

This was created because a) it seemed hacky using RMagick by hand when paperclip had all the bits available and b) it was the cleanest way to handle rotation with images hosted in S3 storage.

The required code can be found in the following gist: http://gist.github.com/206145

Enjoy!


26. Stewie Oct 09, 2009 at 16:19

Next weeks episode: filtering spam?

thanks


27. Jonas Arnklint Oct 13, 2009 at 06:50

Big fan of what you are doing. As I noticed the spam you get here, maybe something like the URL I submitted as "Website URL" can help get rid of it?
Keep up the good work!


28. Memiux Oct 13, 2009 at 10:39

Yeap, I agree with #37. Stewie, filtering spam will be great.


29. JB Oct 14, 2009 at 01:15

Hi,

Thanks for this great episode ! Just a tip for those who are wondering : I wanted to be able to re-crop the image without uploading it again, but the problem was that ":large" style was already cropped. I didn't found a clear documentation on this, but you can apply processors or other properties to some styles only. For instance :

:styles => { :small => "100x100#", :large => "500x500>" },
:processors => [:cropper]

could become :

:styles => {
  :small => {:geometry => "100x100#", :processors => [:cropper]},
  :large => {:geometry => "500x500>"}
}

This way ":large" image isn't cropped, but ":small" is. Then you can define an action to recrop your image, keeping the same code in the view.


30. Brent Collier Oct 14, 2009 at 11:48

Yeah, Jcrop is really nice. I recently used it to add user avatar cropping to one of our apps, but I set it up to work entirely from a single page. I didn't want an avatar change to lead the user away from the profile edit screen, so I used the responds_to_parent plugin to upload the image via ajax, and then immediately render the image for cropping. I'm pretty happy with how it turned out.


31. Veward Drawson Oct 15, 2009 at 06:14

Uhm... Am I falling behind or? I didn't think that coders where a target group for uggs and hair straighteners. But maybe I'm just falling out of style...


32. Erez Ben Shoham Oct 17, 2009 at 00:05

To validate images content types in IE you need to add to your validation 'image/pjpeg' and 'image/x-png'


33. supriya Oct 26, 2009 at 06:06

I followed step by step as you explained in the tutorial. Its working for me till the form in crop view. I can choose the dimensions I want, but when click submit it don't crop the image. It gives the same only the small version(default thumbnail).

I get an error in the terminal,which is

[paperclip] An error was received while processing: #<Paperclip::PaperclipError:
 There was an error processing the thumbnail for paperclip-reprocess,4716,0>
Please help me with this issue.
Thanks


34. supriya Oct 26, 2009 at 23:14

I also tried your project episode-182 but its giving the same error. I also installed the latest Imagemagick. But not working for me.


35. Yavor Ivanov Oct 29, 2009 at 06:54

Hi! I have a very strange problem.

When I try to do this cast I end up getting with a page that loads forever and nothing in the log.

Any idea if this is caused by imagemagic or something else?

btw I updated imagemagic.

Thanks in advance!


36. Yavor Ivanov Oct 29, 2009 at 11:15

I've spend the whole day trying to make this work. And implementing it in my app just doesn't work.

I've tried it on a side project and no problem.

Maybe it's something from my configuration... memcached or something... I am considering everything but still without getting any errors it's damn hard to know what the problem is.

Someone else experienced similar difficulties?


37. Michael Filbin Nov 02, 2009 at 19:34

Hi everyone,

I am not certain if anyone is still monitoring this thread since the last post that did not appear to be spam was on the 29th of October, but I hoping someone can point me in the right direction if you are still listening.

I have run into a snag when trying this example out on an app I've deployed to the Heroku platform. I am using AWS S3 as my asset host because Heroku has a read-only filesystem. I am pretty sure the problem I am having is coming from the way AWS handles assets in the cloud.

Here is the pastie: http://pastie.org/681007

In my app, instead of having an user model with an avitar attribute, I have a submissions model with a photo attribute. I am able to create a submission via the new action and upload a photograph as the paperclip attachment, but when we move create action in the submissions controller, paperclip raises NotIdentifiedByImageMagickError

It would appear that the call to Paperclip::Geometry.from_file() was expecting submissions/1/large.JPG but the url to this photo on AWS S3 is submissions/1/large.JPG?1257217300. The appended 1257217300 appears to be a cache identifier.

Does it appear that I am on the right track here? Does anyone have any suggestions for a work around?


38. Saket Nov 03, 2009 at 07:30

Great Ryan.. Amazing..........


39. Andrea Dec 12, 2009 at 09:30

@Michael Filbin

I had the same problem. To solve this I replaced "avatar.path(style)" with "avatar.to_file(style)" in the avatar_geometry method into the model... seems to work... With Heroku I've another problem with crop, the cropped image seems mantain the original ratio.


40. Hooston Dec 12, 2009 at 22:02

Thanks, very good...


41. Kristian Gerardsson Jan 15, 2010 at 14:40

Just a gotcha.. Windows/ImageMagick doesn't seem to like single quotes in the argument in the reprocessing.

" -crop '#{target.crop_w.to_i}x#{target.crop_h.to_i}+#{target.crop_x.to_i}+#{target.crop_y.to_i}'"

I changed the single quotes ' to \" (escaped double quote), and it worked right away.


42. javon Jan 25, 2010 at 22:35

crop, the cropped image seems mantain the original ratio


43. Matenia Rossides Feb 23, 2010 at 03:21

I have just tried this tutorial, great walkthrough by the way.

I end up with the :large image outputting twice and no crop options overlaid .... the submit button works but .... doesnt seem to let me crop :(
anyone want to help me tackle this?


44. Matenia Rossides Feb 23, 2010 at 14:54

OK, Just fixed 50% of my own problem ....
(n00b = me) forgot to include jquery library
I get the crop options, however, It is not saving the cropped version :( and its still outputting 2 of the :large items ...


45. Matenia Rossides Feb 23, 2010 at 15:02

Never mind ... another N00B mistake ...
MY APOLOGIES TO ALL ..
there was a sneaky image output tag that was hiding ... and i didnt see it until after a good night sleep and a strong coffee in the morning ...

KEEP UP THE AWESOME RAILSCASTS ...


46. Ed online clothing Mar 05, 2010 at 22:22

i will bookmark this blog,
and thanks for your nice post!


47. Ed online clothing Mar 05, 2010 at 22:22

i will bookmark this blog


49. Ed hardy sunglasses Mar 05, 2010 at 22:30

Finally i find your blog.
thanks very much!


50. ed hardy jeans Mar 05, 2010 at 22:38

great post, thanks for pointing out these problems. that is common here.


51. Ed hardy swimwear Mar 05, 2010 at 23:35

good information, thanks for your post.


51. ed hardy jeans Mar 07, 2010 at 01:17

i will check back for more new updates.
well done, and a good blog you have.


52. ed hardy boots Mar 07, 2010 at 01:20

i agree with some of your points.
thanks


53. ed hardy shirt Mar 07, 2010 at 01:26

thought some points i do not agree, but you do have a nice post.thanks


54. program indir Mar 14, 2010 at 12:29

Thank you very rich indeed a site


55. Juan Schwindt Mar 15, 2010 at 13:40

Have you tried the new kid in the block for image handling: dragonfly? I find it excellent, well done and very flexible. I update my RJCROP project to work with dragonfly: http://github.com/jschwindt/dragonfly_crop

Add your comment:

(SKIP THIS ONE)

(required)

(not shown)


(use pastie or gist for code)

sponsored by:
if you want to help:
required:
Get Quicktime Player
Give Back to Open Source