image_size 2.0.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 +4 -4
- data/.github/workflows/check.yml +69 -0
- data/.rubocop.yml +27 -4
- data/.rubocop_todo.yml +11 -8
- data/CHANGELOG.markdown +19 -0
- data/GPL +340 -0
- data/Gemfile +1 -3
- data/LICENSE.txt +56 -0
- data/README.markdown +22 -18
- data/image_size.gemspec +6 -5
- data/lib/image_size/chunky_reader.rb +44 -0
- data/lib/image_size/reader.rb +64 -0
- data/lib/image_size/seekable_io_reader.rb +28 -0
- data/lib/image_size/stream_io_reader.rb +22 -0
- data/lib/image_size/string_reader.rb +21 -0
- data/lib/image_size/uri_reader.rb +88 -0
- data/lib/image_size.rb +159 -85
- data/spec/image_size/chunky_reader_spec.rb +69 -0
- data/spec/image_size_spec.rb +45 -2
- data/spec/images/.gitattributes +1 -0
- data/spec/images/cur/32x256.cur +0 -0
- data/spec/images/ico/32x256.ico +0 -0
- data/spec/images/jp2/163x402.jp2 +0 -0
- data/spec/images/jp2/176x373.jpx +0 -0
- data/spec/images/jp2/224x293.j2c +0 -0
- data/spec/images/jpeg/436x429.jpeg +0 -0
- data/spec/images/jpeg/extraneous-bytes.436x429.jpeg +0 -0
- data/spec/images/mng/61x42.mng +0 -0
- data/spec/images/{apng → png}/192x110.apng +0 -0
- data/spec/images/pnm/22x25.pam +8 -0
- data/spec/images/pnm/22x25.pbm +0 -0
- data/spec/images/pnm/22x25.pgm +4 -0
- data/spec/images/pnm/22x25.ppm +4 -0
- data/spec/images/pnm/ascii.22x25.pbm +27 -0
- data/spec/images/pnm/ascii.22x25.pgm +28 -0
- data/spec/images/pnm/ascii.22x25.ppm +28 -0
- data/spec/images/svg/crlf.72x100.svg +3 -0
- data/spec/images/svg/long.72x100.svg +20 -0
- data/spec/images/svg/long.crlf.72x100.svg +20 -0
- data/spec/images/tiff/big-endian.68x49.tiff +0 -0
- data/spec/images/tiff/little-endian.40x68.tiff +0 -0
- data/spec/images/xbm/crlf.16x32.xbm +11 -0
- data/spec/images/xpm/crlf.24x32.xpm +40 -0
- metadata +86 -32
- data/.travis.yml +0 -24
- data/spec/images/cur/50x256.cur +0 -0
- data/spec/images/ico/256x27.ico +0 -0
- data/spec/images/jpeg/320x240.jpeg +0 -0
- data/spec/images/jpeg/extraneous-bytes.320x240.jpeg +0 -0
- data/spec/images/mng/612x132.mng +0 -0
- data/spec/images/pbm/85x55.pbm +0 -0
- data/spec/images/pgm/90x55.pgm +0 -5
- data/spec/images/tiff/48x64.tiff +0 -0
data/README.markdown
CHANGED
@@ -1,10 +1,10 @@
|
|
1
|
-
[](https://rubygems.org/gems/image_size)
|
2
|
+
[](https://github.com/toy/image_size/actions/workflows/check.yml)
|
3
3
|
|
4
4
|
# image_size
|
5
5
|
|
6
|
-
|
7
|
-
|
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/
|
26
|
+
image_size = ImageSize.path('spec/images/jpeg/436x429.jpeg')
|
27
27
|
|
28
28
|
image_size.format #=> :jpec
|
29
|
-
image_size.width #=>
|
30
|
-
image_size.height #=>
|
31
|
-
image_size.w #=>
|
32
|
-
image_size.h #=>
|
33
|
-
image_size.size #=> [
|
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/
|
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/
|
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/
|
84
|
+
File.open('spec/images/jpeg/436x429.jpeg', 'rb') do |fh|
|
79
85
|
data = fh.read
|
80
86
|
fh.rewind
|
81
87
|
|
@@ -85,9 +91,7 @@ end
|
|
85
91
|
|
86
92
|
## Licence
|
87
93
|
|
88
|
-
This code is free to use under the terms of the Ruby's licence.
|
94
|
+
This code is free to use under the terms of the [Ruby's licence](LICENSE.txt).
|
89
95
|
|
90
|
-
|
91
|
-
|
92
|
-
Original author: "Keisuke Minami": mailto:keisuke@rccn.com
|
93
|
-
Further development by Ivan Kuchin https://github.com/toy/image_size
|
96
|
+
Original author: Keisuke Minami <keisuke@rccn.com>.\
|
97
|
+
Further development 2010-2021 Ivan Kuchin https://github.com/toy/image_size
|
data/image_size.gemspec
CHANGED
@@ -2,10 +2,10 @@
|
|
2
2
|
|
3
3
|
Gem::Specification.new do |s|
|
4
4
|
s.name = 'image_size'
|
5
|
-
s.version = '
|
5
|
+
s.version = '3.0.0'
|
6
6
|
s.summary = %q{Measure image size using pure Ruby}
|
7
|
-
s.description = %q{Measure following file dimensions: apng, bmp, cur, gif, jpeg,
|
8
|
-
s.homepage = "
|
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
|
|
@@ -22,7 +22,8 @@ Gem::Specification.new do |s|
|
|
22
22
|
s.require_paths = %w[lib]
|
23
23
|
|
24
24
|
s.add_development_dependency 'rspec', '~> 3.0'
|
25
|
-
if RUBY_VERSION >= '2.
|
26
|
-
s.add_development_dependency 'rubocop', '~> 0
|
25
|
+
if RUBY_VERSION >= '2.4'
|
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
|