I'm building a website with Rails 4.2 and Mongoid. I'm using mongoid-paperclip, and I'm trying to crop an image down to a square while preserving the dimensions of the short side (so the image will fill the full square). Here's my custom processor:
module Paperclip
class Cropper < Thumbnail
def initialize(file, options = {}, attachment = nil)
super
#preserved_size = [#current_geometry.width, #current_geometry.height].min
#current_geometry.width = #preserved_size
#current_geometry.height = #preserved_size
end
def target
#attachment.instance
end
def transformation_command
if crop_command
crop_command + super.join(' ').sub(/ -crop \S+/, '').split(' ')
else
super
end
end
def crop_command
["-crop", "#{#preserved_size}x#{#preserved_size}+#{#preserved_size}+#{#preserved_size}"]
end
end
end
And the model that it's attached to looks has this line:
has_mongoid_attached_file :image, styles: {square: {processors: [:cropper]}}
But it doesn't seem to work. A version of the image named 'square' is saved, but it's identical to the original. How can I get this to work?
I was able to fix this without using a paperclip processor. In my model, I specified the styles for the image using a lambda:
has_mongoid_attached_file :image, styles: lambda {|a|
tmp = a.queued_for_write[:original]
return {} if tmp.nil?
geometry = Paperclip::Geometry.from_file(tmp)
preserved_size = [geometry.width.to_i, geometry.height.to_i].min
{square: "#{preserved_size}x#{preserved_size}#"}
}
Note that the # at the end of the dimensions ensured that the cropped image was always the specified dimensions, rather than just scaling down the image.
Related
I'm using the following code to get the width of image using Dimensions Gem.
file_path = "http://localhost:3000/uploads/resize/avatar/25/ch05.jpg"
width = Dimensions.width(open(file_path).read)
When I put the image url in url bar it renders the image in browser. what I'm trying to do is to get the width of image. so can anyone know what I'm doing wrong?
So your issue is that Dimensions requires a file path to determine the width of the image. open will return a StringIO and open(...).read will return a String both will fail when using File.open.
Dimensions#width
def width(path)
io_for(path).width
end
Dimensions#io_for
def io_for(path)
Dimensions(File.open(path, "rb")).tap do |io|
io.read
io.close
end
end
To work around this you can download the image to a Tempfile and then use that path to pass to Dimensions.width like so
path = "http://localhost:3000/uploads/resize/avatar/25/ch05.jpg"
t = Tempfile.new # you could add a name but it doesn't matter
t.write(open(path).read) # write the image to the Tempfile
t.close # must close the file before reading it
width = Dimensions.width(t.path) # pass the Tempfile path to Dimensions
t.unlink # deletes the Tempfile
We can make this look a little cleaner like so:
def get_width_of_url_image(url)
t = Tempfile.new.tap do |f|
f.write(open(url).read)
f.close
end
width = Dimensions.width(t.path)
t.unlink and width
end
get_width_of_url_image("https://www.google.com/images/branding/googlelogo/1x/googlelogo_color_272x92dp.png")
#=> 272
I'm working on an App which gets shipment labels from Express Carriers in Ruby on Rails 4. At the moment I´m working with the UPS API and its label which is GIF image.
To be able to generate the PDF containing the GIF label I'm using Prawn which not support GIF. So the GIF is converted to a PNG. I´m able to have the label but when I print it out the quality is very low and difficult to read what is inside the label.
I need to understand how to add a better quality as otherwise, the label is not possible to use it.
The class I'm working on:
class UPSLabelConsolidation
attr_reader :base64_encoded_gif_labels
def initialize(base64_encoded_gif_labels)
#base64_encoded_gif_labels = base64_encoded_gif_labels
end
def perform!
identifier = SecureRandom.hex(3)
tmp_label_files = base64_encoded_gif_labels.each_with_index.map do |base64_encoded_gif_label, index|
tmp_label_file = Tempfile.new(["ups-label-#{identifier}-#{index}", ".png"], Rails.root.join("tmp"))
tmp_label_file.binmode
image = MiniMagick::Image.read(Base64.decode64(base64_encoded_gif_label), "gif")
image.combine_options do |b|
b.rotate "90" # Rotate 90-degrees clockwise
b.crop "800x1000+0+0" # Crop ~200px whitespace from the bottom of the label
end
image.format("png") # Prawn does not support GIF, so we convert it here.
image.write(tmp_label_file.path)
tmp_label_file.rewind
tmp_label_file
end
new_page = false
#pdf = Prawn::Document.new(page_size: [297, 390], margin: 20, page_layout: :portrait) # Margin is ~0.7cm
tmp_label_files.each do |label_file|
#pdf.start_new_page if new_page
#pdf.image label_file, width: 250, position: :left # Width is ~8.8cm
new_page = true
end
end
def write!(path)
if #pdf
#pdf.render_file(path)
else
raise "`#perform!` before `#write!`"
end
end
end
I am trying to get the dimensions of a image using carrierwave but I was hoping to get the dimensions with my own code I created trying to run fastimage after_save or after commit. I get this error:
wrong number of arguments (1 for 0)
Extracted source (around line #45):
# [String] contents of the file
#
def read
file.read if file.respond_to?(:read)
end
carrierwave (0.11.2) lib/carrierwave/uploader/proxy.rb:45:in `read'
fastimage (2.1.0) lib/fastimage.rb:343:in `block in fetch_using_read'
model would look something like this
class Micropost < ApplicationRecord
after_save :change_picture_dimensions
mount_uploader :picture, PictureUploader
def change_picture_dimensions
if :picture?
widthheight = FastImage.size(picture)
if widthheight[0] >= 501
newheightratio = widthheight[1].to_f / widthheight[0].to_f
newheight = newheightratio * 500
self.picture = "<img src=\"" + picture.to_s + "\" width=\"500\" height=\"" + newheight.to_s + "\">"
else
self.picture = "<img src=\"" + picture.to_s + "\" width=\"" + widthheight[0].to_s + "\" height=\"" + widthheight[1].to_s + "\">"
end
end
end
This is just a local file on my system. I can get dimensions using minimagick here but wanted to know more about the process of carrierwave and why I cannot use my method to get the dimensions cause of this error? In my method I am just using a ratio to keep the aspect ratio but fit into the fixed width of a div I have for any image.
EDIT: I realised I need to do it before the object is saved but even with before_create I get the same error.
You cannot assign picture a string. It must be an URL, file or an IO object.
If you want to preprocess your image width to fit 500px just declare the following:
# app/uploaders/picture_uploader.rb
class PictureUploader < CarrierWave::Uploader::Base
...
process resize_to_fit: [500, nil]
end
class Micropost < ApplicationRecord
mount_uploader :picture, PictureUploader
end
To save an image from another server you can do:
micropost = Micropost.create(
picture: 'http://server.example.org/image.png'
)
and now you can render it on the page
= image_tag micropost.picture.url
You can also store image size in your model. Read this documentation how to do this. After you saved your image dimensions into a picture model, you can specify them in image_tag, but it will be redundant I think because browser will detect image size automatically
= image_tag micropost.picture.url, width: picture.width, height: picture.height
I have an CarrierWave ImageUploader which creates a couple of versions of an original image that need to be cropped based on values in my model (crop_x, crop_y, crop_w, and crop_h).
class ImageUploader < CarrierWave::Uploader::Base
include CarrierWave::MiniMagick
...
version :t do
process :cropper
process :resize_to_fill => [75, 75]
end
...
def cropper
manipulate! do |img|
img = img.crop "#{model.crop_x}x#{model.crop_y}+#{model.crop_w}+#{model.crop_h}"
img
end
end
end
The problem that I'm having is that I need to calculate some default cropping values if we don't have any set but I don't know where to put this logic. I tried putting this in my Photo model (which the uploader is mounted to) in a before_validation but this seems to be called after the cropper function has executed. I'm thinking that It either needs to be in the ImageUploader file, or in some callback that occurs before the thumbs are created.
You can do something like this:
process :cropper
def cropper
manipulate! do |img|
if model.crop_x.blank?
image = MiniMagick::Image.open(current_path)
model.crop_w = ( image[:width] * 0.8 ).to_i
model.crop_h = ( image[:height] * 0.8 ).to_i
model.crop_x = ( image[:width] * 0.1 ).to_i
model.crop_y = ( image[:height] * 0.1 ).to_i
end
img.crop "#{model.crop_w}x#{model.crop_h}+#{model.crop_x}+#{model.crop_y}"
end
end
I'm running code equivalent to that in one of my apps.
I have an :xxx image processor, and I have two styles in the model :big and :thumb.
How I can process with :xxx only the :thumb image leaving the :big image untouched ?
I recently had a similar problem and found this solution on a message board. Hope it helps!
has_attached_file :screenshot,
:default_style => :full,
:styles => {
:full => "280x210",
:cropped => { :processors => [:screenshot_crop] }
}
By default, the Rake task refreshes all thumbnails. Keep in mind that it won't touch / process the original image.
You could have a look at the Rakefile and the Attachment class and modify to allow you to specify a specific thumbnail size, but the current design assumes that you want to take the original and redo all thumbnails from the original.
Add this code to your paperclip.rake file:
desc "Reprocesses your attachments style (set CLASS, ATTACHMENT and STYLE)"
task :style => :environment do
module JustForOneDay
NAME = ENV['STYLE']
end
module ::Paperclip
class Attachment
def post_process_styles #:nodoc:
#styles.each do |name, args|
if JustForOneDay::NAME == name
begin
raise RuntimeError.new("Style #{name} has no processors defined.") if args[:processors].blank?
#queued_for_write[name] = args[:processors].inject(#queued_for_write[:original]) do |file, processor|
Paperclip.processor(processor).make(file, args, self)
end
rescue PaperclipError => e
log("An error was received while processing: #{e.inspect}")
(#errors[:processing] ||= []) << e.message if #whiny
end
end
end
end
end
end
for_all_attachments do |instance, name|
result = instance.send(name).reprocess!
end
end
end
Tested with Paperclip 2.3.1.1
In Paperclip 2.3.3 this should be:
def post_process_styles #:nodoc:
styles.each do |name, style|
if JustForOneDay::NAME == name
begin
raise RuntimeError.new("Style #{name} has no processors defined.") if style.processors.blank?
#queued_for_write[name] = style.processors.inject(#queued_for_write[:original]) do |file, processor|
Paperclip.processor(processor).make(file, style.processor_options, self)
end
rescue PaperclipError => e
log("An error was received while processing: #{e.inspect}")
(#errors[:processing] ||= []) << e.message if #whiny
end
end
end
end
It's easy, just go to attachment.rb file in your paperclip version.
I kludged this--it's not elegant, but it worked for me.
One of your styles should have dimensions different from all the other styles. In this way, in your custom Paperclip Processor, you can see if the contents of the command string contain the given dimensions. If so you would do special processing, if not, you would not.
(I clipped this code -- and modified it -- from Ryan Bate's Railscast Episode 182.)
module Paperclip
class Cropper < Thumbnail
def transformation_command
SPECIAL_PROCESSING_FLAG = "150x150"
if crop_command && super.include?(SPECIAL_PROCESSING_FLAG)
crop_command + super.sub(/ -crop \S+/, '')
else
super 'do nothing fancy
end
end
def crop_command
target = #attachment.instance
if target.cropping?
" -crop '#{target.crop_w.to_i}x#{target.crop_h.to_i}+#{target.crop_x.to_i}+#{target.crop_y.to_i}'"
end
end
end
end
In my situation it didn't matter that we reprocessed in the non-special case too, since the end result changed nothing.