image_size 1.0.6 → 1.1.0
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.
- data/README.markdown +20 -6
- data/image_size.gemspec +2 -2
- data/lib/image_size.rb +130 -146
- data/spec/image_size_spec.rb +25 -1
- metadata +6 -6
data/README.markdown
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
# image_size
|
2
2
|
|
3
3
|
measure image size using pure Ruby
|
4
|
-
formats:
|
4
|
+
formats: `bmp`, `gif`, `jpeg`, `pbm`, `pcx`, `pgm`, `png`, `ppm`, `psd`, `swf`, `tiff`, `xbm`, `xpm`
|
5
5
|
|
6
6
|
## Download
|
7
7
|
|
@@ -11,13 +11,27 @@ The latest version of image\_size can be found at http://github.com/toy/image_si
|
|
11
11
|
|
12
12
|
gem install image_size
|
13
13
|
|
14
|
-
##
|
14
|
+
## Examples
|
15
15
|
|
16
|
-
|
17
|
-
ruby "open-uri"
|
16
|
+
require 'image_size'
|
18
17
|
|
19
|
-
|
20
|
-
|
18
|
+
p ImageSize.path('spec/test.jpg').size
|
19
|
+
|
20
|
+
open('spec/test.jpg', 'rb') do |fh|
|
21
|
+
p ImageSize.new(fh).size
|
22
|
+
end
|
23
|
+
|
24
|
+
|
25
|
+
require 'image_size'
|
26
|
+
require 'open-uri'
|
27
|
+
|
28
|
+
open('http://www.rubycgi.org/image/ruby_gtk_book_title.jpg', 'rb') do |fh|
|
29
|
+
p ImageSize.new(fh).size
|
30
|
+
end
|
31
|
+
|
32
|
+
open('http://www.rubycgi.org/image/ruby_gtk_book_title.jpg', 'rb') do |fh|
|
33
|
+
data = fh.read
|
34
|
+
p ImageSize.new(data).size
|
21
35
|
end
|
22
36
|
|
23
37
|
## Licence
|
data/image_size.gemspec
CHANGED
@@ -2,9 +2,9 @@
|
|
2
2
|
|
3
3
|
Gem::Specification.new do |s|
|
4
4
|
s.name = 'image_size'
|
5
|
-
s.version = '1.0
|
5
|
+
s.version = '1.1.0'
|
6
6
|
s.summary = %q{Measure image size using pure Ruby}
|
7
|
-
s.description = %q{Measure following file dimensions:
|
7
|
+
s.description = %q{Measure following file dimensions: bmp, gif, jpeg, pbm, pcx, pgm, png, ppm, psd, swf, tiff, xbm, xpm}
|
8
8
|
s.homepage = "http://github.com/toy/#{s.name}"
|
9
9
|
s.authors = ['Keisuke Minami', 'Ivan Kuchin']
|
10
10
|
s.license = 'MIT'
|
data/lib/image_size.rb
CHANGED
@@ -1,90 +1,105 @@
|
|
1
1
|
require 'stringio'
|
2
|
+
require 'tempfile'
|
2
3
|
|
3
4
|
class ImageSize
|
4
|
-
|
5
|
+
class Size < Array
|
6
|
+
# join using 'x'
|
7
|
+
def to_s
|
8
|
+
join('x')
|
9
|
+
end
|
10
|
+
end
|
5
11
|
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
img_io = def_read_o(img_io)
|
19
|
-
else
|
20
|
-
raise ArgumentError.new("expected instance of IO, StringIO or String, got #{data.class}")
|
12
|
+
class ImageReader # :nodoc:
|
13
|
+
def initialize(data_or_io)
|
14
|
+
@io = case data_or_io
|
15
|
+
when IO, StringIO, Tempfile
|
16
|
+
data_or_io.dup.tap(&:rewind)
|
17
|
+
when String
|
18
|
+
StringIO.new(data_or_io)
|
19
|
+
else
|
20
|
+
raise ArgumentError.new("expected instance of IO, StringIO, Tempfile or String, got #{data.class}")
|
21
|
+
end
|
22
|
+
@read = 0
|
23
|
+
@data = ''
|
21
24
|
end
|
22
25
|
|
23
|
-
|
24
|
-
@
|
26
|
+
def rewind
|
27
|
+
@io.rewind
|
25
28
|
end
|
26
29
|
|
27
|
-
|
28
|
-
|
30
|
+
CHUNK = 1024
|
31
|
+
def [](offset, length)
|
32
|
+
while offset + length > @read
|
33
|
+
@read += CHUNK
|
34
|
+
if data = @io.read(CHUNK)
|
35
|
+
@data << data
|
36
|
+
end
|
37
|
+
end
|
38
|
+
@data[offset, length]
|
29
39
|
end
|
30
40
|
end
|
31
41
|
|
32
|
-
#
|
33
|
-
def
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
42
|
+
# Given path to image finds its format, width and height
|
43
|
+
def self.path(path)
|
44
|
+
open(path, 'rb'){ |f| new(f) }
|
45
|
+
end
|
46
|
+
|
47
|
+
# Given image as IO, StringIO, Tempfile or String finds its format and dimensions
|
48
|
+
def initialize(data)
|
49
|
+
ir = ImageReader.new(data)
|
50
|
+
if @format = detect_format(ir)
|
51
|
+
@width, @height = self.send("size_of_#{@format}", ir)
|
40
52
|
end
|
53
|
+
ir.rewind
|
41
54
|
end
|
42
55
|
|
43
|
-
|
56
|
+
# Image format
|
57
|
+
attr_reader :format
|
58
|
+
|
59
|
+
# Image width
|
60
|
+
attr_reader :width
|
44
61
|
alias :w :width
|
45
62
|
|
46
|
-
|
63
|
+
# Image height
|
64
|
+
attr_reader :height
|
65
|
+
alias :h :height
|
47
66
|
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
self.seek(offset, 0) if offset
|
53
|
-
ret = self.read(length)
|
54
|
-
raise 'cannot read!!' unless ret
|
55
|
-
ret
|
67
|
+
# get image width and height as an array which to_s method returns "#{width}x#{height}"
|
68
|
+
def size
|
69
|
+
if format
|
70
|
+
Size.new([width, height])
|
56
71
|
end
|
57
|
-
io
|
58
72
|
end
|
59
73
|
|
60
|
-
|
74
|
+
private
|
75
|
+
|
76
|
+
def detect_format(ir)
|
77
|
+
head = ir[0, 1024]
|
61
78
|
case
|
62
|
-
when
|
63
|
-
when
|
64
|
-
when
|
65
|
-
when
|
66
|
-
when
|
67
|
-
when
|
68
|
-
when
|
69
|
-
when
|
70
|
-
when
|
71
|
-
when
|
72
|
-
when
|
73
|
-
when
|
74
|
-
when img_top =~ /^[FC]WS/ then :swf
|
75
|
-
when img_top[0, 1] == "\x0a" then :pcx
|
79
|
+
when head =~ /^GIF8[7,9]a/ then :gif
|
80
|
+
when head[0, 8] == "\211PNG\r\n\032\n" then :png
|
81
|
+
when head[0, 2] == "\377\330" then :jpeg
|
82
|
+
when head[0, 2] == 'BM' then :bmp
|
83
|
+
when head =~ /^P[1-7]/ then :ppm
|
84
|
+
when head =~ /\#define\s+\S+\s+\d+/ then :xbm
|
85
|
+
when head[0, 4] == "II*\000" then :tiff
|
86
|
+
when head[0, 4] == "MM\000*" then :tiff
|
87
|
+
when head =~ /\/\* XPM \*\// then :xpm
|
88
|
+
when head[0, 4] == '8BPS' then :psd
|
89
|
+
when head =~ /^[FC]WS/ then :swf
|
90
|
+
when head[0, 1] == "\n" then :pcx
|
76
91
|
end
|
77
92
|
end
|
78
93
|
|
79
|
-
def
|
80
|
-
|
81
|
-
img_io.read_o(4).unpack('vv')
|
94
|
+
def size_of_gif(ir)
|
95
|
+
ir[6, 4].unpack('vv')
|
82
96
|
end
|
83
97
|
|
84
|
-
def
|
85
|
-
|
86
|
-
|
87
|
-
|
98
|
+
def size_of_png(ir)
|
99
|
+
unless ir[12, 4] == 'IHDR'
|
100
|
+
raise 'IHDR not in place for PNG'
|
101
|
+
end
|
102
|
+
ir[16, 8].unpack('NN')
|
88
103
|
end
|
89
104
|
|
90
105
|
JpegCodeCheck = [
|
@@ -92,129 +107,98 @@ private
|
|
92
107
|
"\xc5", "\xc6", "\xc7",
|
93
108
|
"\xc9", "\xca", "\xcb",
|
94
109
|
"\xcd", "\xce", "\xcf",
|
95
|
-
]
|
96
|
-
def
|
97
|
-
|
98
|
-
|
110
|
+
] # :nodoc:
|
111
|
+
def size_of_jpeg(ir)
|
112
|
+
section_marker = "\xFF"
|
113
|
+
offset = 2
|
99
114
|
loop do
|
100
|
-
marker, code, length =
|
101
|
-
|
115
|
+
marker, code, length = ir[offset, 4].unpack('aan')
|
116
|
+
offset += 4
|
117
|
+
raise 'JPEG marker not found' if marker != section_marker
|
102
118
|
|
103
119
|
if JpegCodeCheck.include?(code)
|
104
|
-
|
105
|
-
return [width, height]
|
120
|
+
return ir[offset + 1, 4].unpack('nn').reverse
|
106
121
|
end
|
107
|
-
|
122
|
+
offset += length - 2
|
108
123
|
end
|
109
124
|
end
|
110
125
|
|
111
|
-
def
|
112
|
-
|
126
|
+
def size_of_bmp(ir)
|
127
|
+
ir[18, 8].unpack('VV')
|
113
128
|
end
|
114
129
|
|
115
|
-
def
|
116
|
-
header =
|
130
|
+
def size_of_ppm(ir)
|
131
|
+
header = ir[0, 1024]
|
117
132
|
header.gsub!(/^\#[^\n\r]*/m, '')
|
118
133
|
header =~ /^(P[1-6])\s+?(\d+)\s+?(\d+)/m
|
119
134
|
case $1
|
120
135
|
when 'P1', 'P4' then @format = :pbm
|
121
136
|
when 'P2', 'P5' then @format = :pgm
|
122
137
|
end
|
123
|
-
|
124
138
|
[$2.to_i, $3.to_i]
|
125
139
|
end
|
126
140
|
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
def measure_xbm(img_io)
|
131
|
-
img_io.read_o(1024) =~ /^\#define\s*\S*\s*(\d+)\s*\n\#define\s*\S*\s*(\d+)/mi
|
132
|
-
|
141
|
+
def size_of_xbm(ir)
|
142
|
+
ir[0, 1024] =~ /^\#define\s*\S*\s*(\d+)\s*\n\#define\s*\S*\s*(\d+)/mi
|
133
143
|
[$1.to_i, $2.to_i]
|
134
144
|
end
|
135
145
|
|
136
|
-
def
|
137
|
-
|
138
|
-
|
139
|
-
if
|
140
|
-
|
141
|
-
break
|
146
|
+
def size_of_xpm(ir)
|
147
|
+
length = 1024
|
148
|
+
until (data = ir[0, length]) =~ /"\s*(\d+)\s+(\d+)(\s+\d+\s+\d+){1,2}\s*"/m
|
149
|
+
if data.length != length
|
150
|
+
raise 'XPM size not found'
|
142
151
|
end
|
152
|
+
length += 1024
|
143
153
|
end
|
144
|
-
|
145
|
-
[width, height]
|
154
|
+
[$1.to_i, $2.to_i]
|
146
155
|
end
|
147
156
|
|
148
|
-
def
|
149
|
-
|
157
|
+
def size_of_psd(ir)
|
158
|
+
ir[14, 8].unpack('NN')
|
150
159
|
end
|
151
160
|
|
152
|
-
def
|
153
|
-
|
161
|
+
def size_of_tiff(ir)
|
162
|
+
endian2b = (ir[0, 4] == "II*\000") ? 'v' : 'n'
|
163
|
+
endian4b = endian2b.upcase
|
164
|
+
packspec = [nil, 'C', nil, endian2b, endian4b, nil, 'c', nil, endian2b, endian4b]
|
154
165
|
|
155
|
-
|
156
|
-
|
157
|
-
'C', # BYTE (8-bit unsigned integer)
|
158
|
-
nil, # ASCII
|
159
|
-
endian, # SHORT (16-bit unsigned integer)
|
160
|
-
endian.upcase, # LONG (32-bit unsigned integer)
|
161
|
-
nil, # RATIONAL
|
162
|
-
'c', # SBYTE (8-bit signed integer)
|
163
|
-
nil, # UNDEFINED
|
164
|
-
endian, # SSHORT (16-bit unsigned integer)
|
165
|
-
endian.upcase, # SLONG (32-bit unsigned integer)
|
166
|
-
]
|
167
|
-
|
168
|
-
offset = img_io.read_o(4).unpack(endian.upcase)[0] # Get offset to IFD
|
169
|
-
|
170
|
-
ifd = img_io.read_o(2, offset)
|
171
|
-
num_dirent = ifd.unpack(endian)[0] # Make it useful
|
166
|
+
offset = ir[4, 4].unpack(endian4b)[0]
|
167
|
+
num_dirent = ir[offset, 2].unpack(endian2b)[0]
|
172
168
|
offset += 2
|
173
|
-
num_dirent = offset + (num_dirent * 12)
|
169
|
+
num_dirent = offset + (num_dirent * 12)
|
174
170
|
|
175
|
-
|
176
|
-
|
177
|
-
ifd =
|
178
|
-
|
171
|
+
width = height = nil
|
172
|
+
until width && height
|
173
|
+
ifd = ir[offset, 12]
|
174
|
+
raise 'Reached end of directory entries in TIFF' if ifd.nil? || offset > num_dirent
|
175
|
+
tag, type = ifd.unpack(endian2b * 2)
|
179
176
|
offset += 12
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
177
|
+
|
178
|
+
unless packspec[type].nil?
|
179
|
+
value = ifd[8, 4].unpack(packspec[type])[0]
|
180
|
+
case tag
|
181
|
+
when 0x0100
|
182
|
+
width = value
|
183
|
+
when 0x0101
|
184
|
+
height = value
|
185
|
+
end
|
189
186
|
end
|
190
187
|
end
|
191
|
-
|
192
188
|
[width, height]
|
193
189
|
end
|
194
190
|
|
195
|
-
def
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
[head_part[6] - head_part[4] + 1, head_part[7] - head_part[5] + 1]
|
191
|
+
def size_of_pcx(ir)
|
192
|
+
parts = ir[4, 8].unpack('S4')
|
193
|
+
[parts[2] - parts[0] + 1, parts[3] - parts[1] + 1]
|
200
194
|
end
|
201
195
|
|
202
|
-
def
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
bit_length = Integer("0b#{header.unpack('@8B5').first}")
|
210
|
-
header << img_io.read_o(bit_length * 4 / 8 + 1)
|
211
|
-
str = header.unpack("@8B#{5 + bit_length * 4}")[0]
|
212
|
-
last = 5
|
213
|
-
x_min = Integer("0b#{str[last, bit_length]}")
|
214
|
-
x_max = Integer("0b#{str[(last += bit_length), bit_length]}")
|
215
|
-
y_min = Integer("0b#{str[(last += bit_length), bit_length]}")
|
216
|
-
y_max = Integer("0b#{str[(last += bit_length), bit_length]}")
|
217
|
-
|
196
|
+
def size_of_swf(ir)
|
197
|
+
value_bit_length = ir[8, 1].unpack('B5').first.to_i(2)
|
198
|
+
bit_length = 5 + value_bit_length * 4
|
199
|
+
rect_bits = ir[8, bit_length / 8 + 1].unpack("B#{bit_length}").first
|
200
|
+
values = rect_bits.unpack('@5' + "a#{value_bit_length}" * 4).map{ |bits| bits.to_i(2) }
|
201
|
+
x_min, x_max, y_min, y_max = values
|
218
202
|
[(x_max - x_min) / 20, (y_max - y_min) / 20]
|
219
203
|
end
|
220
204
|
end
|
data/spec/image_size_spec.rb
CHANGED
@@ -20,7 +20,7 @@ describe ImageSize do
|
|
20
20
|
].each do |name, format, width, height|
|
21
21
|
path = File.join(File.dirname(__FILE__), name)
|
22
22
|
|
23
|
-
it "should get format and dimensions for #{name} given
|
23
|
+
it "should get format and dimensions for #{name} given IO" do
|
24
24
|
File.open(path, 'rb') do |fh|
|
25
25
|
is = ImageSize.new(fh)
|
26
26
|
[is.format, is.width, is.height].should == [format, width, height]
|
@@ -40,5 +40,29 @@ describe ImageSize do
|
|
40
40
|
[is.format, is.width, is.height].should == [format, width, height]
|
41
41
|
end
|
42
42
|
end
|
43
|
+
|
44
|
+
it "should get format and dimensions for #{name} given Tempfile" do
|
45
|
+
file_data = File.open(path, 'rb') { |fh| fh.read }
|
46
|
+
Tempfile.open(name) do |tf|
|
47
|
+
tf.write(file_data)
|
48
|
+
tf.rewind
|
49
|
+
is = ImageSize.new(tf)
|
50
|
+
[is.format, is.width, is.height].should == [format, width, height]
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
it "should get format and dimensions for #{name} given IO when run twice" do
|
55
|
+
File.open(path, 'rb') do |fh|
|
56
|
+
is = ImageSize.new(fh)
|
57
|
+
[is.format, is.width, is.height].should == [format, width, height]
|
58
|
+
is = ImageSize.new(fh)
|
59
|
+
[is.format, is.width, is.height].should == [format, width, height]
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
it "should get format and dimensions for #{name} as path" do
|
64
|
+
is = ImageSize.path(path)
|
65
|
+
[is.format, is.width, is.height].should == [format, width, height]
|
66
|
+
end
|
43
67
|
end
|
44
68
|
end
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: image_size
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 19
|
5
5
|
prerelease:
|
6
6
|
segments:
|
7
7
|
- 1
|
8
|
+
- 1
|
8
9
|
- 0
|
9
|
-
|
10
|
-
version: 1.0.6
|
10
|
+
version: 1.1.0
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Keisuke Minami
|
@@ -16,7 +16,7 @@ autorequire:
|
|
16
16
|
bindir: bin
|
17
17
|
cert_chain: []
|
18
18
|
|
19
|
-
date: 2012-02-
|
19
|
+
date: 2012-02-25 00:00:00 Z
|
20
20
|
dependencies:
|
21
21
|
- !ruby/object:Gem::Dependency
|
22
22
|
name: rspec
|
@@ -32,7 +32,7 @@ dependencies:
|
|
32
32
|
version: "0"
|
33
33
|
type: :development
|
34
34
|
version_requirements: *id001
|
35
|
-
description: "Measure following file dimensions:
|
35
|
+
description: "Measure following file dimensions: bmp, gif, jpeg, pbm, pcx, pgm, png, ppm, psd, swf, tiff, xbm, xpm"
|
36
36
|
email:
|
37
37
|
executables: []
|
38
38
|
|
@@ -88,7 +88,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
88
88
|
requirements: []
|
89
89
|
|
90
90
|
rubyforge_project: image_size
|
91
|
-
rubygems_version: 1.8.
|
91
|
+
rubygems_version: 1.8.16
|
92
92
|
signing_key:
|
93
93
|
specification_version: 3
|
94
94
|
summary: Measure image size using pure Ruby
|