image_size 2.1.2 → 3.0.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1e5bf43db03f3e3f62465528a32d65b65592240db8756ef759f17b6b2d0c1f14
4
- data.tar.gz: 4b5cfe6468ba3208528c971ce0873760688dd6a59f170d20fe5dc2934a4ca5ff
3
+ metadata.gz: 44be96b790fd826a8e6471928ce381cda6a0269bb9a24d7111142b54ff1556bb
4
+ data.tar.gz: 494d270c18e0e9583ce226e53a478bf5829ce3823cc212c392dc933445962a89
5
5
  SHA512:
6
- metadata.gz: 18e0d5c54eb5a89ec5412f7b8094fe154d02f609f0d1dc217881a9bde7236d35ac7a9fe3f4c0f82d33c57c35d9368ca0c03c411a3b2d4f09f174ed4633f7dbed
7
- data.tar.gz: 4685c93339d37b5d4b59caa697885cad1bfff21c934f0e243abcb7814eb9262bd5ecfbe7f8fcc12e8bd207aa2a1711a5bfa5f764ee74e6ddf31a4b1e926f0744
6
+ metadata.gz: e33c991f5c78de857a38591d62b6b71f9c95693b0222bdfa7141b94a0359cf76f3e94e3cdf1c424e2935a8f1f397072473c00ac53d0656aeece5ff5e077466dc
7
+ data.tar.gz: aec5519f54c0dafcc0ae424703567276161ca1c5f53306134c3c3995642a67533d7196b1671e1c74158ffed604a90d2ef61f3e7f5d175246b935954b697afe5d
@@ -28,7 +28,36 @@ jobs:
28
28
  with:
29
29
  ruby-version: "${{ matrix.ruby }}"
30
30
  bundler-cache: true
31
- - run: bundle exec rspec
31
+ - run: bundle exec rspec --format documentation
32
+ legacy:
33
+ runs-on: ubuntu-latest
34
+ container: ${{ matrix.container }}
35
+ strategy:
36
+ matrix:
37
+ container:
38
+ - rspec/ci:1.8.7
39
+ - rspec/ci:1.9.3
40
+ fail-fast: false
41
+ steps:
42
+ - uses: actions/checkout@v2
43
+ - run: bundle install
44
+ - run: bundle exec rspec --format documentation
45
+ windows:
46
+ runs-on: windows-latest
47
+ strategy:
48
+ matrix:
49
+ ruby:
50
+ - '2.6'
51
+ - '2.7'
52
+ - '3.0'
53
+ fail-fast: false
54
+ steps:
55
+ - uses: actions/checkout@v2
56
+ - uses: ruby/setup-ruby@v1
57
+ with:
58
+ ruby-version: "${{ matrix.ruby }}"
59
+ bundler-cache: true
60
+ - run: bundle exec rspec --format documentation
32
61
  rubocop:
33
62
  runs-on: ubuntu-latest
34
63
  steps:
data/.rubocop.yml CHANGED
@@ -15,6 +15,9 @@ Layout/CaseIndentation:
15
15
  Layout/EndAlignment:
16
16
  EnforcedStyleAlignWith: variable
17
17
 
18
+ Layout/FirstHashElementIndentation:
19
+ EnforcedStyle: consistent
20
+
18
21
  Layout/LineLength:
19
22
  Max: 120
20
23
 
@@ -65,6 +68,9 @@ Style/HashTransformValues:
65
68
  Style/IfUnlessModifier:
66
69
  Enabled: false
67
70
 
71
+ Style/NumericPredicate:
72
+ Enabled: false
73
+
68
74
  Style/ParallelAssignment:
69
75
  Enabled: false
70
76
 
data/CHANGELOG.markdown CHANGED
@@ -2,6 +2,12 @@
2
2
 
3
3
  ## unreleased
4
4
 
5
+ ## v3.0.0 (2021-10-17)
6
+
7
+ * Read only required chunks of data for files and seekable IOs [@toy](https://github.com/toy)
8
+ * Raise `FormatError` whenever reading data returns less data than expected [#12](https://github.com/toy/image_size/issues/12) [@toy](https://github.com/toy)
9
+ * Add `w`/`width` and `h`/`height` accessors to `Size` [@toy](https://github.com/toy)
10
+
5
11
  ## v2.1.2 (2021-08-21)
6
12
 
7
13
  * Fix for pcx on big endian systems by forcing reading dimensions in little endian byte order [#15](https://github.com/toy/image_size/issues/15) [#16](https://github.com/toy/image_size/pull/16) [@mtasaka](https://github.com/mtasaka)
data/Gemfile CHANGED
@@ -3,3 +3,5 @@
3
3
  source 'https://rubygems.org'
4
4
 
5
5
  gemspec
6
+
7
+ gem 'webrick' if RUBY_VERSION >= '3.0'
data/README.markdown CHANGED
@@ -3,8 +3,8 @@
3
3
 
4
4
  # image_size
5
5
 
6
- measure image size using pure Ruby
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`
6
+ Measure image size using pure Ruby.
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`.
8
8
 
9
9
  ## Installation
10
10
 
@@ -23,20 +23,26 @@ gem 'image_size', '~> 2.0'
23
23
  ## Usage
24
24
 
25
25
  ```ruby
26
- image_size = ImageSize.path('spec/test.jpg')
26
+ image_size = ImageSize.path('spec/images/jpeg/436x429.jpeg')
27
27
 
28
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]
29
+ image_size.width #=> 436
30
+ image_size.height #=> 429
31
+ image_size.w #=> 436
32
+ image_size.h #=> 429
33
+ image_size.size #=> [436, 429]
34
+ image_size.size.to_s #=> "436x429"
35
+ "#{image_size.size}" #=> "436x429"
36
+ image_size.size.width #=> 436
37
+ image_size.size.height #=> 429
38
+ image_size.size.w #=> 436
39
+ image_size.size.h #=> 429
34
40
  ```
35
41
 
36
42
  Or using `IO` object:
37
43
 
38
44
  ```ruby
39
- image_size = File.open('spec/test.jpg', 'rb'){ |fh| ImageSize.new(fh) }
45
+ image_size = File.open('spec/images/jpeg/436x429.jpeg', 'rb'){ |fh| ImageSize.new(fh) }
40
46
  ```
41
47
 
42
48
  Any object responding to `read` and `eof?`:
@@ -68,14 +74,14 @@ So rewind if needed before passing to `ImageSize` and/or rewind after passing to
68
74
  ```ruby
69
75
  require 'image_size'
70
76
 
71
- File.open('spec/test.jpg', 'rb') do |fh|
77
+ File.open('spec/images/jpeg/436x429.jpeg', 'rb') do |fh|
72
78
  image_size = ImageSize.new(fh)
73
79
 
74
80
  fh.rewind
75
81
  data = fh.read
76
82
  end
77
83
 
78
- File.open('spec/test.jpg', 'rb') do |fh|
84
+ File.open('spec/images/jpeg/436x429.jpeg', 'rb') do |fh|
79
85
  data = fh.read
80
86
  fh.rewind
81
87
 
@@ -88,4 +94,4 @@ end
88
94
  This code is free to use under the terms of the [Ruby's licence](LICENSE.txt).
89
95
 
90
96
  Original author: Keisuke Minami <keisuke@rccn.com>.\
91
- Further development 2010-2020 Ivan Kuchin https://github.com/toy/image_size
97
+ Further development 2010-2021 Ivan Kuchin https://github.com/toy/image_size
data/image_size.gemspec CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  Gem::Specification.new do |s|
4
4
  s.name = 'image_size'
5
- s.version = '2.1.2'
5
+ s.version = '3.0.0'
6
6
  s.summary = %q{Measure image size using pure Ruby}
7
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
8
  s.homepage = "https://github.com/toy/#{s.name}"
@@ -24,5 +24,6 @@ Gem::Specification.new do |s|
24
24
  s.add_development_dependency 'rspec', '~> 3.0'
25
25
  if RUBY_VERSION >= '2.4'
26
26
  s.add_development_dependency 'rubocop', '~> 1.0'
27
+ s.add_development_dependency 'rubocop-rspec', '~> 2.0'
27
28
  end
28
29
  end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'image_size/reader'
4
+
5
+ class ImageSize
6
+ module ChunkyReader # :nodoc:
7
+ include Reader
8
+
9
+ # Size of a chunk in which to read
10
+ def chunk_size
11
+ 4096
12
+ end
13
+
14
+ # Including class should define method chunk that accepts the chunk number
15
+ # and returns a string of chunk_size length or shorter for last chunk, or
16
+ # nil for further chunks.
17
+ # Determines required chunks, takes parts of them to construct desired
18
+ # substring, behaves same as str[start, length] except start can't be
19
+ # negative.
20
+ def [](offset, length)
21
+ raise ArgumentError, "expected offset not to be negative, got #{offset}" if offset < 0
22
+ return if length < 0
23
+
24
+ first = offset / chunk_size
25
+ return unless (first_chunk = chunk(first))
26
+
27
+ last = (offset + length - 1) / chunk_size
28
+
29
+ if first >= last
30
+ first_chunk[offset - (first * chunk_size), length]
31
+ else
32
+ return unless (first_piece = first_chunk[offset - (first * chunk_size), chunk_size])
33
+
34
+ chunks = (first.succ...last).map{ |i| chunk(i) }.unshift(first_piece)
35
+
36
+ if (last_chunk = chunk(last))
37
+ chunks.push(last_chunk[0, offset + length - (last * chunk_size)])
38
+ end
39
+
40
+ chunks.join
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'pathname'
4
+ require 'stringio'
5
+
6
+ class ImageSize
7
+ module Reader # :nodoc:
8
+ class << self
9
+ def open(input)
10
+ case
11
+ when input.is_a?(String)
12
+ yield StringReader.new(input)
13
+ when input.is_a?(StringIO)
14
+ yield StringReader.new(input.string)
15
+ when input.respond_to?(:read) && input.respond_to?(:eof?)
16
+ yield for_io(input)
17
+ when input.is_a?(Pathname)
18
+ input.open('rb'){ |f| yield for_io(f) }
19
+ else
20
+ raise ArgumentError, "expected data as String or an object responding to read and eof?, got #{input.class}"
21
+ end
22
+ end
23
+
24
+ private
25
+
26
+ def for_io(io)
27
+ if io.respond_to?(:stat) && !io.stat.file?
28
+ StreamIOReader.new(io)
29
+ else
30
+ begin
31
+ io.seek(0, IO::SEEK_CUR)
32
+ SeekableIOReader.new(io)
33
+ rescue Errno::ESPIPE, Errno::EINVAL
34
+ StreamIOReader.new(io)
35
+ end
36
+ end
37
+ end
38
+ end
39
+
40
+ def fetch(offset, length)
41
+ chunk = self[offset, length]
42
+
43
+ unless chunk && chunk.length == length
44
+ raise FormatError, "Expected #{length} bytes at offset #{offset}, got #{chunk.inspect}"
45
+ end
46
+
47
+ chunk
48
+ end
49
+
50
+ def unpack(offset, length, format)
51
+ fetch(offset, length).unpack(format)
52
+ end
53
+
54
+ if ''.respond_to?(:unpack1)
55
+ def unpack1(offset, length, format)
56
+ fetch(offset, length).unpack1(format)
57
+ end
58
+ else
59
+ def unpack1(offset, length, format)
60
+ fetch(offset, length).unpack(format)[0]
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'image_size/chunky_reader'
4
+
5
+ class ImageSize
6
+ class SeekableIOReader # :nodoc:
7
+ include ChunkyReader
8
+
9
+ def initialize(io)
10
+ @io = io
11
+ @pos = 0
12
+ @chunks = {}
13
+ end
14
+
15
+ private
16
+
17
+ def chunk(i)
18
+ unless @chunks.key?(i)
19
+ @io.seek((chunk_size * i) - @pos, IO::SEEK_CUR)
20
+ data = @io.read(chunk_size)
21
+ @pos += data.length
22
+ @chunks[i] = data
23
+ end
24
+
25
+ @chunks[i]
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'image_size/chunky_reader'
4
+
5
+ class ImageSize
6
+ class StreamIOReader # :nodoc:
7
+ include ChunkyReader
8
+
9
+ def initialize(io)
10
+ @io = io
11
+ @chunks = []
12
+ end
13
+
14
+ private
15
+
16
+ def chunk(i)
17
+ @chunks << @io.read(chunk_size) while i >= @chunks.length && !@io.eof?
18
+
19
+ @chunks[i]
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'image_size/reader'
4
+
5
+ class ImageSize
6
+ class StringReader # :nodoc:
7
+ include Reader
8
+
9
+ def initialize(string)
10
+ @string = if string.respond_to?(:encoding) && string.encoding.name != 'ASCII-8BIT'
11
+ string.dup.force_encoding('ASCII-8BIT')
12
+ else
13
+ string
14
+ end
15
+ end
16
+
17
+ def [](offset, length)
18
+ @string[offset, length]
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,88 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'image_size/reader'
4
+ require 'image_size/chunky_reader'
5
+
6
+ require 'net/https'
7
+ require 'uri'
8
+
9
+ # This is a hacky experiment and not part of public API
10
+ #
11
+ # It adds ability to fetch size of image from http server while downloading only
12
+ # needed chunks if the server recognises Range header
13
+ class ImageSize
14
+ class URIReader # :nodoc:
15
+ include ChunkyReader
16
+
17
+ def initialize(uri, redirects = 5)
18
+ if !@http || @http.address != uri.host || @http.port != uri.port
19
+ @http.finish if @http
20
+ @http = Net::HTTP.new(uri.host, uri.port)
21
+ @http.use_ssl = true if uri.scheme == 'https'
22
+ @http.start
23
+ end
24
+
25
+ @request_uri = uri.request_uri
26
+ response = request_chunk(0)
27
+
28
+ case response
29
+ when Net::HTTPRedirection
30
+ raise "Too many redirects: #{response['location']}" unless redirects > 0
31
+
32
+ initialize(uri + response['location'], redirects - 1)
33
+ when Net::HTTPOK
34
+ @body = response.body
35
+ when Net::HTTPPartialContent
36
+ @chunks = { 0 => response.body }
37
+ else
38
+ raise "Unexpected response: #{response}"
39
+ end
40
+ end
41
+
42
+ def [](offset, length)
43
+ if @body
44
+ @body[offset, length]
45
+ else
46
+ super
47
+ end
48
+ end
49
+
50
+ def chunk(i)
51
+ unless @chunks.key?(i)
52
+ response = request_chunk(i)
53
+ case response
54
+ when Net::HTTPPartialContent
55
+ @chunks[i] = response.body
56
+ else
57
+ raise "Unexpected response: #{response}"
58
+ end
59
+ end
60
+
61
+ @chunks[i]
62
+ end
63
+
64
+ private
65
+
66
+ def request_chunk(i)
67
+ @http.get(@request_uri, 'Range' => "bytes=#{chunk_size * i}-#{(chunk_size * (i + 1)) - 1}")
68
+ end
69
+ end
70
+
71
+ module Reader # :nodoc:
72
+ class << self
73
+ def open_with_uri(input, &block)
74
+ if input.is_a?(URI)
75
+ yield URIReader.new(input)
76
+ else
77
+ open_without_uri(input, &block)
78
+ end
79
+ end
80
+ alias_method :open_without_uri, :open
81
+ alias_method :open, :open_with_uri
82
+ end
83
+ end
84
+
85
+ def self.url(url)
86
+ new(url.is_a?(URI) ? url : URI(url))
87
+ end
88
+ end
data/lib/image_size.rb CHANGED
@@ -1,7 +1,10 @@
1
1
  # encoding: BINARY
2
2
  # frozen_string_literal: true
3
3
 
4
- require 'stringio'
4
+ require 'image_size/reader'
5
+ require 'image_size/seekable_io_reader'
6
+ require 'image_size/stream_io_reader'
7
+ require 'image_size/string_reader'
5
8
 
6
9
  # Determine image format and size
7
10
  class ImageSize
@@ -13,38 +16,23 @@ class ImageSize
13
16
  def to_s
14
17
  join('x')
15
18
  end
16
- end
17
-
18
- class ImageReader # :nodoc:
19
- attr_reader :data
20
19
 
21
- def initialize(data_or_io)
22
- @io = if data_or_io.is_a?(String)
23
- StringIO.new(data_or_io)
24
- elsif data_or_io.respond_to?(:read) && data_or_io.respond_to?(:eof?)
25
- data_or_io
26
- else
27
- raise ArgumentError, "expected data as String or an object responding to read and eof?, got #{data_or_io.class}"
28
- end
29
- @data = String.new # not frozen
20
+ # get first element
21
+ def width
22
+ self[0]
30
23
  end
24
+ alias_method :w, :width
31
25
 
32
- CHUNK = 1024
33
- def [](offset, length)
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
40
- end
41
- @data[offset, length]
26
+ # get second element
27
+ def height
28
+ self[1]
42
29
  end
30
+ alias_method :h, :height
43
31
  end
44
32
 
45
33
  # Given path to image finds its format, width and height
46
34
  def self.path(path)
47
- File.open(path, 'rb'){ |f| new(f) }
35
+ new(Pathname.new(path))
48
36
  end
49
37
 
50
38
  # Used for svg
@@ -59,11 +47,10 @@ class ImageSize
59
47
 
60
48
  # Given image as any class responding to read and eof? or data as String, finds its format and dimensions
61
49
  def initialize(data)
62
- ir = ImageReader.new(data)
63
- @format = detect_format(ir)
64
- return unless @format
65
-
66
- @width, @height = send("size_of_#{@format}", ir)
50
+ Reader.open(data) do |ir|
51
+ @format = detect_format(ir)
52
+ @width, @height = send("size_of_#{@format}", ir) if @format
53
+ end
67
54
  end
68
55
 
69
56
  # Image format
@@ -100,7 +87,7 @@ private
100
87
  when head =~ %r{/\* XPM \*/} then :xpm
101
88
  when head[0, 4] == '8BPS' then :psd
102
89
  when head[0, 3] =~ /[FC]WS/ then :swf
103
- when head =~ SVG_R || (head =~ XML_R && ir[0, 4096][SVG_R]) then :svg
90
+ when head =~ SVG_R || (head =~ XML_R && ir[0, 4096] =~ SVG_R) then :svg
104
91
  when head[0, 2] =~ /\n[\0-\5]/ then :pcx
105
92
  when head[0, 12] =~ /RIFF(?m:....)WEBP/ then :webp
106
93
  when head[0, 4] == "\0\0\1\0" then :ico
@@ -117,7 +104,7 @@ private
117
104
  break if ['IDAT', 'IEND', nil].include?(type)
118
105
  return :apng if type == 'acTL'
119
106
 
120
- length = ir[offset, 4].unpack('N')[0]
107
+ length = ir.unpack1(offset, 4, 'N')
121
108
  offset += 8 + length + 4
122
109
  end
123
110
  :png
@@ -144,7 +131,7 @@ private
144
131
  end
145
132
 
146
133
  def size_of_gif(ir)
147
- ir[6, 4].unpack('vv')
134
+ ir.unpack(6, 4, 'vv')
148
135
  end
149
136
 
150
137
  def size_of_mng(ir)
@@ -152,7 +139,7 @@ private
152
139
  raise FormatError, 'MHDR not in place for MNG'
153
140
  end
154
141
 
155
- ir[16, 8].unpack('NN')
142
+ ir.unpack(16, 8, 'NN')
156
143
  end
157
144
 
158
145
  def size_of_png(ir)
@@ -160,7 +147,7 @@ private
160
147
  raise FormatError, 'IHDR not in place for PNG'
161
148
  end
162
149
 
163
- ir[16, 8].unpack('NN')
150
+ ir.unpack(16, 8, 'NN')
164
151
  end
165
152
  alias_method :size_of_apng, :size_of_png
166
153
 
@@ -178,11 +165,11 @@ private
178
165
  offset += 1 until section_marker != ir[offset + 1, 1]
179
166
  raise FormatError, 'EOF in JPEG' if ir[offset, 1].nil?
180
167
 
181
- _marker, code, length = ir[offset, 4].unpack('aCn')
168
+ code, length = ir.unpack(offset, 4, 'xCn')
182
169
  offset += 4
183
170
 
184
171
  if JPEG_CODE_CHECK.include?(code)
185
- return ir[offset + 1, 4].unpack('nn').reverse
172
+ return ir.unpack(offset + 1, 4, 'nn').reverse
186
173
  end
187
174
 
188
175
  offset += length - 2
@@ -190,11 +177,11 @@ private
190
177
  end
191
178
 
192
179
  def size_of_bmp(ir)
193
- header_size = ir[14, 4].unpack('V')[0]
180
+ header_size = ir.unpack1(14, 4, 'V')
194
181
  if header_size == 12
195
- ir[18, 4].unpack('vv')
182
+ ir.unpack(18, 4, 'vv')
196
183
  else
197
- ir[18, 8].unpack('VV').map do |n|
184
+ ir.unpack(18, 8, 'VV').map do |n|
198
185
  if n > 0x7fff_ffff
199
186
  0x1_0000_0000 - n # absolute value of converted to signed
200
187
  else
@@ -216,7 +203,7 @@ private
216
203
  def size_of_pam(ir)
217
204
  width = height = nil
218
205
  offset = 3
219
- loop do
206
+ until width && height
220
207
  if ir[offset, 1] == '#'
221
208
  offset += 1 until ["\n", '', nil].include?(ir[offset, 1])
222
209
  offset += 1
@@ -235,7 +222,6 @@ private
235
222
  raise FormatError, "Unexpected data in PAM header: #{chunk.inspect}"
236
223
  end
237
224
  offset += $&.length
238
- break if width && height
239
225
  end
240
226
  end
241
227
  [width, height]
@@ -259,23 +245,23 @@ private
259
245
  end
260
246
 
261
247
  def size_of_psd(ir)
262
- ir[14, 8].unpack('NN').reverse
248
+ ir.unpack(14, 8, 'NN').reverse
263
249
  end
264
250
 
265
251
  def size_of_tiff(ir)
266
- endian2b = ir[0, 4] == "II*\000" ? 'v' : 'n'
252
+ endian2b = ir.fetch(0, 4) == "II*\000" ? 'v' : 'n'
267
253
  endian4b = endian2b.upcase
268
254
  packspec = [nil, 'C', nil, endian2b, endian4b, nil, 'c', nil, endian2b, endian4b]
269
255
 
270
- offset = ir[4, 4].unpack(endian4b)[0]
271
- num_dirent = ir[offset, 2].unpack(endian2b)[0]
256
+ offset = ir.unpack1(4, 4, endian4b)
257
+ num_dirent = ir.unpack1(offset, 2, endian2b)
272
258
  offset += 2
273
259
  num_dirent = offset + (num_dirent * 12)
274
260
 
275
261
  width = height = nil
276
262
  until width && height
277
- ifd = ir[offset, 12]
278
- raise FormatError, 'Reached end of directory entries in TIFF' if ifd.nil? || offset > num_dirent
263
+ ifd = ir.fetch(offset, 12)
264
+ raise FormatError, 'Reached end of directory entries in TIFF' if offset > num_dirent
279
265
 
280
266
  tag, type = ifd.unpack(endian2b * 2)
281
267
  offset += 12
@@ -294,14 +280,14 @@ private
294
280
  end
295
281
 
296
282
  def size_of_pcx(ir)
297
- parts = ir[4, 8].unpack('v4')
283
+ parts = ir.unpack(4, 8, 'v4')
298
284
  [parts[2] - parts[0] + 1, parts[3] - parts[1] + 1]
299
285
  end
300
286
 
301
287
  def size_of_swf(ir)
302
- value_bit_length = ir[8, 1].unpack('B5').first.to_i(2)
303
- bit_length = 5 + value_bit_length * 4
304
- rect_bits = ir[8, bit_length / 8 + 1].unpack("B#{bit_length}").first
288
+ value_bit_length = ir.unpack1(8, 1, 'B5').to_i(2)
289
+ bit_length = 5 + (value_bit_length * 4)
290
+ rect_bits = ir.unpack1(8, (bit_length / 8) + 1, "B#{bit_length}")
305
291
  values = rect_bits[5..-1].unpack("a#{value_bit_length}" * 4).map{ |bits| bits.to_i(2) }
306
292
  x_min, x_max, y_min, y_max = values
307
293
  [(x_max - x_min) / 20, (y_max - y_min) / 20]
@@ -309,7 +295,8 @@ private
309
295
 
310
296
  def size_of_svg(ir)
311
297
  attributes = {}
312
- ir.data[SVG_R, 1].scan(/(\S+)=(?:'([^']*)'|"([^"]*)"|([^'"\s]*))/) do |name, v0, v1, v2|
298
+ svg_tag = ir[0, 1024][SVG_R, 1] || ir[0, 4096][SVG_R, 1]
299
+ svg_tag.scan(/(\S+)=(?:'([^']*)'|"([^"]*)"|([^'"\s]*))/) do |name, v0, v1, v2|
313
300
  attributes[name] = v0 || v1 || v2
314
301
  end
315
302
  dpi = self.class.dpi
@@ -330,20 +317,20 @@ private
330
317
  end
331
318
 
332
319
  def size_of_ico(ir)
333
- ir[6, 2].unpack('CC').map{ |v| v.zero? ? 256 : v }
320
+ ir.unpack(6, 2, 'CC').map{ |v| v.zero? ? 256 : v }
334
321
  end
335
322
  alias_method :size_of_cur, :size_of_ico
336
323
 
337
324
  def size_of_webp(ir)
338
- case ir[12, 4]
325
+ case ir.fetch(12, 4)
339
326
  when 'VP8 '
340
- ir[26, 4].unpack('vv').map{ |v| v & 0x3fff }
327
+ ir.unpack(26, 4, 'vv').map{ |v| v & 0x3fff }
341
328
  when 'VP8L'
342
- n = ir[21, 4].unpack('V')[0]
343
- [(n & 0x3fff) + 1, (n >> 14 & 0x3fff) + 1]
329
+ n = ir.unpack1(21, 4, 'V')
330
+ [(n & 0x3fff) + 1, ((n >> 14) & 0x3fff) + 1]
344
331
  when 'VP8X'
345
- w16, w8, h16, h8 = ir[24, 6].unpack('vCvC')
346
- [(w16 | w8 << 16) + 1, (h16 | h8 << 16) + 1]
332
+ w16, w8, h16, h8 = ir.unpack(24, 6, 'vCvC')
333
+ [(w16 | (w8 << 16)) + 1, (h16 | (h8 << 16)) + 1]
347
334
  end
348
335
  end
349
336
 
@@ -355,13 +342,13 @@ private
355
342
  break if stop && offset >= stop
356
343
  break if ir[offset, 4] == '' || ir[offset, 4].nil?
357
344
 
358
- size = ir[offset, 4].unpack('N')[0]
359
- type = ir[offset + 4, 4]
345
+ size = ir.unpack1(offset, 4, 'N')
346
+ type = ir.fetch(offset + 4, 4)
360
347
 
361
348
  data_offset = 8
362
349
  case size
363
350
  when 1
364
- size = ir[offset, 8].unpack('Q>')[0]
351
+ size = ir.unpack1(offset, 8, 'Q>')
365
352
  data_offset = 16
366
353
  raise FormatError, "Unexpected xl-box size #{size}" if (1..15).include?(size)
367
354
  when 2..7
@@ -373,7 +360,7 @@ private
373
360
  offset += data_offset
374
361
  in_header = true
375
362
  elsif in_header && type == 'ihdr'
376
- return ir[offset + data_offset, 8].unpack('NN').reverse
363
+ return ir.unpack(offset + data_offset, 8, 'NN').reverse
377
364
  else
378
365
  break if size.zero? # box to the end of file
379
366
 
@@ -384,6 +371,6 @@ private
384
371
  alias_method :size_of_jpx, :size_of_jp2
385
372
 
386
373
  def size_of_j2c(ir)
387
- ir[8, 8].unpack('NN')
374
+ ir.unpack(8, 8, 'NN')
388
375
  end
389
376
  end
@@ -0,0 +1,69 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rspec'
4
+
5
+ require 'image_size/chunky_reader'
6
+
7
+ describe ImageSize::ChunkyReader do
8
+ context :[] do
9
+ test_reader = Class.new do
10
+ include ImageSize::ChunkyReader
11
+
12
+ def initialize(string)
13
+ @string = string
14
+ end
15
+
16
+ private
17
+
18
+ def chunk(i)
19
+ @string[i * chunk_size, chunk_size]
20
+ end
21
+ end
22
+
23
+ custom_chunk_size_reader = Class.new(test_reader) do
24
+ def chunk_size
25
+ 100
26
+ end
27
+ end
28
+
29
+ {
30
+ 'empty string' => '',
31
+ 'a bit of data' => 'foo bar baz',
32
+ 'a lot of data' => File.open('GPL', 'rb', &:read),
33
+ }.each do |data_description, data|
34
+ {
35
+ 'default' => test_reader.new(data),
36
+ 'custom' => custom_chunk_size_reader.new(data),
37
+ }.each do |chunk_size_description, reader|
38
+ context "for #{data_description} using reader with #{chunk_size_description} chunk size" do
39
+ it 'raises ArgumentError for negative offset' do
40
+ [-1, 0, 1, 100].each do |length|
41
+ expect{ reader[-1, length] }.to raise_exception(ArgumentError)
42
+ end
43
+ end
44
+
45
+ it 'behaves same as fetching a string for any offset and length' do
46
+ full_chunks = data.length / reader.chunk_size
47
+ offsets = [0, 1, full_chunks - 1, full_chunks, full_chunks + 1].map do |i|
48
+ [-1, 0, 1].map do |add|
49
+ (i * reader.chunk_size) + add
50
+ end
51
+ end.flatten
52
+
53
+ offsets.each do |offset|
54
+ next if offset < 0
55
+
56
+ offsets.each do |offset_b|
57
+ length = offset_b - offset
58
+ expect(reader[offset, length]).to eq(data[offset, length]),
59
+ "for offset #{offset} and length #{length}\n"\
60
+ "expected: #{data[offset, length].inspect}\n"\
61
+ " got: #{reader[offset, length].inspect}"
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end
@@ -1,13 +1,35 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'rspec'
4
+
4
5
  require 'image_size'
6
+ require 'image_size/uri_reader'
7
+
5
8
  require 'tempfile'
9
+ require 'shellwords'
10
+ require 'webrick'
6
11
 
7
12
  describe ImageSize do
13
+ before :all do
14
+ @server = WEBrick::HTTPServer.new({
15
+ :Logger => WEBrick::Log.new(StringIO.new),
16
+ :AccessLog => [],
17
+ :BindAddress => '127.0.0.1',
18
+ :Port => 0, # get the next available port
19
+ :DocumentRoot => '.',
20
+ })
21
+ @server_thread = Thread.new{ @server.start }
22
+ @server_base_url = URI("http://localhost:#{@server.config[:Port]}/")
23
+ end
24
+
25
+ after :all do
26
+ @server.shutdown
27
+ @server_thread.join
28
+ end
29
+
8
30
  max_filesize = 16_384
9
31
 
10
- (Dir['spec/images/*/*.*'] + [__FILE__]).each do |path|
32
+ (Dir['spec/images/*/*.*'] + [__FILE__[%r{spec/.+?\z}]]).each do |path|
11
33
  filesize = File.size(path)
12
34
  warn "#{path} is too big #{filesize} (max #{max_filesize})" if filesize > max_filesize
13
35
 
@@ -50,13 +72,22 @@ describe ImageSize do
50
72
  end
51
73
  end
52
74
 
75
+ context 'given as unseekable IO' do
76
+ it 'gets format and dimensions' do
77
+ IO.popen(%W[cat #{path}].shelljoin, 'rb') do |io|
78
+ image_size = ImageSize.new(io)
79
+ expect(image_size).to have_attributes(attributes)
80
+ expect(io).not_to be_closed
81
+ end
82
+ end
83
+ end
84
+
53
85
  context 'given as StringIO' do
54
86
  it 'gets format and dimensions' do
55
87
  io = StringIO.new(file_data)
56
88
  image_size = ImageSize.new(io)
57
89
  expect(image_size).to have_attributes(attributes)
58
90
  expect(io).not_to be_closed
59
- expect(io.pos).to_not be_zero
60
91
  io.rewind
61
92
  expect(io.read).to eq(file_data)
62
93
  end
@@ -84,6 +115,13 @@ describe ImageSize do
84
115
  expect(image_size).to have_attributes(attributes)
85
116
  end
86
117
  end
118
+
119
+ context 'fetching from webserver' do
120
+ it 'gets format and dimensions' do
121
+ image_size = ImageSize.url(@server_base_url + path)
122
+ expect(image_size).to have_attributes(attributes)
123
+ end
124
+ end
87
125
  end
88
126
  end
89
127
 
@@ -0,0 +1 @@
1
+ * -text
@@ -0,0 +1,3 @@
1
+ <svg width="1in" height="100" xmlns="http://www.w3.org/2000/svg">
2
+ <circle cx="36" cy="50" r="30" stroke="black" stroke-width="10" fill="red" />
3
+ </svg>
@@ -0,0 +1,20 @@
1
+ <?xml version="1.0" encoding="UTF-8" standalone="no"?>
2
+ <!--[[[[[[[[[[[[[[[[[[[[[[[[[[[[]]]]]]]]]]]]]]]]]]]]]]]]]]]]-->
3
+ <!--[ [[[[[[[[[[[[[[[[[[[[[[[[]]]]]]]]]]]]]]]]]]]]]]]] ]-->
4
+ <!--[ [ [[[[[[[[[[[[[[[[[[[[]]]]]]]]]]]]]]]]]]]] ] ]-->
5
+ <!--[ [ [ [[[[[[[[[[[[[[[[]]]]]]]]]]]]]]]] ] ] ]-->
6
+ <!--[ [ [ [ [[[[[[[[[[[[]]]]]]]]]]]] ] ] ] ]-->
7
+ <!--[ [ [ [ [ [[[[[[[[]]]]]]]] ] ] ] ] ]-->
8
+ <!--[ [ [ [ [ [ [[[[]]]] ] ] ] ] ] ]-->
9
+ <!--[ [ [ [ [ [ [ ] ] ] ] ] ] ]-->
10
+ <!--[ [ [ [ [ [ [ ] ] ] ] ] ] ]-->
11
+ <!--[ [ [ [ [ [ [[[[]]]] ] ] ] ] ] ]-->
12
+ <!--[ [ [ [ [ [[[[[[[[]]]]]]]] ] ] ] ] ]-->
13
+ <!--[ [ [ [ [[[[[[[[[[[[]]]]]]]]]]]] ] ] ] ]-->
14
+ <!--[ [ [ [[[[[[[[[[[[[[[[]]]]]]]]]]]]]]]] ] ] ]-->
15
+ <!--[ [ [[[[[[[[[[[[[[[[[[[[]]]]]]]]]]]]]]]]]]]] ] ]-->
16
+ <!--[ [[[[[[[[[[[[[[[[[[[[[[[[]]]]]]]]]]]]]]]]]]]]]]]] ]-->
17
+ <!--[[[[[[[[[[[[[[[[[[[[[[[[[[[[]]]]]]]]]]]]]]]]]]]]]]]]]]]]-->
18
+ <svg width="1in" height="100" xmlns="http://www.w3.org/2000/svg">
19
+ <circle cx="36" cy="50" r="30" stroke="black" stroke-width="10" fill="red" />
20
+ </svg>
@@ -0,0 +1,20 @@
1
+ <?xml version="1.0" encoding="UTF-8" standalone="no"?>
2
+ <!--[[[[[[[[[[[[[[[[[[[[[[[[[[[[]]]]]]]]]]]]]]]]]]]]]]]]]]]]-->
3
+ <!--[ [[[[[[[[[[[[[[[[[[[[[[[[]]]]]]]]]]]]]]]]]]]]]]]] ]-->
4
+ <!--[ [ [[[[[[[[[[[[[[[[[[[[]]]]]]]]]]]]]]]]]]]] ] ]-->
5
+ <!--[ [ [ [[[[[[[[[[[[[[[[]]]]]]]]]]]]]]]] ] ] ]-->
6
+ <!--[ [ [ [ [[[[[[[[[[[[]]]]]]]]]]]] ] ] ] ]-->
7
+ <!--[ [ [ [ [ [[[[[[[[]]]]]]]] ] ] ] ] ]-->
8
+ <!--[ [ [ [ [ [ [[[[]]]] ] ] ] ] ] ]-->
9
+ <!--[ [ [ [ [ [ [ ] ] ] ] ] ] ]-->
10
+ <!--[ [ [ [ [ [ [ ] ] ] ] ] ] ]-->
11
+ <!--[ [ [ [ [ [ [[[[]]]] ] ] ] ] ] ]-->
12
+ <!--[ [ [ [ [ [[[[[[[[]]]]]]]] ] ] ] ] ]-->
13
+ <!--[ [ [ [ [[[[[[[[[[[[]]]]]]]]]]]] ] ] ] ]-->
14
+ <!--[ [ [ [[[[[[[[[[[[[[[[]]]]]]]]]]]]]]]] ] ] ]-->
15
+ <!--[ [ [[[[[[[[[[[[[[[[[[[[]]]]]]]]]]]]]]]]]]]] ] ]-->
16
+ <!--[ [[[[[[[[[[[[[[[[[[[[[[[[]]]]]]]]]]]]]]]]]]]]]]]] ]-->
17
+ <!--[[[[[[[[[[[[[[[[[[[[[[[[[[[[]]]]]]]]]]]]]]]]]]]]]]]]]]]]-->
18
+ <svg width="1in" height="100" xmlns="http://www.w3.org/2000/svg">
19
+ <circle cx="36" cy="50" r="30" stroke="black" stroke-width="10" fill="red" />
20
+ </svg>
@@ -0,0 +1,11 @@
1
+ #define cursor_width 16
2
+ #define cursor_height 32
3
+ static unsigned char cursor_bits[] = {
4
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
5
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
6
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x84, 0x10,
7
+ 0xe8, 0x0b, 0x90, 0x04, 0xa8, 0x0a, 0x88, 0x08,
8
+ 0xfe, 0x3f, 0x88, 0x08, 0xa8, 0x0a, 0x90, 0x04,
9
+ 0xe8, 0x0b, 0x84, 0x10, 0x80, 0x00, 0x00, 0x00,
10
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
11
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
@@ -0,0 +1,40 @@
1
+ /* XPM */
2
+ static char *test2[] = {
3
+ /* columns rows colors chars-per-pixel */
4
+ "24 32 2 1 ",
5
+ " c red",
6
+ ". c white",
7
+ /* pixels */
8
+ "........................",
9
+ "........................",
10
+ "........................",
11
+ "........................",
12
+ "........................",
13
+ "........................",
14
+ ".. .................. ",
15
+ ".. ................ ",
16
+ "... .............. .",
17
+ ".... ............ ..",
18
+ "..... .......... ...",
19
+ "...... ........ ....",
20
+ "....... ...... .....",
21
+ "........ .... ......",
22
+ "......... .. .......",
23
+ ".......... ........",
24
+ "........... .........",
25
+ "............ .........",
26
+ "........... ........",
27
+ ".......... . .......",
28
+ "......... ... ......",
29
+ "........ ..... .....",
30
+ "....... ....... ....",
31
+ "...... ......... ...",
32
+ "..... ........... ..",
33
+ ".... ............. .",
34
+ "... ............... ",
35
+ ".. ................. ",
36
+ ".. ................... ",
37
+ "........................",
38
+ "........................",
39
+ "........................"
40
+ };
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: image_size
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.1.2
4
+ version: 3.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Keisuke Minami
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2021-08-21 00:00:00.000000000 Z
12
+ date: 2021-10-17 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rspec
@@ -39,6 +39,20 @@ dependencies:
39
39
  - - "~>"
40
40
  - !ruby/object:Gem::Version
41
41
  version: '1.0'
42
+ - !ruby/object:Gem::Dependency
43
+ name: rubocop-rspec
44
+ requirement: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - "~>"
47
+ - !ruby/object:Gem::Version
48
+ version: '2.0'
49
+ type: :development
50
+ prerelease: false
51
+ version_requirements: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - "~>"
54
+ - !ruby/object:Gem::Version
55
+ version: '2.0'
42
56
  description: 'Measure following file dimensions: apng, bmp, cur, gif, ico, j2c, jp2,
43
57
  jpeg, jpx, mng, pam, pbm, pcx, pgm, png, ppm, psd, svg, swf, tiff, webp, xbm, xpm'
44
58
  email:
@@ -57,7 +71,15 @@ files:
57
71
  - README.markdown
58
72
  - image_size.gemspec
59
73
  - lib/image_size.rb
74
+ - lib/image_size/chunky_reader.rb
75
+ - lib/image_size/reader.rb
76
+ - lib/image_size/seekable_io_reader.rb
77
+ - lib/image_size/stream_io_reader.rb
78
+ - lib/image_size/string_reader.rb
79
+ - lib/image_size/uri_reader.rb
80
+ - spec/image_size/chunky_reader_spec.rb
60
81
  - spec/image_size_spec.rb
82
+ - spec/images/.gitattributes
61
83
  - spec/images/bmp/v2.42x50.bmp
62
84
  - spec/images/bmp/v3-bottom2top.42x50.bmp
63
85
  - spec/images/bmp/v3-top2bottom.42x50.bmp
@@ -82,6 +104,9 @@ files:
82
104
  - spec/images/pnm/ascii.22x25.ppm
83
105
  - spec/images/psd/16x20.psd
84
106
  - spec/images/svg/72x100.svg
107
+ - spec/images/svg/crlf.72x100.svg
108
+ - spec/images/svg/long.72x100.svg
109
+ - spec/images/svg/long.crlf.72x100.svg
85
110
  - spec/images/swf/450x200.swf
86
111
  - spec/images/tiff/big-endian.68x49.tiff
87
112
  - spec/images/tiff/little-endian.40x68.tiff
@@ -89,14 +114,16 @@ files:
89
114
  - spec/images/webp/lossless.16x32.webp
90
115
  - spec/images/webp/lossy.16x32.webp
91
116
  - spec/images/xbm/16x32.xbm
117
+ - spec/images/xbm/crlf.16x32.xbm
92
118
  - spec/images/xpm/24x32.xpm
119
+ - spec/images/xpm/crlf.24x32.xpm
93
120
  homepage: https://github.com/toy/image_size
94
121
  licenses:
95
122
  - Ruby
96
123
  metadata:
97
124
  bug_tracker_uri: https://github.com/toy/image_size/issues
98
125
  changelog_uri: https://github.com/toy/image_size/blob/master/CHANGELOG.markdown
99
- documentation_uri: https://www.rubydoc.info/gems/image_size/2.1.2
126
+ documentation_uri: https://www.rubydoc.info/gems/image_size/3.0.0
100
127
  source_code_uri: https://github.com/toy/image_size
101
128
  post_install_message:
102
129
  rdoc_options: []
@@ -113,12 +140,14 @@ required_rubygems_version: !ruby/object:Gem::Requirement
113
140
  - !ruby/object:Gem::Version
114
141
  version: '0'
115
142
  requirements: []
116
- rubygems_version: 3.2.16
143
+ rubygems_version: 3.2.29
117
144
  signing_key:
118
145
  specification_version: 4
119
146
  summary: Measure image size using pure Ruby
120
147
  test_files:
148
+ - spec/image_size/chunky_reader_spec.rb
121
149
  - spec/image_size_spec.rb
150
+ - spec/images/.gitattributes
122
151
  - spec/images/bmp/v2.42x50.bmp
123
152
  - spec/images/bmp/v3-bottom2top.42x50.bmp
124
153
  - spec/images/bmp/v3-top2bottom.42x50.bmp
@@ -143,6 +172,9 @@ test_files:
143
172
  - spec/images/pnm/ascii.22x25.ppm
144
173
  - spec/images/psd/16x20.psd
145
174
  - spec/images/svg/72x100.svg
175
+ - spec/images/svg/crlf.72x100.svg
176
+ - spec/images/svg/long.72x100.svg
177
+ - spec/images/svg/long.crlf.72x100.svg
146
178
  - spec/images/swf/450x200.swf
147
179
  - spec/images/tiff/big-endian.68x49.tiff
148
180
  - spec/images/tiff/little-endian.40x68.tiff
@@ -150,4 +182,6 @@ test_files:
150
182
  - spec/images/webp/lossless.16x32.webp
151
183
  - spec/images/webp/lossy.16x32.webp
152
184
  - spec/images/xbm/16x32.xbm
185
+ - spec/images/xbm/crlf.16x32.xbm
153
186
  - spec/images/xpm/24x32.xpm
187
+ - spec/images/xpm/crlf.24x32.xpm