image_size 1.5.0 → 2.1.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.
data/Gemfile CHANGED
@@ -1,3 +1,5 @@
1
- source "https://rubygems.org"
1
+ # frozen_string_literal: true
2
+
3
+ source 'https://rubygems.org'
2
4
 
3
5
  gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,56 @@
1
+ Ruby is copyrighted free software by Keisuke Minami <keisuke@rccn.com>.
2
+ You can redistribute it and/or modify it under either the terms of the GPL
3
+ version 2 (see the file GPL), or the conditions below:
4
+
5
+ 1. You may make and give away verbatim copies of the source form of the
6
+ software without restriction, provided that you duplicate all of the
7
+ original copyright notices and associated disclaimers.
8
+
9
+ 2. You may modify your copy of the software in any way, provided that
10
+ you do at least ONE of the following:
11
+
12
+ a) place your modifications in the Public Domain or otherwise
13
+ make them Freely Available, such as by posting said
14
+ modifications to Usenet or an equivalent medium, or by allowing
15
+ the author to include your modifications in the software.
16
+
17
+ b) use the modified software only within your corporation or
18
+ organization.
19
+
20
+ c) give non-standard binaries non-standard names, with
21
+ instructions on where to get the original software distribution.
22
+
23
+ d) make other distribution arrangements with the author.
24
+
25
+ 3. You may distribute the software in object code or binary form,
26
+ provided that you do at least ONE of the following:
27
+
28
+ a) distribute the binaries and library files of the software,
29
+ together with instructions (in the manual page or equivalent)
30
+ on where to get the original distribution.
31
+
32
+ b) accompany the distribution with the machine-readable source of
33
+ the software.
34
+
35
+ c) give non-standard binaries non-standard names, with
36
+ instructions on where to get the original software distribution.
37
+
38
+ d) make other distribution arrangements with the author.
39
+
40
+ 4. You may modify and include the part of the software into any other
41
+ software (possibly commercial). But some files in the distribution
42
+ are not written by the author, so that they are not under these terms.
43
+
44
+ For the list of those files and their copying conditions, see the
45
+ file LEGAL.
46
+
47
+ 5. The scripts and library files supplied as input to or produced as
48
+ output from the software do not automatically fall under the
49
+ copyright of the software, but belong to whomever generated them,
50
+ and may be sold commercially, and may be aggregated with this
51
+ software.
52
+
53
+ 6. THIS SOFTWARE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR
54
+ IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
55
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
56
+ PURPOSE.
data/README.markdown CHANGED
@@ -1,55 +1,91 @@
1
+ [![Gem Version](https://img.shields.io/gem/v/image_size.svg?style=flat)](https://rubygems.org/gems/image_size)
2
+ [![Build Status](https://github.com/toy/image_size/actions/workflows/check.yml/badge.svg)](https://github.com/toy/image_size/actions/workflows/check.yml)
3
+
1
4
  # image_size
2
5
 
3
6
  measure image size using pure Ruby
4
- formats: `apng`, `bmp`, `cur`, `gif`, `jpeg`, `ico`, `mng`, `pbm`, `pcx`, `pgm`, `png`, `ppm`, `psd`, `swf`, `tiff`, `xbm`, `xpm`, `webp`
7
+ formats: `apng`, `bmp`, `cur`, `gif`, `ico`, `j2c`, `jp2`, `jpeg`, `jpx`, `mng`, `pam`, `pbm`, `pcx`, `pgm`, `png`, `ppm`, `psd`, `svg`, `swf`, `tiff`, `webp`, `xbm`, `xpm`
5
8
 
6
- [![Build Status](https://travis-ci.org/toy/image_size.png?branch=master)](https://travis-ci.org/toy/image_size)
9
+ ## Installation
7
10
 
8
- ## Download
11
+ ```sh
12
+ gem install image_size
13
+ ```
9
14
 
10
- The latest version of image\_size can be found at http://github.com/toy/image_size
15
+ ### Bundler
11
16
 
12
- ## Installation
17
+ Add to your `Gemfile`:
13
18
 
14
- gem install image_size
19
+ ```ruby
20
+ gem 'image_size', '~> 2.0'
21
+ ```
15
22
 
16
23
  ## Usage
17
24
 
18
- image_size = ImageSize.path('spec/images/jpeg/320x240.jpeg')
19
- image_size.format #=> :jpec
20
- image_size.width #=> 320
21
- image_size.height #=> 240
22
- image_size.size #=> [320, 240]
25
+ ```ruby
26
+ image_size = ImageSize.path('spec/test.jpg')
23
27
 
24
- `width` and `height` have aliases `w` and `h`.
28
+ image_size.format #=> :jpec
29
+ image_size.width #=> 320
30
+ image_size.height #=> 240
31
+ image_size.w #=> 320
32
+ image_size.h #=> 240
33
+ image_size.size #=> [320, 240]
34
+ ```
25
35
 
26
- ## Examples
36
+ Or using `IO` object:
27
37
 
28
- require 'image_size'
38
+ ```ruby
39
+ image_size = File.open('spec/test.jpg', 'rb'){ |fh| ImageSize.new(fh) }
40
+ ```
29
41
 
30
- ImageSize.path('spec/test.jpg')
42
+ Any object responding to `read` and `eof?`:
31
43
 
32
- open('spec/test.jpg', 'rb') do |fh|
33
- ImageSize.new(fh)
34
- end
44
+ ```ruby
45
+ require 'image_size'
35
46
 
47
+ image_size = ImageSize.new(ARGF)
48
+ ```
36
49
 
37
- require 'image_size'
38
- require 'open-uri'
50
+ Works with `open-uri` if needed:
39
51
 
40
- open('http://www.rubycgi.org/image/ruby_gtk_book_title.jpg', 'rb') do |fh|
41
- ImageSize.new(fh)
42
- end
52
+ ```ruby
53
+ require 'image_size'
54
+ require 'open-uri'
43
55
 
44
- open('http://www.rubycgi.org/image/ruby_gtk_book_title.jpg', 'rb') do |fh|
45
- data = fh.read
46
- ImageSize.new(data)
47
- end
56
+ image_size = URI.parse('http://www.rubycgi.org/image/ruby_gtk_book_title.jpg').open('rb') do |fh|
57
+ ImageSize.new(fh)
58
+ end
48
59
 
49
- ## Licence
60
+ image_size = open('http://www.rubycgi.org/image/ruby_gtk_book_title.jpg', 'rb') do |fh|
61
+ ImageSize.new(fh)
62
+ end
63
+ ```
64
+
65
+ Note that starting with version `2.0.0` the object given to `ImageSize` will not be rewound before or after use.
66
+ So rewind if needed before passing to `ImageSize` and/or rewind after passing to `ImageSize` before reading data.
67
+
68
+ ```ruby
69
+ require 'image_size'
50
70
 
51
- This code is free to use under the terms of the Ruby's licence.
71
+ File.open('spec/test.jpg', 'rb') do |fh|
72
+ image_size = ImageSize.new(fh)
73
+
74
+ fh.rewind
75
+ data = fh.read
76
+ end
77
+
78
+ File.open('spec/test.jpg', 'rb') do |fh|
79
+ data = fh.read
80
+ fh.rewind
81
+
82
+ image_size = ImageSize.new(fh)
83
+ end
84
+ ```
85
+
86
+ ## Licence
52
87
 
53
- ## Contact
88
+ This code is free to use under the terms of the [Ruby's licence](LICENSE.txt).
54
89
 
55
- Original author: "Keisuke Minami": mailto:keisuke@rccn.com
90
+ Original author: Keisuke Minami <keisuke@rccn.com>.\
91
+ Further development 2010-2020 Ivan Kuchin https://github.com/toy/image_size
data/image_size.gemspec CHANGED
@@ -2,14 +2,19 @@
2
2
 
3
3
  Gem::Specification.new do |s|
4
4
  s.name = 'image_size'
5
- s.version = '1.5.0'
5
+ s.version = '2.1.1'
6
6
  s.summary = %q{Measure image size using pure Ruby}
7
- s.description = %q{Measure following file dimensions: apng, bmp, cur, gif, jpeg, ico, mng, pbm, pcx, pgm, png, ppm, psd, swf, tiff, xbm, xpm, webp}
8
- s.homepage = "http://github.com/toy/#{s.name}"
7
+ s.description = %q{Measure following file dimensions: apng, bmp, cur, gif, ico, j2c, jp2, jpeg, jpx, mng, pam, pbm, pcx, pgm, png, ppm, psd, svg, swf, tiff, webp, xbm, xpm}
8
+ s.homepage = "https://github.com/toy/#{s.name}"
9
9
  s.authors = ['Keisuke Minami', 'Ivan Kuchin']
10
10
  s.license = 'Ruby'
11
11
 
12
- s.rubyforge_project = s.name
12
+ s.metadata = {
13
+ 'bug_tracker_uri' => "https://github.com/toy/#{s.name}/issues",
14
+ 'changelog_uri' => "https://github.com/toy/#{s.name}/blob/master/CHANGELOG.markdown",
15
+ 'documentation_uri' => "https://www.rubydoc.info/gems/#{s.name}/#{s.version}",
16
+ 'source_code_uri' => "https://github.com/toy/#{s.name}",
17
+ }
13
18
 
14
19
  s.files = `git ls-files`.split("\n")
15
20
  s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
@@ -17,4 +22,7 @@ Gem::Specification.new do |s|
17
22
  s.require_paths = %w[lib]
18
23
 
19
24
  s.add_development_dependency 'rspec', '~> 3.0'
25
+ if RUBY_VERSION >= '2.4'
26
+ s.add_development_dependency 'rubocop', '~> 1.0'
27
+ end
20
28
  end
data/lib/image_size.rb CHANGED
@@ -1,11 +1,13 @@
1
- # -- coding: ASCII-8BIT --
2
- #
1
+ # encoding: BINARY
2
+ # frozen_string_literal: true
3
+
3
4
  require 'stringio'
4
- require 'tempfile'
5
5
 
6
+ # Determine image format and size
6
7
  class ImageSize
7
8
  class FormatError < StandardError; end
8
9
 
10
+ # Array joining with 'x'
9
11
  class Size < Array
10
12
  # join using 'x'
11
13
  def to_s
@@ -15,34 +17,26 @@ class ImageSize
15
17
 
16
18
  class ImageReader # :nodoc:
17
19
  attr_reader :data
20
+
18
21
  def initialize(data_or_io)
19
- @io = case data_or_io
20
- when IO, StringIO, Tempfile
21
- data_or_io.dup.tap(&:rewind)
22
- when String
22
+ @io = if data_or_io.is_a?(String)
23
23
  StringIO.new(data_or_io)
24
+ elsif data_or_io.respond_to?(:read) && data_or_io.respond_to?(:eof?)
25
+ data_or_io
24
26
  else
25
- raise ArgumentError.new("expected instance of IO, StringIO, Tempfile or String, got #{data_or_io.class}")
27
+ raise ArgumentError, "expected data as String or an object responding to read and eof?, got #{data_or_io.class}"
26
28
  end
27
- @read = 0
28
- @data = ''
29
- end
30
-
31
- def close
32
- @io.rewind
33
- @io.close if IO === @io
29
+ @data = String.new # not frozen
34
30
  end
35
31
 
36
32
  CHUNK = 1024
37
33
  def [](offset, length)
38
- while offset + length > @read
39
- @read += CHUNK
40
- if data = @io.read(CHUNK)
41
- if data.respond_to?(:encoding)
42
- data.force_encoding(@data.encoding)
43
- end
44
- @data << data
45
- end
34
+ while !@io.eof? && @data.length < offset + length
35
+ data = @io.read(CHUNK)
36
+ break unless data
37
+
38
+ data.force_encoding(@data.encoding) if data.respond_to?(:encoding)
39
+ @data << data
46
40
  end
47
41
  @data[offset, length]
48
42
  end
@@ -50,7 +44,7 @@ class ImageSize
50
44
 
51
45
  # Given path to image finds its format, width and height
52
46
  def self.path(path)
53
- open(path, 'rb'){ |f| new(f) }
47
+ File.open(path, 'rb'){ |f| new(f) }
54
48
  end
55
49
 
56
50
  # Used for svg
@@ -63,13 +57,13 @@ class ImageSize
63
57
  @dpi = dpi.to_f
64
58
  end
65
59
 
66
- # Given image as IO, StringIO, Tempfile or String finds its format and dimensions
60
+ # Given image as any class responding to read and eof? or data as String, finds its format and dimensions
67
61
  def initialize(data)
68
62
  ir = ImageReader.new(data)
69
- if @format = detect_format(ir)
70
- @width, @height = self.send("size_of_#{@format}", ir)
71
- end
72
- ir.close
63
+ @format = detect_format(ir)
64
+ return unless @format
65
+
66
+ @width, @height = send("size_of_#{@format}", ir)
73
67
  end
74
68
 
75
69
  # Image format
@@ -77,11 +71,11 @@ class ImageSize
77
71
 
78
72
  # Image width
79
73
  attr_reader :width
80
- alias :w :width
74
+ alias_method :w, :width
81
75
 
82
76
  # Image height
83
77
  attr_reader :height
84
- alias :h :height
78
+ alias_method :h, :height
85
79
 
86
80
  # get image width and height as an array which to_s method returns "#{width}x#{height}"
87
81
  def size
@@ -90,29 +84,29 @@ class ImageSize
90
84
 
91
85
  private
92
86
 
93
- SVG_R = /<svg\b([^>]*)>/
87
+ SVG_R = /<svg\b([^>]*)>/.freeze
88
+ XML_R = /<\?xml|<!--/.freeze
94
89
  def detect_format(ir)
95
90
  head = ir[0, 1024]
96
91
  case
97
- when head[0, 6] =~ /GIF8[79]a/ then :gif
98
- when head[0, 8] == "\211PNG\r\n\032\n" then detect_png_type(ir)
99
- when head[0, 8] == "\212MNG\r\n\032\n" then :mng
100
- when head[0, 2] == "\377\330" then :jpeg
101
- when head[0, 2] == 'BM' then :bmp
102
- when head[0, 2] =~ /P[1-7]/ then :ppm
103
- when head =~ /\#define\s+\S+\s+\d+/ then :xbm
104
- when head[0, 4] == "II*\000" then :tiff
105
- when head[0, 4] == "MM\000*" then :tiff
106
- when head =~ /\/\* XPM \*\// then :xpm
107
- when head[0, 4] == '8BPS' then :psd
108
- when head[0, 3] =~ /[FC]WS/ then :swf
109
- when head[SVG_R] ||
110
- head =~ /<\?xml|<!--/ && ir[0, 4096][SVG_R]
111
- then :svg
112
- when head[0, 2] =~ /\n[\000-\005]/ then :pcx
113
- when head[0, 12] =~ /RIFF(?m:....)WEBP/ then :webp
114
- when head[0, 4] == "\000\000\001\000" then :ico
115
- when head[0, 4] == "\000\000\002\000" then :cur
92
+ when head[0, 6] =~ /GIF8[79]a/ then :gif
93
+ when head[0, 8] == "\211PNG\r\n\032\n" then detect_png_type(ir)
94
+ when head[0, 8] == "\212MNG\r\n\032\n" then :mng
95
+ when head[0, 2] == "\377\330" then :jpeg
96
+ when head[0, 2] == 'BM' then :bmp
97
+ when head[0, 3] =~ /P[1-6]\s|P7\n/ then detect_pnm_type(ir)
98
+ when head =~ /\#define\s+\S+\s+\d+/ then :xbm
99
+ when %W[II*\0 MM\0*].include?(head[0, 4]) then :tiff
100
+ when head =~ %r{/\* XPM \*/} then :xpm
101
+ when head[0, 4] == '8BPS' then :psd
102
+ when head[0, 3] =~ /[FC]WS/ then :swf
103
+ when head =~ SVG_R || (head =~ XML_R && ir[0, 4096][SVG_R]) then :svg
104
+ when head[0, 2] =~ /\n[\0-\5]/ then :pcx
105
+ when head[0, 12] =~ /RIFF(?m:....)WEBP/ then :webp
106
+ when head[0, 4] == "\0\0\1\0" then :ico
107
+ when head[0, 4] == "\0\0\2\0" then :cur
108
+ when head[0, 12] == "\0\0\0\fjP \r\n\207\n" then detect_jpeg2000_type(ir)
109
+ when head[0, 4] == "\377O\377Q" then :j2c
116
110
  end
117
111
  end
118
112
 
@@ -129,6 +123,26 @@ private
129
123
  :png
130
124
  end
131
125
 
126
+ def detect_pnm_type(ir)
127
+ case ir[0, 2]
128
+ when 'P1', 'P4' then :pbm
129
+ when 'P2', 'P5' then :pgm
130
+ when 'P3', 'P6' then :ppm
131
+ when 'P7' then :pam
132
+ end
133
+ end
134
+
135
+ def detect_jpeg2000_type(ir)
136
+ return unless ir[16, 4] == 'ftyp'
137
+
138
+ # using xl-box would be weird, but doesn't seem to contradict specification
139
+ skip = ir[12, 4] == "\0\0\0\1" ? 16 : 8
140
+ case ir[12 + skip, 4]
141
+ when 'jp2 ' then :jp2
142
+ when 'jpx ' then :jpx
143
+ end
144
+ end
145
+
132
146
  def size_of_gif(ir)
133
147
  ir[6, 4].unpack('vv')
134
148
  end
@@ -137,6 +151,7 @@ private
137
151
  unless ir[12, 4] == 'MHDR'
138
152
  raise FormatError, 'MHDR not in place for MNG'
139
153
  end
154
+
140
155
  ir[16, 8].unpack('NN')
141
156
  end
142
157
 
@@ -144,16 +159,17 @@ private
144
159
  unless ir[12, 4] == 'IHDR'
145
160
  raise FormatError, 'IHDR not in place for PNG'
146
161
  end
162
+
147
163
  ir[16, 8].unpack('NN')
148
164
  end
149
165
  alias_method :size_of_apng, :size_of_png
150
166
 
151
- JpegCodeCheck = [
152
- "\xc0", "\xc1", "\xc2", "\xc3",
153
- "\xc5", "\xc6", "\xc7",
154
- "\xc9", "\xca", "\xcb",
155
- "\xcd", "\xce", "\xcf",
156
- ] # :nodoc:
167
+ JPEG_CODE_CHECK = %W[
168
+ \xC0 \xC1 \xC2 \xC3
169
+ \xC5 \xC6 \xC7
170
+ \xC9 \xCA \xCB
171
+ \xCD \xCE \xCF
172
+ ].freeze
157
173
  def size_of_jpeg(ir)
158
174
  section_marker = "\xFF"
159
175
  offset = 2
@@ -162,12 +178,13 @@ private
162
178
  offset += 1 until section_marker != ir[offset + 1, 1]
163
179
  raise FormatError, 'EOF in JPEG' if ir[offset, 1].nil?
164
180
 
165
- marker, code, length = ir[offset, 4].unpack('aan')
181
+ _marker, code, length = ir[offset, 4].unpack('aan')
166
182
  offset += 4
167
183
 
168
- if JpegCodeCheck.include?(code)
184
+ if JPEG_CODE_CHECK.include?(code)
169
185
  return ir[offset + 1, 4].unpack('nn').reverse
170
186
  end
187
+
171
188
  offset += length - 2
172
189
  end
173
190
  end
@@ -191,12 +208,38 @@ private
191
208
  header = ir[0, 1024]
192
209
  header.gsub!(/^\#[^\n\r]*/m, '')
193
210
  header =~ /^(P[1-6])\s+?(\d+)\s+?(\d+)/m
194
- case $1
195
- when 'P1', 'P4' then @format = :pbm
196
- when 'P2', 'P5' then @format = :pgm
197
- end
198
211
  [$2.to_i, $3.to_i]
199
212
  end
213
+ alias_method :size_of_pbm, :size_of_ppm
214
+ alias_method :size_of_pgm, :size_of_ppm
215
+
216
+ def size_of_pam(ir)
217
+ width = height = nil
218
+ offset = 3
219
+ loop do
220
+ if ir[offset, 1] == '#'
221
+ offset += 1 until ["\n", '', nil].include?(ir[offset, 1])
222
+ offset += 1
223
+ else
224
+ chunk = ir[offset, 32]
225
+ case chunk
226
+ when /\AWIDTH (\d+)\n/
227
+ width = $1.to_i
228
+ when /\AHEIGHT (\d+)\n/
229
+ height = $1.to_i
230
+ when /\AENDHDR\n/
231
+ break
232
+ when /\A(?:DEPTH|MAXVAL) \d+\n/, /\ATUPLTYPE \S+\n/
233
+ # ignore
234
+ else
235
+ raise FormatError, "Unexpected data in PAM header: #{chunk.inspect}"
236
+ end
237
+ offset += $&.length
238
+ break if width && height
239
+ end
240
+ end
241
+ [width, height]
242
+ end
200
243
 
201
244
  def size_of_xbm(ir)
202
245
  ir[0, 1024] =~ /^\#define\s*\S*\s*(\d+)\s*\n\#define\s*\S*\s*(\d+)/mi
@@ -209,6 +252,7 @@ private
209
252
  if data.length != length
210
253
  raise FormatError, 'XPM size not found'
211
254
  end
255
+
212
256
  length += 1024
213
257
  end
214
258
  [$1.to_i, $2.to_i]
@@ -219,7 +263,7 @@ private
219
263
  end
220
264
 
221
265
  def size_of_tiff(ir)
222
- endian2b = (ir[0, 4] == "II*\000") ? 'v' : 'n'
266
+ endian2b = ir[0, 4] == "II*\000" ? 'v' : 'n'
223
267
  endian4b = endian2b.upcase
224
268
  packspec = [nil, 'C', nil, endian2b, endian4b, nil, 'c', nil, endian2b, endian4b]
225
269
 
@@ -232,17 +276,18 @@ private
232
276
  until width && height
233
277
  ifd = ir[offset, 12]
234
278
  raise FormatError, 'Reached end of directory entries in TIFF' if ifd.nil? || offset > num_dirent
279
+
235
280
  tag, type = ifd.unpack(endian2b * 2)
236
281
  offset += 12
237
282
 
238
- unless packspec[type].nil?
239
- value = ifd[8, 4].unpack(packspec[type])[0]
240
- case tag
241
- when 0x0100
242
- width = value
243
- when 0x0101
244
- height = value
245
- end
283
+ next if packspec[type].nil?
284
+
285
+ value = ifd[8, 4].unpack(packspec[type])[0]
286
+ case tag
287
+ when 0x0100
288
+ width = value
289
+ when 0x0101
290
+ height = value
246
291
  end
247
292
  end
248
293
  [width, height]
@@ -257,7 +302,7 @@ private
257
302
  value_bit_length = ir[8, 1].unpack('B5').first.to_i(2)
258
303
  bit_length = 5 + value_bit_length * 4
259
304
  rect_bits = ir[8, bit_length / 8 + 1].unpack("B#{bit_length}").first
260
- values = rect_bits.unpack('@5' + "a#{value_bit_length}" * 4).map{ |bits| bits.to_i(2) }
305
+ values = rect_bits[5..-1].unpack("a#{value_bit_length}" * 4).map{ |bits| bits.to_i(2) }
261
306
  x_min, x_max, y_min, y_max = values
262
307
  [(x_max - x_min) / 20, (y_max - y_min) / 20]
263
308
  end
@@ -269,18 +314,18 @@ private
269
314
  end
270
315
  dpi = self.class.dpi
271
316
  [attributes['width'], attributes['height']].map do |length|
272
- if length
273
- pixels = case length.downcase.strip[/(?:em|ex|px|in|cm|mm|pt|pc|%)\z/]
274
- when 'em', 'ex', '%' then nil
275
- when 'in' then length.to_f * dpi
276
- when 'cm' then length.to_f * dpi / 2.54
277
- when 'mm' then length.to_f * dpi / 25.4
278
- when 'pt' then length.to_f * dpi / 72
279
- when 'pc' then length.to_f * dpi / 6
280
- else length.to_f
281
- end
282
- pixels.round if pixels
317
+ next unless length
318
+
319
+ pixels = case length.downcase.strip[/(?:em|ex|px|in|cm|mm|pt|pc|%)\z/]
320
+ when 'em', 'ex', '%' then nil
321
+ when 'in' then length.to_f * dpi
322
+ when 'cm' then length.to_f * dpi / 2.54
323
+ when 'mm' then length.to_f * dpi / 25.4
324
+ when 'pt' then length.to_f * dpi / 72
325
+ when 'pc' then length.to_f * dpi / 6
326
+ else length.to_f
283
327
  end
328
+ pixels.round if pixels
284
329
  end
285
330
  end
286
331
 
@@ -301,4 +346,44 @@ private
301
346
  [(w16 | w8 << 16) + 1, (h16 | h8 << 16) + 1]
302
347
  end
303
348
  end
349
+
350
+ def size_of_jp2(ir)
351
+ offset = 12
352
+ stop = nil
353
+ in_header = false
354
+ loop do
355
+ break if stop && offset >= stop
356
+ break if ir[offset, 4] == '' || ir[offset, 4].nil?
357
+
358
+ size = ir[offset, 4].unpack('N')[0]
359
+ type = ir[offset + 4, 4]
360
+
361
+ data_offset = 8
362
+ case size
363
+ when 1
364
+ size = ir[offset, 8].unpack('Q>')[0]
365
+ data_offset = 16
366
+ raise FormatError, "Unexpected xl-box size #{size}" if (1..15).include?(size)
367
+ when 2..7
368
+ raise FormatError, "Reserved box size #{size}"
369
+ end
370
+
371
+ if type == 'jp2h'
372
+ stop = offset + size unless size.zero?
373
+ offset += data_offset
374
+ in_header = true
375
+ elsif in_header && type == 'ihdr'
376
+ return ir[offset + data_offset, 8].unpack('NN').reverse
377
+ else
378
+ break if size.zero? # box to the end of file
379
+
380
+ offset += size
381
+ end
382
+ end
383
+ end
384
+ alias_method :size_of_jpx, :size_of_jp2
385
+
386
+ def size_of_j2c(ir)
387
+ ir[8, 8].unpack('NN')
388
+ end
304
389
  end