class EM::FastImage

Constants

DefaultTimeout
LocalFileChunkSize

Attributes

bytes_read[R]
size[R]
type[R]

Public Class Methods

new(uri, options={}) click to toggle source
# File lib/em-fastimage.rb, line 163
def initialize(uri, options={})
  @property = options[:type_only] ? :type : :size
  @timeout = options[:timeout] || DefaultTimeout
  @uri = uri

  if uri.respond_to?(:read)
    fetch_using_read(uri)
  else
    begin
      @parsed_uri = Addressable::URI.parse(uri)
    rescue Addressable::URI::InvalidURIError
      # fetch_using_open_uri
    else
      if @parsed_uri.scheme == "http" || @parsed_uri.scheme == "https"
        fetch_using_http
      else
        # fetch_using_open_uri
      end
    end
  end

  uri.rewind if uri.respond_to?(:rewind)

  raise SizeNotFound if options[:raise_on_failure] && @property == :size && !@size

rescue Timeout::Error, SocketError, Errno::ECONNREFUSED, Errno::EHOSTUNREACH, Errno::ECONNRESET,
  ImageFetchFailure, EOFError, Errno::ENOENT
  raise ImageFetchFailure if options[:raise_on_failure]
rescue NoMethodError  # 1.8.7p248 can raise this due to a net/http bug
  raise ImageFetchFailure if options[:raise_on_failure]
rescue UnknownImageType
  raise UnknownImageType if options[:raise_on_failure]
rescue CannotParseImage
  if options[:raise_on_failure]
    if @property == :size
      raise SizeNotFound
    else
      raise ImageFetchFailure
    end
  end

end
size(uri, options={}) click to toggle source

Returns an array containing the width and height of the image. It will return nil if the image could not be fetched, or if the image type was not recognised.

By default there is a timeout of 2 seconds for opening and reading from a remote server. This can be changed by passing a :timeout => number_of_seconds in the options.

If you wish FastImage to raise if it cannot size the image for any reason, then pass :raise_on_failure => true in the options.

FastImage knows about GIF, JPEG, BMP, TIFF, PNG and PSD files.

Example

require 'fastimage'

FastImage.size("http://stephensykes.com/images/ss.com_x.gif")
=> [266, 56]
FastImage.size("http://stephensykes.com/images/pngimage")
=> [16, 16]
FastImage.size("http://farm4.static.flickr.com/3023/3047236863_9dce98b836.jpg")
=> [500, 375]
FastImage.size("http://www-ece.rice.edu/~wakin/images/lena512.bmp")
=> [512, 512]
FastImage.size("test/fixtures/test.jpg")
=> [882, 470]
FastImage.size("http://pennysmalls.com/does_not_exist")
=> nil
FastImage.size("http://pennysmalls.com/does_not_exist", :raise_on_failure=>true)
=> raises FastImage::ImageFetchFailure
FastImage.size("http://stephensykes.com/favicon.ico", :raise_on_failure=>true)
=> raises FastImage::UnknownImageType
FastImage.size("http://stephensykes.com/favicon.ico", :raise_on_failure=>true, :timeout=>0.01)
=> raises FastImage::ImageFetchFailure
FastImage.size("http://stephensykes.com/images/faulty.jpg", :raise_on_failure=>true)
=> raises FastImage::SizeNotFound

Supported options

:timeout

Overrides the default timeout of 2 seconds. Applies both to reading from and opening the http connection.

:raise_on_failure

If set to true causes an exception to be raised if the image size cannot be found for any reason.

# File lib/em-fastimage.rb, line 116
def self.size(uri, options={})
  (o = new(uri, options)).size
  o
end
type(uri, options={}) click to toggle source

Returns an symbol indicating the image type fetched from a uri. It will return nil if the image could not be fetched, or if the image type was not recognised.

By default there is a timeout of 2 seconds for opening and reading from a remote server. This can be changed by passing a :timeout => number_of_seconds in the options.

If you wish FastImage to raise if it cannot find the type of the image for any reason, then pass :raise_on_failure => true in the options.

Example

require 'fastimage'

FastImage.type("http://stephensykes.com/images/ss.com_x.gif")
=> :gif
FastImage.type("http://stephensykes.com/images/pngimage")
=> :png
FastImage.type("http://farm4.static.flickr.com/3023/3047236863_9dce98b836.jpg")
=> :jpeg
FastImage.type("http://www-ece.rice.edu/~wakin/images/lena512.bmp")
=> :bmp
FastImage.type("test/fixtures/test.jpg")
=> :jpeg
FastImage.type("http://pennysmalls.com/does_not_exist")
=> nil
File.open("/some/local/file.gif", "r") {|io| FastImage.type(io)}
=> :gif
FastImage.type("test/fixtures/test.tiff")
=> :tiff
FastImage.type("test/fixtures/test.psd")
=> :psd

Supported options

:timeout

Overrides the default timeout of 2 seconds. Applies both to reading from and opening the http connection.

:raise_on_failure

If set to true causes an exception to be raised if the image type cannot be found for any reason.

# File lib/em-fastimage.rb, line 159
def self.type(uri, options={})
  new(uri, options.merge(:type_only=>true)).type
end

Private Instance Methods

fetch_using_http() click to toggle source
# File lib/em-fastimage.rb, line 208
def fetch_using_http
  @redirect_count = 0

  fetch_using_http_from_parsed_uri
end
fetch_using_http_from_parsed_uri() click to toggle source
# File lib/em-fastimage.rb, line 214
  def fetch_using_http_from_parsed_uri
    setup_http
    @http.get.callback do |res|
#      begin
#        newly_parsed_uri = Addressable::URI.parse(res['Location'])
#        # The new location may be relative - check for that
#        if newly_parsed_uri.scheme != "http" && newly_parsed_uri.scheme != "https"
#          @parsed_uri.path = res['Location']
#        else
#          @parsed_uri = newly_parsed_uri
#        end
#      rescue Addressable::URI::InvalidURIError
#      else
#        fetch_using_http_from_parsed_uri
#        break
#      end
#

      read_fiber = Fiber.new do
        res.response do |str|
          Fiber.yield str
        end
      end
      succeed(parse_packets FiberStream.new(read_fiber))

    end.errback do |err|
      fail(err)
    end
  end
fetch_using_open_uri() click to toggle source
# File lib/em-fastimage.rb, line 292
def fetch_using_open_uri
  open(@uri) do |s|
    fetch_using_read(s)
  end
end
fetch_using_read(readable) click to toggle source
# File lib/em-fastimage.rb, line 268
def fetch_using_read(readable)
  # Pathnames respond to read, but always return the first
  # chunk of the file unlike an IO (even though the
  # docuementation for it refers to IO). Need to supply
  # an offset in this case.
  if readable.is_a?(Pathname)
    read_fiber = Fiber.new do
      offset = 0
      while str = readable.read(LocalFileChunkSize, offset)
        Fiber.yield str
        offset += LocalFileChunkSize
      end
    end
  else
    read_fiber = Fiber.new do
      while str = readable.read(LocalFileChunkSize)
        Fiber.yield str
      end
    end
  end

  succeed(parse_packets FiberStream.new(read_fiber))
end
parse_packets(stream) click to toggle source
# File lib/em-fastimage.rb, line 298
def parse_packets(stream)
  @stream = stream

  begin
    result = send("parse_#{@property}")
    if result
      instance_variable_set("@#{@property}", result)
    else
      raise CannotParseImage
    end
  rescue FiberError
    raise CannotParseImage
  end
end
parse_size() click to toggle source
# File lib/em-fastimage.rb, line 313
def parse_size
  @type = parse_type unless @type
  send("parse_size_for_#{@type}")
end
parse_size_for_bmp() click to toggle source
# File lib/em-fastimage.rb, line 436
def parse_size_for_bmp
  d = @stream.read(32)[14..28]
  header = d.unpack("C")[0]

  result = if header == 40
             d[4..-1].unpack('l<l<')
           else
             d[4..8].unpack('SS')
           end

  # ImageHeight is expressed in pixels. The absolute value is necessary because ImageHeight can be negative
  [result.first, result.last.abs]
end
parse_size_for_gif() click to toggle source
# File lib/em-fastimage.rb, line 386
def parse_size_for_gif
  @stream.read(11)[6..10].unpack('SS')
end
parse_size_for_jpeg() click to toggle source
# File lib/em-fastimage.rb, line 394
def parse_size_for_jpeg
  loop do
    @state = case @state
    when nil
      @stream.read(2)
      :started
    when :started
      @stream.read_byte == 0xFF ? :sof : :started
    when :sof
      case @stream.read_byte
      when 0xe1 # APP1
        skip_chars = @stream.read_int - 2
        data = @stream.read(skip_chars)
        io = StringIO.new(data)
        if io.read(4) == "Exif"
          io.read(2)
          @exif = Exif.new(IOStream.new(io)) rescue nil
        end
        :started
      when 0xe0..0xef
        :skipframe
      when 0xC0..0xC3, 0xC5..0xC7, 0xC9..0xCB, 0xCD..0xCF
        :readsize
      when 0xFF
        :sof
      else
        :skipframe
      end
    when :skipframe
      skip_chars = @stream.read_int - 2
      @stream.read(skip_chars)
      :started
    when :readsize
      s = @stream.read(3)
      height = @stream.read_int
      width = @stream.read_int
      width, height = height, width if @exif && @exif.rotated?
      return [width, height]
    end
  end
end
parse_size_for_png() click to toggle source
# File lib/em-fastimage.rb, line 390
def parse_size_for_png
  @stream.read(25)[16..24].unpack('NN')
end
parse_size_for_psd() click to toggle source
# File lib/em-fastimage.rb, line 520
def parse_size_for_psd
  @stream.read(26).unpack("x14NN").reverse
end
parse_size_for_tiff() click to toggle source
# File lib/em-fastimage.rb, line 511
def parse_size_for_tiff
  exif = Exif.new(@stream)
  if exif.rotated?
    [exif.height, exif.width]
  else
    [exif.width, exif.height]
  end
end
parse_type() click to toggle source
# File lib/em-fastimage.rb, line 367
def parse_type
  case @stream.peek(2)
  when "BM"
    :bmp
  when "GI"
    :gif
  when 0xff.chr + 0xd8.chr
    :jpeg
  when 0x89.chr + "P"
    :png
  when "II", "MM"
    :tiff
  when '8B'
    :psd
  else
    raise UnknownImageType
  end
end
proxy_uri() click to toggle source
# File lib/em-fastimage.rb, line 244
def proxy_uri
  begin
    proxy = ENV['http_proxy'] && ENV['http_proxy'] != "" ? Addressable::URI.parse(ENV['http_proxy']) : nil
  rescue Addressable::URI::InvalidURIError
    proxy = nil
  end
  proxy
end
setup_http() click to toggle source
# File lib/em-fastimage.rb, line 253
def setup_http
  proxy = proxy_uri
  real_uri = "#{@parsed_uri.host}"# :#{@parsed_uri.inferred_port}"
  conn_opts = {}
  if proxy
    conn_opts[:proxy] = { host: proxy.host, port: proxy.port }
  end
  if @parsed_uri.scheme == 'https'
    conn_opts[:ssl] = {} # I don't know how to put the keys in here
  end

  @http = EM::HttpRequest.new(@parsed_uri, conn_opts)
  
end