Undefined method manipulate! - carrierwave with minimagick - ruby-on-rails

I am using carrierwave with minimagick to upload an image and crop it to a square. However I get the following error:
undefined method 'manipulate!' for #<Class:0x692db10>
it seems to make no sense, as i have included the correct class, and that part works fine. Heres my current uploader class.
class ImageUploader < CarrierWave::Uploader::Base
include CarrierWave::MiniMagick
##sizes = {
"2000" => 2048,
"1500" => 1500,
"1000" => 1024,
"500" => 512,
"250" => 256,
"100" => 128
}
storage :file
def store_dir
"uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"
end
version :square do
manipulate! do |img|
size = img.dimensions.min
end
process resize_to_fill: [size, size]
end
end
to be clear, we are talking about the :square version. Does anyone have any idea what could be wrong?

It seems manipulate! belongs to RMagick adapter, for MiniMagick you should use something like mogrify.
Indeed, there's such method, but you're trying to use it in a class scope, while it's an instance method. There's a bunch of useful class methods you can use already.
If you still need manipulate!, make something like this:
process :radial_blur => 10
def radial_blur(amount)
manipulate! do |img|
img.radial_blur(amount)
img = yield(img) if block_given?
img
end
end

Related

How to spec CarrierWave MiniMagick manipulate

Having troubles writing RSpec tests for my implementation of "auto_orient" on my CarrierWave uploader. Im also not finding much love on the matter online.
I understand that the manipulate method would be specced by the gem, but Im just wanting to validate that my ImageUploader is implementing it correctly with RSpec.
class ImageUploader < CarrierWave::Uploader::Base
include CarrierWave::MiniMagick
...
version :tile do
process :auto_orient
end
# Fix images being uploaded the wrong orientation
def auto_orient
manipulate! do |img|
img = img.auto_orient
end
end
let(:uploader) { ImageUploader.new(profile, :avatar) }
before do
ImageUploader.enable_processing = true
File.open(File.join(Rails.root, '/spec/support/images/logo.png')) { |f| uploader.store!(f) }
end
after do
ImageUploader.enable_processing = false
uploader.remove!
end
xit 'runs auto_orient on the image' do
# ???
end
Thanks
I'm not 100% sure how to do this in MiniMagick exactly, but here's how I approached the problem using RMagick if that helps. Should be a similar approach I would think.
uploader.cache_stored_file!
# For minimagick, you would probably use MiniMagick::Image::read
mg = ::Magick::Image::read(uploader.file.file).first
# Not sure if minimagick has an "orient" attribute, might try reading the data using something from here instead: https://github.com/probablycorey/mini_magick/blob/master/lib/mini_magick.rb#L204
expect(mg.orientation.to_i).to eq(1)

How to use carrierwave/minimagick composite to put my logo on the southeast corner of every image uploaded?

I have a model that allows users to upload pictures using carrierwave. I want to put my logo in the southeast corner of each image and then I want the image to save just as it would normally, (example.com/images/1). I know that I have to use composite but despite several hours of googling I am nowhere closer. This was my best guess.
class PictureUploader < CarrierWave::Uploader::Base
include CarrierWave::MiniMagick
process resize_to_limit: [400, 400]
process :logo
def logo
manipulate! do |img|
logo = ::MiniMagick::Image.open("#{Rails Name}/app/assets/images/logo.png")
img = img.composite(logo, Magick::SouthEastGravity, 15, 0, Magick::OverCompositeOp)
end
end
I believe you can just change this line
process resize_to_limit: [400, 400]
to this
process resize_to_limit: [400, 400, 'SouthEast']
And then you may or may not need some of that extra minimagick code you were adding about gravity.
process :watermark
def watermark
second_image = MiniMagick::Image.open("https://s3.amazonaws.com/....logo.png")
manipulate! do |img|
img.composite(second_image) do |c|
c.compose "Over" # OverCompositeOp
c.gravity "Southeast" # copy second_image onto first_image from (20, 20)
end
end
end
Got this to work.

Carrierwave and Jcrop, delete original after crop?

I'm having a difficult time getting Carrierwave to delete the original file after the cropped versions are made. I'm making a 600 pixel version of the upload for the user to crop but after the crop I want that version to get deleted since it's never used on the site.
I've tried several solutions found online but they all delete the large version before the crop, not after.
Here is my Carrierwave uploader:
# encoding: utf-8
class AvatarUploader < CarrierWave::Uploader::Base
include CarrierWave::RMagick
storage :file
# storage :fog
def store_dir
"uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"
end
# Only allows jpg, jpeg, or png
def extension_white_list
%w(jpg jpeg png)
end
resize_to_limit(600, 600)
version :profile do
process :crop
resize_to_fill(120, 120)
def full_filename (for_file = model.file)
"profile.png"
end
end
version :timeline do
process :crop
resize_to_fill(50, 50)
def full_filename (for_file = model.file)
"timeline.png"
end
end
version :navigation do
process :crop
resize_to_fill(20, 20)
def full_filename (for_file = model.file)
"navigation.png"
end
end
def crop
if model.crop_x.present?
resize_to_limit(600, 600)
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
end
You could either use an after_store callback to remove the original file (using File.delete) or you could modify the original so that it's the largest size you need:
version :normal do
process :resize_to_limit => [600,600]
end
For those who will try to solve the issue for the case of adding some revision information to the image name (e.g. cropping), I found a way to do this.
# :large is the cropped version
version :small_square, from_version: :large do
before :cache, :reset_revision
after :store, :remove_old_revision
def full_filename(for_file)
fname = super(for_file)
"#{ fname.pathmap('%n') }_#{ model.image_revision }#{ fname.pathmap('%x') }"
end
def full_original_filename
"#{ super.pathmap('%n') }_#{ model.image_revision }#{ super.pathmap('%x') }"
end
def remove_old_revision(file = nil)
File.delete(#old_path) if #old_path && File.exists?(#old_path)
end
def reset_revision(file = nil)
# Otherwise we'll get `cannot update new record` error
if model.persisted?
# Creating an instance variable that will be used after storing the cropped version
#old_path = File.join(Rails.public_path, model.public_send(mounted_as).store_dir, full_original_filename)
# I use :image_revision column in the DB
model.update_columns(image_revision: SecureRandom::hex(8))
else
model.image_revision = SecureRandom::hex(8)
end
end
process :crop_small_square
end
So, it's storing the path of the first version of the cropped version before changing the resulting image name and then deleting the first version.
Took me some time to figure out the way to attach some abracadabra to the end of a definite version of file: it takes the update action, rather than just assigning the attribute. Carrierwave's docs aren't that accurate.

Duplicating Carrierwave images makes them darker?

I have an Event model that has many photographs. I have an image uploader mounted to the Photographs attribute, and regular uploads and everything is working fine.
However, when I try and duplicate an event, recreating a new photograph object for the new event, the new image is darker than the original, and if I duplicate the duplicate event, it gets darker still.
I have played around with it, but have no solution.
My Uploader code:
class ImageUploader < CarrierWave::Uploader::Base
include CarrierWave::RMagick
include CarrierWave::Processing::RMagick
def store_dir
"uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"
end
def cache_dir
"#{Rails.root}/tmp/carrierwave"
end
process :colorspace => :rgb
# Remove EXIF data
process :strip
# Create different versions of your uploaded files:
version :thumb do
process :resize_to_limit => [640, 640]
end
version :preview_thumb do
process :resize_to_limit => [600, 600]
end
version :wine_thumb do
process :resize_to_limit => [160, 440]
end
version :logo_thumb do
process :resize_to_limit => [90, 90]
end
end
And my duplcation code (in Active Admin):
member_action :create_duplicate_event, method: :post do
old_event = Event.find(params[:id])
photograph_urls = old_event.photographs.map(&:image_url)
attributes = old_event.attributes.except("photographs", "id")
new_photos = []
photograph_urls.each do |photo|
new_photo = Photograph.new({
remote_image_url: photo
})
if new_photo.save
new_photos << new_photo
end
end
#event = Event.new(attributes)
#event.photograph_ids = new_photos.map(&:id)
render "/admin/events/_events_form/"
end
The :rgb tag was an attempt to fix. But no luck.
Ruby 2.1 and Rails 4.0
Ok, after a lot of playing around and searching I managed to fix this problem.
First, you need to download the .icc color profiles, which can be found here. It says for windows but they seemed to work for me on my Mac.
Once you have put the .icc files into a /lib/color_profiles directory, add the following code to your uploader:
class ImageUploader < CarrierWave::Uploader::Base
include CarrierWave::RMagick
process :convert_image_from_cmyk_to_rgb
#versions, and any other uploader code go here
def convert_image_from_cmyk_to_rgb
manipulate! do |image|
if image.colorspace == Magick::CMYKColorspace
image.strip!
image.add_profile("#{Rails.root}/lib/USWebCoatedSWOP.icc")
image.colorspace == Magick::SRGBColorspace
image.add_profile("#{Rails.root}/lib/sRGB.icc")
end
image
end
end
end
This converts CMYK images to RGB, and keeps the profiles keeping nice, while keeping RGB images as they were, and not ruining them.
I hope this helps someone in the future, and saves them the hours I spent working this out.

How do I use model attributes/activerecord methods in carrierwave version processing?

This is another question in pursuit of an answer to this question.
In my watermark processor if I set the watermark image's path to a static watermark image, everything works just fine.
I have three models: Watermark, Gallery, and Photo. Watermark has_many :galleries. Gallery belongs_to :watermark and has_many :photos. Photo belongs_to :gallery and mount_uploader :image, PhotoUploader.
Here's what I'd like to do in photo_uploader.rb:
version :large do
process :watermark
process :resize_to_limit => [600, 600]
end
def watermark
manipulate! do |img|
watermark = Magick::Image.read(model.gallery.watermark.image_url).first
img = img.composite(watermark, Magick::CenterGravity, Magick::OverCompositeOp)
end
end
While using model methods/attributes works fine in the store_dir method, it doesn't work in the watermark processor. How can I pass the model.gallery.watermark.image_url argument to the watermark processor?
the problem is the that by the time the image is mounted on the Model, the variables inside the watemark def, inside the uploader class, are not avialiabled. Thats what did. I'm using mongoid to deal with mongoDB. I set a param inside the Model, that will hold the value that i want eg. the value the_current_model.gallery.watermark.image_url. This param will be user_image.
class Asset
include Mongoid::Document
mount_uploader :image, AssetUploader
field :user_image
attr_accessible :user_image
after_save :mark_it
private
def mark_it
image.recreate_versions! if user_image.present?
end
end
The key to solve this problem is recreate the image (as you see on the model calling the mark_it def) after the data being saved on the database and the param user_image allowed to be used for you uploader class.
class AssetUploader < CarrierWave::Uploader::Base
include CarrierWave::RMagick
storage :file
# storage :fog
after :cache, :unlink_original_cache
# Override the directory where uploaded files will be stored.
# This is a sensible default for uploaders that are meant to be mounted:
def store_dir
"uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"
end
# Provide a default URL as a default if there hasn't been a file uploaded:
# def default_url
# "/images/fallback/" + [version_name, "default.png"].compact.join('_')
# end
process :resize_to_fit => [800, 800]
process :quality => 80
version :thumb do
process :resize_to_fit => [200, 200]
end
version :large do
process :watermark
end
def watermark
if model.user_name.present?
manipulate! do |img|
text = Magick::Draw.new
text.gravity = Magick::CenterGravity
text.fill = 'white'
text.pointsize = 40
text.stroke = 'none'
text.annotate(img, 0, 0, 0, 0, "#{model.user_name}")
img
end
end
end
def unlink_original(file)
File.delete path if version_name.blank?
end
def unlink_original_cache(file)
File.delete if version_name.blank?
end
def extension_white_list
%w(jpg jpeg gif png)
end
# Override the filename of the uploaded files:
# Avoid using model.id or version_name here, see uploader/store.rb for details.
# def filename
# "something.jpg" if original_filename
# end
end
Now the key is here:
def watermark
if model.user_name.present?
manipulate! do |img|
text = Magick::Draw.new
text.gravity = Magick::CenterGravity
text.fill = 'white'
text.pointsize = 40
text.stroke = 'none'
text.annotate(img, 0, 0, 0, 0, "#{model.user_name}")
img
end
end
end
The method watermark only creates the image with the watermark on it, if the user_name param on the model is set (created on the db). So when the data is saved on the db, this param is avaliabled for you uploader class to use as want.
Thats what I did, and it worked very well!
I hope it could help!

Resources