I was having a heck of a time getting this to work, and still am. I'll get to the heart of it. I'm following Ryan Bates tutorial to make cropping work using Jcrop and Carrierwave. I've opted to use MiniMagick because even after reinstalling ImageMagick and RMagick on my machine I get an error that kills the rails server on my local machine. Anyway switching to MiniMagick fixed that for me. So everything is really nice up until this point. I have different sized images being produced, and they're being uploaded successfully. But once I try to crop I get this error:
undefined method `crop!' for #<MiniMagick::CommandBuilder:0x000001052e4608>
This is confusing the heck out of me because I'm using pretty much the exact same code as Bates:
def crop
if model.crop_x.present?
resize_to_limit(700, 700)
manipulate! do |img|
x = model.crop_x.to_i
y = model.crop_y.to_i
w = model.crop_w.to_i
h = model.crop_h.to_i
img.crop!(x, y, w, h)
end
end
end
Anyway, it's that crop method that's failing. So I thought to myself, that's an ImageMagick command... So I looked at the ImageMagick doco, and I couldn't find the crop method with the bang, so I tried it without, and then the error turns to this:
No such file or directory - /var/folders/dF/dFNM2+Y7FVScn4+OxVHKOU+++TI/-Tmp-/mini_magick20111207-34409-1tnaa07.jpg
Anyway, something isn't making a ton of sense to me, any help would be appreciated! Thanks for reading!
In short:
img.crop("#{size}#{offset}") # Doesn't return an image...
img # ...so you'll need to call it yourself
Here's a better explanation of why this happened as opposed to a cut/paste style solution.
RMagick and MiniMagick aren't interchangeable. RMagick has a very Ruby-like DSL and as such employs methods that take multiple arguments:
rmagick_image.crop(x_offset, y_offset, width, height) # Returns an image object
rmagick_image.crop!(x_offset, y_offset, width, height) # Edits object in place
MiniMagick instead dynamically generates methods by iterating through a list of MOGRIFY_COMMANDS
that match up with numerous dash-prefixed options specified in ImageMagick's mogrify
documentation. Each of those methods pass their arguments directly to mogrify
and none return an image object:
minimagick_image.crop('100x200') # Translates to `mogrify -crop 100x200 image.ext`
minimagick_image.polaroid('12') # Executes `mogrify -polaroid 12 image.ext`
In kind, RMagick has crop!
and MiniMagick doesn't.
According to the ImageMagick docs, mogrify -crop
takes an argument geometry
. The geometry
argument is explained here. You'll notice that all of those arguments are strings, so instead of crop(100,200)
you would use crop('100x200')
or crop('100%)
. It's not very Ruby-like, but that's part of what makes MiniMagick so lightweight.
With that knowledge, we can deduce how to crop with MiniMagick. mogrify -crop
can take a geometry as a string width
xheight
+xoffset
+yoffset
, so we just need to build a similar string.
Given w
,h
,x
, and y
you could use whichever of the following you find most readable:
# Concatenating plus signs with plus signs is atrociously confusing.
# Recommended only if you want to drive your future self insane.
mogrify_arg = w + 'x' + h + '+' + x + '+' + y
# Readable but inefficient
mogrify_arg = [ w, 'x', h, '+', x, '+', y ].join('')
# Questionable readability
mogrify_arg = "#{w}x#{h}+#{x}+#{y}"
# Slick, performant, but potentially risky: `<<` modifies the receiving object in place
# `w` is actually changing here to "WxH+X+Y"...
mogrify_arg = w << 'x' << h << '+' << x << '+' << y
# A lovely, self-documenting version
size = w << 'x' << h
offset = '+' << x '+' << y
mogrify_arg = "#{size}#{offset}"
Here's a complete example:
def crop
if model.crop_x.present?
resize_to_limit(700, 700)
manipulate! do |img|
x = model.crop_x
y = model.crop_y
w = model.crop_w
h = model.crop_h
size = w << 'x' << h
offset = '+' << x << '+' << y
img.crop("#{size}#{offset}") # Doesn't return an image...
img # ...so you'll need to call it yourself
end
end
end