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.
Related
I am working on a site which needs to have a newsletter page, with thumbnails. The PDFs are stored on a different site, but that site does not have thumbnails, so I must generate them myself and link the thumbnail to that site.
I cannot store the original PDF — the linked version is canonical, and having out-of-date cached versions is unacceptable. The additional storage usage might also cause an extra monthly expense for the non-profit organisation the site is for, so I do not want to leave these lying around when they're not needed.
I have the following code:
class DocumentUploader < CarrierWave::Uploader::Base
include CarrierWave::MiniMagick
storage :file
def store_dir
"uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"
end
def content_type_whitelist
'application/pdf'
end
def full_filename(for_file)
parent_name = super(for_file)
return unless parent_name
extension = '.pdf'
base_name = parent_name.chomp(extension)
if version_name
base_name = base_name[version_name.size.succ..-1]
end
[base_name, version_name].compact.join("_") + '.png'
end
# Process files as they are uploaded:
process convert_pdf_front: [500, 500]
version :medium do
process convert_pdf_front: [160, 160]
end
version :small do
process convert_pdf_front: [80, 80]
end
private
def convert_pdf_front(width, height)
manipulate! do |img|
img.format('png') do |c|
c.resize "#{width}x#{height}"
end
img
end
end
end
This works as expected, but the medium and small versions are converted from the original PDF, making for a rather slow process.
If I replace the processing code with this:
version :full do
process convert_pdf_front: [500, 500]
end
version :medium, from_version: :full do
process convert: 'png'
process resize_to_fit: [160, 160]
end
version :small, from_version: :full do
process convert: 'png'
process resize_to_fit: [80, 80]
end
then the medium and small versions are converted from the full version, but the original PDF is left lying around.
Is there a way to use from_version to do the conversion from the converted original document — that is, from the 500x500 PNG rather than the PDF — that doesn't leave the original PDF cluttering the place up? Is there a special version name that points to the original document after it's gone through any process commands, rather than before?
(I would rather not simply have a reaper process nuke the PDFs later, as Carrierwave regards those as the originals. I'd like Carrierwave to treat the 500x500 PNG as the original in all cases and never store the PDF once it's done with the conversion process.)
We are using carrierwave gem to upload our images on a rails project.
We need to support the creation of the object without an image, but the application will run on several platforms and we don't want to support objects without images in everyone o that platforms, so we want to have a default image.
So this was the first solution
class IconUploader < CarrierWave::Uploader::Base
include CarrierWave::MiniMagick
def store_dir
"uploads/#{model.id}"
end
def default_url
"/images/icons/" + [version_name, "default.png"].compact.join('_')
end
version :thumbnail do
process resize_to_fill: [200, 200]
end
version :high_resolution do
process resize_to_fill: [400, 400]
end
end
The problem with this is that all the devices will come to the server (not to S3) looking for that default image.
At the same time its a problem with the multiple versions of the image.
So this second solution came:
class CategoryController < ApplicationController
...
def create
#category = Category.new(category_params)
#uploader = IconUploader.new(#category, :icon)
uploader.cache!(File.open("app/assets/images/photos/picture.jpg"))
#category.image=#uploader
#object.save
end
...
end
This solves the problem of the multiple resolutions and it always goes to S3 to get the image. But the problem here is that it will duplicate the default image on S3 many times.
Our third solution could be a to use an already created image on Amazon as default URL, but it has the same problem with the version so we don't even implement it.
Can any one think of a better solution for this problem?
so I have a site already in production using the carrierwave gem with images stored on amazon s3. my uploader uses the store_dir method to specify a particular structure to put in processed images.
well, now in my dev environment I've added the carrierwave_direct gem to start uploading directly to S3. The problem is this gem completely overrides the store_dir and filename defaults in my uploader. I can't push my fully working uploader live because all my old image links will be broken.
it's my understanding that the CWdirect gem would upload a raw image file to a "temp" directory on S3, then S3 responds and gives you a key variable so you can grab this file and process it as you see fit. so, should i be using a completely separate image uploader class in carrierwave to process the images and place them in the correct folders? meaning I'll have one uploader dedicated to carrierwave_direct that uploads wherever this gem seems to want to upload to; and I'll use another uploader.rb class linked to my real model that keeps my current store_dir and filename structure?
In any case, my basic question is, how can I use CarrierWave_Direct gem if I already have CW running in production with images in a specific folder structure?
Ok, so I did figure out how to do this and I'll explain below. My hunch was correct to use two different CarrierWave uploader classes--one class dedicated to uploading to S3 (using CarrierWave_Direct gem), and a second class used only for image processing (the class I was already using in production). I'll try and post relevant code below but if anyone has questions let me know. I'm unsure why I haven't seen others using separate classes like this but it seems to work for me.
My image Uploader class app\uploaders\image_uploader.rb utilizing carrierwave_direct gem:
class ImageUploader < CarrierWave::Uploader::Base
include CarrierWaveDirect::Uploader
include ActiveModel::Conversion
extend ActiveModel::Naming
include CarrierWave::MimeTypes
process :set_content_type
# Add a white list of extensions which are allowed to be uploaded.
# For images you might use something like this:
def extension_white_list
%w(jpg jpeg gif png)
end
# Include the Sprockets helpers for Rails 3.1+ asset pipeline compatibility:
include Sprockets::Helpers::RailsHelper
include Sprockets::Helpers::IsolatedHelper
# Override the directory where uploaded files will be stored.
# CarrierWaveDirect::Uploader puts raw uploaded files in this directory on S3 as a first step
def store_dir
"unprocessed_uploads"
end
end
**notice there's no processing being done in this class
My image PROCESSING class app\uploaders\image_processor.rb (what was already in place in production):
class ImageProcessor < CarrierWave::Uploader::Base
# Include RMagick or MiniMagick support:
# include CarrierWave::RMagick
include CarrierWave::MiniMagick
include CarrierWave::MimeTypes
process :set_content_type
# Include the Sprockets helpers for Rails 3.1+ asset pipeline compatibility:
include Sprockets::Helpers::RailsHelper
include Sprockets::Helpers::IsolatedHelper
# Choose what kind of storage to use for this uploader:
storage :fog
# 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}"
# "uploads/#{model.class.to_s.underscore}/path/#{model.id}"
end
# Provide a default URL as a default if there hasn't been a file uploaded:
def default_url
"logos/" + [version_name, "default.png"].compact.join('_')
end
# Process files fetched from S3 after they are uploaded:
def make_thumbnail(width, height)
# uses MiniMagick classes to get a square, centered thumbnail image
manipulate! do |img|
if img[:width] < img[:height]
remove = ((img[:height] - img[:width])/2).round
img.shave("0x#{remove}")
elsif img[:width] > img[:height]
remove = ((img[:width] - img[:height])/2).round
img.shave("#{remove}x0")
end
img.resize("#{width}x#{height}")
img
end
end
# Create different versions of your uploaded files:
# the process statement below isn't defined within a version block on purpose--this means the ORIGINAL uploaded photo is constrained to 1050 pics
process :resize_to_limit => [1050, 1050]
process :quality => 85 # this reduces filesize greatly and saves space
version :thumb do
process :make_thumbnail => [100, 100]
process :quality => 85 # this reduces filesize greatly and saves space
end
version :big_thumb do
process :make_thumbnail => [350, 350]
process :quality => 85 # this reduces filesize greatly and saves space
end
# Add a white list of extensions which are allowed to be uploaded.
# For images you might use something like this:
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
if original_filename
if model && model.read_attribute(:image).present?
model.read_attribute(:image)
else
"#{secure_token}.#{file.extension}"
end
end
end
protected
def secure_token
var = :"##{mounted_as}_secure_token"
model.instance_variable_get(var) or model.instance_variable_set(var, SecureRandom.uuid)
end
end
My Photo model (summarized):
class Photo < ActiveRecord::Base
mount_uploader :image, ImageProcessor
def save_and_process_image(options = {})
s3_unprocessed_image_url = self.image.asset_host + '/' + self.key
# this next line downloads the image from S3
# and this save line below will process the image and reupload to S3 according to ImageProcessor settings
self.remote_image_url = s3_unprocessed_image_url
save
end
end
I also have Photo controller and view code available, if ya'll want it let me know. Basically I use the ImageUploader class to do the initial upload to S3 to a folder called unprocessed_uploads. Then S3 responds with a key field in the URL which I pass on to ImageProcessor class--this is attached to a Photo and processes the thumbnail and other images then re-uploads them to my uploads folder on S3.
This separation meant i didn't need to change my current folder structure on S3 when adding the carrierwave_direct gem. Hope this helps others. Let me know if you need more code I'm kind of tired of typing :)
UPDATE--Adding more code:
Photos Controller:
class PhotosController < ApplicationController
def index
#photos = #photos.sort_by(&:created_at)
#uploader = ImageUploader.new
#uploader.success_action_redirect = new_tank_photo_url(#tank)
respond_to do |format|
format.html # index.html.erb
format.json { render json: #photos }
end
end
def create
respond_to do |format|
if #photo.save_and_process_image
format.html { redirect_to tank_photos_path(#tank), notice: 'Photo uploaded successfully and is being processed...' }
format.json { render json: #photo, status: :created, location: #photo }
else
format.html { render :new }
format.json { render json: #photo.errors, status: :unprocessable_entity }
end
end
end
Photos Index view, the form with the Upload button:
<%= direct_upload_form_for #uploader, :html => {:class => "form-inline"} do |f| %>
<%= f.file_field :image %>
<%= f.submit "Upload", :class => "btn btn-primary btn-medium" %>
<% end %>
So with the above view/controller code added, here is a summary of the steps taken. Please note the difference between the ImageUploader class and the ImageProcessor class:
In my Photos Controller I create the #uploader variable which is of class ImageUploader--this means that images submitted in my index/view form which uses #uploader will upload the image to a "temp" folder--Since this uses the ImageUploader class then no processing is done when this image is uploaded (yet).
When the user clicks "Upload" button on the form, the Photo>>create action is called. This action calls #photo.save_and_process_image -- look at the model to see that this method actually grabs the recently uploaded image from the S3 'temp' folder, processes it, then re-uploads the processed images to their final destination. This is all possible because my Photo model is linked to the ImageProcessor class, which does the processing/uploading. It's not linked to the separate ImageUploader class.
Hope this helps explain what i have going on
I have searched for quite a bit on this issue and cannot seem to get it to work (stackoverflow, the Carrierwave Railscasts).
First off, I have zero issues uploading the image. That is working fine. Now the image is quite big and I would like to make it smaller. Currently my avatar_uploader.rb file is:
require 'carrierwave/orm/activerecord'
class AvatarUploader < CarrierWave::Uploader::Base
include CarrierWave::RMagick
storage :file
def store_dir
"uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"
end
version :thumb do
process :resize_to_limit => [50,50]
end
def extension_white_list
%w(jpg jpeg gif png)
end
end
After watching the railscasts (even though it's from the 2011), it seems you have to pass :thumb to your view, such as:
<%= image_tag(user.avatar(:thumb).to_s) %>
This is giving me an error in the browser of:
wrong number of arguments (1 for 0)
I'm not even sure it's necessary to have to pass :thumb into your view. Is it?
I know this is a fairly easy fix, so any help would be greatly appreciated!
It looks like the syntax has changed, since then. When in doubt, consult the official documentation:
uploader.url # => '/url/to/my_file.png' # size: 800x800
uploader.thumb.url # => '/url/to/thumb_my_file.png' # size: 200x200
or, in your case, probably:
user.avatar.thumb.url
In my Rails app I would like to allow users to upload either image or non-image files via Carrierwave. Currently Carrierwave is working fine handeling and processing image files, but unfortunately its dropping non-images files completely. Is there a clean way for a single Carrierwave uploader to process both image and non-image files?
I'll include my current uploader below:
class AssetUploader < CarrierWave::Uploader::Base
include CarrierWave::MiniMagick
storage :file
def store_dir
"uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"
end
version :thumb do
process :resize_to_fill => [300, 300]
end
version :icon do
process :resize_to_fill => [48, 48]
end
def extension_white_list
%w(jpg jpeg gif png pdf doc xls docx xlsx ppt)
end
end
I had this exact problem. I solved it with the hoary comp sci solution of one-more-level-of-indirection: a trampoline/thunk method that dynamically decides whether to process based on file extension.
You can find the implementation here: https://gist.github.com/995663
(the naive approach of introducing logic in the version block actually doesn't work because of how the CarrierWave DSL works - the logic needs to be deferred until invocation)
I hope that helps.
I know this has been answered, but aaron's answer doesn't completely solve the problem. You can use what he suggested at https://gist.github.com/995663 if you only need to call process, but not if you want to selectively process with version.
For version, see the carrierwave wiki page here: https://github.com/jnicklas/carrierwave/wiki/How-to%3A-Do-conditional-processing
You can now do this in your code, and it will only process the version block on images
version :thumb, :if => :image? do
process ...
end
protected
def image?(new_file)
new_file.content_type.include? 'image'
end