mirror of
https://github.com/sdsykes/fastimage.git
synced 2025-09-20 00:01:59 -04:00
Add check for animated GIFs
This commit is contained in:
parent
bad902712c
commit
df31108b00
115
lib/fastimage.rb
115
lib/fastimage.rb
@ -70,7 +70,7 @@ if RUBY_VERSION < "2.2"
|
||||
end
|
||||
|
||||
class FastImage
|
||||
attr_reader :size, :type, :content_length, :orientation
|
||||
attr_reader :size, :type, :content_length, :orientation, :animated
|
||||
|
||||
attr_reader :bytes_read
|
||||
|
||||
@ -179,6 +179,34 @@ class FastImage
|
||||
new(uri, options.merge(:type_only=>true)).type
|
||||
end
|
||||
|
||||
# Returns a boolean value indicating the image is animated.
|
||||
# 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.animated?("test/fixtures/test.gif")
|
||||
# => false
|
||||
# FastImage.animated?("test/fixtures/animated.gif")
|
||||
# => true
|
||||
#
|
||||
# === 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.
|
||||
#
|
||||
def self.animated?(uri, options={})
|
||||
new(uri, options.merge(:animated_only=>true)).animated
|
||||
end
|
||||
|
||||
def initialize(uri, options={})
|
||||
@uri = uri
|
||||
@options = {
|
||||
@ -189,7 +217,13 @@ class FastImage
|
||||
:http_header => {}
|
||||
}.merge(options)
|
||||
|
||||
@property = @options[:type_only] ? :type : :size
|
||||
@property = if @options[:animated_only]
|
||||
:animated
|
||||
elsif @options[:type_only]
|
||||
:type
|
||||
else
|
||||
:size
|
||||
end
|
||||
|
||||
@type, @state = nil
|
||||
|
||||
@ -369,7 +403,7 @@ class FastImage
|
||||
|
||||
begin
|
||||
result = send("parse_#{@property}")
|
||||
if result
|
||||
if result != nil
|
||||
# extract exif orientation if it was found
|
||||
if @property == :size && result.size == 3
|
||||
@orientation = result.pop
|
||||
@ -391,6 +425,13 @@ class FastImage
|
||||
send("parse_size_for_#{@type}")
|
||||
end
|
||||
|
||||
def parse_animated
|
||||
@type = parse_type unless @type
|
||||
return nil if @type == nil
|
||||
|
||||
@type == :gif ? send("parse_animated_for_#{@type}") : false
|
||||
end
|
||||
|
||||
def fetch_using_base64(uri)
|
||||
data = uri.split(',')[1]
|
||||
decoded = Base64.decode64(data)
|
||||
@ -524,8 +565,69 @@ class FastImage
|
||||
end
|
||||
alias_method :parse_size_for_cur, :parse_size_for_ico
|
||||
|
||||
class Gif # :nodoc:
|
||||
def initialize(stream)
|
||||
@stream = stream
|
||||
end
|
||||
|
||||
def width_and_height
|
||||
@stream.read(11)[6..10].unpack('SS')
|
||||
end
|
||||
|
||||
# Checks if a delay between frames exists and if it does, then the GIFs is
|
||||
# animated
|
||||
def animated?
|
||||
delay = 0
|
||||
|
||||
@stream.read(10) # "GIF" + version (3) + width (2) + height (2)
|
||||
|
||||
fields = @stream.read(3).unpack("C")[0] # fields (1) + bg color (1) + pixel ratio (1)
|
||||
|
||||
# Skip Global Color Table if it exists
|
||||
if fields & 0x80
|
||||
# 2 * (depth + 1) colors, each occupying 3 bytes (RGB)
|
||||
@stream.skip(3 * 2 ** ((fields & 0x7) + 1))
|
||||
end
|
||||
|
||||
loop do
|
||||
block_type = @stream.read(1).unpack("C")[0]
|
||||
if block_type == 0x21
|
||||
extension_type = @stream.read(1).unpack("C")[0]
|
||||
size = @stream.read(1).unpack("C")[0]
|
||||
if extension_type == 0xF9
|
||||
delay = @stream.read(4).unpack("CSC")[1] # fields (1) + delay (2) + transparent index (1)
|
||||
break
|
||||
elsif extension_type == 0xFF
|
||||
@stream.skip(size) # application ID (8) + version (3)
|
||||
else
|
||||
return # unrecognized extension
|
||||
end
|
||||
skip_sub_blocks
|
||||
else
|
||||
return # unrecognized block
|
||||
end
|
||||
end
|
||||
|
||||
delay > 0
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def skip_sub_blocks
|
||||
loop do
|
||||
size = @stream.read(1).unpack("C")[0]
|
||||
if size == 0
|
||||
break
|
||||
else
|
||||
@stream.skip(size)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def parse_size_for_gif
|
||||
@stream.read(11)[6..10].unpack('SS')
|
||||
gif = Gif.new(@stream)
|
||||
gif.width_and_height
|
||||
end
|
||||
|
||||
def parse_size_for_png
|
||||
@ -785,4 +887,9 @@ class FastImage
|
||||
svg = Svg.new(@stream)
|
||||
svg.width_and_height
|
||||
end
|
||||
|
||||
def parse_animated_for_gif
|
||||
gif = Gif.new(@stream)
|
||||
gif.animated?
|
||||
end
|
||||
end
|
||||
|
BIN
test/fixtures/animated.gif
vendored
Normal file
BIN
test/fixtures/animated.gif
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 978 KiB |
@ -14,6 +14,7 @@ GoodFixtures = {
|
||||
"test_coreheader.bmp"=>[:bmp, [40, 27]],
|
||||
"test_v5header.bmp"=>[:bmp, [40, 27]],
|
||||
"test.gif"=>[:gif, [17, 32]],
|
||||
"animated.gif"=>[:gif, [400, 400]],
|
||||
"test.jpg"=>[:jpeg, [882, 470]],
|
||||
"test.png"=>[:png, [30, 20]],
|
||||
"test2.jpg"=>[:jpeg, [250, 188]],
|
||||
@ -102,6 +103,12 @@ class FastImageTest < Test::Unit::TestCase
|
||||
end
|
||||
end
|
||||
|
||||
def test_should_report_animated_correctly
|
||||
assert_equal nil, FastImage.animated?(TestUrl + "test.png")
|
||||
assert_equal false, FastImage.animated?(TestUrl + "test.gif")
|
||||
assert_equal true, FastImage.animated?(TestUrl + "animated.gif")
|
||||
end
|
||||
|
||||
def test_should_return_nil_on_fetch_failure
|
||||
assert_nil FastImage.size(TestUrl + "does_not_exist")
|
||||
end
|
||||
|
Loading…
x
Reference in New Issue
Block a user