ruby-imagespec 0.3.1 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/lib/image_spec.rb +5 -1
- data/lib/image_spec/parser.rb +18 -0
- data/lib/image_spec/parser/bmp.rb +24 -0
- data/lib/image_spec/parser/gif.rb +24 -0
- data/lib/image_spec/parser/jpeg.rb +65 -0
- data/lib/image_spec/parser/png.rb +24 -0
- data/lib/image_spec/parser/swf.rb +76 -0
- metadata +43 -18
- data/lib/parser.rb +0 -21
- data/lib/parser/gif.rb +0 -29
- data/lib/parser/jpeg.rb +0 -69
- data/lib/parser/png.rb +0 -29
- data/lib/parser/swf.rb +0 -78
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 5b959efb6a395dbe1ba3d6ffc3716ebb40489839
|
4
|
+
data.tar.gz: 6c556dded2c7788fe947855eec6afc736c5f4008
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 21ae732a659fb3b433d84e466a3ce83cc565798251216363384f7ff61a43b17e8968fa9ccc59c39bf731c609ca0a9398e706c3c83e468dec25e6bcf787221e1d
|
7
|
+
data.tar.gz: 2add58ef7912adefbc39edbf06dc6b4b17628af5d00548e141f8f7fbf8aa4e8b8c71fc484fb17934b83542411a6b941fadec71c129803c152e924e58be7e960c
|
data/lib/image_spec.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
require 'open-uri'
|
2
|
-
require File.join(File.dirname(__FILE__), 'parser')
|
3
2
|
|
4
3
|
class ImageSpec
|
4
|
+
Error = Class.new(StandardError)
|
5
5
|
|
6
6
|
def initialize(file)
|
7
7
|
@attributes = Parser.parse(stream_for(file))
|
@@ -28,3 +28,7 @@ class ImageSpec
|
|
28
28
|
end
|
29
29
|
|
30
30
|
end
|
31
|
+
|
32
|
+
%w|parser|.each do |name|
|
33
|
+
require "image_spec/#{name}"
|
34
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module ImageSpec::Parser
|
2
|
+
|
3
|
+
def self.formats
|
4
|
+
@@formats ||= constants.collect { |format| const_get(format) }
|
5
|
+
end
|
6
|
+
|
7
|
+
def self.parse(stream)
|
8
|
+
formats.each do |format|
|
9
|
+
return format.attributes(stream) if format.detected?(stream)
|
10
|
+
end
|
11
|
+
raise ImageSpec::Error, "#{stream.inspect} is not a supported image format. Sorry bub :("
|
12
|
+
end
|
13
|
+
|
14
|
+
end
|
15
|
+
|
16
|
+
%w|gif jpeg png swf bmp|.each do |name|
|
17
|
+
require "image_spec/parser/#{name}"
|
18
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# encoding: ascii-8bit
|
2
|
+
class ImageSpec::Parser::BMP
|
3
|
+
CONTENT_TYPE = 'image/bmp'
|
4
|
+
|
5
|
+
def self.attributes(stream)
|
6
|
+
width, height = dimensions(stream)
|
7
|
+
{:width => width, :height => height, :content_type => CONTENT_TYPE, :dimensions => [width, height], :file_size => size(stream)}
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.detected?(stream)
|
11
|
+
stream.rewind
|
12
|
+
stream.read(2) == 'BM'
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.dimensions(stream)
|
16
|
+
stream.seek(18)
|
17
|
+
stream.read(8).unpack('LL')
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.size(stream)
|
21
|
+
stream.size
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# encoding: ascii-8bit
|
2
|
+
class ImageSpec::Parser::GIF
|
3
|
+
CONTENT_TYPE = 'image/gif'
|
4
|
+
|
5
|
+
def self.attributes(stream)
|
6
|
+
width, height = dimensions(stream)
|
7
|
+
{:width => width, :height => height, :content_type => CONTENT_TYPE, :dimensions => [width, height], :file_size => size(stream)}
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.detected?(stream)
|
11
|
+
stream.rewind
|
12
|
+
stream.read(4) == 'GIF8'
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.dimensions(stream)
|
16
|
+
stream.seek(6)
|
17
|
+
stream.read(4).unpack('SS')
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.size(stream)
|
21
|
+
stream.size
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
# encoding: ascii-8bit
|
2
|
+
class ImageSpec::Parser::JPEG
|
3
|
+
|
4
|
+
CONTENT_TYPE = 'image/jpeg'
|
5
|
+
TYPE_JFIF = Regexp.new '^\xff\xd8\xff\xe0\x00\x10JFIF', nil, 'n'
|
6
|
+
TYPE_EXIF = Regexp.new '^\xff\xd8\xff\xe1(.*){2}Exif', nil, 'n'
|
7
|
+
|
8
|
+
def self.attributes(stream)
|
9
|
+
width, height = dimensions(stream)
|
10
|
+
{:width => width, :height => height, :content_type => CONTENT_TYPE, :dimensions => [width, height], :file_size => size(stream)}
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.detected?(stream)
|
14
|
+
stream.rewind
|
15
|
+
case stream.read(10)
|
16
|
+
when TYPE_JFIF, TYPE_EXIF then true
|
17
|
+
else false
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.dimensions(stream)
|
22
|
+
stream.rewind
|
23
|
+
raise ImageSpec::Error, 'malformed JPEG' unless stream.readbyte.chr == "\xFF" && stream.readbyte.chr == "\xD8" # SOI
|
24
|
+
|
25
|
+
class << stream
|
26
|
+
def readint
|
27
|
+
(readbyte.ord << 8) + readbyte.ord
|
28
|
+
end
|
29
|
+
|
30
|
+
def readframe
|
31
|
+
read(readint - 2)
|
32
|
+
end
|
33
|
+
|
34
|
+
def readsof
|
35
|
+
[readint, readbyte.chr, readint, readint, readbyte.chr]
|
36
|
+
end
|
37
|
+
|
38
|
+
def next
|
39
|
+
c = readbyte.chr while c != "\xFF"
|
40
|
+
c = readbyte.chr while c == "\xFF"
|
41
|
+
c
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
while marker = stream.next
|
46
|
+
case marker
|
47
|
+
when "\xC0".."\xC3", "\xC5".."\xC7", "\xC9".."\xCB", "\xCD".."\xCF"
|
48
|
+
length, bits, height, width, components = stream.readsof
|
49
|
+
raise ImageSpec::Error, 'malformed JPEG' unless length == 8 + components[0].ord * 3
|
50
|
+
return [width, height]
|
51
|
+
when "\xD9", "\xDA"
|
52
|
+
break
|
53
|
+
when "\xFE"
|
54
|
+
@comment = stream.readframe
|
55
|
+
else
|
56
|
+
stream.readframe
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def self.size(stream)
|
62
|
+
stream.size
|
63
|
+
end
|
64
|
+
|
65
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# encoding: ascii-8bit
|
2
|
+
class ImageSpec::Parser::PNG
|
3
|
+
CONTENT_TYPE = 'image/png'
|
4
|
+
|
5
|
+
def self.attributes(stream)
|
6
|
+
width, height = dimensions(stream)
|
7
|
+
{:width => width, :height => height, :content_type => CONTENT_TYPE, :dimensions => [width, height], :file_size => size(stream)}
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.detected?(stream)
|
11
|
+
stream.rewind
|
12
|
+
stream.read(4) == "\x89PNG"
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.dimensions(stream)
|
16
|
+
stream.seek(0x10)
|
17
|
+
stream.read(8).unpack('NN')
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.size(stream)
|
21
|
+
stream.size
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
# encoding: ascii-8bit
|
2
|
+
require 'zlib'
|
3
|
+
|
4
|
+
class ImageSpec::Parser::SWF
|
5
|
+
CONTENT_TYPE = 'application/x-shockwave-flash'
|
6
|
+
|
7
|
+
def self.attributes(stream)
|
8
|
+
width, height, version = dimensions(stream)
|
9
|
+
{:width => width, :height => height, :content_type => CONTENT_TYPE, :version => version, :dimensions => [width, height], :file_size => size(stream)}
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.detected?(stream)
|
13
|
+
stream.rewind
|
14
|
+
stream.read(3) =~ /(F|C)WS/ ? true : false
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.dimensions(stream)
|
18
|
+
# Read the entire stream into memory because the
|
19
|
+
# dimensions aren't stored in a standard location
|
20
|
+
stream.rewind
|
21
|
+
contents = stream.read.force_encoding("ASCII-8BIT")
|
22
|
+
|
23
|
+
# Our 'signature' is the first 3 bytes
|
24
|
+
# Either FWS or CWS. CWS indicates compression
|
25
|
+
signature = contents[0..2]
|
26
|
+
|
27
|
+
# SWF version
|
28
|
+
version = contents[3].unpack('C').join.to_i
|
29
|
+
|
30
|
+
# Determine the length of the uncompressed stream
|
31
|
+
length = contents[4..7].unpack('V').join.to_i
|
32
|
+
|
33
|
+
# If we do, in fact, have compression
|
34
|
+
if signature == 'CWS'
|
35
|
+
# Decompress the body of the SWF
|
36
|
+
body = Zlib::Inflate.inflate( contents[8..length] )
|
37
|
+
|
38
|
+
# And reconstruct the stream contents to the first 8 bytes (header)
|
39
|
+
# Plus our decompressed body
|
40
|
+
contents = contents[0..7] + body
|
41
|
+
end
|
42
|
+
|
43
|
+
# Determine the nbits of our dimensions rectangle
|
44
|
+
nbits = contents.unpack('C'*contents.length)[8] >> 3
|
45
|
+
|
46
|
+
# Determine how many bits long this entire RECT structure is
|
47
|
+
rectbits = 5 + nbits * 4 # 5 bits for nbits, as well as nbits * number of fields (4)
|
48
|
+
|
49
|
+
# Determine how many bytes rectbits composes (ceil(rectbits/8))
|
50
|
+
rectbytes = (rectbits.to_f / 8).ceil
|
51
|
+
|
52
|
+
# Unpack the RECT structure from the stream in little-endian bit order, then join it into a string
|
53
|
+
rect = contents[8..(8 + rectbytes)].unpack("#{'B8' * rectbytes}").join()
|
54
|
+
|
55
|
+
# Read in nbits incremenets starting from 5
|
56
|
+
dimensions = Array.new
|
57
|
+
4.times do |n|
|
58
|
+
s = 5 + (n * nbits) # Calculate our start index
|
59
|
+
e = s + (nbits - 1) # Calculate our end index
|
60
|
+
dimensions[n] = rect[s..e].to_i(2) # Read that range (binary) and convert it to an integer
|
61
|
+
end
|
62
|
+
|
63
|
+
# The values we have here are in "twips"
|
64
|
+
# 20 twips to a pixel (that's why SWFs are fuzzy sometimes!)
|
65
|
+
width = (dimensions[1] - dimensions[0]) / 20
|
66
|
+
height = (dimensions[3] - dimensions[2]) / 20
|
67
|
+
|
68
|
+
# If you can't figure this one out, you probably shouldn't have read this far
|
69
|
+
return [width, height, version]
|
70
|
+
end
|
71
|
+
|
72
|
+
def self.size(stream)
|
73
|
+
stream.size
|
74
|
+
end
|
75
|
+
|
76
|
+
end
|
metadata
CHANGED
@@ -1,8 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ruby-imagespec
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
5
|
-
prerelease:
|
4
|
+
version: 0.4.1
|
6
5
|
platform: ruby
|
7
6
|
authors:
|
8
7
|
- Brandon Anderson
|
@@ -12,22 +11,48 @@ authors:
|
|
12
11
|
autorequire:
|
13
12
|
bindir: bin
|
14
13
|
cert_chain: []
|
15
|
-
date:
|
14
|
+
date: 2015-06-23 00:00:00.000000000 Z
|
16
15
|
dependencies:
|
17
16
|
- !ruby/object:Gem::Dependency
|
18
17
|
name: rake
|
19
18
|
requirement: !ruby/object:Gem::Requirement
|
20
|
-
none: false
|
21
19
|
requirements:
|
22
|
-
- -
|
20
|
+
- - ">="
|
23
21
|
- !ruby/object:Gem::Version
|
24
22
|
version: '0'
|
25
23
|
type: :development
|
26
24
|
prerelease: false
|
27
25
|
version_requirements: !ruby/object:Gem::Requirement
|
28
|
-
none: false
|
29
26
|
requirements:
|
30
|
-
- -
|
27
|
+
- - ">="
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '0'
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: bundler
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
requirements:
|
34
|
+
- - ">="
|
35
|
+
- !ruby/object:Gem::Version
|
36
|
+
version: '0'
|
37
|
+
type: :development
|
38
|
+
prerelease: false
|
39
|
+
version_requirements: !ruby/object:Gem::Requirement
|
40
|
+
requirements:
|
41
|
+
- - ">="
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: '0'
|
44
|
+
- !ruby/object:Gem::Dependency
|
45
|
+
name: test-unit
|
46
|
+
requirement: !ruby/object:Gem::Requirement
|
47
|
+
requirements:
|
48
|
+
- - ">="
|
49
|
+
- !ruby/object:Gem::Version
|
50
|
+
version: '0'
|
51
|
+
type: :development
|
52
|
+
prerelease: false
|
53
|
+
version_requirements: !ruby/object:Gem::Requirement
|
54
|
+
requirements:
|
55
|
+
- - ">="
|
31
56
|
- !ruby/object:Gem::Version
|
32
57
|
version: '0'
|
33
58
|
description:
|
@@ -38,35 +63,35 @@ extra_rdoc_files: []
|
|
38
63
|
files:
|
39
64
|
- README
|
40
65
|
- init.rb
|
41
|
-
- lib/parser/gif.rb
|
42
|
-
- lib/parser/png.rb
|
43
|
-
- lib/parser/swf.rb
|
44
|
-
- lib/parser/jpeg.rb
|
45
66
|
- lib/image_spec.rb
|
46
|
-
- lib/parser.rb
|
67
|
+
- lib/image_spec/parser.rb
|
68
|
+
- lib/image_spec/parser/bmp.rb
|
69
|
+
- lib/image_spec/parser/gif.rb
|
70
|
+
- lib/image_spec/parser/jpeg.rb
|
71
|
+
- lib/image_spec/parser/png.rb
|
72
|
+
- lib/image_spec/parser/swf.rb
|
47
73
|
homepage: http://github.com/dim/ruby-imagespec
|
48
74
|
licenses: []
|
75
|
+
metadata: {}
|
49
76
|
post_install_message:
|
50
77
|
rdoc_options: []
|
51
78
|
require_paths:
|
52
79
|
- lib
|
53
80
|
required_ruby_version: !ruby/object:Gem::Requirement
|
54
|
-
none: false
|
55
81
|
requirements:
|
56
|
-
- -
|
82
|
+
- - ">="
|
57
83
|
- !ruby/object:Gem::Version
|
58
84
|
version: 1.9.1
|
59
85
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
60
|
-
none: false
|
61
86
|
requirements:
|
62
|
-
- -
|
87
|
+
- - ">="
|
63
88
|
- !ruby/object:Gem::Version
|
64
89
|
version: 1.3.6
|
65
90
|
requirements: []
|
66
91
|
rubyforge_project:
|
67
|
-
rubygems_version:
|
92
|
+
rubygems_version: 2.4.7
|
68
93
|
signing_key:
|
69
|
-
specification_version:
|
94
|
+
specification_version: 4
|
70
95
|
summary: Image/Flash extract width/height dimensions extractor
|
71
96
|
test_files: []
|
72
97
|
has_rdoc:
|
data/lib/parser.rb
DELETED
@@ -1,21 +0,0 @@
|
|
1
|
-
Dir[File.join(File.dirname(__FILE__), 'parser/*.rb')].each { |parser| require parser }
|
2
|
-
|
3
|
-
class ImageSpec
|
4
|
-
Error = Class.new(StandardError)
|
5
|
-
|
6
|
-
module Parser
|
7
|
-
|
8
|
-
def self.formats
|
9
|
-
@@formats ||= constants.collect { |format| const_get(format) }
|
10
|
-
end
|
11
|
-
|
12
|
-
def self.parse(stream)
|
13
|
-
formats.each do |format|
|
14
|
-
return format.attributes(stream) if format.detected?(stream)
|
15
|
-
end
|
16
|
-
raise Error, "#{stream.inspect} is not a supported image format. Sorry bub :("
|
17
|
-
end
|
18
|
-
|
19
|
-
end
|
20
|
-
|
21
|
-
end
|
data/lib/parser/gif.rb
DELETED
@@ -1,29 +0,0 @@
|
|
1
|
-
# encoding: ascii-8bit
|
2
|
-
class ImageSpec
|
3
|
-
module Parser
|
4
|
-
class GIF
|
5
|
-
|
6
|
-
CONTENT_TYPE = 'image/gif'
|
7
|
-
|
8
|
-
def self.attributes(stream)
|
9
|
-
width, height = dimensions(stream)
|
10
|
-
{:width => width, :height => height, :content_type => CONTENT_TYPE, :dimensions => [width, height], :file_size => size(stream)}
|
11
|
-
end
|
12
|
-
|
13
|
-
def self.detected?(stream)
|
14
|
-
stream.rewind
|
15
|
-
stream.read(4) == 'GIF8'
|
16
|
-
end
|
17
|
-
|
18
|
-
def self.dimensions(stream)
|
19
|
-
stream.seek(6)
|
20
|
-
stream.read(4).unpack('SS')
|
21
|
-
end
|
22
|
-
|
23
|
-
def self.size(stream)
|
24
|
-
stream.size
|
25
|
-
end
|
26
|
-
|
27
|
-
end
|
28
|
-
end
|
29
|
-
end
|
data/lib/parser/jpeg.rb
DELETED
@@ -1,69 +0,0 @@
|
|
1
|
-
# encoding: ascii-8bit
|
2
|
-
class ImageSpec
|
3
|
-
module Parser
|
4
|
-
class JPEG
|
5
|
-
|
6
|
-
CONTENT_TYPE = 'image/jpeg'
|
7
|
-
TYPE_JFIF = Regexp.new '^\xff\xd8\xff\xe0\x00\x10JFIF', nil, 'n'
|
8
|
-
TYPE_EXIF = Regexp.new '^\xff\xd8\xff\xe1(.*){2}Exif', nil, 'n'
|
9
|
-
|
10
|
-
def self.attributes(stream)
|
11
|
-
width, height = dimensions(stream)
|
12
|
-
{:width => width, :height => height, :content_type => CONTENT_TYPE, :dimensions => [width, height], :file_size => size(stream)}
|
13
|
-
end
|
14
|
-
|
15
|
-
def self.detected?(stream)
|
16
|
-
stream.rewind
|
17
|
-
case stream.read(10)
|
18
|
-
when TYPE_JFIF, TYPE_EXIF then true
|
19
|
-
else false
|
20
|
-
end
|
21
|
-
end
|
22
|
-
|
23
|
-
def self.dimensions(stream)
|
24
|
-
stream.rewind
|
25
|
-
raise ImageSpec::Error, 'malformed JPEG' unless stream.readbyte.chr == "\xFF" && stream.readbyte.chr == "\xD8" # SOI
|
26
|
-
|
27
|
-
class << stream
|
28
|
-
def readint
|
29
|
-
(readbyte.ord << 8) + readbyte.ord
|
30
|
-
end
|
31
|
-
|
32
|
-
def readframe
|
33
|
-
read(readint - 2)
|
34
|
-
end
|
35
|
-
|
36
|
-
def readsof
|
37
|
-
[readint, readbyte.chr, readint, readint, readbyte.chr]
|
38
|
-
end
|
39
|
-
|
40
|
-
def next
|
41
|
-
c = readbyte.chr while c != "\xFF"
|
42
|
-
c = readbyte.chr while c == "\xFF"
|
43
|
-
c
|
44
|
-
end
|
45
|
-
end
|
46
|
-
|
47
|
-
while marker = stream.next
|
48
|
-
case marker
|
49
|
-
when "\xC0".."\xC3", "\xC5".."\xC7", "\xC9".."\xCB", "\xCD".."\xCF"
|
50
|
-
length, bits, height, width, components = stream.readsof
|
51
|
-
raise ImageSpec::Error, 'malformed JPEG' unless length == 8 + components[0].ord * 3
|
52
|
-
return [width, height]
|
53
|
-
when "\xD9", "\xDA"
|
54
|
-
break
|
55
|
-
when "\xFE"
|
56
|
-
@comment = stream.readframe
|
57
|
-
else
|
58
|
-
stream.readframe
|
59
|
-
end
|
60
|
-
end
|
61
|
-
end
|
62
|
-
|
63
|
-
def self.size(stream)
|
64
|
-
stream.size
|
65
|
-
end
|
66
|
-
|
67
|
-
end
|
68
|
-
end
|
69
|
-
end
|
data/lib/parser/png.rb
DELETED
@@ -1,29 +0,0 @@
|
|
1
|
-
# encoding: ascii-8bit
|
2
|
-
class ImageSpec
|
3
|
-
module Parser
|
4
|
-
class PNG
|
5
|
-
|
6
|
-
CONTENT_TYPE = 'image/png'
|
7
|
-
|
8
|
-
def self.attributes(stream)
|
9
|
-
width, height = dimensions(stream)
|
10
|
-
{:width => width, :height => height, :content_type => CONTENT_TYPE, :dimensions => [width, height], :file_size => size(stream)}
|
11
|
-
end
|
12
|
-
|
13
|
-
def self.detected?(stream)
|
14
|
-
stream.rewind
|
15
|
-
stream.read(4) == "\x89PNG"
|
16
|
-
end
|
17
|
-
|
18
|
-
def self.dimensions(stream)
|
19
|
-
stream.seek(0x10)
|
20
|
-
stream.read(8).unpack('NN')
|
21
|
-
end
|
22
|
-
|
23
|
-
def self.size(stream)
|
24
|
-
stream.size
|
25
|
-
end
|
26
|
-
|
27
|
-
end
|
28
|
-
end
|
29
|
-
end
|
data/lib/parser/swf.rb
DELETED
@@ -1,78 +0,0 @@
|
|
1
|
-
# encoding: ascii-8bit
|
2
|
-
require 'zlib'
|
3
|
-
|
4
|
-
class ImageSpec
|
5
|
-
module Parser
|
6
|
-
class SWF
|
7
|
-
|
8
|
-
CONTENT_TYPE = 'application/x-shockwave-flash'
|
9
|
-
|
10
|
-
def self.attributes(stream)
|
11
|
-
width, height = dimensions(stream)
|
12
|
-
{:width => width, :height => height, :content_type => CONTENT_TYPE, :dimensions => [width, height], :file_size => size(stream)}
|
13
|
-
end
|
14
|
-
|
15
|
-
def self.detected?(stream)
|
16
|
-
stream.rewind
|
17
|
-
stream.read(3) =~ /(F|C)WS/ ? true : false
|
18
|
-
end
|
19
|
-
|
20
|
-
def self.dimensions(stream)
|
21
|
-
# Read the entire stream into memory because the
|
22
|
-
# dimensions aren't stored in a standard location
|
23
|
-
stream.rewind
|
24
|
-
contents = stream.read.force_encoding("ASCII-8BIT")
|
25
|
-
|
26
|
-
# Our 'signature' is the first 3 bytes
|
27
|
-
# Either FWS or CWS. CWS indicates compression
|
28
|
-
signature = contents[0..2]
|
29
|
-
|
30
|
-
# Determine the length of the uncompressed stream
|
31
|
-
length = contents[4..7].unpack('V').join.to_i
|
32
|
-
|
33
|
-
# If we do, in fact, have compression
|
34
|
-
if signature == 'CWS'
|
35
|
-
# Decompress the body of the SWF
|
36
|
-
body = Zlib::Inflate.inflate( contents[8..length] )
|
37
|
-
|
38
|
-
# And reconstruct the stream contents to the first 8 bytes (header)
|
39
|
-
# Plus our decompressed body
|
40
|
-
contents = contents[0..7] + body
|
41
|
-
end
|
42
|
-
|
43
|
-
# Determine the nbits of our dimensions rectangle
|
44
|
-
nbits =contents.unpack('c'*contents.length)[8] >> 3
|
45
|
-
|
46
|
-
# Determine how many bits long this entire RECT structure is
|
47
|
-
rectbits = 5 + nbits * 4 # 5 bits for nbits, as well as nbits * number of fields (4)
|
48
|
-
|
49
|
-
# Determine how many bytes rectbits composes (ceil(rectbits/8))
|
50
|
-
rectbytes = (rectbits.to_f / 8).ceil
|
51
|
-
|
52
|
-
# Unpack the RECT structure from the stream in little-endian bit order, then join it into a string
|
53
|
-
rect = contents[8..(8 + rectbytes)].unpack("#{'B8' * rectbytes}").join()
|
54
|
-
|
55
|
-
# Read in nbits incremenets starting from 5
|
56
|
-
dimensions = Array.new
|
57
|
-
4.times do |n|
|
58
|
-
s = 5 + (n * nbits) # Calculate our start index
|
59
|
-
e = s + (nbits - 1) # Calculate our end index
|
60
|
-
dimensions[n] = rect[s..e].to_i(2) # Read that range (binary) and convert it to an integer
|
61
|
-
end
|
62
|
-
|
63
|
-
# The values we have here are in "twips"
|
64
|
-
# 20 twips to a pixel (that's why SWFs are fuzzy sometimes!)
|
65
|
-
width = (dimensions[1] - dimensions[0]) / 20
|
66
|
-
height = (dimensions[3] - dimensions[2]) / 20
|
67
|
-
|
68
|
-
# If you can't figure this one out, you probably shouldn't have read this far
|
69
|
-
return [width, height]
|
70
|
-
end
|
71
|
-
|
72
|
-
def self.size(stream)
|
73
|
-
stream.size
|
74
|
-
end
|
75
|
-
|
76
|
-
end
|
77
|
-
end
|
78
|
-
end
|