Reprocessing images of different versions in Carrierwave - ruby-on-rails

Using Carrierwave, I created 3 versions of an avatar - an original, a small_thumb and a large_thumb using the following lines:
process :resize_to_limit => [400, 400]
version :big_thumb do
process :resize_to_limit => [80, 80]
end
version :small_thumb do
process :resize_to_limit => [50, 50]
end
I added an additional method in my AvatarUploader class:
def reprocess(x,y,w,h)
manipulate! do |img|
img.crop(x.to_i, y.to_i, w.to_i, h.to_i, true)
end
resize_to_limit(180,180)
end
which is called in my model after an update is performed:
attr_accessor :crop_x, :crop_y, :crop_w, :crop_h
after_update :reprocess_image, :if => :cropping?
def cropping?
!crop_x.blank? && !crop_y.blank? && !crop_w.blank? && !crop_h.blank?
end
private
def reprocess_image
image.reprocess(crop_x,crop_y,crop_w,crop_h)
end
I have managed to crop and resize the original version, but I can't seem to update the 2 thumbnails along with it. I tried a few different techniques to no avail.
Any suggestions?

Try
image.recreate_versions!
Sorry, on the bus. I can't expound on that.

You need to call image.cache_stored_file! before calling recreate_versions!
It's weird because the method itself calls that if the file is cached, but for some reason it wasn't working.
So that would be something like:
def reprocess_image
image.reprocess(crop_x, crop_y, crop_w, crop_h)
image.cache_stored_file!
image.recreate_versions!
end

This HowTo was most helpful for me (even if I don't use fog):
https://github.com/carrierwaveuploader/carrierwave/wiki/How-to:-Recreate-and-reprocess-your-files-stored-on-fog
I added a reprocess method on my model and then called it in for each loop in my rake task:
def reprocess
begin
self.process_photo_upload = true
self.photo.cache_stored_file!
self.photo.retrieve_from_cache!(photo.cache_name)
self.photo.recreate_versions!
self.save!
rescue => e
STDERR.puts "ERROR: MyModel: #{id} -> #{e.to_s}"
end
end
Rake:
task :reprocess_photos => :environment do
MyModel.all.each{|mm| mm.reprocess}
end
PS: Rails 4.2

I haven't tried but maybe putting something like.
def reprocess_image
image.reprocess(crop_x,crop_y,crop_w,crop_h)
image.recreate_versions!
end

check this latest RailsCast:
http://railscasts.com/episodes/182-cropping-images-revised
after cropping one version of the image, you can then either calculate the cropping coordinates for the other versions, or probably easier, scale down the cropped image with the same aspect ratios as the other versions of the original image

Related

Undefined method manipulate! - carrierwave with minimagick

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

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.

carrierwave: creating version based on image properties

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

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.

CarrierWave: create 1 uploader for multiple types of files

I want to create 1 uploader for multiple types of files (images, pdf, video)
For each content_type will different actions
How I can define what content_type of file?
For example:
if image?
version :thumb do
process :proper_resize
end
elsif video?
version :thumb do
something
end
end
I came across this, and it looks like an example of how to solve this problem: https://gist.github.com/995663.
The uploader first gets loaded when you call mount_uploader, at which point things like if image? or elsif video? won't work, because there is no file to upload defined yet. You'll need the methods to be called when the class is instantiated instead.
What the link I gave above does, is rewrite the process method, so that it takes a list of file extensions, and processes only if your file matches one of those extensions
# create a new "process_extensions" method. It is like "process", except
# it takes an array of extensions as the first parameter, and registers
# a trampoline method which checks the extension before invocation
def self.process_extensions(*args)
extensions = args.shift
args.each do |arg|
if arg.is_a?(Hash)
arg.each do |method, args|
processors.push([:process_trampoline, [extensions, method, args]])
end
else
processors.push([:process_trampoline, [extensions, arg, []]])
end
end
end
# our trampoline method which only performs processing if the extension matches
def process_trampoline(extensions, method, args)
extension = File.extname(original_filename).downcase
extension = extension[1..-1] if extension[0,1] == '.'
self.send(method, *args) if extensions.include?(extension)
end
You can then use this to call what used to be process
IMAGE_EXTENSIONS = %w(jpg jpeg gif png)
DOCUMENT_EXTENSIONS = %(exe pdf doc docm xls)
def extension_white_list
IMAGE_EXTENSIONS + DOCUMENT_EXTENSIONS
end
process_extensions IMAGE_EXTENSIONS, :resize_to_fit => [1024, 768]
For versions, there's a page on the carrierwave wiki that allows you to conditionally process versions, if you're on >0.5.4. https://github.com/jnicklas/carrierwave/wiki/How-to%3A-Do-conditional-processing. You'll have to change the version code to look like this:
version :big, :if => :image? do
process :resize_to_limit => [160, 100]
end
protected
def image?(new_file)
new_file.content_type.include? 'image'
end

Resources