Get actual dimensions of image uploaded with carrierwave using fastimage - ruby-on-rails

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

Related

How to save base64 encoded image with ruby on rails and carrierwave

I receive a base64 encoded image from another server and need to save it to my postgres database. I am using rails 6 and carrierwave. Here is my model:
class Quin < ApplicationRecord
before_create :create_graphic
mount_base64_uploader :new_image, NewImageUploader
def create_graphic
url = "https://my-api-url.com"
url_response = HTTParty.post(url,:body => data)
self.new_image = "data:image/png;base64," + url_response.parsed_response
puts "parsed response is #{url_response.parsed_response.inspect}"
end
end
For some reason, when I save, new_image is just saved as nil. I know I am receiving a response. It's really long but here's a truncated sample from logs:
parsed response is "\x89PNG\r\n\x1A\n\x00\x00\x00\rIHDR\x00\x00\x05\xA0\x00\x00\x05\xA0\b\x02\x00\x00\x00'\xC2s\x9F\x00\x01\x00\x00IDATx\x9C\xEC\xFD[\x93$\xD9u\xE7\x8B\xFD\xD7\xF2\xAC\xCA\xEA\xAA\x06\x01j\xCE\x90U\r\x1C\xE9\x98I/2=\xC8Ht\x17\xCD\xF4A\bp83\x94t\x8E$3}#\x9D3:g\x86\x04\x89\xA6\x1D\x99\x1E\xF4\r\xC0\xE1H\x86jp(\xD3\x9B\xDEd\x04\xD0] n\x8D\xEE\xCA\xAC\x88\xCC\xF0\xB5\xF4\xB0\xF6\xCD/q\xCB\xCC\xC8\xC8\xA8\xFA\xFF:;+#\xC2}\xFB\xF6\xED\xDB=\xF6\xFE\xEFu\x11w\a!\x84\x10B\b!\x84\x10B\xC8)\xA3\xC7\xAE\x00!\x84\x10B\b!\x84\x10B\xC8m\xA1\xC0A\b!\x84\x10B\b!\x84\x90\x93\x87\x02\a!\x84\x10B\b!\x84\x10BN\x1E\n\x1C\x84\x10B\b!\x84\x10B\b9y(p\x10B\b!\x84\x10B\b!\xE4\xE4\xA1\xC0A\b!\x84\x10B\b!\x84\x90\x93\x87\x02\a!\x84\x10B\b!\x84\x10BN\x1E\n\x1C\x84\x10B\b!
How do I fix this and save the base64 encoded image that is returned?

path name contains null byte for image url while existing

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

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.

Cannot alter model attribute directly when carrierwave uploader is mounted

I am using activeadmin, and the following code is supposed to assign a photo to a Card if found in a directory.
The Card model won't accept the new photo, it never assigns the new value, save returns true.
UPDATE: The problem is that the attribute has a carrierwave uploader mounted on it. Commenting out the mount made it work, but it really isn't an acceptable solution since this is an action that will have to be made repeatedly... Is there a way to unmount the uploader or set the attribute value directly with the uploader mounted? I can't seem to find anything on that on the web...
UPDATE: Even ActiveRecord::Base.connection.execute("update cards set photo='#{f.to_s}' where id=#{card.id};") fails when carrierwave is mounted... It's taken over for good I guess
UPDATE: It turns out my question is a duplicate of
Manually updating attributes mounted by Carrierwave Uploader
from the log:
Card Load (0.5ms) SELECT `cards`.* FROM `cards` WHERE `cards`.`gatherer_id` = 244675 LIMIT 1
(0.2ms) BEGIN
Card Load (0.4ms) SELECT `cards`.* FROM `cards` WHERE `cards`.`id` = 1377 LIMIT 1
SQL (0.5ms) UPDATE `cards` SET `photo` = NULL, `updated_at` = '2013-11-22 11:20:44' WHERE `cards`.`id` = 1377
(4.1ms) COMMIT
After carrierwave is not mounted and a value for the attribute is updated just fine and afterwards, when carrierwave is mounted again it returns the file location according to the uploader store_dir normally. The only problem is I cannot set the value when the uploader is mounted. As a last resort I will do it with plain old sql, I just don't like the idea much.
activeadmin action:
ActiveAdmin.register_page 'Import FTP photos' do
menu label: 'Import FTP photos', parent: 'Cards'
#action_item do
# link_to 'View Site', '/'
#end
content do
require 'fileutils'
para 'Importing photos...'
from_dir = 'var/uploads/card_photos'
uploads_dir = 'public/uploads/card/photo/'
Dir.foreach(from_dir) do |f|
next if f == '.' or f == '..'
from_file = from_dir + '/' + f.to_s
id = f.gsub(/\.\w+$/, '')
card = Card.find_by_gatherer_id(id)
if card
to_dir = uploads_dir+card.id.to_s
to_file = to_dir + '/' + f.to_s
Dir.mkdir(to_dir) if Dir.new(to_dir).nil?
FileUtils.remove(to_file) if FileTest.exists?(to_file)
FileUtils.mv(from_file, to_file)
card.photo = f.to_s
card.save
end
end
end
end
card model
class Card < ActiveRecord::Base
mount_uploader :photo, PhotoUploader
belongs_to :card_set
belongs_to :card_artist
def to_s
caption
end
private
def card_params
params.permit!
end
end

Carrierwave Cropping

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.

Resources