Undefined Method crop! Using Carrierwave with Mini

2019-02-02 08:29发布

问题:

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!

回答1:

Had the same problems, my solution was this method

def cropped_image(params)
    image = MiniMagick::Image.open(self.image.path)
    crop_params = "#{params[:w]}x#{params[:h]}+#{params[:x]}+#{params[:y]}"
    image.crop(crop_params)

    image
end

Just modify my method for your case.
The key is in which format pass variables to crop method, hope this helps you.



回答2:

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 widthxheight+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


回答3:

I was able to get this to work by adding the X and Y parameters to the crop command as indicated by @mikhail-nikalyukin

def crop
  manipulate! do |img|
    img.crop "750x600+0+0"
    img.strip

    img = yield(img) if block_given?
    img
  end
end