Get image dimensions using Refile - ruby-on-rails

Using the Refile gem to handle file uploading in Rails, what is the best way to determine image height and width during / after it has been uploaded? There is no built in support for this AFAIK, and I can't figure out how to do it using MiniMagick.

#russellb's comment almost got me there, but wasn't quite correct. If you have a Refile::File called #file, you need to:
fileIO = #file.to_io.to_io
mm = MiniMagick::Image.open(fileIO)
mm.width # image width
mm.height # image height
Yes, that's two calls to #to_io >...< The first to_io gives you a Tempfile, which isn't what MiniMagick wants. Hope this helps someone!
-- update --
Additional wrinkle: this will fail if the file is very small (<~20kb, from: ruby-forum.com/topic/106583) because you won't get a tempfile from to_io, but a StringIO. You need to fork your code if you get a StringIO and do:
mm = MiniMagick::Image.read(fileio.read)
So my full code is now:
# usually this is a Tempfile; but if the image is small, it will be
# a StringIO instead >:[
fileio = file.to_io
if fileio.is_a?(StringIO)
mm = MiniMagick::Image.read(fileio.read)
else
file = fileio.to_io
mm = MiniMagick::Image.open(file)
end

Refile attachments have a to_io method (see Refile::File docs) which returns an IO object that you can pass to MiniMagick.
Assuming you have an Image model with a file attachment (id stored in a file_id string column) and width and height columns you can use the following callback:
class Image < ActiveRecord::Base
attachment :file
before_save :set_dimensions, if: :file_id_changed?
def set_dimensions
image = MiniMagick::Image.open(file.to_io)
self.width = image.width
self.height = image.height
end
end
Hope that helps.

You can use MiniMagick to do this (but need to be using the latest version).
image = MiniMagick::Image.open('my_image.jpg')
image.height #=> 300
image.width #=> 1300
This is all pretty well documented in the README.md for the gem: https://github.com/minimagick/minimagick

Related

Process `auto_orient` with Cloudinary upload

I had a regular file upload that I now changed to using Cloudinary.
Upon upload I did the following to prevent orientation glitches when uploading images from a mobile device (See exif image rotation issue using carrierwave and rmagick to upload to s3 for details):
process :rotate
process :store_dimensions
def rotate
manipulate! do |image|
image.tap(&:auto_orient)
end
end
def store_dimensions
# This does not work with cloudinary #18
if file && model
model.width, model.height = ::MiniMagick::Image.open(file.file)[:dimensions]
end
end
Neither rotation nor storing the dimensions work, since I switched to cloudinary.
Now Cloudinary has an official tutuorial that shows how to do this but it simply does not work and other people seem to have the same issue and neither of the provided options worked for me:
I was able to get it working using a variation of the first option:
after_save :update_dimensions
def update_dimensions
if self.image != nil && self.image.metadata.present?
width = self.image.metadata["width"]
height = self.image.metadata["height"]
self.update_column(:width, width)
self.update_column(:height, height)
end
end
Important: since we're inside a after_save callback here it's crucial to use update_column so that we don't trigger another callback and end up in an infinite loop.
Fix for the provided solution:
self.image.present? returned false but self.image != nil returend true.

Watermarking with MiniMagick

Versions:
Ruby 2.2.3
Rails 4.2.4
mini_magick: 4.2.10
Carrierwave 0.10.0
Description
I am trying to create a watermarker for a small gallery using CarrierWave as uploader.
I want the watermark to be sized compared to the current image. Therefore I am trying to use a .svg-file with different opacities and a transparent background.
I am using a watermarker based on Carrierwave add a watermark to processed images
require 'mini_magick'
class Watermarker
def initialize(original_path, watermark_path)
#original_path = original_path.to_s
#watermark_path = watermark_path.to_s
end
def watermark!(options = {})
options[:gravity] ||= 'SouthEast'
image = MiniMagick::Image.open(#original_path)
watermark = MiniMagick::Image.open(#watermark_path)
result = image.composite(watermark, 'png') do |c|
c.gravity options[:gravity]
end
result.write #original_path
end
end
And calling this as a process from my uploader.
My problems:
I cannot get the watermarker to input the picture with transparent background. I played around with:
https://github.com/minimagick/minimagick#composite
http://www.imagemagick.org/script/composite.php
But no progress.
I cannot adjust the size of the overlay image properly. There is a lot of settings for the geometry command but I'm stuck.
Any ideas and help would be great.

Ruby, RSVG and PNG streams

I'm trying to do an image conversion in a rails app from SVG to PNG. ImageMagick didn't work out for me, due to Heroku not able / wanting to upgrade IM at this time. I'm testing out some ideas of using RSVG2 / Cairo in dev but running into a roadblock.
I can easily convert and save the SVG to PNG like this:
#svg_test.rb
require 'debugger'
require 'rubygems'
require 'rsvg2'
SRC = 'test.svg'
DST = 'test.png'
svg = RSVG::Handle.new_from_file(SRC)
surface = Cairo::ImageSurface.new(Cairo::FORMAT_ARGB32, 800, 800)
context = Cairo::Context.new(surface)
context.render_rsvg_handle(svg)
surface.write_to_png(DST)
But this only lets me write PNG files out. In the app, I need to be able to generate these on the fly, then send them down to the client browser as data. And I can't figure out how to do this, or even if its supported. I know I can call surface.data to get the raw data at least, but I don't know enough about image formats to know how to get this as a PNG.
Thanks
Ah ha! I was so close and its pretty obvious in hindsight. Simply call the surface.write_to_png function with a StringIO object. This fills the string object, which you can then get the bytes for. Here's the finished svg_to_png function I wrote, along with a sample controller that calls it. Hope this helps someone else somewhere.
ImageConvert function:
def self.svg_to_png(svg)
svg = RSVG::Handle.new_from_data(svg)
surface = Cairo::ImageSurface.new(Cairo::FORMAT_ARGB32, 800, 800)
context = Cairo::Context.new(surface)
context.render_rsvg_handle(svg)
b = StringIO.new
surface.write_to_png(b)
return b.string
end
Test controller:
def svg_img
path = File.expand_path('../../../public/images/test.svg', __FILE__)
f = File.open(path, 'r')
t = ImageConvert.svg_to_png(f.read)
send_data(t , :filename => 'test.png', :type=>'image/png')
end

Converting PDFs to PNGs with Dragonfly

I have a Dragonfly processor which should take a given PDF and return a PNG of the first page of the document.
When I run this processor via the console, I get back the PNG as expected, however, when in the context of Rails, I'm getting it as a PDF.
My code is roughly similar to this:
def to_pdf_thumbnail(temp_object)
tempfile = new_tempfile('png')
args = "'#{temp_object.path}[0]' '#{tempfile.path}'"
full_command = "convert #{args}"
result = `#{full_command}`
tempfile
end
def new_tempfile(ext=nil)
tempfile = ext ? Tempfile.new(['dragonfly', ".#{ext}"]) : Tempfile.new('dragonfly')
tempfile.binmode
tempfile.close
tempfile
end
Now, tempfile is definitely creating a .png file, but the convert is generating a PDF (when run from within Rails 3).
Any ideas as to what the issue might be here? Is something getting confused about the content type?
I should add that both this and a standard conversion (asset.png.url) both yield a PDF with the PDF content as a small block in the middle of the (A4) image.
An approach I’m using for this is to generate the thumbnail PNG on the fly via the thumb method from Dragonfly’s ImageMagick plugin:
<%= image_tag rails_model.file.thumb('100x100#', format: 'png', frame: 0).url %>
So long as Ghostscript is installed, ImageMagick/Dragonfly will honour the format / frame (i.e. page of the PDF) settings. If file is an image rather than a PDF, it will be converted to a PNG, and the frame number ignored (unless it’s a GIF).
Try this
def to_pdf_thumbnail(temp_object)
ret = ''
tempfile = new_tempfile('png')
system("convert",tmp_object.path[0],tmpfile.path)
tempfile.open {|f| ret = f.read }
ret
end
The problem is you are likely handing convert ONE argument not two
Doesn't convert rely on the extension to determine the type? Are you sure the tempfiles have the proper extensions?

Fast way to get remote image dimensions

I'm using the imagesize gem to check the sizes of remote images and then only push images that are big enough into an array.
require 'open-uri'
require 'image_size'
data = Nokogiri::HTML(open(url))
images = []
forcenocache = Time.now.to_i # No cache because jquery load event doesn't fire for cached images
data.css("img").each do |image|
image_path = URI.join(site, URI.encode(image[:src]))
open(image_path, "rb") do |fh|
image_size = ImageSize.new(fh.read).get_size()
unless image_size[0] < 200 || image_size[1] < 100
image_element = "<img src=\"#{image_path}?#{forcenocache}\">"
images.push(image_element)
end
end
end
I tried using JS on the front-end to check image dimensions but there seems to be a browser limit to how many images can be loaded at once.
Doing it with imagesize is much slower than using JS. Any better and faster ways to do this?
I think this gem does what you want https://github.com/sdsykes/fastimage
FastImage finds the size or type of an
image given its uri by fetching as
little as needed

Resources