Carrierwave Cropping - ruby-on-rails

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.

Related

How to manage quality of an image with gem MiniMagik

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

Get actual dimensions of image uploaded with carrierwave using fastimage

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

How to crop image manually with paperclip?

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.

CarrierWave - Set width and max height of images

I use CarrierWave and I want to resize the images to a width of 220px and a max-height of 220px.
If I use process :resize_to_fit => [220,220] it could be that the width is not 220px. What can I do?
If I interpret the question correctly:
for a portrait image (say 480px wide, 640px high) you would want to scale it down to 220px wide, then crop it down to 220px high, resulting in a square image.
for a landscape image, you would want to scale it down to 220px wide (the height would therefore be less than 220px).
If that's right, you want a two-step process:
Resize to 220px wide, preserving the aspect ratio
Crop to 220px high (if portrait)
You can do so by writing your own processor using the manipulate! command (see CarrierWave's own for some inspiration).
I think this is roughly what you're after
process :resize => [220, 220]
protected
def resize(width, height, gravity = 'Center')
manipulate! do |img|
img.combine_options do |cmd|
cmd.resize width.to_s
if img[:width] < img[:height]
cmd.gravity gravity
cmd.background "rgba(255,255,255,0.0)"
cmd.extent "#{width}x#{height}"
end
end
img = yield(img) if block_given?
img
end
end
An improvment of Andy H's answer:
process :resize => [220, 220]
protected
def resize(width, height, gravity = 'Center')
manipulate! do |img|
img.combine_options do |cmd|
cmd.resize "#{width}"
if img[:width] < img[:height]
cmd.gravity gravity
cmd.background "rgba(255,255,255,0.0)"
cmd.extent "#{width}x#{height}"
end
end
img = yield(img) if block_given?
img
end
end

paperclip run processors on selected style

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.

Resources