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.
Related
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
I want to keep my uploaded images upto 700 pixels.
If any image gets bigger I use the following code to get a new width.
This is my uploader file.
def store_dimensions
if file && model
width, height = ::MiniMagick::Image.open(file.file)[:dimensions]
if width>700
return 700
else
return width
end
end
Then I created a version named best_fit
process :store_dimensions
version :best_fit do
process :resize_to_fill => [store_dimensions,200]
end
It can't find the store_dimensions method. On the other hand, if I use self keyword while declaring store_dimensions method, then it works, but then the "file" identifier is become an unknown entity.
How can I get the value of the uploaded image and according to that I can create a new version of it.
The following code saved my ass today. I'm happy that I solved it.
def store_dimensions
if file && model
width, height = ::MiniMagick::Image.open(file.file)[:dimensions]
if width>700
Rails.logger.info "#{width}"
finalHeight=((700*height)/width)
self.class.version :best_fit do
process :resize_to_fill => [700,finalHeight]
end
else
Rails.logger.info "#{width}"
self.class.version :best_fit do
process :resize_to_fill => [width,height]
end
end
end
end
#run the store_dimensions methods
process :store_dimensions
For any lost wanderers, this is what you're looking for:
class Uploader
version :my_version do
process :my_processor
end
def my_processor
# model is available here!
manipulate! do |img|
img.combine_options do |cmd|
cmd.resize [model.width, model.height].join('x')
end
img
end
end
end
Example of custom process method
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.
I'm using Carrierwave to upload some images and generate different versions of them (size, color...). Every image is given a unique name when uploaded and I'm now trying to process the images in the background in order to avoid blocking web processes. I use carrierwave_backgrounder for that.
The problem is that when my image is processed, a new name is given to it. So the model doesn't know this new name and the original image is now twice on the server.
I end up with this kind of files:
f9f97657-eaab-40ce-b965-31bb128066ee.jpg // First uploaded images
e4244551-7f43-4c03-8747-e8f2f2e57156.jpg // Copy of the original image created while processed in the background
thumb_e4244551-7f43-4c03-8747-e8f2f2e57156.jpg
And event.image returns f9f97657-eaab-40ce-b965-31bb128066ee.jpg.
How can I avoid to generate a new filename if we are just processing the different versions in the background and not uploading it?
My model:
class Event < ActiveRecord::Base
mount_uploader :cover_image, CoverImageUploader
process_in_background :cover_image
end
My uploader:
class CoverImageUploader < CarrierWave::Uploader::Base
include CarrierWave::RMagick
include CarrierWave::MimeTypes
include Sprockets::Helpers::RailsHelper
include Sprockets::Helpers::IsolatedHelper
include ::CarrierWave::Backgrounder::Delay
def filename
"#{secure_token}.#{file.extension}" if original_filename
end
process :set_content_type
version :thumb do
process resize_to_fill: [200, 200]
end
# ...
# Some other versions processing...
# ...
protected
def secure_token
var = :"##{mounted_as}_secure_token"
model.instance_variable_get(var) || model.instance_variable_set(var, SecureRandom.uuid)
end
end
can try this:
def filename
if original_filename.present?
if model.respond_to?("#{mounted_as}_processing") && model.send("#{mounted_as}_processing")
#name ||= model.send("#{mounted_as}_identifier")
else
#name ||= "#{secure_token}.#{file.extension}"
end
end
end
only works if you have a "processing" column in your table
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!