class FastImage
Constants
- LocalFileChunkSize
- VERSION
Attributes
Public Class Methods
# File lib/fastimage.rb, line 132 def initialize(source, options={}) @source = source @options = { :type_only => false, :raise_on_failure => false, :proxy => nil, :http_header => {} }.merge(options) @property = @options[:type_only] ? :type : :size @type, @state = nil if @source.respond_to?(:read) @path = @source.path if @source.respond_to? :path fetch_using_read else @path = @source fetch_using_file_open end raise SizeNotFound if @options[:raise_on_failure] && @property == :size && !@size rescue ImageFetchFailure, EOFError, Errno::ENOENT, Errno::EISDIR 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 ensure source.rewind if source.respond_to?(:rewind) end
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.
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, ICO, CUR, PNG, PSD, SVG and WEBP files.
Example¶ ↑
require 'fastimage' FastImage.size("example.gif") => [266, 56] FastImage.size("does_not_exist") => nil FastImage.size("does_not_exist", :raise_on_failure => true) => raises FastImage::ImageFetchFailure FastImage.size("example.png", :raise_on_failure => true) => [16, 16] FastImage.size("app.icns", :raise_on_failure=>true) => raises FastImage::UnknownImageType FastImage.size("faulty.jpg", :raise_on_failure=>true) => raises FastImage::SizeNotFound
Supported options¶ ↑
- :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/fastimage.rb, line 92 def self.size(source, options={}) new(source, options).size end
Returns an symbol indicating the image type located at source. It will return nil if the image could not be fetched, or if the image type was not recognised.
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("example.gif") => :gif FastImage.type("image.png") => :png FastImage.type("photo.jpg") => :jpeg FastImage.type("lena512.bmp") => :bmp FastImage.type("does_not_exist") => nil File.open("file.gif", "r") {|io| FastImage.type(io)} => :gif FastImage.type("test/fixtures/test.tiff") => :tiff FastImage.type("test/fixtures/test.psd") => :psd
Supported options¶ ↑
- :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/fastimage.rb, line 128 def self.type(source, options={}) new(source, options.merge(:type_only=>true)).type end
Private Instance Methods
# File lib/fastimage.rb, line 198 def fetch_using_file_open File.open(@source) do |file| fetch_using_read(file) end end
# File lib/fastimage.rb, line 174 def fetch_using_read(readable = @source) # 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 parse_packets FiberStream.new(read_fiber) end
# File lib/fastimage.rb, line 204 def parse_packets(stream) @stream = stream begin result = send("parse_#{@property}") if result # extract exif orientation if it was found if @property == :size && result.size == 3 @orientation = result.pop else @orientation = 1 end instance_variable_set("@#{@property}", result) else raise CannotParseImage end rescue FiberError raise CannotParseImage end end
# File lib/fastimage.rb, line 226 def parse_size @type = parse_type unless @type send("parse_size_for_#{@type}") end
# File lib/fastimage.rb, line 407 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
# File lib/fastimage.rb, line 355 def parse_size_for_gif @stream.read(11)[6..10].unpack('SS') end
# File lib/fastimage.rb, line 348 def parse_size_for_ico icons = @stream.read(6)[4..5].unpack('v').first sizes = icons.times.map { @stream.read(16).unpack('C2').map { |x| x == 0 ? 256 : x } }.sort_by { |w,h| w * h } sizes.last end
# File lib/fastimage.rb, line 363 def parse_size_for_jpeg exif = nil loop do @state = case @state when nil @stream.skip(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) new_exif = Exif.new(IOStream.new(io)) rescue nil exif ||= new_exif # only use the first APP1 segment 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.skip(skip_chars) :started when :readsize @stream.skip(3) height = @stream.read_int width = @stream.read_int width, height = height, width if exif && exif.rotated? return [width, height, exif ? exif.orientation : 1] end end end
# File lib/fastimage.rb, line 359 def parse_size_for_png @stream.read(25)[16..24].unpack('NN') end
# File lib/fastimage.rb, line 538 def parse_size_for_psd @stream.read(26).unpack("x14NN").reverse end
# File lib/fastimage.rb, line 612 def parse_size_for_svg svg = Svg.new(@stream) svg.width_and_height end
# File lib/fastimage.rb, line 529 def parse_size_for_tiff exif = Exif.new(@stream) if exif.rotated? [exif.height, exif.width, exif.orientation] else [exif.width, exif.height, exif.orientation] end end
# File lib/fastimage.rb, line 421 def parse_size_for_webp vp8 = @stream.read(16)[12..15] @stream.read(4).unpack("V") # len case vp8 when "VP8 " parse_size_vp8 when "VP8L" parse_size_vp8l when "VP8X" parse_size_vp8x else nil end end
# File lib/fastimage.rb, line 436 def parse_size_vp8 w, h = @stream.read(10).unpack("@6vv") [w & 0x3fff, h & 0x3fff] end
# File lib/fastimage.rb, line 441 def parse_size_vp8l @stream.skip(1) # 0x2f b1, b2, b3, b4 = @stream.read(4).bytes.to_a [1 + (((b2 & 0x3f) << 8) | b1), 1 + (((b4 & 0xF) << 10) | (b3 << 2) | ((b2 & 0xC0) >> 6))] end
# File lib/fastimage.rb, line 447 def parse_size_vp8x flags = @stream.read(4).unpack("C")[0] b1, b2, b3, b4, b5, b6 = @stream.read(6).unpack("CCCCCC") width, height = 1 + b1 + (b2 << 8) + (b3 << 16), 1 + b4 + (b5 << 8) + (b6 << 16) if flags & 8 > 0 # exif # parse exif for orientation # TODO: find or create test images for this end return [width, height] end
# File lib/fastimage.rb, line 307 def parse_type parsed_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" case @stream.peek(11)[8..10] when "APC", "CR\002" nil # do not recognise CRW or CR2 as tiff else :tiff end when '8B' :psd when "\0\0" # ico has either a 1 (for ico format) or 2 (for cursor) at offset 3 case @stream.peek(3).bytes.to_a.last when 1 then :ico when 2 then :cur end when "RI" :webp if @stream.peek(12)[8..11] == "WEBP" when '<s', /<[?!]/ # Peek 10 more chars each time, and if end of file is reached just raise # unknown. We assume the <svg tag cannot be within 10 chars of the end of # the file, and is within the first 250 chars. begin :svg if (1..25).detect {|n| @stream.peek(10 * n).include?("<svg")} rescue FiberError, CannotParseImage nil end end parsed_type or raise UnknownImageType end