36

I'm looking for an easy way to get width and height dimensions for image files in Ruby without having to use ImageMagick or ImageScience (running Snow Leapard).

gruner
  • 1,534
  • 2
  • 15
  • 26

8 Answers8

52

As of June 2012, FastImage which "finds the size or type of an image given its uri by fetching as little as needed" is a good option. It works with local images and those on remote servers.

An IRB example from the readme:

require 'fastimage'

FastImage.size("http://stephensykes.com/images/ss.com_x.gif")
=> [266, 56]  # width, height

Standard array assignment in a script:

require 'fastimage'

size_array = FastImage.size("http://stephensykes.com/images/ss.com_x.gif")

puts "Width: #{size_array[0]}"
puts "Height: #{size_array[1]}"

Or, using multiple assignment in a script:

require 'fastimage'

width, height = FastImage.size("http://stephensykes.com/images/ss.com_x.gif")

puts "Width: #{width}"
puts "Height: #{height}"
Alan W. Smith
  • 21,861
  • 3
  • 64
  • 88
34

You could try these (untested):

http://snippets.dzone.com/posts/show/805

PNG:

IO.read('image.png')[0x10..0x18].unpack('NN')
=> [713, 54]

GIF:

IO.read('image.gif')[6..10].unpack('SS')
=> [130, 50]

BMP:

d = IO.read('image.bmp')[14..28]
d[0] == 40 ? d[4..-1].unpack('LL') : d[4..8].unpack('SS')

JPG:

class JPEG
  attr_reader :width, :height, :bits

  def initialize(file)
    if file.kind_of? IO
      examine(file)
    else
      File.open(file, 'rb') { |io| examine(io) }
    end
  end

private
  def examine(io)
    raise 'malformed JPEG' unless io.getc == 0xFF && io.getc == 0xD8 # SOI

    class << io
      def readint; (readchar << 8) + readchar; end
      def readframe; read(readint - 2); end
      def readsof; [readint, readchar, readint, readint, readchar]; end
      def next
        c = readchar while c != 0xFF
        c = readchar while c == 0xFF
        c
      end
    end

    while marker = io.next
      case marker
        when 0xC0..0xC3, 0xC5..0xC7, 0xC9..0xCB, 0xCD..0xCF # SOF markers
          length, @bits, @height, @width, components = io.readsof
          raise 'malformed JPEG' unless length == 8 + components * 3
        when 0xD9, 0xDA:  break # EOI, SOS
        when 0xFE:        @comment = io.readframe # COM
        when 0xE1:        io.readframe # APP1, contains EXIF tag
        else              io.readframe # ignore frame
      end
    end
  end
end
ChristopheD
  • 100,699
  • 26
  • 154
  • 173
  • 5
    Ooo, sweet, goin' after the bytes directly! Old-school! – the Tin Man Dec 03 '10 at 04:51
  • Ruby 1.9 has broken the JPEG class you cite here; I've submitted an additional answer with my modification of that class to work both under Ruby 1.8.7 and Ruby 1.9. – matt Sep 13 '12 at 19:04
  • @ChristopheD: thanks; I'm just getting into Ruby 1.9 (using 1.9.3) and was previously using the code you'd found, so I enjoyed figuring out why that code had broken and how to fix it! – matt Sep 25 '12 at 03:42
31

There's also a new (July 2011) library that wasn't around at the time the question was originally asked: the Dimensions rubygem (which seems to be authored by the same Sam Stephenson responsible for the byte-manipulation techniques also suggested here.)

Below code sample from project's README

require 'dimensions'

Dimensions.dimensions("upload_bird.jpg")  # => [300, 225]
Dimensions.width("upload_bird.jpg")       # => 300
Dimensions.height("upload_bird.jpg")      # => 225
David Mohundro
  • 10,902
  • 5
  • 37
  • 43
hopper
  • 12,140
  • 7
  • 48
  • 50
  • This gem worked out really well for me. On the surface it doesn't do much but the internal classes are highly flexible and well-written. – bloudermilk Oct 10 '13 at 00:40
15

There's a handy method in the paperclip gem:

>> Paperclip::Geometry.from_file("/path/to/image.jpg")
=> 180x180

This only works if identify is installed. If it isn't, if PHP is installed, you could do something like this:

system(%{php -r '$w = getimagesize("#{path}"); echo("${w[0]}x${w[1]}");'})
# eg returns "200x100" (width x height)
Zubin
  • 8,254
  • 7
  • 42
  • 52
9

I have finally found a nice quick way to get dimensions of an image. You should use MiniMagick.

require 'mini_magick'

image = MiniMagick::Image.open('http://www.thetvdb.com/banners/fanart/original/81189-43.jpg')
assert_equal 1920, image[:width]
assert_equal 1080, image[:height]
ErvalhouS
  • 3,922
  • 1
  • 21
  • 36
Adam Harte
  • 9,820
  • 7
  • 48
  • 82
5

libimage-size is a Ruby library for calculating image sizes for a wide variety of graphical formats. A gem is available, or you can download the source tarball and extract the image_size.rb file.

bta
  • 39,855
  • 5
  • 67
  • 92
3

Here's a version of the JPEG class from ChristopheD's answer that works in both Ruby 1.8.7 and Ruby 1.9. This allows you to get the width and height of a JPEG (.jpg) image file by looking directly at the bits. (Alternatively, just use the Dimensions gem, as suggested in another answer.)

class JPEG
  attr_reader :width, :height, :bits
  def initialize(file)
    if file.kind_of? IO
      examine(file)
    else
      File.open(file, 'rb') { |io| examine(io) }
    end
  end
private
  def examine(io)
    if RUBY_VERSION >= "1.9"
      class << io
        def getc; super.bytes.first; end
        def readchar; super.bytes.first; end
      end
    end
    class << io
      def readint; (readchar << 8) + readchar; end
      def readframe; read(readint - 2); end
      def readsof; [readint, readchar, readint, readint, readchar]; end
      def next
        c = readchar while c != 0xFF
        c = readchar while c == 0xFF
        c
      end
    end
    raise 'malformed JPEG' unless io.getc == 0xFF && io.getc == 0xD8 # SOI
    while marker = io.next
      case marker
        when 0xC0..0xC3, 0xC5..0xC7, 0xC9..0xCB, 0xCD..0xCF # SOF markers
          length, @bits, @height, @width, components = io.readsof
          raise 'malformed JPEG' unless length == 8 + components * 3
        # colons not allowed in 1.9, change to "then"
        when 0xD9, 0xDA then  break # EOI, SOS
        when 0xFE then        @comment = io.readframe # COM
        when 0xE1 then        io.readframe # APP1, contains EXIF tag
        else                  io.readframe # ignore frame
      end
    end
  end
end
matt
  • 447,615
  • 74
  • 748
  • 977
2

For PNGs I got this modified version of ChristopeD's method to work.

File.binread(path, 64)[0x10..0x18].unpack('NN')
Eneroth3
  • 344
  • 1
  • 9