I'm trying to upload a PNG and save a bunch of thumbnails. The thumbnails should all be JPG and not have any transparency. Somehow the file is saved as jpg but it has transparency...
Here's my Uploader:
# encoding: utf-8
class WinePhotoUploader < CarrierWave::Uploader::Base
# Include RMagick or MiniMagick support:
include CarrierWave::RMagick
# include CarrierWave::MiniMagick
# Choose what kind of storage to use for this uploader:
storage :file
# 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
"assets/wines/#{version_name}"
end
CarrierWave::SanitizedFile.sanitize_regexp = /[^[:word:]\.\-\+]/
# Provide a default URL as a default if there hasn't been a file uploaded:
def default_url
# For Rails 3.1+ asset pipeline compatibility:
ActionController::Base.helpers.asset_path("pages/wines/#{version_name}/default.png")
end
process :for_all
process :convert => 'jpg'
version :croppable, :if => :new_upload? do
process :resize_and_pad => [1200, 1200, 'white']
end
version :show, :if => :new_upload? do
process :resize_to_fit => [nil, '425']
end
version :general, :from_version => :show, :if => :new_upload? do
process :resize_and_pad => [150, 375, 'white']
end
version :thumb, :from_version => :croppable, :if => :cropping? do
process :rotate_to => [-30]
process :crop_via_model
process :resize_to_fit => [150, 150]
end
def for_all
manipulate! do |img|
img.trim
img.gravity = Magick::CenterGravity
# I only use these two because this shit isn't working...these don't seem to help!
# Neither does the flatten method...even though other posts on stackoverflow.com say
# that it works.
img.background_color = 'white'
img.alpha Magick::OpaqueAlphaChannel
img.unsharp_mask 0.3, 0.3, 5, 0
img
end
end
def extend_to(w, h)
manipulate! do |img|
img.gravity = Magick::CenterGravity
img.extent w, h
img
end
end
def rotate_to(deg)
manipulate! do |img|
img.gravity = Magick::CenterGravity
img.distort Magick::ScaleRotateTranslateDistortion, deg
#img.repage # WTF?!?!?!? No repage method?!
img
end
end
def crop_via_model
manipulate! do |img|
img.gravity = Magick::CenterGravity
img.crop model.crop_x.to_i, model.crop_y.to_i, model.crop_w.to_i, model.crop_h.to_i
img
end
end
def flatten
manipulate! do |img|
img_list = Magick::ImageList.new
img_list.from_blob img.to_blob
img_list.new_image(img_list.first.columns, img_list.first.rows) { self.background_color = "white" } # Create new "layer" with white background and size of original image
img = img_list.reverse.flatten_images
img
end
end
def new_upload? picture
!model.cropping?
end
def cropping? picture
model.cropping?
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 tif)
end
# Override the filename of the uploaded files:
# Avoid using model.id or version_name here, see uploader/store.rb for details.
def filename
"#{ model.get_permalink(:normalized => true) }.jpg"
end
# Remove this when https://github.com/carrierwaveuploader/carrierwave/issues/1164 is solved.
def recreate_versions!(*versions)
if versions.any?
from_versions = versions.group_by { |v| self.class.versions[v][:options][:from_version] }
from_versions.each do |from, versions|
next if from.nil?
file = CarrierWave::SanitizedFile.new(self.versions[from.to_sym].file)
store_versions!(file, versions)
end
else
super(*versions)
end
end
end
Oh...and apparently carrierwave doesn't seem to want to log anything so I can't tell what it's doing.
There appears to be a quirk with imagemagick and order of operations when converting file types.
Full github issue can be found here: https://github.com/carrierwaveuploader/carrierwave/issues/133#issuecomment-615254
Basically manipulate is called once for every process you have. manipulate opens the current file path, makes changes then writes. Because of this it seems that any process lines that are called after the format conversion are performed on the original file, not the newly converted one.
In order to fix this, you either have to do all of your process operations within a single manipulate block or make sure that the conversion is the last process to run.
The example of this from the github issue is:
process :convert_and_scale
def convert_and_scale
manipulate! do |img|
img.format 'png'
img.resize '100x32'
img
end
end
Related
Unsupported color conversion request
*** WebP::EncoderError Exception: Cannot read input picture file
got this error on certain jpg file. while converting into jpg again from google does the job but why is this?
'''webp_path = "#{filename.ext}.webp"
# Encode (convert) image to webp format with passed options
WebP.encode(path, webp_path, options)
# HACK: Changing of this two instance variables is the only way
# I found to make CarrierWave save new file that was created
# by encoding original image.
#filename = webp_path.split('/').pop
#file = CarrierWave::SanitizedFile.new(
tempfile: webp_path,
filename: webp_path,
content_type: 'image/webp'
)'''
This is what I use successfully, obviously need MiniMagick installed on the dev computer and server:
class ImagesUploader < CarrierWave::Uploader::Base
include CarrierWave::MiniMagick
def store_dir
"uploads/story/#{model.created_at.strftime('%Y').to_s.underscore}/#{model.created_at.strftime('%m').to_s.underscore}/#{model.created_at.strftime('%d').to_s.underscore}"
end
def default_url(*args)
"/fallback.webp"
end
process :resize_to_fill => [1200, 677]
process convert_to_webp: [{ quality: 80 }]
version :preview do
process convert_to_webp: [{ quality: 80, resize_w: 470, resize_h: 265 }]
end
def extension_allowlist
%w(jpg jpeg gif png webp)
end
private
def convert_to_webp(options = {})
# Build path for new file
webp_path = "#{path}.webp"
# Encode (convert) image to webp format with passed options
WebP.encode(path, webp_path, options)
# HACK: Changing of this two instance variables is the only way
# I found to make CarrierWave save new file that was created
# by encoding original image.
#filename = webp_path.split('/').pop.split('.').first + '.webp'
#file = CarrierWave::SanitizedFile.new(
tempfile: webp_path,
filename: webp_path,
content_type: 'image/webp'
)
end
end
Currently with Carrierwave, after uploading a file like foo.png when creating different versions like so:
class ImageUploader < CarrierWave::Uploader::Base
include CarrierWave::MiniMagick
storage :fog
def store_dir
"#{model.class.to_s.underscore}/#{model.id}"
end
version :thumb do
process :resize_to_fit => [500, 500]
end
end
that results in the files being uploaded as:
thumb_foo.png
foo.png
I want to move "thumb" to the end of the filename for SEO reasons. Based on their docs here I added:
def full_filename(for_file)
if parent_name = super(for_file)
extension = File.extname(parent_name)
base_name = parent_name.chomp(extension)
[base_name, version_name].compact.join("_") + extension
end
end
def full_original_filename
parent_name = super
extension = File.extname(parent_name)
base_name = parent_name.chomp(extension)
[base_name, version_name].compact.join("_") + extension
end
The docs say this should result in:
foo_thumb.png
foo.png
However, I end up actually getting the following:
thumb_foo_thumb.png
foo.png
Any idea what I'm doing wrong?
Simply use #full_filename under the version block:
class AvatarUploaer < CarrierWave::Uploader::Base
include CarrierWave::MiniMagick
storage :file
version :thumb do
process resize_to_fill: [50, 50]
def full_filename(for_file = model.logo.file)
parts = for_file.split('.')
extension = parts[-1]
name = parts[0...-1].join('.')
"#{name}_#{version_name}.#{extension}"
end
end
end
The result will be following:
/Users/user/app/uploads/1x1.gif
/Users/user/app/uploads/1x1_thumb.gif
More information in Wiki: How to: Migrate from one model to another
MVP example: https://gist.github.com/itsNikolay/2394f84f31db33d4c3dc6634068b0259
If you have a lot of versions, the accepted answer can get a little tedious.
I ended up overriding full_filename for everything instead of in each individual version definition. It works fine. This is for Carrierwave 1.0
photo_uploader.rb
# Override the filename of the uploaded files:
def full_filename(name)
"#{File.basename(name, '.*')}_#{version_name || 'original'}#{File.extname(name)}"
end
I'm using the built in File.basename and File.extname methods instead of doing it manually as seen in the accepted answer (although that's where I started and that code works fine too).
Note: I wanted to add "original" to the unversioned upload just so my directory listing looked cleaner. That part could be removed fairly easily.
foo_mobile.jpg
foo_original.jpg
foo_square.jpg
In the current version of CarrierWave if you have an uploader defined like this:
class LogoUploader < CarrierWave::Uploader::Base
# ...
def filename
"original_#{model.logo.file.extension}" if original_filename
end
version :small do
process :resize_to_fit => [190, 190]
process :convert => 'png'
end
version :icon do
process :resize_to_fill => [50, 50]
process :convert => 'png'
end
# ...
end
and attach a file name somefile.jpg, you will end up with files named original.jpg, original_small.png and original_icon.png respectively.
My default image is working with carrierwave, except I can't get a thumb version of the default. #profile.photo and #profile.photo.thumb both return the same default_profile.png. How do I get this to work
Also, when I do upload a photo, #profile.photo.thumb does correctly return the resized version of the uploaded photo.
In /assets/images:
I have default_profile.png and thumb_default_profile.png, the latter is, of course, a scaled down version of the former. I also tried renaming the thumb file to default_profile_thumb.png
relevant portion of profile_photo_uploader.rb:
def default_url
"default_profile.png"
end
version :thumb do
process :resize_to_fill => [60, 60]
end
def default_url
"default_profile.png"
end
version :thumb do
process :resize_to_fill => [60, 60]
def default_url
"thumb_default_profile.png"
end
end
Nicer way todo:
def default_url(*args)
ActionController::Base.helpers.asset_path([version_name, "default_profile.png"].compact.join('_'))
end
I have Image model:
class Image < ActiveRecord::Base
mount_uploader :file, ModuleImageUploader
end
To upload image I use carrierwave + cloudinary:
class ModuleImageUploader < CarrierWave::Uploader::Base
include Cloudinary::CarrierWave
process :resize_to_limit => [700, 700]
version :mini do
process :resize_and_pad => [50, 50, '#ffffff']
end
version :thumb do
process :resize_and_pad => [100, 100, '#ffffff']
end
def public_id
return SecureRandom.uuid
end
end
I created new model AccountMediaContent:
class AccountMediaContent < ActiveRecord::Base
mount_uploader :image, AccountMediaContentImageUploader
end
with it's uploader which also uses carrierwave:
class AccountMediaContentImageUploader < CarrierWave::Uploader::Base
include Cloudinary::CarrierWave
process :resize_to_limit => [700, 700]
version :mini do
process :resize_and_pad => [50, 50, '#ffffff']
end
version :thumb do
process :resize_and_pad => [100, 100, '#ffffff']
end
def extension_white_list
%w(jpg jpeg gif png)
end
end
Right now I need to the way to transfer the image from Image to AccountMediaContent. So, that means if I had such file in Image:
http://res.cloudinary.com/isdfldg/image/upload/v1344344359/4adcda41-49c0-4b01-9f3e-6b3e817d0e4e.jpg
Then it means that I need the exact same file in AccountMediaContent so the link to the file will be the same. Is there any way to achieve this?
The optimal solution for this would be to have a new model which represents the image, and then link it to both models.
Ok my solution is not really good but anyway. What I did is I wrote the script which downloaded already existing images Image in Cloudinary and then I attached them to new model AccountMediaContent.
My task looks like this:
Image.find_in_batches do |imgs_in_batch|
imgs_in_batch.each do |img|
# Downloading image to tmp folder (works on heroku too)
file_format = img.file.format
img_url = img.file.url
tmp_file = "#{Rails.root.join('tmp')}/tmp-img.#{file_format}"
File.open(tmp_file, 'wb') do |fo|
fo.write open(img_url).read
end
# Creating AccountMediaContent with old image (it'll be uploaded to cloudinary.
AccountMediaContent.create(image: File.open(tmp_file))
FileUtils.rm(tmp_file)
end
end
Hope it'll be useful for someone.
The ultimate goal is to be able to upload PDFs, have thumbnails created, and have them saved as PNGs. One thing I should mention is that many of the PDFs are multipage. I have a temporary work around for that, as CarrierWave currently doesn't like them. It's included in the manipulate function I define myself.
Currently what I have is working, except that the original PDF file get's its extension changed to .png, even though I don't believe I'm telling it to. The content remains PDF, which is what I want, but having the extension change, breaks everything. Here's the my whole uploader:
class PdfUploader < CarrierWave::Uploader::Base
def cache_dir
"#{Rails.root}/tmp/uploads"
end
include CarrierWave::RMagick
storage :fog
def store_dir
"uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"
end
# Create different versions of your uploaded files:
version :for_crop do
convert('png')
process :resize_to_limit_png => [700, 700]
end
version :thumb do
convert('png')
process :crop
process :resize_to_limit_png => [100, 100]
end
version :medium do
convert('png')
process :crop
process :resize_to_limit_png => [300, 300]
end
version :large do
convert('png')
process :crop
process :resize_to_limit_png => [700, 700]
end
def extension_white_list
%w(jpg jpeg gif png pdf)
end
# Crop Method, grabs the crop ratio and applies the crop
# Called when creating versions
def crop
if model.crop_x.present?
manipulate!(:format => 'png') 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
def manipulate!(options={})
cache_stored_file! if !cached?
image = ::Magick::Image.read(current_path)
frames = if image.size > 1
list = ::Magick::ImageList.new
#image.each do |frame|
# list << yield( frame )
#end
list = image.first
list
else
frame = image.first
frame = yield( frame ) if block_given?
frame
end
if options[:format]
frames.write("#{options[:format]}:#{current_path}")
else
frames.write(current_path)
end
destroy_image(frames)
rescue ::Magick::ImageMagickError => e
raise CarrierWave::ProcessingError, I18n.translate(:"errors.messages.rmagick_processing_error", :e => e)
end
def resize_to_limit_png(width, height)
manipulate!(:format => 'png') do |img|
geometry = Magick::Geometry.new(width, height, 0, 0, Magick::GreaterGeometry)
new_img = img.change_geometry(geometry) do |new_width, new_height|
img.resize(new_width, new_height)
end
destroy_image(img)
new_img = yield(new_img) if block_given?
new_img
end
def filename
(super.chomp(File.extname(super))).to_s + '.png'
end
end
end
So, I did some temporary janky customization just to make this work until CarrierWave handles multipage PDFs. But I wanted to include it in the event that I messed up somewhere in there. Any help would be much appreciated! Thank you!