gif-info 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.document +5 -0
- data/Gemfile +14 -0
- data/Gemfile.lock +33 -0
- data/LICENSE.txt +20 -0
- data/README.md +52 -0
- data/Rakefile +37 -0
- data/VERSION +1 -0
- data/ajax-loader-0.gif +0 -0
- data/ajax-loader.gif +0 -0
- data/bin/gif-dump +41 -0
- data/bin/gif-info +44 -0
- data/gif-info.gemspec +86 -0
- data/lib/gif-info.rb +407 -0
- data/lib/gif-info/block.rb +84 -0
- data/lib/gif-info/blocks/application-extension.rb +38 -0
- data/lib/gif-info/blocks/comment-extension.rb +35 -0
- data/lib/gif-info/blocks/global-color-table.rb +25 -0
- data/lib/gif-info/blocks/graphics-control-extension.rb +43 -0
- data/lib/gif-info/blocks/header-block.rb +35 -0
- data/lib/gif-info/blocks/image-data.rb +35 -0
- data/lib/gif-info/blocks/image-descriptor.rb +45 -0
- data/lib/gif-info/blocks/local-color-table.rb +25 -0
- data/lib/gif-info/blocks/logical-screen-descriptor.rb +43 -0
- data/lib/gif-info/blocks/plain-text-extension.rb +77 -0
- data/lib/gif-info/blocks/trailer.rb +32 -0
- data/lib/gif-info/body.rb +105 -0
- data/lib/gif-info/color-table.rb +32 -0
- data/lib/gif-info/data-block.rb +56 -0
- data/lib/gif-info/dynamic-block.rb +66 -0
- data/lib/gif-info/fixed-block.rb +82 -0
- data/lib/gif-info/raw-block.rb +78 -0
- data/test +112 -0
- metadata +157 -0
data/.document
ADDED
data/Gemfile
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
source "http://rubygems.org"
|
2
|
+
# Add dependencies required to use your gem here.
|
3
|
+
# Example:
|
4
|
+
gem "struct-fx", ">= 0.1.1"
|
5
|
+
gem "abstract", ">= 1.0.0"
|
6
|
+
gem "frozen-objects", ">= 0.2.0"
|
7
|
+
|
8
|
+
# Add dependencies to develop your gem here.
|
9
|
+
# Include everything needed to run rake, tests, features, etc.
|
10
|
+
group :development do
|
11
|
+
gem "bundler", "~> 1.0.0"
|
12
|
+
gem "jeweler", "~> 1.5.2"
|
13
|
+
gem "riot", ">= 0.12.1"
|
14
|
+
end
|
data/Gemfile.lock
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
GEM
|
2
|
+
remote: http://rubygems.org/
|
3
|
+
specs:
|
4
|
+
abstract (1.0.0)
|
5
|
+
bit-packer (0.1.0)
|
6
|
+
lookup-hash (>= 0.2.0)
|
7
|
+
frozen-objects (0.2.0)
|
8
|
+
git (1.2.5)
|
9
|
+
hash-utils (0.11.0)
|
10
|
+
jeweler (1.5.2)
|
11
|
+
bundler (~> 1.0.0)
|
12
|
+
git (>= 1.2.5)
|
13
|
+
rake
|
14
|
+
lookup-hash (0.2.0)
|
15
|
+
hash-utils (>= 0.11.0)
|
16
|
+
rake (0.8.7)
|
17
|
+
riot (0.12.2)
|
18
|
+
rr
|
19
|
+
rr (1.0.2)
|
20
|
+
struct-fx (0.1.1)
|
21
|
+
bit-packer (>= 0.1.0)
|
22
|
+
frozen-objects (>= 0.2.0)
|
23
|
+
|
24
|
+
PLATFORMS
|
25
|
+
ruby
|
26
|
+
|
27
|
+
DEPENDENCIES
|
28
|
+
abstract (>= 1.0.0)
|
29
|
+
bundler (~> 1.0.0)
|
30
|
+
frozen-objects (>= 0.2.0)
|
31
|
+
jeweler (~> 1.5.2)
|
32
|
+
riot (>= 0.12.1)
|
33
|
+
struct-fx (>= 0.1.1)
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2011 Martin Kozák
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,52 @@
|
|
1
|
+
Gif Info
|
2
|
+
========
|
3
|
+
|
4
|
+
**gif-info** is analyzer of the [GIF image format][1]. It performs complete
|
5
|
+
analysis of internal GIF block structure and streams it as an "object
|
6
|
+
stream" with metainformations of each block. Also can *interpret* internal
|
7
|
+
structure by providing the simple object-like interface to base image
|
8
|
+
file informations. Works above all seekable IO streams, so allows
|
9
|
+
processing of the big files too. Doesn't perform [LZW][2] decompressing,
|
10
|
+
returns raw data for both color tables and images.
|
11
|
+
|
12
|
+
Two different approaches are available: **sequential** and **static**.
|
13
|
+
First one yields "stream" of objects which are equivalent to functional
|
14
|
+
blocks in the GIF file and which contain low-level GIF data. It's
|
15
|
+
equivalent of for example [SAX][3] parser although, of sure, less
|
16
|
+
complex. The other one provides classical single object-like access to
|
17
|
+
interpreted file informations.
|
18
|
+
|
19
|
+
Examples of both are available in the `bin` directory. `git-info`
|
20
|
+
command writes out content of the static information object, `git-dump`
|
21
|
+
dumps content of low level blocks stream.
|
22
|
+
|
23
|
+
Modifiing the file and writing changes back is possible (see [StructFx][5]
|
24
|
+
library documentation). It isn't implemented directly by this library,
|
25
|
+
but should be easy to implement it if you will need it -- with exception
|
26
|
+
of data blocks as comments or image data -- it's necessary split them
|
27
|
+
to blocks manually in your writing routine. Other structures provided
|
28
|
+
by the library contains binary serialization routines implicitly.
|
29
|
+
|
30
|
+
Contributing
|
31
|
+
------------
|
32
|
+
|
33
|
+
1. Fork it.
|
34
|
+
2. Create a branch (`git checkout -b 20101220-my-change`).
|
35
|
+
3. Commit your changes (`git commit -am "Added something"`).
|
36
|
+
4. Push to the branch (`git push origin 20101220-my-change`).
|
37
|
+
5. Create an [Issue][6] with a link to your branch.
|
38
|
+
6. Enjoy a refreshing Diet Coke and wait.
|
39
|
+
|
40
|
+
Copyright
|
41
|
+
---------
|
42
|
+
|
43
|
+
Copyright © 2011 [Martin Kozák][7]. See `LICENSE.txt` for
|
44
|
+
further details.
|
45
|
+
|
46
|
+
[1]: http://www.matthewflickinger.com/lab/whatsinagif/
|
47
|
+
[2]: http://en.wikipedia.org/wiki/LZW
|
48
|
+
[3]: http://en.wikipedia.org/wiki/Simple_API_for_XML
|
49
|
+
[4]: http://en.wikipedia.org/wiki/Document_Object_Model
|
50
|
+
[5]: https://github.com/martinkozak/struct-fx
|
51
|
+
[6]: http://github.com/martinkozak/gif-info/issues
|
52
|
+
[7]: http://www.martinkozak.net/
|
data/Rakefile
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require 'rubygems'
|
3
|
+
require 'bundler'
|
4
|
+
begin
|
5
|
+
Bundler.setup(:default, :development)
|
6
|
+
rescue Bundler::BundlerError => e
|
7
|
+
$stderr.puts e.message
|
8
|
+
$stderr.puts "Run `bundle install` to install missing gems"
|
9
|
+
exit e.status_code
|
10
|
+
end
|
11
|
+
require 'rake'
|
12
|
+
|
13
|
+
require 'jeweler'
|
14
|
+
Jeweler::Tasks.new do |gem|
|
15
|
+
# gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
|
16
|
+
gem.name = "gif-info"
|
17
|
+
gem.homepage = "http://github.com/martinkozak/gif-info"
|
18
|
+
gem.license = "MIT"
|
19
|
+
gem.summary = 'Pure Ruby analyzer of the GIF image format. Performs complete analysis of internal GIF block structure and streams it as an objects stream with metainformations of each block. Also can interpret internal structure by providing the simple object-like interface to base image file informations. Works above seekable IO streams, so allows processing of the big files too. Doesn\'t perform LZW decompressing, returns raw data for both color tables and images.'
|
20
|
+
gem.email = "martinkozak@martinkozak.net"
|
21
|
+
gem.authors = ["Martin Kozák"]
|
22
|
+
# Include your dependencies below. Runtime dependencies are required when using your gem,
|
23
|
+
# and development dependencies are only needed for development (ie running rake tasks, tests, etc)
|
24
|
+
# gem.add_runtime_dependency 'jabber4r', '> 0.1'
|
25
|
+
# gem.add_development_dependency 'rspec', '> 1.2.3'
|
26
|
+
end
|
27
|
+
Jeweler::RubygemsDotOrgTasks.new
|
28
|
+
|
29
|
+
require 'rake/rdoctask'
|
30
|
+
Rake::RDocTask.new do |rdoc|
|
31
|
+
version = File.exist?('VERSION') ? File.read('VERSION') : ""
|
32
|
+
|
33
|
+
rdoc.rdoc_dir = 'rdoc'
|
34
|
+
rdoc.title = "hash-utils #{version}"
|
35
|
+
rdoc.rdoc_files.include('README*')
|
36
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
37
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.1.0
|
data/ajax-loader-0.gif
ADDED
Binary file
|
data/ajax-loader.gif
ADDED
Binary file
|
data/bin/gif-dump
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# encoding: utf-8
|
3
|
+
# (c) 2011 Martin Kozák (martinkozak@martinkozak.net)f
|
4
|
+
|
5
|
+
if ARGV[0].nil? or (ARGV[0] == "--help")
|
6
|
+
puts "Usage: gif-dump [--help|--version|FILE]"
|
7
|
+
puts "\t--help display this help and exit"
|
8
|
+
puts "\t--version output version information and exit"
|
9
|
+
|
10
|
+
exit
|
11
|
+
elsif ARGV[0] == "--version"
|
12
|
+
puts "gif-info 0.1.0"
|
13
|
+
puts "Copyright (C) 2011 Martin Kozák (martinkozak@martinkozak.net)"
|
14
|
+
puts "Dumps all structure informations from given GIF file."
|
15
|
+
exit
|
16
|
+
end
|
17
|
+
|
18
|
+
###
|
19
|
+
|
20
|
+
require "gif-info"
|
21
|
+
|
22
|
+
def __render(struct, level)
|
23
|
+
struct.members.each do |item|
|
24
|
+
if item == :packed
|
25
|
+
puts (" " * level) << "packed"
|
26
|
+
__render(struct[item].data, level + 1)
|
27
|
+
else
|
28
|
+
puts (" " * level) << item.to_s << " " << struct[item].to_s
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
# Analyzes
|
34
|
+
File::open(ARGV[0], "rb") do |io|
|
35
|
+
GifInfo::parse(io) do |block|
|
36
|
+
if block.kind_of? GifInfo::FixedBlock
|
37
|
+
puts "\n" << block.class.name
|
38
|
+
__render(block.header.data, 1)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
data/bin/gif-info
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# encoding: utf-8
|
3
|
+
# (c) 2011 Martin Kozák (martinkozak@martinkozak.net)f
|
4
|
+
|
5
|
+
if ARGV[0].nil? or (ARGV[0] == "--help")
|
6
|
+
puts "Usage: gif-info [--help|--version|FILE]"
|
7
|
+
puts "\t--help display this help and exit"
|
8
|
+
puts "\t--version output version information and exit"
|
9
|
+
|
10
|
+
exit
|
11
|
+
elsif ARGV[0] == "--version"
|
12
|
+
puts "gif-info 0.1.0"
|
13
|
+
puts "Copyright (C) 2011 Martin Kozák (martinkozak@martinkozak.net)"
|
14
|
+
puts "Writes out informations about given GIF file."
|
15
|
+
exit
|
16
|
+
end
|
17
|
+
|
18
|
+
###
|
19
|
+
|
20
|
+
require "gif-info"
|
21
|
+
|
22
|
+
# Analyzes
|
23
|
+
f = GifInfo::analyze_file(ARGV[0])
|
24
|
+
|
25
|
+
# Writes out
|
26
|
+
puts "type " << f.type.to_s
|
27
|
+
puts "version " << f.version.to_s
|
28
|
+
puts "images_count " << f.images_count.to_s
|
29
|
+
puts "width " << f.width.to_s
|
30
|
+
puts "height " << f.height.to_s
|
31
|
+
puts "color_resolution " << f.color_resolution.to_s
|
32
|
+
|
33
|
+
if not f.pixel_aspect_ratio.nil?
|
34
|
+
puts "pixel_aspect_ratio " << f.pixel_aspect_ratio.to_s
|
35
|
+
end
|
36
|
+
|
37
|
+
puts "transparent " << f.transparent?.to_s
|
38
|
+
puts "interlaced " << f.interlaced?.to_s
|
39
|
+
puts "animated " << f.animated?.to_s
|
40
|
+
puts "cyclic " << f.cyclic?.to_s
|
41
|
+
|
42
|
+
if f.comments.length > 0
|
43
|
+
puts "comment " << f.comments.first
|
44
|
+
end
|
data/gif-info.gemspec
ADDED
@@ -0,0 +1,86 @@
|
|
1
|
+
# Generated by jeweler
|
2
|
+
# DO NOT EDIT THIS FILE DIRECTLY
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
|
4
|
+
# -*- encoding: utf-8 -*-
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = %q{gif-info}
|
8
|
+
s.version = "0.1.0"
|
9
|
+
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
+
s.authors = ["Martin Kozák"]
|
12
|
+
s.date = %q{2011-03-10}
|
13
|
+
s.email = %q{martinkozak@martinkozak.net}
|
14
|
+
s.executables = ["gif-dump", "gif-info"]
|
15
|
+
s.extra_rdoc_files = [
|
16
|
+
"LICENSE.txt",
|
17
|
+
"README.md"
|
18
|
+
]
|
19
|
+
s.files = [
|
20
|
+
".document",
|
21
|
+
"Gemfile",
|
22
|
+
"Gemfile.lock",
|
23
|
+
"LICENSE.txt",
|
24
|
+
"README.md",
|
25
|
+
"Rakefile",
|
26
|
+
"VERSION",
|
27
|
+
"ajax-loader-0.gif",
|
28
|
+
"ajax-loader.gif",
|
29
|
+
"bin/gif-dump",
|
30
|
+
"bin/gif-info",
|
31
|
+
"gif-info.gemspec",
|
32
|
+
"lib/gif-info.rb",
|
33
|
+
"lib/gif-info/block.rb",
|
34
|
+
"lib/gif-info/blocks/application-extension.rb",
|
35
|
+
"lib/gif-info/blocks/comment-extension.rb",
|
36
|
+
"lib/gif-info/blocks/global-color-table.rb",
|
37
|
+
"lib/gif-info/blocks/graphics-control-extension.rb",
|
38
|
+
"lib/gif-info/blocks/header-block.rb",
|
39
|
+
"lib/gif-info/blocks/image-data.rb",
|
40
|
+
"lib/gif-info/blocks/image-descriptor.rb",
|
41
|
+
"lib/gif-info/blocks/local-color-table.rb",
|
42
|
+
"lib/gif-info/blocks/logical-screen-descriptor.rb",
|
43
|
+
"lib/gif-info/blocks/plain-text-extension.rb",
|
44
|
+
"lib/gif-info/blocks/trailer.rb",
|
45
|
+
"lib/gif-info/body.rb",
|
46
|
+
"lib/gif-info/color-table.rb",
|
47
|
+
"lib/gif-info/data-block.rb",
|
48
|
+
"lib/gif-info/dynamic-block.rb",
|
49
|
+
"lib/gif-info/fixed-block.rb",
|
50
|
+
"lib/gif-info/raw-block.rb",
|
51
|
+
"test"
|
52
|
+
]
|
53
|
+
s.homepage = %q{http://github.com/martinkozak/gif-info}
|
54
|
+
s.licenses = ["MIT"]
|
55
|
+
s.require_paths = ["lib"]
|
56
|
+
s.rubygems_version = %q{1.6.2}
|
57
|
+
s.summary = %q{Pure Ruby analyzer of the GIF image format. Performs complete analysis of internal GIF block structure and streams it as an objects stream with metainformations of each block. Also can interpret internal structure by providing the simple object-like interface to base image file informations. Works above seekable IO streams, so allows processing of the big files too. Doesn't perform LZW decompressing, returns raw data for both color tables and images.}
|
58
|
+
|
59
|
+
if s.respond_to? :specification_version then
|
60
|
+
s.specification_version = 3
|
61
|
+
|
62
|
+
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
63
|
+
s.add_runtime_dependency(%q<struct-fx>, [">= 0.1.1"])
|
64
|
+
s.add_runtime_dependency(%q<abstract>, [">= 1.0.0"])
|
65
|
+
s.add_runtime_dependency(%q<frozen-objects>, [">= 0.2.0"])
|
66
|
+
s.add_development_dependency(%q<bundler>, ["~> 1.0.0"])
|
67
|
+
s.add_development_dependency(%q<jeweler>, ["~> 1.5.2"])
|
68
|
+
s.add_development_dependency(%q<riot>, [">= 0.12.1"])
|
69
|
+
else
|
70
|
+
s.add_dependency(%q<struct-fx>, [">= 0.1.1"])
|
71
|
+
s.add_dependency(%q<abstract>, [">= 1.0.0"])
|
72
|
+
s.add_dependency(%q<frozen-objects>, [">= 0.2.0"])
|
73
|
+
s.add_dependency(%q<bundler>, ["~> 1.0.0"])
|
74
|
+
s.add_dependency(%q<jeweler>, ["~> 1.5.2"])
|
75
|
+
s.add_dependency(%q<riot>, [">= 0.12.1"])
|
76
|
+
end
|
77
|
+
else
|
78
|
+
s.add_dependency(%q<struct-fx>, [">= 0.1.1"])
|
79
|
+
s.add_dependency(%q<abstract>, [">= 1.0.0"])
|
80
|
+
s.add_dependency(%q<frozen-objects>, [">= 0.2.0"])
|
81
|
+
s.add_dependency(%q<bundler>, ["~> 1.0.0"])
|
82
|
+
s.add_dependency(%q<jeweler>, ["~> 1.5.2"])
|
83
|
+
s.add_dependency(%q<riot>, [">= 0.12.1"])
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
data/lib/gif-info.rb
ADDED
@@ -0,0 +1,407 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
# (c) 2011 Martin Kozák (martinkozak@martinkozak.net)
|
3
|
+
|
4
|
+
require "stringio"
|
5
|
+
require "struct-fx"
|
6
|
+
|
7
|
+
require "gif-info/blocks/header-block"
|
8
|
+
require "gif-info/blocks/logical-screen-descriptor"
|
9
|
+
require "gif-info/blocks/global-color-table"
|
10
|
+
require "gif-info/blocks/local-color-table"
|
11
|
+
require "gif-info/blocks/plain-text-extension"
|
12
|
+
require "gif-info/blocks/comment-extension"
|
13
|
+
require "gif-info/blocks/application-extension"
|
14
|
+
require "gif-info/blocks/graphics-control-extension"
|
15
|
+
require "gif-info/blocks/image-descriptor"
|
16
|
+
require "gif-info/blocks/image-data"
|
17
|
+
require "gif-info/blocks/trailer"
|
18
|
+
|
19
|
+
##
|
20
|
+
# Primary GifInfo module.
|
21
|
+
#
|
22
|
+
|
23
|
+
class GifInfo
|
24
|
+
|
25
|
+
############################################################
|
26
|
+
## STATIC PART
|
27
|
+
############################################################
|
28
|
+
|
29
|
+
##
|
30
|
+
# Defines structure of the block head.
|
31
|
+
#
|
32
|
+
|
33
|
+
BLOCK_HEAD = StructFx::new do
|
34
|
+
uint8 :block_introducer
|
35
|
+
uint8 :extension_label
|
36
|
+
end
|
37
|
+
|
38
|
+
##
|
39
|
+
# Parses the GIF file.
|
40
|
+
#
|
41
|
+
# @param [IO] io IO object where offset 0 is start of the GIF file
|
42
|
+
# @yield [Blocks::ApplicationExtension]
|
43
|
+
# @yield [Blocks::CommentExtension]
|
44
|
+
# @yield [Blocks::GlobalColorTable]
|
45
|
+
# @yield [Blocks::GraphicsControlExtension]
|
46
|
+
# @yield [Blocks::HeaderBlock]
|
47
|
+
# @yield [Blocks::ImageData]
|
48
|
+
# @yield [Blocks::ImageDescriptor]
|
49
|
+
# @yield [Blocks::LocalColorTable]
|
50
|
+
# @yield [Blocks::LogicalScreenDescriptor]
|
51
|
+
# @yield [Blocks::PlainTextExtension]
|
52
|
+
# @yield [Blocks::Trailer]
|
53
|
+
# @see http://www.matthewflickinger.com/lab/whatsinagif/
|
54
|
+
#
|
55
|
+
|
56
|
+
def self.parse(io)
|
57
|
+
__parse(io) do |block|
|
58
|
+
|
59
|
+
# Saves position of beginning of the next block set by
|
60
|
+
# inner parser
|
61
|
+
position = io.pos
|
62
|
+
|
63
|
+
# Yields block and allows application seek trough the file
|
64
|
+
# according to its needs
|
65
|
+
yield block
|
66
|
+
|
67
|
+
# Seeks to position at beginning of the next block
|
68
|
+
io.seek(position)
|
69
|
+
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
|
74
|
+
private
|
75
|
+
|
76
|
+
##
|
77
|
+
# Just performs parsing of the GIF file.
|
78
|
+
#
|
79
|
+
|
80
|
+
def self.__parse(io)
|
81
|
+
if io.kind_of? String
|
82
|
+
io = StringIO::new(io)
|
83
|
+
end
|
84
|
+
|
85
|
+
# Header Block
|
86
|
+
yield Blocks::HeaderBlock::new(io)
|
87
|
+
|
88
|
+
# Logical Screen Descriptor
|
89
|
+
yield lsd = Blocks::LogicalScreenDescriptor::new(io)
|
90
|
+
|
91
|
+
# Global Color Table
|
92
|
+
packed = lsd.header.data.packed.data
|
93
|
+
if packed.global_color_table
|
94
|
+
size = packed.global_color_table_size
|
95
|
+
yield Blocks::GlobalColorTable::new(io, size)
|
96
|
+
end
|
97
|
+
|
98
|
+
# Extensions
|
99
|
+
reader = self::BLOCK_HEAD
|
100
|
+
length = reader.length
|
101
|
+
|
102
|
+
loop do
|
103
|
+
reader << (io.read(length) << "\0") # adds zero character for cases, trailer is last byte of the file
|
104
|
+
header = self::BLOCK_HEAD.data
|
105
|
+
io.seek(-length, IO::SEEK_CUR)
|
106
|
+
|
107
|
+
case header.block_introducer
|
108
|
+
when 0x21 # extension
|
109
|
+
case header.extension_label
|
110
|
+
when 0xF9 # graphics control
|
111
|
+
yield Blocks::GraphicsControlExtension::new(io)
|
112
|
+
when 0xFF # application
|
113
|
+
yield Blocks::ApplicationExtension::new(io)
|
114
|
+
when 0xFE # comment
|
115
|
+
yield Blocks::CommentExtension::new(io)
|
116
|
+
when 0x01 # plain text
|
117
|
+
yield Blocks::PlainTextExtension::new(io)
|
118
|
+
else
|
119
|
+
raise Exception::new("Invalid format: 0x01, 0xF9, 0xFE or 0xFF expected, but 0x" << header.block_introducer.to_s(16).upcase << " found at position " << (io.pos + 1).to_s << ".")
|
120
|
+
end
|
121
|
+
|
122
|
+
when 0x2C # image descriptor
|
123
|
+
|
124
|
+
# Image Descriptor
|
125
|
+
yield desc = Blocks::ImageDescriptor::new(io)
|
126
|
+
|
127
|
+
# Local Color Table
|
128
|
+
if desc.header.data.packed.data.local_color_table
|
129
|
+
size = lsd.header.data.packed.data.local_color_table_size
|
130
|
+
yield Blocks::LocalColorTable::new(io, size)
|
131
|
+
end
|
132
|
+
|
133
|
+
# Image Data
|
134
|
+
yield Blocks::ImageData::new(io)
|
135
|
+
|
136
|
+
when 0x3B # trailer
|
137
|
+
yield Blocks::Trailer::new(io)
|
138
|
+
break
|
139
|
+
|
140
|
+
else
|
141
|
+
raise Exception::new("Invalid format: 0x21, 0x2C or 0x3B expected, but 0x" << header.block_introducer.to_s(16).upcase << " found at position " << io.pos.to_s << ".")
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
|
147
|
+
public
|
148
|
+
|
149
|
+
##
|
150
|
+
# Considers stream, performs analyzing and returns info object
|
151
|
+
# as result.
|
152
|
+
#
|
153
|
+
# @param [IO] io IO object with GIF data
|
154
|
+
# @return [GifInfo] info object
|
155
|
+
#
|
156
|
+
|
157
|
+
def self.analyze_io(io)
|
158
|
+
self::new(io)
|
159
|
+
end
|
160
|
+
|
161
|
+
##
|
162
|
+
# Analyzes string with GIF data and returns info object.
|
163
|
+
#
|
164
|
+
|
165
|
+
def self.analyze_string(string)
|
166
|
+
self::analyze_io(StringIO::new(string))
|
167
|
+
end
|
168
|
+
|
169
|
+
##
|
170
|
+
# Analyzes file and returns info object back.
|
171
|
+
#
|
172
|
+
# @param [String] filename file path
|
173
|
+
# @return [GifInfo] info object
|
174
|
+
#
|
175
|
+
|
176
|
+
def self.analyze_file(filename)
|
177
|
+
File::open(filename, "rb") do |io|
|
178
|
+
self.analyze_io(io)
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
|
183
|
+
############################################################
|
184
|
+
## INSTANCE PART
|
185
|
+
############################################################
|
186
|
+
|
187
|
+
##
|
188
|
+
# Type of the file. If it isn't +:GIF+, it means, file isn't
|
189
|
+
# valid GIF gile.
|
190
|
+
#
|
191
|
+
# @return [Symbol] always +:GIF+
|
192
|
+
#
|
193
|
+
|
194
|
+
attr_reader :type
|
195
|
+
@type
|
196
|
+
|
197
|
+
##
|
198
|
+
# Version of the GIF standard. Can be +:87a+ or +?89a+.
|
199
|
+
# @return [Symbol] GIF standard version
|
200
|
+
#
|
201
|
+
|
202
|
+
attr_reader :version
|
203
|
+
@version
|
204
|
+
|
205
|
+
##
|
206
|
+
# Width of the image. (Width of the canvas.)
|
207
|
+
# @return [Integer] width
|
208
|
+
#
|
209
|
+
|
210
|
+
attr_reader :width
|
211
|
+
@width
|
212
|
+
|
213
|
+
##
|
214
|
+
# Height of the image. (Height of the canvas.)
|
215
|
+
# @return [Integer] height
|
216
|
+
#
|
217
|
+
|
218
|
+
attr_reader :height
|
219
|
+
@height
|
220
|
+
|
221
|
+
##
|
222
|
+
# Pixel aspect ratio. See standard what it is.
|
223
|
+
# @return [Integer] pixel aspect ration
|
224
|
+
#
|
225
|
+
|
226
|
+
attr_reader :pixel_aspect_ratio
|
227
|
+
@pixel_aspect_ratio
|
228
|
+
|
229
|
+
##
|
230
|
+
# Color resolution in number of colors which can be encoded
|
231
|
+
# according to global table. If local tables found, it's +nil+.
|
232
|
+
#
|
233
|
+
# @return [Integer] color count
|
234
|
+
#
|
235
|
+
|
236
|
+
attr_reader :color_resolution
|
237
|
+
@color_resolution
|
238
|
+
|
239
|
+
##
|
240
|
+
# Comments inside the file. GIF file can theorethically contain
|
241
|
+
# unlimtited number of the comment blocks, so it returns array.
|
242
|
+
#
|
243
|
+
# @return [Array] array with comments
|
244
|
+
#
|
245
|
+
|
246
|
+
attr_reader :comments
|
247
|
+
@comments
|
248
|
+
|
249
|
+
##
|
250
|
+
# Contains images count. More images in one file are characteristics
|
251
|
+
# for the animated GIF files.
|
252
|
+
#
|
253
|
+
# @return [Integer] images count
|
254
|
+
#
|
255
|
+
|
256
|
+
attr_reader :images_count
|
257
|
+
@images_count
|
258
|
+
|
259
|
+
##
|
260
|
+
# Indicates animation is cyclic.
|
261
|
+
#
|
262
|
+
|
263
|
+
@cyclic
|
264
|
+
|
265
|
+
##
|
266
|
+
# Indicates, file is animated.
|
267
|
+
#
|
268
|
+
|
269
|
+
@animated
|
270
|
+
|
271
|
+
##
|
272
|
+
# Indicates, at least one image in file has some color transparent.
|
273
|
+
#
|
274
|
+
|
275
|
+
@transparent
|
276
|
+
|
277
|
+
##
|
278
|
+
# Indicates, at least one image in file is rendered interlaced.
|
279
|
+
#
|
280
|
+
|
281
|
+
@interlaced
|
282
|
+
|
283
|
+
##
|
284
|
+
# Constructor.
|
285
|
+
# @param [IO] io IO object with GIF data
|
286
|
+
#
|
287
|
+
|
288
|
+
def initialize(io)
|
289
|
+
@comments = [ ]
|
290
|
+
@images_count = 0
|
291
|
+
@cyclic = false
|
292
|
+
@animated = false
|
293
|
+
@transparent = false
|
294
|
+
@interlaced = false
|
295
|
+
|
296
|
+
self.consider! io
|
297
|
+
end
|
298
|
+
|
299
|
+
##
|
300
|
+
# Considers IO object, analyzes it and sets result ot the object.
|
301
|
+
# @param [IO] io IO object with GIF data
|
302
|
+
#
|
303
|
+
|
304
|
+
def consider!(io)
|
305
|
+
self.class::parse(io) do |block|
|
306
|
+
|
307
|
+
# Header Block
|
308
|
+
if block.kind_of? GifInfo::Blocks::HeaderBlock
|
309
|
+
header = block.header.data
|
310
|
+
@type = header.signature.to_sym # type
|
311
|
+
@version = header.version.to_sym # version
|
312
|
+
|
313
|
+
# Logical Screen Descriptor
|
314
|
+
elsif block.kind_of? GifInfo::Blocks::LogicalScreenDescriptor
|
315
|
+
header = block.header.data
|
316
|
+
packed = header.packed.data
|
317
|
+
|
318
|
+
@width = header.canvas_width # width
|
319
|
+
@height = header.canvas_height # height
|
320
|
+
|
321
|
+
value = header.pixel_aspect_ratio # pixel aspect ratio
|
322
|
+
value = nil if value <= 0 #
|
323
|
+
@pixel_aspect_ratio = value #
|
324
|
+
|
325
|
+
if packed.global_color_table # color resolution
|
326
|
+
@color_resolution = 2 ** (packed.global_color_table_size + 1)
|
327
|
+
end
|
328
|
+
|
329
|
+
# Comments
|
330
|
+
elsif block.kind_of? GifInfo::Blocks::CommentExtension
|
331
|
+
@comments << block.body.data # comments
|
332
|
+
|
333
|
+
# Image Descriptor
|
334
|
+
elsif block.kind_of? GifInfo::Blocks::ImageDescriptor
|
335
|
+
packed = block.header.data.packed.data
|
336
|
+
|
337
|
+
@images_count += 1 # images count
|
338
|
+
@animated = true if @images_count == 2 # animated
|
339
|
+
@interlaced |= packed.interlace # interlaced
|
340
|
+
|
341
|
+
if packed.local_color_table and (not @color_resolution.nil?) and (packed.local_color_table_size != @color_resolution)
|
342
|
+
@color_resolution = nil # resets color resolution if local color table found
|
343
|
+
end # and global and local aren't equivalent
|
344
|
+
|
345
|
+
# Application Extension
|
346
|
+
elsif block.kind_of? GifInfo::Blocks::ApplicationExtension
|
347
|
+
header = block.header.data
|
348
|
+
if (header.application_identifier == "NETSCAPE") and (header.application_authentication_code == "2.0")
|
349
|
+
@cyclic = true # cyclic
|
350
|
+
end
|
351
|
+
|
352
|
+
# Graphics Control Extension
|
353
|
+
elsif block.kind_of? GifInfo::Blocks::GraphicsControlExtension
|
354
|
+
packed = block.header.data.packed.data
|
355
|
+
@transparent |= packed.transparent_color # transparent
|
356
|
+
|
357
|
+
end
|
358
|
+
|
359
|
+
end
|
360
|
+
|
361
|
+
##
|
362
|
+
# Indicates animation is cyclic. In strange GIF files can be set
|
363
|
+
# although file isn't animated and contains single image only.
|
364
|
+
#
|
365
|
+
# @return [Boolean] +true+ if yes, +false+ in otherwise
|
366
|
+
#
|
367
|
+
|
368
|
+
def cyclic?
|
369
|
+
@cyclic
|
370
|
+
end
|
371
|
+
|
372
|
+
##
|
373
|
+
# Indicates file is animated. It can be set to +true+ although
|
374
|
+
# animation isn't run because it's set if file contains
|
375
|
+
# more images.
|
376
|
+
#
|
377
|
+
# @return [Boolean] +true+ if yes, +false+ in otherwise
|
378
|
+
#
|
379
|
+
|
380
|
+
def animated?
|
381
|
+
@animated
|
382
|
+
end
|
383
|
+
|
384
|
+
##
|
385
|
+
# Indicates, at least one image in file has some color
|
386
|
+
# transparent. It of sure doesn't mean, image is transparent.
|
387
|
+
# It only means, transparency is turned on.
|
388
|
+
#
|
389
|
+
# @return [Boolean] +true+ if yes, +false+ in otherwise
|
390
|
+
#
|
391
|
+
|
392
|
+
def transparent?
|
393
|
+
@transparent
|
394
|
+
end
|
395
|
+
|
396
|
+
##
|
397
|
+
# Indicates, at least one image in file is rendered interlaced.
|
398
|
+
# @return [Boolean] +true+ if yes, +false+ in otherwise
|
399
|
+
#
|
400
|
+
|
401
|
+
def interlaced?
|
402
|
+
@interlaced
|
403
|
+
end
|
404
|
+
|
405
|
+
end
|
406
|
+
|
407
|
+
end
|