image_size 3.2.0 → 3.4.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 +11 -9
- data/.github/workflows/rubocop.yml +2 -2
- data/.rubocop.yml +2 -32
- data/CHANGELOG.markdown +12 -0
- data/README.markdown +9 -8
- data/image_size.gemspec +5 -3
- data/lib/image_size/format_error.rb +5 -0
- data/lib/image_size/isobmff.rb +99 -0
- data/lib/image_size/media_types.rb +32 -0
- data/lib/image_size/reader.rb +17 -0
- data/lib/image_size/uri.rb +3 -0
- data/lib/image_size/uri_reader.rb +1 -0
- data/lib/image_size.rb +99 -54
- data/spec/image_size/chunky_reader_spec.rb +1 -1
- data/spec/image_size/isobmff_spec.rb +352 -0
- data/spec/image_size_spec.rb +53 -17
- data/spec/images/heif/ap_maloletka_mariupol.452x301.heic +0 -0
- data/spec/images/heif/maxar_mariupol.400x300.avif +0 -0
- data/spec/images/heif/multiple.169x83.heic +0 -0
- data/spec/images/heif/rotate.73x173.avif +0 -0
- data/spec/images/heif/sequence.7x5.avif +0 -0
- data/spec/test_server.rb +7 -6
- metadata +25 -9
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d88e4be6d69bedc0f976c9b73544a20ac08ab66d6b44ce1061d1374f9d2a9dc8
|
4
|
+
data.tar.gz: 5b2986943b8bbeb339d0c31d0a98c5847a1a86141d5e754adaf0bd01e3d8d60f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1b75df7c37cbcca0886eade3e47b082cde0468331c954d0605130cba564bb7718d777628599fea7332e2ae1ef9f15b80ce77072352a109dadbf9749fea049572
|
7
|
+
data.tar.gz: ffc57b47280776fc7be3cb2866699f32f90a2e036b2dca703ac4262aa77750032ffb54700437958ba7b4948c934d5ea6e41f2acad4e7e71ddec84a98f18edabf
|
data/.github/workflows/check.yml
CHANGED
@@ -10,9 +10,6 @@ jobs:
|
|
10
10
|
strategy:
|
11
11
|
matrix:
|
12
12
|
ruby:
|
13
|
-
- '2.0'
|
14
|
-
- '2.1'
|
15
|
-
- '2.2'
|
16
13
|
- '2.3'
|
17
14
|
- '2.4'
|
18
15
|
- '2.5'
|
@@ -20,12 +17,13 @@ jobs:
|
|
20
17
|
- '2.7'
|
21
18
|
- '3.0'
|
22
19
|
- '3.1'
|
23
|
-
-
|
24
|
-
-
|
20
|
+
- '3.2'
|
21
|
+
- '3.3'
|
25
22
|
- jruby-9.3
|
23
|
+
- jruby-9.4
|
26
24
|
fail-fast: false
|
27
25
|
steps:
|
28
|
-
- uses: actions/checkout@
|
26
|
+
- uses: actions/checkout@v3
|
29
27
|
- uses: ruby/setup-ruby@v1
|
30
28
|
with:
|
31
29
|
ruby-version: "${{ matrix.ruby }}"
|
@@ -37,11 +35,13 @@ jobs:
|
|
37
35
|
strategy:
|
38
36
|
matrix:
|
39
37
|
container:
|
40
|
-
- rspec/ci:1.8.7
|
41
38
|
- rspec/ci:1.9.3
|
39
|
+
- ruby:2.0
|
40
|
+
- ruby:2.1
|
41
|
+
- ruby:2.2
|
42
42
|
fail-fast: false
|
43
43
|
steps:
|
44
|
-
- uses: actions/checkout@
|
44
|
+
- uses: actions/checkout@v3
|
45
45
|
- run: bundle install
|
46
46
|
- run: bundle exec rspec --format documentation
|
47
47
|
windows:
|
@@ -53,9 +53,11 @@ jobs:
|
|
53
53
|
- '2.7'
|
54
54
|
- '3.0'
|
55
55
|
- '3.1'
|
56
|
+
- '3.2'
|
57
|
+
- '3.3'
|
56
58
|
fail-fast: false
|
57
59
|
steps:
|
58
|
-
- uses: actions/checkout@
|
60
|
+
- uses: actions/checkout@v3
|
59
61
|
- uses: ruby/setup-ruby@v1
|
60
62
|
with:
|
61
63
|
ruby-version: "${{ matrix.ruby }}"
|
data/.rubocop.yml
CHANGED
@@ -15,9 +15,6 @@ Layout/CaseIndentation:
|
|
15
15
|
Layout/EndAlignment:
|
16
16
|
EnforcedStyleAlignWith: variable
|
17
17
|
|
18
|
-
Layout/FirstHashElementIndentation:
|
19
|
-
EnforcedStyle: consistent
|
20
|
-
|
21
18
|
Layout/LineLength:
|
22
19
|
Max: 120
|
23
20
|
|
@@ -25,9 +22,6 @@ Layout/SpaceBeforeBlockBraces:
|
|
25
22
|
EnforcedStyle: no_space
|
26
23
|
EnforcedStyleForEmptyBraces: no_space
|
27
24
|
|
28
|
-
Lint/PercentStringArray: # broken in rubocop 0.55.0
|
29
|
-
Enabled: false
|
30
|
-
|
31
25
|
Metrics/BlockLength:
|
32
26
|
Exclude:
|
33
27
|
- 'spec/**/*.rb'
|
@@ -41,42 +35,18 @@ Metrics/MethodLength:
|
|
41
35
|
Naming/MethodParameterName:
|
42
36
|
Enabled: false
|
43
37
|
|
44
|
-
Style/AccessorGrouping:
|
45
|
-
Enabled: false
|
46
|
-
|
47
38
|
Style/Alias:
|
48
39
|
EnforcedStyle: prefer_alias_method
|
49
40
|
|
50
|
-
Style/
|
51
|
-
Enabled: false
|
52
|
-
|
53
|
-
Style/Encoding:
|
54
|
-
Enabled: false
|
55
|
-
|
56
|
-
Style/FileRead:
|
41
|
+
Style/ArgumentsForwarding:
|
57
42
|
Enabled: false
|
58
43
|
|
59
|
-
Style/
|
60
|
-
Enabled: true
|
61
|
-
|
62
|
-
Style/HashSyntax:
|
63
|
-
EnforcedStyle: hash_rockets
|
64
|
-
|
65
|
-
Style/HashTransformKeys:
|
66
|
-
Enabled: false
|
67
|
-
|
68
|
-
Style/HashTransformValues:
|
69
|
-
Enabled: false
|
70
|
-
|
71
|
-
Style/IfUnlessModifier:
|
44
|
+
Style/EmptyCaseCondition:
|
72
45
|
Enabled: false
|
73
46
|
|
74
47
|
Style/NumericPredicate:
|
75
48
|
Enabled: false
|
76
49
|
|
77
|
-
Style/ParallelAssignment:
|
78
|
-
Enabled: false
|
79
|
-
|
80
50
|
Style/SafeNavigation:
|
81
51
|
Enabled: false
|
82
52
|
|
data/CHANGELOG.markdown
CHANGED
@@ -2,6 +2,18 @@
|
|
2
2
|
|
3
3
|
## unreleased
|
4
4
|
|
5
|
+
## v3.4.0 (2024-01-16)
|
6
|
+
|
7
|
+
* Provide access to media types using media_type and media_types methods [#22](https://github.com/toy/image_size/issues/22) [@toy](https://github.com/toy)
|
8
|
+
* Allow fetching from HTTP server by requiring image_size/uri [@toy](https://github.com/toy)
|
9
|
+
* Fix for ArgumentError when requiring only image_size/uri_reader (without image_size) [@toy](https://github.com/toy)
|
10
|
+
* Require ruby 1.9.3 [@toy](https://github.com/toy)
|
11
|
+
|
12
|
+
## v3.3.0 (2023-05-30)
|
13
|
+
|
14
|
+
* Support `HEIF` (`HEIC` and `AVIF`) images [#19](https://github.com/toy/image_size/issues/19) [@toy](https://github.com/toy)
|
15
|
+
* Fix handling `JPEG 2000` 64 bit size boxes [@toy](https://github.com/toy)
|
16
|
+
|
5
17
|
## v3.2.0 (2022-11-03)
|
6
18
|
|
7
19
|
* Support `EMF` images [#21](https://github.com/toy/image_size/pull/21) [@opoudjis](https://github.com/opoudjis)
|
data/README.markdown
CHANGED
@@ -1,11 +1,11 @@
|
|
1
1
|
[](https://rubygems.org/gems/image_size)
|
2
|
-
[](https://github.com/toy/image_size/actions/workflows/check.yml)
|
3
|
+
[](https://github.com/toy/image_size/actions/workflows/rubocop.yml)
|
4
4
|
|
5
5
|
# image_size
|
6
6
|
|
7
|
-
Measure image size using pure Ruby.
|
8
|
-
Formats: `apng`, `bmp`, `cur`, `emf`, `gif`, `ico`, `j2c`, `jp2`, `jpeg`, `jpx`, `mng`, `pam`, `pbm`, `pcx`, `pgm`, `png`, `ppm`, `psd`, `svg`, `swf`, `tiff`, `webp`, `xbm`, `xpm`.
|
7
|
+
Measure image size/dimensions using pure Ruby.
|
8
|
+
Formats: `apng`, `avif`, `bmp`, `cur`, `emf`, `gif`, `heic`, `heif`, `ico`, `j2c`, `jp2`, `jpeg`, `jpx`, `mng`, `pam`, `pbm`, `pcx`, `pgm`, `png`, `ppm`, `psd`, `svg`, `swf`, `tiff`, `webp`, `xbm`, `xpm`.
|
9
9
|
|
10
10
|
## Installation
|
11
11
|
|
@@ -26,7 +26,7 @@ gem 'image_size', '~> 3.0'
|
|
26
26
|
```ruby
|
27
27
|
image_size = ImageSize.path('spec/images/jpeg/436x429.jpeg')
|
28
28
|
|
29
|
-
image_size.format #=> :
|
29
|
+
image_size.format #=> :jpeg
|
30
30
|
image_size.width #=> 436
|
31
31
|
image_size.height #=> 429
|
32
32
|
image_size.w #=> 436
|
@@ -38,6 +38,8 @@ image_size.size.width #=> 436
|
|
38
38
|
image_size.size.height #=> 429
|
39
39
|
image_size.size.w #=> 436
|
40
40
|
image_size.size.h #=> 429
|
41
|
+
image_size.media_type #=> "image/jpeg"
|
42
|
+
image_size.media_types #=> ["image/jpeg"]
|
41
43
|
```
|
42
44
|
|
43
45
|
Or using `IO` object:
|
@@ -96,8 +98,7 @@ If server recognises Range header, only needed chunks will be fetched even for T
|
|
96
98
|
of data will be fetched, in most cases first few kilobytes (TIFF images is an exception).
|
97
99
|
|
98
100
|
```ruby
|
99
|
-
require 'image_size'
|
100
|
-
require 'image_size/uri_reader'
|
101
|
+
require 'image_size/uri'
|
101
102
|
|
102
103
|
url = 'http://upload.wikimedia.org/wikipedia/commons/b/b4/Mardin_1350660_1350692_33_images.jpg'
|
103
104
|
p ImageSize.url(url).size
|
@@ -144,4 +145,4 @@ puts Benchmark.measure{ p ImageSize.url(url).size }
|
|
144
145
|
This code is free to use under the terms of the [Ruby's licence](LICENSE.txt).
|
145
146
|
|
146
147
|
Original author: Keisuke Minami <keisuke@rccn.com>.\
|
147
|
-
Further development 2010-
|
148
|
+
Further development 2010-2024 Ivan Kuchin https://github.com/toy/image_size
|
data/image_size.gemspec
CHANGED
@@ -2,13 +2,15 @@
|
|
2
2
|
|
3
3
|
Gem::Specification.new do |s|
|
4
4
|
s.name = 'image_size'
|
5
|
-
s.version = '3.
|
6
|
-
s.summary = %q{Measure image size using pure Ruby}
|
7
|
-
s.description = %q{Measure following file dimensions: apng, bmp, cur, emf, gif, ico, j2c, jp2, jpeg, jpx, mng, pam, pbm, pcx, pgm, png, ppm, psd, svg, swf, tiff, webp, xbm, xpm}
|
5
|
+
s.version = '3.4.0'
|
6
|
+
s.summary = %q{Measure image size/dimensions using pure Ruby}
|
7
|
+
s.description = %q{Measure following file dimensions: apng, avif, bmp, cur, emf, gif, heic, heif, 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}"
|
9
9
|
s.authors = ['Keisuke Minami', 'Ivan Kuchin']
|
10
10
|
s.license = 'Ruby'
|
11
11
|
|
12
|
+
s.required_ruby_version = '>= 1.9.3'
|
13
|
+
|
12
14
|
s.metadata = {
|
13
15
|
'bug_tracker_uri' => "https://github.com/toy/#{s.name}/issues",
|
14
16
|
'changelog_uri' => "https://github.com/toy/#{s.name}/blob/master/CHANGELOG.markdown",
|
@@ -0,0 +1,99 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'image_size/format_error'
|
4
|
+
|
5
|
+
require 'set'
|
6
|
+
|
7
|
+
class ImageSize
|
8
|
+
class ISOBMFF # :nodoc:
|
9
|
+
class Box # :nodoc:
|
10
|
+
attr_reader :type, :offset, :size, :relative_data_offset, :index
|
11
|
+
|
12
|
+
def initialize(attributes)
|
13
|
+
@type = attributes.fetch(:type)
|
14
|
+
@offset = attributes.fetch(:offset)
|
15
|
+
@size = attributes.fetch(:size) == 0 ? nil : attributes[:size]
|
16
|
+
@relative_data_offset = attributes.fetch(:relative_data_offset)
|
17
|
+
@index = attributes.fetch(:index)
|
18
|
+
end
|
19
|
+
|
20
|
+
def data_offset
|
21
|
+
offset + relative_data_offset
|
22
|
+
end
|
23
|
+
|
24
|
+
def data_size
|
25
|
+
size ? size - relative_data_offset : nil
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
class FullBox < Box # :nodoc:
|
30
|
+
attr_reader :version, :flags
|
31
|
+
|
32
|
+
def initialize(attributes)
|
33
|
+
super
|
34
|
+
@version = attributes.fetch(:version)
|
35
|
+
@flags = attributes.fetch(:flags)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
S64_OVERFLOW = 1 << 63
|
40
|
+
|
41
|
+
def initialize(options = {})
|
42
|
+
@full = options.fetch(:full, []).to_set
|
43
|
+
@last = options.fetch(:last, []).to_set
|
44
|
+
@recurse = options.fetch(:recurse, []).to_set
|
45
|
+
end
|
46
|
+
|
47
|
+
def walk(reader, offset = 0, length = nil)
|
48
|
+
max_offset = length ? offset + length : S64_OVERFLOW
|
49
|
+
index = 1
|
50
|
+
while offset < max_offset && !['', nil].include?(reader[offset, 4])
|
51
|
+
size = reader.unpack1(offset, 4, 'N')
|
52
|
+
type = reader.fetch(offset + 4, 4)
|
53
|
+
relative_data_offset = 8
|
54
|
+
|
55
|
+
case size
|
56
|
+
when 1
|
57
|
+
size = reader.unpack1(offset + 8, 8, 'Q>')
|
58
|
+
relative_data_offset += 8
|
59
|
+
raise FormatError, "Unexpected ISOBMFF xl-box size #{size}" if size < 16
|
60
|
+
when 2..7
|
61
|
+
raise FormatError, "Reserved ISOBMFF box size #{size}"
|
62
|
+
end
|
63
|
+
|
64
|
+
attributes = {
|
65
|
+
type: type,
|
66
|
+
offset: offset,
|
67
|
+
size: size,
|
68
|
+
relative_data_offset: relative_data_offset,
|
69
|
+
index: index,
|
70
|
+
}
|
71
|
+
|
72
|
+
if @full.include?(type)
|
73
|
+
version_n_flags = reader.unpack1(offset + relative_data_offset, 4, 'N')
|
74
|
+
attributes[:version] = version_n_flags >> 24
|
75
|
+
attributes[:flags] = version_n_flags & 0xffffff
|
76
|
+
|
77
|
+
attributes[:relative_data_offset] += 4
|
78
|
+
|
79
|
+
yield FullBox.new(attributes)
|
80
|
+
else
|
81
|
+
yield Box.new(attributes)
|
82
|
+
end
|
83
|
+
|
84
|
+
break if size == 0 || @last.include?(type)
|
85
|
+
|
86
|
+
index += 1
|
87
|
+
offset += size
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
def recurse(reader, offset = 0, length = nil, &block)
|
92
|
+
walk(reader, offset, length) do |box|
|
93
|
+
yield box
|
94
|
+
|
95
|
+
recurse(reader, box.data_offset, box.data_size, &block) if @recurse.include?(box.type)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class ImageSize
|
4
|
+
MEDIA_TYPES = {
|
5
|
+
apng: %w[image/apng image/vnd.mozilla.apng],
|
6
|
+
avif: %w[image/avif],
|
7
|
+
bmp: %w[image/bmp],
|
8
|
+
cur: %w[image/vnd.microsoft.icon],
|
9
|
+
emf: %w[image/emf],
|
10
|
+
gif: %w[image/gif],
|
11
|
+
heic: %w[image/heic image/heif],
|
12
|
+
ico: %w[image/x-icon image/vnd.microsoft.icon],
|
13
|
+
j2c: %w[image/j2c],
|
14
|
+
jp2: %w[image/jp2],
|
15
|
+
jpeg: %w[image/jpeg],
|
16
|
+
jpx: %w[image/jpx],
|
17
|
+
mng: %w[video/x-mng image/x-mng],
|
18
|
+
pam: %w[image/x-portable-arbitrarymap],
|
19
|
+
pbm: %w[image/x-portable-bitmap image/x-portable-anymap],
|
20
|
+
pcx: %w[image/x-pcx image/vnd.zbrush.pcx],
|
21
|
+
pgm: %w[image/x-portable-graymap image/x-portable-anymap],
|
22
|
+
png: %w[image/png],
|
23
|
+
ppm: %w[image/x-portable-pixmap image/x-portable-anymap],
|
24
|
+
psd: %w[image/vnd.adobe.photoshop],
|
25
|
+
svg: %w[image/svg+xml],
|
26
|
+
swf: %w[application/x-shockwave-flash application/vnd.adobe.flash.movie],
|
27
|
+
tiff: %w[image/tiff],
|
28
|
+
webp: %w[image/webp],
|
29
|
+
xbm: %w[image/x-xbitmap],
|
30
|
+
xpm: %w[image/x-xpixmap],
|
31
|
+
}.freeze
|
32
|
+
end
|
data/lib/image_size/reader.rb
CHANGED
@@ -5,6 +5,19 @@ require 'stringio'
|
|
5
5
|
|
6
6
|
class ImageSize
|
7
7
|
module Reader # :nodoc:
|
8
|
+
class Stream # :nodoc:
|
9
|
+
def initialize(reader, offset)
|
10
|
+
@reader = reader
|
11
|
+
@offset = offset
|
12
|
+
end
|
13
|
+
|
14
|
+
def unpack1(length, format)
|
15
|
+
result = @reader.unpack1(@offset, length, format)
|
16
|
+
@offset += length
|
17
|
+
result
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
8
21
|
class << self
|
9
22
|
def open(input)
|
10
23
|
case
|
@@ -60,5 +73,9 @@ class ImageSize
|
|
60
73
|
fetch(offset, length).unpack(format)[0]
|
61
74
|
end
|
62
75
|
end
|
76
|
+
|
77
|
+
def stream(offset)
|
78
|
+
Stream.new(self, offset)
|
79
|
+
end
|
63
80
|
end
|
64
81
|
end
|
data/lib/image_size.rb
CHANGED
@@ -1,6 +1,9 @@
|
|
1
1
|
# encoding: BINARY
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
|
+
require 'image_size/isobmff'
|
5
|
+
require 'image_size/format_error'
|
6
|
+
require 'image_size/media_types'
|
4
7
|
require 'image_size/reader'
|
5
8
|
require 'image_size/seekable_io_reader'
|
6
9
|
require 'image_size/stream_io_reader'
|
@@ -8,8 +11,6 @@ require 'image_size/string_reader'
|
|
8
11
|
|
9
12
|
# Determine image format and size
|
10
13
|
class ImageSize
|
11
|
-
class FormatError < StandardError; end
|
12
|
-
|
13
14
|
# Array joining with 'x'
|
14
15
|
class Size < Array
|
15
16
|
# join using 'x'
|
@@ -69,6 +70,19 @@ class ImageSize
|
|
69
70
|
Size.new([width, height]) if format
|
70
71
|
end
|
71
72
|
|
73
|
+
# Media type (formerly known as a MIME type)
|
74
|
+
def media_type
|
75
|
+
MEDIA_TYPES.fetch(format, []).first
|
76
|
+
end
|
77
|
+
|
78
|
+
# All media types:
|
79
|
+
# * commonly used and official like for apng and ico
|
80
|
+
# * main and compatible like for heic and pnm (pbm, pgm, ppm)
|
81
|
+
# * multiple unregistered like for mng
|
82
|
+
def media_types
|
83
|
+
MEDIA_TYPES.fetch(format, [])
|
84
|
+
end
|
85
|
+
|
72
86
|
private
|
73
87
|
|
74
88
|
SVG_R = /<svg\b([^>]*)>/.freeze
|
@@ -77,25 +91,27 @@ private
|
|
77
91
|
head = ir[0, 1024]
|
78
92
|
case
|
79
93
|
when head.nil? || head.empty? then nil
|
80
|
-
when head[0, 6] =~
|
94
|
+
when head[0, 6] =~ /\AGIF8[79]a\z/ then :gif
|
81
95
|
when head[0, 8] == "\211PNG\r\n\032\n" then detect_png_type(ir)
|
82
96
|
when head[0, 8] == "\212MNG\r\n\032\n" then :mng
|
83
97
|
when head[0, 2] == "\377\330" then :jpeg
|
84
98
|
when head[0, 2] == 'BM' then :bmp
|
85
|
-
when head[0, 3] =~
|
99
|
+
when head[0, 3] =~ /\AP([1-6]\s|7\n)\z/ then detect_pnm_type(ir)
|
86
100
|
when head =~ /\#define\s+\S+\s+\d+/ then :xbm
|
87
101
|
when %W[II*\0 MM\0*].include?(head[0, 4]) then :tiff
|
88
102
|
when head =~ %r{/\* XPM \*/} then :xpm
|
89
103
|
when head[0, 4] == '8BPS' then :psd
|
90
|
-
when head[0, 3] =~
|
104
|
+
when head[0, 3] =~ /\A[FC]WS\z/ then :swf
|
91
105
|
when head =~ SVG_R || (head =~ XML_R && ir[0, 4096] =~ SVG_R) then :svg
|
92
106
|
when head[0, 2] =~ /\n[\0-\5]/ then :pcx
|
93
|
-
when head[0, 12] =~
|
107
|
+
when head[0, 12] =~ /\ARIFF(?m:....)WEBP\z/ then :webp
|
94
108
|
when head[0, 4] == "\0\0\1\0" then :ico
|
95
109
|
when head[0, 4] == "\0\0\2\0" then :cur
|
96
110
|
when head[0, 12] == "\0\0\0\fjP \r\n\207\n" then detect_jpeg2000_type(ir)
|
97
111
|
when head[0, 4] == "\377O\377Q" then :j2c
|
98
112
|
when head[0, 4] == "\1\0\0\0" && head[40, 4] == ' EMF' then :emf
|
113
|
+
when head[4, 8] =~ /\Aftypavi[fs]\z/ then :avif
|
114
|
+
when head[4, 8] =~ /\Aftyp(hei[cs]|mif[12]|msf1)\z/ then :heic
|
99
115
|
end
|
100
116
|
end
|
101
117
|
|
@@ -107,7 +123,7 @@ private
|
|
107
123
|
return :apng if type == 'acTL'
|
108
124
|
|
109
125
|
length = ir.unpack1(offset, 4, 'N')
|
110
|
-
offset +=
|
126
|
+
offset += length + 8 + 4
|
111
127
|
end
|
112
128
|
:png
|
113
129
|
end
|
@@ -126,7 +142,7 @@ private
|
|
126
142
|
|
127
143
|
# using xl-box would be weird, but doesn't seem to contradict specification
|
128
144
|
skip = ir[12, 4] == "\0\0\0\1" ? 16 : 8
|
129
|
-
case ir[
|
145
|
+
case ir[skip + 12, 4]
|
130
146
|
when 'jp2 ' then :jp2
|
131
147
|
when 'jpx ' then :jpx
|
132
148
|
end
|
@@ -137,17 +153,13 @@ private
|
|
137
153
|
end
|
138
154
|
|
139
155
|
def size_of_mng(ir)
|
140
|
-
unless ir[12, 4] == 'MHDR'
|
141
|
-
raise FormatError, 'MHDR not in place for MNG'
|
142
|
-
end
|
156
|
+
raise FormatError, 'MHDR not in place for MNG' unless ir[12, 4] == 'MHDR'
|
143
157
|
|
144
158
|
ir.unpack(16, 8, 'NN')
|
145
159
|
end
|
146
160
|
|
147
161
|
def size_of_png(ir)
|
148
|
-
unless ir[12, 4] == 'IHDR'
|
149
|
-
raise FormatError, 'IHDR not in place for PNG'
|
150
|
-
end
|
162
|
+
raise FormatError, 'IHDR not in place for PNG' unless ir[12, 4] == 'IHDR'
|
151
163
|
|
152
164
|
ir.unpack(16, 8, 'NN')
|
153
165
|
end
|
@@ -165,14 +177,12 @@ private
|
|
165
177
|
loop do
|
166
178
|
offset += 1 until [nil, section_marker].include? ir[offset, 1]
|
167
179
|
offset += 1 until section_marker != ir[offset + 1, 1]
|
168
|
-
raise FormatError, 'EOF in JPEG'
|
180
|
+
raise FormatError, 'EOF in JPEG' unless ir[offset, 1]
|
169
181
|
|
170
182
|
code, length = ir.unpack(offset, 4, 'xCn')
|
171
183
|
offset += 4
|
172
184
|
|
173
|
-
if JPEG_CODE_CHECK.include?(code)
|
174
|
-
return ir.unpack(offset + 1, 4, 'nn').reverse
|
175
|
-
end
|
185
|
+
return ir.unpack(offset + 1, 4, 'nn').reverse if JPEG_CODE_CHECK.include?(code)
|
176
186
|
|
177
187
|
offset += length - 2
|
178
188
|
end
|
@@ -237,9 +247,7 @@ private
|
|
237
247
|
def size_of_xpm(ir)
|
238
248
|
length = 1024
|
239
249
|
until (data = ir[0, length]) =~ /"\s*(\d+)\s+(\d+)(\s+\d+\s+\d+){1,2}\s*"/m
|
240
|
-
if data.length != length
|
241
|
-
raise FormatError, 'XPM size not found'
|
242
|
-
end
|
250
|
+
raise FormatError, 'XPM size not found' if data.length != length
|
243
251
|
|
244
252
|
length += 1024
|
245
253
|
end
|
@@ -268,7 +276,7 @@ private
|
|
268
276
|
tag, type = ifd.unpack(endian2b * 2)
|
269
277
|
offset += 12
|
270
278
|
|
271
|
-
next
|
279
|
+
next unless packspec[type]
|
272
280
|
|
273
281
|
value = ifd[8, 4].unpack(packspec[type])[0]
|
274
282
|
case tag
|
@@ -288,7 +296,7 @@ private
|
|
288
296
|
|
289
297
|
def size_of_swf(ir)
|
290
298
|
value_bit_length = ir.unpack1(8, 1, 'B5').to_i(2)
|
291
|
-
bit_length =
|
299
|
+
bit_length = (value_bit_length * 4) + 5
|
292
300
|
rect_bits = ir.unpack1(8, (bit_length / 8) + 1, "B#{bit_length}")
|
293
301
|
values = rect_bits[5..-1].unpack("a#{value_bit_length}" * 4).map{ |bits| bits.to_i(2) }
|
294
302
|
x_min, x_max, y_min, y_max = values
|
@@ -336,38 +344,13 @@ private
|
|
336
344
|
end
|
337
345
|
end
|
338
346
|
|
347
|
+
JP2_WALKER = ImageSize::ISOBMFF.new(
|
348
|
+
recurse: %w[jp2h],
|
349
|
+
last: %w[jp2h]
|
350
|
+
)
|
339
351
|
def size_of_jp2(ir)
|
340
|
-
|
341
|
-
|
342
|
-
in_header = false
|
343
|
-
loop do
|
344
|
-
break if stop && offset >= stop
|
345
|
-
break if ir[offset, 4] == '' || ir[offset, 4].nil?
|
346
|
-
|
347
|
-
size = ir.unpack1(offset, 4, 'N')
|
348
|
-
type = ir.fetch(offset + 4, 4)
|
349
|
-
|
350
|
-
data_offset = 8
|
351
|
-
case size
|
352
|
-
when 1
|
353
|
-
size = ir.unpack1(offset, 8, 'Q>')
|
354
|
-
data_offset = 16
|
355
|
-
raise FormatError, "Unexpected xl-box size #{size}" if (1..15).include?(size)
|
356
|
-
when 2..7
|
357
|
-
raise FormatError, "Reserved box size #{size}"
|
358
|
-
end
|
359
|
-
|
360
|
-
if type == 'jp2h'
|
361
|
-
stop = offset + size unless size.zero?
|
362
|
-
offset += data_offset
|
363
|
-
in_header = true
|
364
|
-
elsif in_header && type == 'ihdr'
|
365
|
-
return ir.unpack(offset + data_offset, 8, 'NN').reverse
|
366
|
-
else
|
367
|
-
break if size.zero? # box to the end of file
|
368
|
-
|
369
|
-
offset += size
|
370
|
-
end
|
352
|
+
JP2_WALKER.recurse(ir) do |box|
|
353
|
+
return ir.unpack(box.data_offset, 8, 'NN').reverse if box.type == 'ihdr'
|
371
354
|
end
|
372
355
|
end
|
373
356
|
alias_method :size_of_jpx, :size_of_jp2
|
@@ -391,4 +374,66 @@ private
|
|
391
374
|
(n.to_f * dpi / 2540).round
|
392
375
|
end
|
393
376
|
end
|
377
|
+
|
378
|
+
HEIF_WALKER = ImageSize::ISOBMFF.new(
|
379
|
+
recurse: %w[meta iprp ipco],
|
380
|
+
full: %w[meta hdlr pitm ipma ispe],
|
381
|
+
last: %w[meta]
|
382
|
+
)
|
383
|
+
def size_of_heif(ir)
|
384
|
+
pitm = nil
|
385
|
+
ipma = nil
|
386
|
+
ispes = {}
|
387
|
+
claps = {}
|
388
|
+
irots = {}
|
389
|
+
|
390
|
+
HEIF_WALKER.recurse(ir) do |box, _path|
|
391
|
+
case box.type
|
392
|
+
when 'hdlr'
|
393
|
+
raise FormatError, "hdlr box too small (#{box.data_size})" if box.data_size < 8
|
394
|
+
|
395
|
+
return nil unless ir[box.data_offset + 4, 4] == 'pict'
|
396
|
+
when 'pitm'
|
397
|
+
raise FormatError, 'second pitm box encountered' if pitm
|
398
|
+
|
399
|
+
pitm = box.version == 0 ? ir.unpack1(box.data_offset, 2, 'n') : ir.unpack1(box.data_offset, 4, 'N')
|
400
|
+
when 'ipma'
|
401
|
+
stream = ir.stream(box.data_offset)
|
402
|
+
|
403
|
+
property_index_16b = (box.flags & 1) == 1
|
404
|
+
|
405
|
+
ipma ||= {}
|
406
|
+
stream.unpack1(4, 'N').times do
|
407
|
+
item_id = box.version == 0 ? stream.unpack1(2, 'n') : stream.unpack1(4, 'N')
|
408
|
+
ipma[item_id] ||= Array.new(stream.unpack1(1, 'C')) do
|
409
|
+
property_index_16b ? stream.unpack1(2, 'n') & 0x7fff : stream.unpack1(1, 'C') & 0x7f
|
410
|
+
end
|
411
|
+
end
|
412
|
+
when 'ispe'
|
413
|
+
ispes[box.index] ||= ir.unpack(box.data_offset, 8, 'NN')
|
414
|
+
when 'clap'
|
415
|
+
width_n, width_d, height_n, height_d = ir.unpack(box.data_offset, 16, 'N4')
|
416
|
+
claps[box.index] ||= [Rational(width_n, width_d).round, Rational(height_n, height_d).round]
|
417
|
+
when 'irot'
|
418
|
+
irots[box.index] ||= ir.unpack1(box.data_offset, 1, 'C') & 0b11
|
419
|
+
end
|
420
|
+
end
|
421
|
+
|
422
|
+
return unless ipma
|
423
|
+
|
424
|
+
properties = ipma[pitm || ipma.keys.min]
|
425
|
+
return unless properties
|
426
|
+
|
427
|
+
dimensions = claps.values_at(*properties).compact.first || ispes.values_at(*properties).compact.first
|
428
|
+
return unless dimensions
|
429
|
+
|
430
|
+
irot = irots.values_at(*properties).compact.first
|
431
|
+
if irot && irot.odd?
|
432
|
+
dimensions.reverse
|
433
|
+
else
|
434
|
+
dimensions
|
435
|
+
end
|
436
|
+
end
|
437
|
+
alias_method :size_of_avif, :size_of_heif
|
438
|
+
alias_method :size_of_heic, :size_of_heif
|
394
439
|
end
|
@@ -29,7 +29,7 @@ describe ImageSize::ChunkyReader do
|
|
29
29
|
{
|
30
30
|
'empty string' => '',
|
31
31
|
'a bit of data' => 'foo bar baz',
|
32
|
-
'a lot of data' => File.
|
32
|
+
'a lot of data' => File.binread('GPL'),
|
33
33
|
}.each do |data_description, data|
|
34
34
|
{
|
35
35
|
'default' => test_reader.new(data),
|
@@ -0,0 +1,352 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'rspec'
|
4
|
+
require 'stringio'
|
5
|
+
|
6
|
+
require 'image_size/string_reader'
|
7
|
+
require 'image_size/isobmff'
|
8
|
+
|
9
|
+
describe ImageSize::ISOBMFF do
|
10
|
+
boxes = Class.new do
|
11
|
+
def self.build(&block)
|
12
|
+
builder = new
|
13
|
+
builder.instance_eval(&block)
|
14
|
+
builder.string
|
15
|
+
end
|
16
|
+
|
17
|
+
def initialize
|
18
|
+
@io = StringIO.new
|
19
|
+
end
|
20
|
+
|
21
|
+
def box(type, size, size64 = nil)
|
22
|
+
@io << [size, type, size64].pack("Na4#{'Q>' if size64}")
|
23
|
+
yield if block_given?
|
24
|
+
end
|
25
|
+
|
26
|
+
def data(content)
|
27
|
+
@io << content
|
28
|
+
end
|
29
|
+
|
30
|
+
def string
|
31
|
+
@io.string
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
let(:instance){ described_class.new(options) }
|
36
|
+
|
37
|
+
let(:string_reader){ ImageSize::StringReader.new(data) }
|
38
|
+
|
39
|
+
describe '#walk' do
|
40
|
+
let(:options){ {} }
|
41
|
+
|
42
|
+
def is_expected
|
43
|
+
expect{ |b| instance.walk(string_reader, &b) }
|
44
|
+
end
|
45
|
+
|
46
|
+
describe 'for multiple boxes' do
|
47
|
+
let(:data) do
|
48
|
+
boxes.build do
|
49
|
+
box('abcd', 8 + 42){ data 'x' * 42 }
|
50
|
+
box('efgh', 8 + 10)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
it do
|
55
|
+
is_expected.to yield_successive_args(
|
56
|
+
having_attributes(type: 'abcd', data_offset: 8, data_size: 42),
|
57
|
+
having_attributes(type: 'efgh', data_offset: 58, data_size: 10)
|
58
|
+
)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
describe 'for empty data' do
|
63
|
+
let(:data){ '' }
|
64
|
+
|
65
|
+
it{ is_expected.not_to yield_control }
|
66
|
+
end
|
67
|
+
|
68
|
+
describe 'for not enough data' do
|
69
|
+
let(:data){ 'test' }
|
70
|
+
|
71
|
+
it{ is_expected.to raise_error ImageSize::FormatError }
|
72
|
+
end
|
73
|
+
|
74
|
+
describe 'for a box without content' do
|
75
|
+
let(:data){ boxes.build{ box('test', 8) } }
|
76
|
+
|
77
|
+
it{ is_expected.to yield_successive_args(having_attributes(type: 'test', data_offset: 8, data_size: 0)) }
|
78
|
+
end
|
79
|
+
|
80
|
+
describe 'for a box with content' do
|
81
|
+
let(:data){ boxes.build{ box('test', 8 + 42) } }
|
82
|
+
|
83
|
+
it do
|
84
|
+
is_expected.to yield_successive_args(having_attributes(type: 'test', data_offset: 8, data_size: 42))
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
describe 'for not enough data in second box' do
|
89
|
+
let(:data) do
|
90
|
+
boxes.build do
|
91
|
+
box('test', 8)
|
92
|
+
data 'test'
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
it{ is_expected.to yield_control.and raise_error ImageSize::FormatError }
|
97
|
+
end
|
98
|
+
|
99
|
+
describe 'for size-less box' do
|
100
|
+
let(:data){ boxes.build{ box('test', 0) } }
|
101
|
+
|
102
|
+
it do
|
103
|
+
is_expected.to yield_successive_args(having_attributes(type: 'test', data_offset: 8, data_size: nil))
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
(2..7).each do |size|
|
108
|
+
describe "for wrong small box size #{size}" do
|
109
|
+
let(:data){ boxes.build{ box('test', size) } }
|
110
|
+
|
111
|
+
it{ is_expected.to raise_error ImageSize::FormatError }
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
describe 'for a big box without content' do
|
116
|
+
let(:data){ boxes.build{ box('test', 1, 16) } }
|
117
|
+
|
118
|
+
it do
|
119
|
+
is_expected.to yield_successive_args(having_attributes(type: 'test', data_offset: 16, data_size: 0))
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
describe 'for a big box with content' do
|
124
|
+
let(:data){ boxes.build{ box('test', 1, 16 + 42) } }
|
125
|
+
|
126
|
+
it do
|
127
|
+
is_expected.to yield_successive_args(having_attributes(type: 'test', data_offset: 16, data_size: 42))
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
describe 'for a full box' do
|
132
|
+
let(:options){ { full: %w[test] } }
|
133
|
+
|
134
|
+
let(:data) do
|
135
|
+
boxes.build do
|
136
|
+
box('test', 8 + 42)
|
137
|
+
data 'abcd'
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
it do
|
142
|
+
is_expected.to yield_successive_args(
|
143
|
+
having_attributes(
|
144
|
+
type: 'test',
|
145
|
+
data_offset: 12,
|
146
|
+
data_size: 38,
|
147
|
+
version: 0x61,
|
148
|
+
flags: 0x626364
|
149
|
+
)
|
150
|
+
)
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
describe 'for a big full box' do
|
155
|
+
let(:options){ { full: %w[test] } }
|
156
|
+
|
157
|
+
let(:data) do
|
158
|
+
boxes.build do
|
159
|
+
box('test', 1, 16 + 42)
|
160
|
+
data 'abcd'
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
it do
|
165
|
+
is_expected.to yield_successive_args(
|
166
|
+
having_attributes(
|
167
|
+
type: 'test',
|
168
|
+
data_offset: 20,
|
169
|
+
data_size: 38,
|
170
|
+
version: 0x61,
|
171
|
+
flags: 0x626364
|
172
|
+
)
|
173
|
+
)
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
16.times do |size|
|
178
|
+
describe "for wrong big box size #{size}" do
|
179
|
+
let(:data){ boxes.build{ box('test', 1, size) } }
|
180
|
+
|
181
|
+
it{ is_expected.to raise_error ImageSize::FormatError }
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
context 'given offset' do
|
186
|
+
let(:data) do
|
187
|
+
boxes.build do
|
188
|
+
box('test', 8)
|
189
|
+
box('fooo', 8)
|
190
|
+
box('barr', 8)
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
def is_expected
|
195
|
+
expect{ |b| instance.walk(string_reader, offset, &b) }
|
196
|
+
end
|
197
|
+
|
198
|
+
describe 'for offset at the end' do
|
199
|
+
let(:offset){ 24 }
|
200
|
+
|
201
|
+
it{ is_expected.not_to yield_control }
|
202
|
+
end
|
203
|
+
|
204
|
+
describe 'for offset at second box' do
|
205
|
+
let(:offset){ 8 }
|
206
|
+
|
207
|
+
it do
|
208
|
+
is_expected.to yield_successive_args(
|
209
|
+
having_attributes(type: 'fooo', data_offset: 16, data_size: 0),
|
210
|
+
having_attributes(type: 'barr', data_offset: 24, data_size: 0)
|
211
|
+
)
|
212
|
+
end
|
213
|
+
end
|
214
|
+
end
|
215
|
+
|
216
|
+
context 'given offset and length' do
|
217
|
+
def is_expected
|
218
|
+
expect{ |b| instance.walk(string_reader, offset, length, &b) }
|
219
|
+
end
|
220
|
+
|
221
|
+
describe 'for offset at second box' do
|
222
|
+
let(:data) do
|
223
|
+
boxes.build do
|
224
|
+
box('test', 8)
|
225
|
+
box('fooo', 8)
|
226
|
+
box('barr', 8)
|
227
|
+
end
|
228
|
+
end
|
229
|
+
let(:offset){ 8 }
|
230
|
+
let(:length){ 8 }
|
231
|
+
|
232
|
+
it do
|
233
|
+
is_expected.to yield_successive_args(having_attributes(type: 'fooo', data_offset: 16, data_size: 0))
|
234
|
+
end
|
235
|
+
end
|
236
|
+
end
|
237
|
+
end
|
238
|
+
|
239
|
+
describe '#recurse' do
|
240
|
+
let(:data) do
|
241
|
+
boxes.build do
|
242
|
+
box('fooA', 8 + 8 + 2) do
|
243
|
+
box('fooB', 8 + 2){ data '11' }
|
244
|
+
end
|
245
|
+
box('barA', 8 + 8 + 8 + 2) do
|
246
|
+
box('barB', 8 + 8 + 2) do
|
247
|
+
box('barC', 8 + 2) do
|
248
|
+
data '22'
|
249
|
+
end
|
250
|
+
end
|
251
|
+
end
|
252
|
+
box('bazA', 8 + 8 + 2) do
|
253
|
+
box('bazB', 8 + 2){ data '33' }
|
254
|
+
end
|
255
|
+
end
|
256
|
+
end
|
257
|
+
|
258
|
+
context 'when configured to recures all' do
|
259
|
+
let(:options){ { recurse: %w[fooA barA barB bazA] } }
|
260
|
+
|
261
|
+
it 'recurses complete tree' do
|
262
|
+
enum = instance.to_enum(:recurse, string_reader)
|
263
|
+
|
264
|
+
expect(enum.next).to have_attributes(type: 'fooA', data_offset: 8, data_size: 10)
|
265
|
+
|
266
|
+
expect(enum.next).to have_attributes(type: 'fooB', data_offset: 16, data_size: 2)
|
267
|
+
|
268
|
+
expect(enum.next).to have_attributes(type: 'barA', data_offset: 26, data_size: 18)
|
269
|
+
|
270
|
+
expect(enum.next).to have_attributes(type: 'barB', data_offset: 34, data_size: 10)
|
271
|
+
|
272
|
+
expect(enum.next).to have_attributes(type: 'barC', data_offset: 42, data_size: 2)
|
273
|
+
|
274
|
+
expect(enum.next).to have_attributes(type: 'bazA', data_offset: 52, data_size: 10)
|
275
|
+
|
276
|
+
expect(enum.next).to have_attributes(type: 'bazB', data_offset: 60, data_size: 2)
|
277
|
+
|
278
|
+
expect{ enum.next }.to raise_exception(StopIteration)
|
279
|
+
end
|
280
|
+
|
281
|
+
it 'returns nil' do
|
282
|
+
expect(instance.recurse(string_reader){ :foo }).to be_nil
|
283
|
+
end
|
284
|
+
end
|
285
|
+
|
286
|
+
context 'when configured to recurse part' do
|
287
|
+
let(:options){ { recurse: %w[barA] } }
|
288
|
+
|
289
|
+
it 'recurses requested part' do
|
290
|
+
enum = instance.to_enum(:recurse, string_reader)
|
291
|
+
|
292
|
+
expect(enum.next).to have_attributes(type: 'fooA', data_offset: 8, data_size: 10)
|
293
|
+
|
294
|
+
expect(enum.next).to have_attributes(type: 'barA', data_offset: 26, data_size: 18)
|
295
|
+
|
296
|
+
expect(enum.next).to have_attributes(type: 'barB', data_offset: 34, data_size: 10)
|
297
|
+
|
298
|
+
expect(enum.next).to have_attributes(type: 'bazA', data_offset: 52, data_size: 10)
|
299
|
+
|
300
|
+
expect{ enum.next }.to raise_exception(StopIteration)
|
301
|
+
end
|
302
|
+
|
303
|
+
it 'returns nil' do
|
304
|
+
expect(instance.recurse(string_reader){ :foo }).to be_nil
|
305
|
+
end
|
306
|
+
end
|
307
|
+
|
308
|
+
context 'when configured to not recurse' do
|
309
|
+
let(:options){ {} }
|
310
|
+
|
311
|
+
it 'does not recurse' do
|
312
|
+
enum = instance.to_enum(:recurse, string_reader)
|
313
|
+
|
314
|
+
expect(enum.next).to have_attributes(type: 'fooA', data_offset: 8, data_size: 10)
|
315
|
+
|
316
|
+
expect(enum.next).to have_attributes(type: 'barA', data_offset: 26, data_size: 18)
|
317
|
+
|
318
|
+
expect(enum.next).to have_attributes(type: 'bazA', data_offset: 52, data_size: 10)
|
319
|
+
|
320
|
+
expect{ enum.next }.to raise_exception(StopIteration)
|
321
|
+
end
|
322
|
+
|
323
|
+
it 'returns nil' do
|
324
|
+
expect(instance.recurse(string_reader){ :foo }).to be_nil
|
325
|
+
end
|
326
|
+
end
|
327
|
+
|
328
|
+
context 'when configured to stop' do
|
329
|
+
let(:options){ { recurse: %w[fooA barA barB bazA], last: %w[barA] } }
|
330
|
+
|
331
|
+
it 'recurses complete tree' do
|
332
|
+
enum = instance.to_enum(:recurse, string_reader)
|
333
|
+
|
334
|
+
expect(enum.next).to have_attributes(type: 'fooA', data_offset: 8, data_size: 10)
|
335
|
+
|
336
|
+
expect(enum.next).to have_attributes(type: 'fooB', data_offset: 16, data_size: 2)
|
337
|
+
|
338
|
+
expect(enum.next).to have_attributes(type: 'barA', data_offset: 26, data_size: 18)
|
339
|
+
|
340
|
+
expect(enum.next).to have_attributes(type: 'barB', data_offset: 34, data_size: 10)
|
341
|
+
|
342
|
+
expect(enum.next).to have_attributes(type: 'barC', data_offset: 42, data_size: 2)
|
343
|
+
|
344
|
+
expect{ enum.next }.to raise_exception(StopIteration)
|
345
|
+
end
|
346
|
+
|
347
|
+
it 'returns nil' do
|
348
|
+
expect(instance.recurse(string_reader){ :foo }).to be_nil
|
349
|
+
end
|
350
|
+
end
|
351
|
+
end
|
352
|
+
end
|
data/spec/image_size_spec.rb
CHANGED
@@ -10,6 +10,10 @@ require 'shellwords'
|
|
10
10
|
|
11
11
|
require 'test_server'
|
12
12
|
|
13
|
+
RSpec.configure do |config|
|
14
|
+
config.order = :random
|
15
|
+
end
|
16
|
+
|
13
17
|
describe ImageSize do
|
14
18
|
before :all do
|
15
19
|
@server = TestServer.new
|
@@ -19,6 +23,19 @@ describe ImageSize do
|
|
19
23
|
@server.finish
|
20
24
|
end
|
21
25
|
|
26
|
+
def retry_on(exception_class)
|
27
|
+
attempt = 1
|
28
|
+
begin
|
29
|
+
yield
|
30
|
+
rescue exception_class => e
|
31
|
+
warn "Attempt #{attempt}: #{e.inspect}"
|
32
|
+
raise unless attempt < 3
|
33
|
+
|
34
|
+
attempt += 1
|
35
|
+
retry
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
22
39
|
def supported_formats
|
23
40
|
ImageSize.private_instance_methods.map{ |name| name[/\Asize_of_(.*)\z/, 1] }.compact.sort
|
24
41
|
end
|
@@ -45,19 +62,26 @@ describe ImageSize do
|
|
45
62
|
describe "for #{path}" do
|
46
63
|
let(:name){ File.basename(path) }
|
47
64
|
let(:attributes) do
|
48
|
-
match = /(\d+)x(\d+)\.([^.]+)$/.match(name)
|
49
|
-
|
65
|
+
if (match = /(\d+)x(\d+)\.([^.]+)$/.match(name))
|
66
|
+
width = match[1].to_i
|
67
|
+
height = match[2].to_i
|
68
|
+
format = match[3].to_sym
|
69
|
+
end
|
50
70
|
size = format && [width, height]
|
71
|
+
media_types = ImageSize::MEDIA_TYPES[format] || []
|
72
|
+
media_type = format && media_types.first.to_s
|
51
73
|
{
|
52
|
-
:
|
53
|
-
:
|
54
|
-
:
|
55
|
-
:
|
56
|
-
:
|
57
|
-
:
|
74
|
+
format: format,
|
75
|
+
width: width,
|
76
|
+
height: height,
|
77
|
+
w: width,
|
78
|
+
h: height,
|
79
|
+
size: size,
|
80
|
+
media_type: media_type,
|
81
|
+
media_types: media_types,
|
58
82
|
}
|
59
83
|
end
|
60
|
-
let(:file_data){ File.
|
84
|
+
let(:file_data){ File.binread(path) }
|
61
85
|
let(:file_size){ file_data.length }
|
62
86
|
|
63
87
|
before do
|
@@ -148,14 +172,18 @@ describe ImageSize do
|
|
148
172
|
context 'supporting range' do
|
149
173
|
context 'without redirects' do
|
150
174
|
it 'gets format and dimensions' do
|
151
|
-
image_size =
|
175
|
+
image_size = retry_on Timeout::Error do
|
176
|
+
ImageSize.url(file_url)
|
177
|
+
end
|
152
178
|
expect(image_size).to have_attributes(attributes)
|
153
179
|
end
|
154
180
|
end
|
155
181
|
|
156
182
|
context 'with redirects' do
|
157
183
|
it 'gets format and dimensions' do
|
158
|
-
image_size =
|
184
|
+
image_size = retry_on Timeout::Error do
|
185
|
+
ImageSize.url("#{file_url}?redirect=5")
|
186
|
+
end
|
159
187
|
expect(image_size).to have_attributes(attributes)
|
160
188
|
end
|
161
189
|
end
|
@@ -163,7 +191,9 @@ describe ImageSize do
|
|
163
191
|
context 'with too many redirects' do
|
164
192
|
it 'gets format and dimensions' do
|
165
193
|
expect do
|
166
|
-
|
194
|
+
retry_on Timeout::Error do
|
195
|
+
ImageSize.url("#{file_url}?redirect=6")
|
196
|
+
end
|
167
197
|
end.to raise_error(/Too many redirects/)
|
168
198
|
end
|
169
199
|
end
|
@@ -172,14 +202,18 @@ describe ImageSize do
|
|
172
202
|
context 'not supporting range' do
|
173
203
|
context 'without redirects' do
|
174
204
|
it 'gets format and dimensions' do
|
175
|
-
image_size =
|
205
|
+
image_size = retry_on Timeout::Error do
|
206
|
+
ImageSize.url("#{file_url}?ignore_range")
|
207
|
+
end
|
176
208
|
expect(image_size).to have_attributes(attributes)
|
177
209
|
end
|
178
210
|
end
|
179
211
|
|
180
212
|
context 'with redirects' do
|
181
213
|
it 'gets format and dimensions' do
|
182
|
-
image_size =
|
214
|
+
image_size = retry_on Timeout::Error do
|
215
|
+
ImageSize.url("#{file_url}?ignore_range&redirect=5")
|
216
|
+
end
|
183
217
|
expect(image_size).to have_attributes(attributes)
|
184
218
|
end
|
185
219
|
end
|
@@ -187,7 +221,9 @@ describe ImageSize do
|
|
187
221
|
context 'with too many redirects' do
|
188
222
|
it 'gets format and dimensions' do
|
189
223
|
expect do
|
190
|
-
|
224
|
+
retry_on Timeout::Error do
|
225
|
+
ImageSize.url("#{file_url}?ignore_range&redirect=6")
|
226
|
+
end
|
191
227
|
end.to raise_error(/Too many redirects/)
|
192
228
|
end
|
193
229
|
end
|
@@ -203,8 +239,8 @@ describe ImageSize do
|
|
203
239
|
end
|
204
240
|
|
205
241
|
{
|
206
|
-
:
|
207
|
-
:
|
242
|
+
png: "\211PNG\r\n\032\n",
|
243
|
+
jpeg: "\377\330",
|
208
244
|
}.each do |type, data|
|
209
245
|
it "raises FormatError if invalid #{type} given" do
|
210
246
|
expect do
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
data/spec/test_server.rb
CHANGED
@@ -1,18 +1,19 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require 'webrick'
|
4
|
+
require 'stringio'
|
4
5
|
|
5
6
|
class TestServer
|
6
7
|
attr_reader :base_url
|
7
8
|
|
8
9
|
def initialize(host = '127.0.0.1')
|
9
10
|
server_options = {
|
10
|
-
:
|
11
|
-
:
|
12
|
-
:
|
13
|
-
:
|
14
|
-
:
|
15
|
-
:
|
11
|
+
Logger: WEBrick::Log.new(StringIO.new),
|
12
|
+
AccessLog: [],
|
13
|
+
BindAddress: host,
|
14
|
+
Port: 0, # get the next available port
|
15
|
+
DocumentRoot: '.',
|
16
|
+
RequestCallback: proc do |req, res|
|
16
17
|
redirect = req.query['redirect'].to_i
|
17
18
|
if redirect > 0
|
18
19
|
res.set_redirect(
|
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: 3.
|
4
|
+
version: 3.4.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:
|
12
|
+
date: 2024-01-16 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rspec
|
@@ -53,9 +53,9 @@ dependencies:
|
|
53
53
|
- - "~>"
|
54
54
|
- !ruby/object:Gem::Version
|
55
55
|
version: '2.0'
|
56
|
-
description: 'Measure following file dimensions: apng, bmp, cur, emf, gif,
|
57
|
-
jp2, jpeg, jpx, mng, pam, pbm, pcx, pgm, png, ppm, psd, svg, swf,
|
58
|
-
xpm'
|
56
|
+
description: 'Measure following file dimensions: apng, avif, bmp, cur, emf, gif, heic,
|
57
|
+
heif, ico, j2c, jp2, jpeg, jpx, mng, pam, pbm, pcx, pgm, png, ppm, psd, svg, swf,
|
58
|
+
tiff, webp, xbm, xpm'
|
59
59
|
email:
|
60
60
|
executables: []
|
61
61
|
extensions: []
|
@@ -74,12 +74,17 @@ files:
|
|
74
74
|
- image_size.gemspec
|
75
75
|
- lib/image_size.rb
|
76
76
|
- lib/image_size/chunky_reader.rb
|
77
|
+
- lib/image_size/format_error.rb
|
78
|
+
- lib/image_size/isobmff.rb
|
79
|
+
- lib/image_size/media_types.rb
|
77
80
|
- lib/image_size/reader.rb
|
78
81
|
- lib/image_size/seekable_io_reader.rb
|
79
82
|
- lib/image_size/stream_io_reader.rb
|
80
83
|
- lib/image_size/string_reader.rb
|
84
|
+
- lib/image_size/uri.rb
|
81
85
|
- lib/image_size/uri_reader.rb
|
82
86
|
- spec/image_size/chunky_reader_spec.rb
|
87
|
+
- spec/image_size/isobmff_spec.rb
|
83
88
|
- spec/image_size/seekable_io_reader_spec.rb
|
84
89
|
- spec/image_size_spec.rb
|
85
90
|
- spec/images/.gitattributes
|
@@ -91,6 +96,11 @@ files:
|
|
91
96
|
- spec/images/emf/77x77.emf
|
92
97
|
- spec/images/empty
|
93
98
|
- spec/images/gif/668x481.gif
|
99
|
+
- spec/images/heif/ap_maloletka_mariupol.452x301.heic
|
100
|
+
- spec/images/heif/maxar_mariupol.400x300.avif
|
101
|
+
- spec/images/heif/multiple.169x83.heic
|
102
|
+
- spec/images/heif/rotate.73x173.avif
|
103
|
+
- spec/images/heif/sequence.7x5.avif
|
94
104
|
- spec/images/ico/32x256.ico
|
95
105
|
- spec/images/jp2/163x402.jp2
|
96
106
|
- spec/images/jp2/176x373.jpx
|
@@ -130,7 +140,7 @@ licenses:
|
|
130
140
|
metadata:
|
131
141
|
bug_tracker_uri: https://github.com/toy/image_size/issues
|
132
142
|
changelog_uri: https://github.com/toy/image_size/blob/master/CHANGELOG.markdown
|
133
|
-
documentation_uri: https://www.rubydoc.info/gems/image_size/3.
|
143
|
+
documentation_uri: https://www.rubydoc.info/gems/image_size/3.4.0
|
134
144
|
source_code_uri: https://github.com/toy/image_size
|
135
145
|
post_install_message:
|
136
146
|
rdoc_options: []
|
@@ -140,19 +150,20 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
140
150
|
requirements:
|
141
151
|
- - ">="
|
142
152
|
- !ruby/object:Gem::Version
|
143
|
-
version:
|
153
|
+
version: 1.9.3
|
144
154
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
145
155
|
requirements:
|
146
156
|
- - ">="
|
147
157
|
- !ruby/object:Gem::Version
|
148
158
|
version: '0'
|
149
159
|
requirements: []
|
150
|
-
rubygems_version: 3.
|
160
|
+
rubygems_version: 3.4.20
|
151
161
|
signing_key:
|
152
162
|
specification_version: 4
|
153
|
-
summary: Measure image size using pure Ruby
|
163
|
+
summary: Measure image size/dimensions using pure Ruby
|
154
164
|
test_files:
|
155
165
|
- spec/image_size/chunky_reader_spec.rb
|
166
|
+
- spec/image_size/isobmff_spec.rb
|
156
167
|
- spec/image_size/seekable_io_reader_spec.rb
|
157
168
|
- spec/image_size_spec.rb
|
158
169
|
- spec/images/.gitattributes
|
@@ -164,6 +175,11 @@ test_files:
|
|
164
175
|
- spec/images/emf/77x77.emf
|
165
176
|
- spec/images/empty
|
166
177
|
- spec/images/gif/668x481.gif
|
178
|
+
- spec/images/heif/ap_maloletka_mariupol.452x301.heic
|
179
|
+
- spec/images/heif/maxar_mariupol.400x300.avif
|
180
|
+
- spec/images/heif/multiple.169x83.heic
|
181
|
+
- spec/images/heif/rotate.73x173.avif
|
182
|
+
- spec/images/heif/sequence.7x5.avif
|
167
183
|
- spec/images/ico/32x256.ico
|
168
184
|
- spec/images/jp2/163x402.jp2
|
169
185
|
- spec/images/jp2/176x373.jpx
|