Watermark images with paperclip, rails 4 - ruby-on-rails

I've been attempting to add watermarks to my images, following the answer listed in watermark with paperclip :
Watermark.rb:
module Paperclip
class Watermark < Processor
# Handles watermarking of images that are uploaded.
attr_accessor :current_geometry, :target_geometry, :format, :whiny, :convert_options, :watermark_path, :watermark_offset, :overlay, :position
def initialize file, options = {}, attachment = nil
super
geometry = options[:geometry]
#file = file
#crop = geometry[-1,1] == '#'
#target_geometry = Geometry.parse geometry
#current_geometry = Geometry.from_file #file
#convert_options = options[:convert_options]
#whiny = options[:whiny].nil? ? true : options[:whiny]
#format = options[:format]
#watermark_path = options[:watermark_path]
#position = options[:position].nil? ? "SouthEast" : options[:position]
#watermark_offset = options[:watermark_offset]
#overlay = options[:overlay].nil? ? true : false
#current_format = File.extname(#file.path)
#basename = File.basename(#file.path, #current_format)
end
# TODO: extend watermark
# Returns true if the +target_geometry+ is meant to crop.
def crop?
#crop
end
# Returns true if the image is meant to make use of additional convert options.
def convert_options?
not #convert_options.blank?
end
# Performs the conversion of the +file+ into a watermark. Returns the Tempfile
# that contains the new image.
def make
dst = Tempfile.new([#basename, #format].compact.join("."))
dst.binmode
if watermark_path
command = "composite"
params = %W[-gravity #{#position}]
params += %W[-geometry '#{#watermark_offset}'] if #watermark_offset
params += %W['#{watermark_path}' '#{fromfile}']
params += transformation_command
params << "'#{tofile(dst)}'"
else
command = "convert"
params = ["'#{fromfile}'"]
params += transformation_command
params << "'#{tofile(dst)}'"
end
begin
Paperclip.run(command, params.join(' '))
rescue ArgumentError, Cocaine::CommandLineError
raise PaperclipError, "There was an error processing the watermark for #{#basename}" if #whiny
end
dst
end
def fromfile
File.expand_path(#file.path)
end
def tofile(destination)
File.expand_path(destination.path)
end
def transformation_command
scale, crop = #current_geometry.transformation_to(#target_geometry, crop?)
trans = %W[-resize '#{scale}']
trans += %W[-crop '#{crop}' +repage] if crop
trans << convert_options if convert_options?
trans
end
end
end
and model code:
has_attached_file :image,
:processors => [:watermark],
:styles => {
:large => "640x480",
:thumb => "100x100",
:medium => "300x300",
:content => {
:geometry => '150x153',
:watermark_path => Rails.root.join('app/assets/images/watermark.jpg'),
:position => 'SouthWest'
},
},
dependent: :allow_destroy
I've attempted to update this to work with Rails 4 (moving attr_accessor to params in the model), but I get the error:
uninitialized constant Paperclip::Watermark::PaperclipError
Any tips on how to get implement watermarks in a rails 4 app?
UPDATE: I was able to get past the uninitialized constant error with Graeme's suggestion below of changing:
raise PaperclipError, "There was an error processing the watermark for #{#basename}" if #whiny
to:
raise Paperclip::Error.new("There was an error processing the watermark for #{#basename}") if #whiny
I also had to remove the following from the model in order for the upload to process:
:url => "/images/:style/:id_:style.:extension",
:path => ":rails_root/app/assets/images/:style/:id_:style.:extension"
I don't understand what the purpose of :url and :path are in this scenario, when users are uploading images?
The problem is, even though the images now get uploaded, no watermark is being displayed. Thoughts?
Update 2:
In order to get the watermark displaying correctly, I had to change the model to:
has_attached_file :image,
:processors => [:watermark],
:url => "/system/:class/:attachment/:id_partition/:style/:filename",
:path => ":rails_root/public/system/:class/:attachment/:id_partition/:style/:filename",
:styles => {
:large => "640x480",
:thumb => "100x100",
:medium => {
:processors => [:watermark],
:geometry => '300x300',
:watermark_path => Rails.root.join('app/assets/images/icon.gif'),
:position => 'SouthWest'
},
},
dependent: :allow_destroy
The key piece was removing :content => . Only remaining issue is that the watermark is scaling up to fit the entire width of the image. Any recommendations on how to stop the watermark-scaling?

The problem with the watermark being stretched is the Imagemagick command which is combining the two images together and then resizing the result.
Effectively the command being run will be (I've abbreviated the actual filenames for clarity):
composite -gravity SouthWest icon.gif uploaded_image.gif -resize 300x300 output_image.gif
As you see the images are joined and then resized.
The command I believe you need is:
convert uploaded_image.gif -resize 300x300 icon.gif -gravity SouthWest -composite output_image.gif
i.e. resize the uploaded image and then add on the watermark.
I've tested this using composite and convert on the command line and it does what I believe you are looking for.
To achieve it in the code, you need to change if watermark_path statement in the make method:
def make
dst = Tempfile.new([#basename, #format].compact.join("."))
dst.binmode
if watermark_path
# -- original code --
# command = "composite"
# params = %W[-gravity #{#position}]
# params += %W[-geometry '#{#watermark_offset}'] if #watermark_offset
# params += %W['#{watermark_path}' '#{fromfile}']
# params += transformation_command
# params << "'#{tofile(dst)}'"
# -- new code --
command = "convert"
params = %W['#{fromfile}']
params += transformation_command
params += %W['#{watermark_path}' -gravity #{#position} -composite]
params << "'#{tofile(dst)}'"
else
command = "convert"
params = ["'#{fromfile}'"]
params += transformation_command
params << "'#{tofile(dst)}'"
end
begin
Paperclip.run(command, params.join(' '))
rescue ArgumentError, Cocaine::CommandLineError
raise PaperclipError, "There was an error processing the watermark for #{#basename}" if #whiny
end
dst
end
Disclaimer: I have not actually tested this so please forgive any errors.

PaperclipError doesn't exist.
Try changing:
raise PaperclipError, "There was an error processing the watermark for #{#basename}" if #whiny
to:
raise Paperclip::Error.new("There was an error processing the watermark for #{#basename}") if #whiny

Since you are now asking a different question I'll make a new answer.
:path is used to define where Paperclip will store the uploaded image. By default this will be :rails_root/public/system/:class/:attachment/:id_partition/:style/:filename.
:url is used to access the image later. By default this will be /system/:class/:attachment/:id_partition/:style/:filename.
(Actually, to save having to duplicate the url part, :path is really defined as :rails_root/public:url by default.)
By specifying them in the model you are changing the save location. I wouldn't recommend putting them in the assets directory as assets are really part of your application and you don't want user uploaded files to be going there.
As to why you are not seeing the watermark with the uploaded image, I guess the Imagemagick composite command is not being called correctly. Try running it on the command line, or adding the parameter -debug to see why it is failing.

Related

CarrierWave Renaming the Original, when I only Specified to do so for Versions

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!

Paperclip sharpening processor causes resizing styles not to work

I'm trying to sharpen images uploaded through paperclip. The sharpen code is working but it causes the styles to not work. The code is like this:
has_attached_file :photo,
:styles => {
:thumb => {:geometry => "100x100>"},
:medium => {:geometry => "300x300>"},
:original => {:geometry => "1024x1024>"}
},
:processors => [:sharpen],
:storage => :s3,
:s3_credentials => "#{Rails.root}/config/s3.yml",
:path => "/:style/:id/:filename"
Now if I remove the processors option, the uploaded images are resized as specified. However if I include the processors option, all the resulting images are of original size.
My sharpen processor looks like this:
module Paperclip
class Sharpen < Paperclip::Processor
def initialize file, options = {}, attachment = nil
super
#file = file
#current_format = File.extname(#file.path)
#basename = File.basename(#file.path, #current_format)
end
def make
dst = Tempfile.new(#basename)
dst.binmode
command = "#{File.expand_path(#file.path)} -unsharp 1.5×1.0+1.5+0.02 #{File.expand_path(dst.path)}"
begin
success = Paperclip.run("convert", command)
rescue PaperclipCommandLineError
raise PaperclipError, "There was an error converting sharpening the image for #{#basename}"
end
dst
end
end
end
Any thoughts?
Try adding :thumbnail to processors list:
:processors => [:thumbnail, :sharpen]
By default :thumbnail is there but now you are overriding that setting.
"Multiple processors can be specified, and they will be invoked in the order they are defined in the :processors array. Each successive processor will be given the result of the previous processor's execution. All processors will receive the same parameters, which are what you define in the :styles hash."
https://github.com/thoughtbot/paperclip

paperclip processors in rails3

I'm trying to upload mp3s to S3 using paperclip which works fine, but when I try using a custom processor to create a lower quality audio file I run into problems.
It seems to work, but instead of outputting the whole song in 128k, it only does the first 6 seconds. (it works fine when i try using ffmpeg at the command line) And it does it for both "styles" not just the snippet style, the original gets processed as well which I don't want.
song.rb
class Song < ActiveRecord::Base
belongs_to :record
#after_save :create_metadata
Paperclip.interpolates :record_id do |attachment, style|
attachment.instance.record_id
end
has_attached_file :mp3,
:styles => {
:snippet => { :processors => [:audio_prehear] }
},
:storage => 's3',
:s3_credentials => "#{RAILS_ROOT}/config/s3_credentials.yml"....
My processor
module Paperclip
class AudioPrehear < Processor
attr_accessor :resolution, :whiny
def initialize(file, options = {}, attachment = nil)
super
#file = file
#whiny = options[:whiny].nil? ? true : options[:whiny]
#basename = File.basename(#file.path, File.extname(#file.path))
end
def make
target = File.dirname(#file.path) + "/" + #basename + ".mp3"
convert File.expand_path(file.path), target
dst = File.open target
end
def convert (infile, outfile)
cmd = "-y -i #{infile} -ab 128k #{outfile}"
begin
success = Paperclip.run('ffmpeg', cmd)
rescue PaperclipCommandLineError
raise PaperclipError, "There was an error processing the preview for #{#basename}" if whiny
end
end
end
end
log
[paperclip] ffmpeg -y -i /var/folders/Ue/UehOEbyxHkS9voyeIiUx3++++TI/-Tmp-/stream20101223-93966-18vy7po-0.mp3 -ab 128k /var/folders/Ue/UehOEbyxHkS9voyeIiUx3++++TI/-Tmp-/stream20101223-93966-18vy7po-0.mp3 2>/dev/null
....
[paperclip] Saving attachments.
[paperclip] saving music/7635/snippet/monkey.mp3
[paperclip] saving music/7635/original/monkey.mp3
....

Rails Paperclip how to use filter options of ImageMagick?

I recently implemented Paperclip with Rails and want to try out some of the filter options from ImageMagick such as blur. I've not been able to find any examples of how to do this. Does it get passed through :style as another option?
:styles => { :medium => "300x300#", :thumb => "100x100#" }
#plang's answer was correct but I wanted to give the exact solution to the blur, just in case someone was looking and found this question:
:convert_options => { :all => "-blur 0x8" }
// -blur {radius}x{sigma}
Which changed this:
To this:
I did not test this, but you should be able to use the "convert_options" parameter, like this:
:convert_options => { :all => ‘-colorspace Gray’ }
Have a look at https://github.com/thoughtbot/paperclip/blob/master/lib/paperclip/thumbnail.rb
I personnaly use my own processor.
In Model:
has_attached_file :logo,
:url => PaperclipAssetsController.config_url,
:path => PaperclipAssetsController.config_path,
:styles => {
:grayscale => { :processors => [:grayscale] }
}
In lib:
module Paperclip
# Handles grayscale conversion of images that are uploaded.
class Grayscale < Processor
def initialize file, options = {}, attachment = nil
super
#format = File.extname(#file.path)
#basename = File.basename(#file.path, #format)
end
def make
src = #file
dst = Tempfile.new([#basename, #format])
dst.binmode
begin
parameters = []
parameters << ":source"
parameters << "-colorspace Gray"
parameters << ":dest"
parameters = parameters.flatten.compact.join(" ").strip.squeeze(" ")
success = Paperclip.run("convert", parameters, :source => "#{File.expand_path(src.path)}[0]", :dest => File.expand_path(dst.path))
rescue PaperclipCommandLineError => e
raise PaperclipError, "There was an error during the grayscale conversion for #{#basename}" if #whiny
end
dst
end
end
end
This might not be 100% necessary for a simple grayscale conversion, but it works!
Rails 5, Paperclip 5 update
Instead of having to add a library now, you can just call out to ImageMagick's convert command on the system to use its grayscale option. You can do the same for blur or any of the other ImageMagick options, but I needed to do this for conversion to grayscale.
In your model (client that has a logo):
class Client < ApplicationRecord
has_attached_file :logo,
styles: { thumb: "243x243#", grayscale: "243x243#" }
# ensure it's an image
validates_attachment_content_type :logo, content_type: /\Aimage\/.*\z/
# optional, just for name and url to be required
validates :name, presence: true
validates :url, presence: true
after_save :convert_grayscale
def convert_grayscale
system "convert #{self.logo.path(:thumb)} -grayscale Rec709Luminance #{self.logo.path(:grayscale)}"
end
def logo_attached?
self.logo.file?
end
end
Then just use in the view like this (per Paperclips github docs).
In your view:
<%= image_tag(client.logo.url(:grayscale), class: 'thumbnail', alt: client.name, title: client.name) %>
or with a link if you prefer:
<%= link_to(image_tag(client.logo.url(:grayscale), class: 'thumbnail', alt: client.name, title: client.name), client.url ) %>

multiple paperclip attachements with watermark processor

I'm having a ton of trouble with uploading multiple attachments with paperclip and processing them with a watermark.
I have 2 models, Ad and Photo.
The Ad has_many :photos and the Photo belongs_to :ad.
To be more exact in /models/ad.rb I have:
class Ad < ActiveRecord::Base
has_many :photos, :dependent => :destroy
accepts_nested_attributes_for :photos, :allow_destroy => true
end
and my photo.rb file looks like this:
class Photo < ActiveRecord::Base
belongs_to :ad
has_attached_file :data,
:styles => {
:thumb => "100x100#",
:first => {
:processors => [:watermark],
:geometry => '300x250#',
:watermark_path => ':rails_root/public/images/watermark.png',
:position => 'SouthEast' },
:large => {
:processors => [:watermark],
:geometry => '640x480#',
:watermark_path => ':rails_root/public/images/watermark.png',
:position => 'SouthEast' }
}
end
In my view I use this to add the file fields
<% f.fields_for :photos do |p| %>
<%= p.label :data, 'Poza:' %> <%= p.file_field :data %>
<% end %>
In my controller, in the edit action i use 4.times {#ad.photos.build} to generate the file fields.
It all works fine and dandy if I don't use the watermark processor, if I use a normal has_attached_file declaration, like this:
has_attached_file :data,
:styles => {
:thumb => "100x100#",
:first => '300x250#',
:large => '640x480#'
}
But when I use the watermark processor I always get this error:
NoMethodError in PublicController#update_ad
You have a nil object when you didn't expect it!
You might have expected an instance of ActiveRecord::Base.
The error occurred while evaluating nil.[]
..............................
/usr/lib/ruby/gems/1.8/gems/activerecord-2.3.5/lib/active_record/nested_attributes.rb:350:in `assign_nested_attributes_for_collection_association'
/usr/lib/ruby/gems/1.8/gems/activerecord-2.3.5/lib/active_record/nested_attributes.rb:345:in `each'
/usr/lib/ruby/gems/1.8/gems/activerecord-2.3.5/lib/active_record/nested_attributes.rb:345:in `assign_nested_attributes_for_collection_association'
/usr/lib/ruby/gems/1.8/gems/activerecord-2.3.5/lib/active_record/nested_attributes.rb:243:in `photos_attributes='
/usr/lib/ruby/gems/1.8/gems/activerecord-2.3.5/lib/active_record/base.rb:2746:in `send'
/usr/lib/ruby/gems/1.8/gems/activerecord-2.3.5/lib/active_record/base.rb:2746:in `attributes='
/usr/lib/ruby/gems/1.8/gems/activerecord-2.3.5/lib/active_record/base.rb:2742:in `each'
/usr/lib/ruby/gems/1.8/gems/activerecord-2.3.5/lib/active_record/base.rb:2742:in `attributes='
/usr/lib/ruby/gems/1.8/gems/activerecord-2.3.5/lib/active_record/base.rb:2628:in `update_attributes'
/home/alexg/Sites/vandmasina/app/controllers/public_controller.rb:217
/home/alexg/Sites/vandmasina/app/controllers/public_controller.rb:216:in `update_ad'
The parameters are ok, as far as I can say
Parameters:
{"commit"=>"Salveaza modificarile",
"ad"=>{"price"=>"6000",
"oras"=>"9",
"photos_attributes"=>{"0"=>{"data"=>#<File:/tmp/RackMultipart20100928-5130-b42noe-0>},
"1"=>{"data"=>#<File:/tmp/RackMultipart20100928-5130-r0ukcr-0>},
"2"=>{"data"=>#<File:/tmp/RackMultipart20100928-5130-mb23ei-0>},
"3"=>{"data"=>#<File:/tmp/RackMultipart20100928-5130-1bpkm3b-0>}},
The Watermark processor /lib/paperclip_processors/watermark.rb looks like this:
module Paperclip
class Watermark < Processor
class InstanceNotGiven < ArgumentError;
end
def initialize(file, options = {},attachment = nil)
super
#file = file
#current_format = File.extname(#file.path)
#basename = File.basename(#file.path, #current_format)
#watermark = ':rails_root/public/images/watermark.png'
#current_geometry = Geometry.from_file file # This is pretty slow
#watermark_geometry = watermark_dimensions
end
def watermark_dimensions
return #watermark_dimensions if #watermark_dimensions
#watermark_dimensions = Geometry.from_file #watermark
end
def make
dst = Tempfile.new([#basename, #format].compact.join("."))
watermark = " \\( #{#watermark} -extract #{#current_geometry.width.to_i}x#{#current_geometry.height.to_i}+#{#watermark_geometry.height.to_i /
2}+#{#watermark_geometry.width.to_i / 2} \\) "
command = "-gravity center " + watermark + File.expand_path(#file.path) + " " +File.expand_path(dst.path)
begin
success = Paperclip.run("composite", command.gsub(/\s+/, " "))
rescue PaperclipCommandLineError
raise PaperclipError, "There was an error processing the watermark for #{#basename}" if #whiny_thumbnails
end
dst
end
end
end
I have tried the processor in a normal app, without multiple attachments and it works perfect. It doesn't work with nested_attributes as far as I can tell.
The app is rails 2.3.5 with ruby 1.8.7 and paperclip 2.3.11
If you can provide any help it would be appreciated a lot, since I've been trying to figure this out for 2 days now :)
If you use rails 3 and paperclip > 2.3.3, try https://gist.github.com/843418 source.
Oh, man, that was a tough one!
You have few errors in your code and none is related to nested models. My explanation is for Rails 3 & Paperclip 2.3.3
a) the :rails_root thing doesn't work. This interpolation is used only in url/path and not on custom options. So you should replace it with Rails.root.join("public/images...")
b) you simply ignore the :watermark_path option and you use only hardcoded path (in initialization method). So it doesn't matter what you have in your :styles as it always go for .../images/watermark.png. The :rails_root thingy there again so it cannot work.
c) when you pass a parameter to Paperclip.run("composite", "foo bar there") it actually executes this command:
composite 'foo bar there'
can you see the single quotes? Because of that the composite command see your parameters as one huge parameter and doesn't understand it at all. If you pass it as an array, then every item is enclosed in the quotes, not the array as a whole.
So here is the improved version of watermark processor:
module Paperclip
class Watermark < Processor
class InstanceNotGiven < ArgumentError;
end
def initialize(file, options = {},attachment = nil)
super
#file = file
#current_format = File.extname(#file.path)
#basename = File.basename(#file.path, #current_format)
# PAWIEN: use default value only if option is not specified
#watermark = options[:watermark_path] || Rails.root.join('public/images/watermark.png')
#current_geometry = Geometry.from_file file # This is pretty slow
#watermark_geometry = watermark_dimensions
end
def watermark_dimensions
return #watermark_dimensions if #watermark_dimensions
#watermark_dimensions = Geometry.from_file #watermark
end
def make
dst = Tempfile.new([#basename, #format].compact.join("."))
dst.binmode
begin
# PAWIEN: change original "stringy" approach to arrayish approach
# inspired by the thumbnail processor
options = [
"-gravity",
"center",
"#{#watermark}",
"-extract",
"#{#current_geometry.width.to_i}x#{#current_geometry.height.to_i}+#{#watermark_geometry.height.to_i / 2}+#{#watermark_geometry.width.to_i / 2}",
File.expand_path(#file.path),
File.expand_path(dst.path)
].flatten.compact.join(" ").strip.squeeze(" ")
success = Paperclip.run("composite", options)
rescue PaperclipCommandLineError
raise PaperclipError, "There was an error processing the watermark for #{#basename}" if #whiny_thumbnails
end
dst
end
end
end
Hope that helped you!
UPDATE: You need to use latest paperclip gem from github
gem 'paperclip', '>= 2.3.3', :git => "http://github.com/thoughtbot/paperclip.git"
In this version the formats of #run were changed once again so I've updated the code. It really should work as I've created test application and it's doing what supposed.
UPDATE 2: Repo with working example:
git://repo.or.cz/paperclip-mass-example.git
Just from a quick glance it looks like watermark_path should be "#{Rails.root}/..." though it looks like you have a lot going on here.
Also, I don't see your form as in form_for. Make sure you have {:multipart => true}

Resources