image_size 2.1.2 → 3.0.0

Sign up to get free protection for your applications and to get access to all the features.
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