image_size 3.2.0 → 3.3.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 +10 -8
- data/.github/workflows/rubocop.yml +2 -2
- data/CHANGELOG.markdown +5 -0
- data/README.markdown +5 -5
- data/image_size.gemspec +3 -3
- data/lib/image_size/format_error.rb +5 -0
- data/lib/image_size/isobmff.rb +99 -0
- data/lib/image_size/reader.rb +17 -0
- data/lib/image_size.rb +79 -40
- data/spec/image_size/isobmff_spec.rb +352 -0
- 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
- metadata +22 -8
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4d0b43a481f6a18e76f7cf8325240014d2a1e07a52cfb462be37444e020ff036
|
4
|
+
data.tar.gz: 21edad5bbdd1a4e4b60953ee2769bd0648e8deb389f220f0dbd120f6a9dee09d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 78e9a671390fc80747273ccd6555e2faa915c4186261f473e86aa274f3e93e75c5e26f4674f3fea307d8a94ca21e352dfeb44a2046f20fb453d7e394f56c13b5
|
7
|
+
data.tar.gz: 3dd7259403bb9aebc0d75834330f8db1b21d68bc619ec9204475e46e4c46b818ce28af2712d57a0c7ef3d7bb1a5922f3d91ba3a6b6779c7421e2fd2d9eaf4182
|
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,12 @@ jobs:
|
|
20
17
|
- '2.7'
|
21
18
|
- '3.0'
|
22
19
|
- '3.1'
|
23
|
-
-
|
24
|
-
- jruby-9.2
|
20
|
+
- '3.2'
|
25
21
|
- jruby-9.3
|
22
|
+
- jruby-9.4
|
26
23
|
fail-fast: false
|
27
24
|
steps:
|
28
|
-
- uses: actions/checkout@
|
25
|
+
- uses: actions/checkout@v3
|
29
26
|
- uses: ruby/setup-ruby@v1
|
30
27
|
with:
|
31
28
|
ruby-version: "${{ matrix.ruby }}"
|
@@ -39,9 +36,12 @@ jobs:
|
|
39
36
|
container:
|
40
37
|
- 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'
|
56
57
|
fail-fast: false
|
58
|
+
continue-on-error: ${{ matrix.ruby == '3.2' }}
|
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/CHANGELOG.markdown
CHANGED
@@ -2,6 +2,11 @@
|
|
2
2
|
|
3
3
|
## unreleased
|
4
4
|
|
5
|
+
## v3.3.0 (2023-05-30)
|
6
|
+
|
7
|
+
* Support `HEIF` (`HEIC` and `AVIF`) images [#19](https://github.com/toy/image_size/issues/19) [@toy](https://github.com/toy)
|
8
|
+
* Fix handling `JPEG 2000` 64 bit size boxes [@toy](https://github.com/toy)
|
9
|
+
|
5
10
|
## v3.2.0 (2022-11-03)
|
6
11
|
|
7
12
|
* 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
|
|
@@ -144,4 +144,4 @@ puts Benchmark.measure{ p ImageSize.url(url).size }
|
|
144
144
|
This code is free to use under the terms of the [Ruby's licence](LICENSE.txt).
|
145
145
|
|
146
146
|
Original author: Keisuke Minami <keisuke@rccn.com>.\
|
147
|
-
Further development 2010-
|
147
|
+
Further development 2010-2023 Ivan Kuchin https://github.com/toy/image_size
|
data/image_size.gemspec
CHANGED
@@ -2,9 +2,9 @@
|
|
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.3.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'
|
@@ -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
|
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,8 @@
|
|
1
1
|
# encoding: BINARY
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
|
+
require 'image_size/isobmff'
|
5
|
+
require 'image_size/format_error'
|
4
6
|
require 'image_size/reader'
|
5
7
|
require 'image_size/seekable_io_reader'
|
6
8
|
require 'image_size/stream_io_reader'
|
@@ -8,8 +10,6 @@ require 'image_size/string_reader'
|
|
8
10
|
|
9
11
|
# Determine image format and size
|
10
12
|
class ImageSize
|
11
|
-
class FormatError < StandardError; end
|
12
|
-
|
13
13
|
# Array joining with 'x'
|
14
14
|
class Size < Array
|
15
15
|
# join using 'x'
|
@@ -77,25 +77,27 @@ private
|
|
77
77
|
head = ir[0, 1024]
|
78
78
|
case
|
79
79
|
when head.nil? || head.empty? then nil
|
80
|
-
when head[0, 6] =~
|
80
|
+
when head[0, 6] =~ /\AGIF8[79]a\z/ then :gif
|
81
81
|
when head[0, 8] == "\211PNG\r\n\032\n" then detect_png_type(ir)
|
82
82
|
when head[0, 8] == "\212MNG\r\n\032\n" then :mng
|
83
83
|
when head[0, 2] == "\377\330" then :jpeg
|
84
84
|
when head[0, 2] == 'BM' then :bmp
|
85
|
-
when head[0, 3] =~
|
85
|
+
when head[0, 3] =~ /\AP([1-6]\s|7\n)\z/ then detect_pnm_type(ir)
|
86
86
|
when head =~ /\#define\s+\S+\s+\d+/ then :xbm
|
87
87
|
when %W[II*\0 MM\0*].include?(head[0, 4]) then :tiff
|
88
88
|
when head =~ %r{/\* XPM \*/} then :xpm
|
89
89
|
when head[0, 4] == '8BPS' then :psd
|
90
|
-
when head[0, 3] =~
|
90
|
+
when head[0, 3] =~ /\A[FC]WS\z/ then :swf
|
91
91
|
when head =~ SVG_R || (head =~ XML_R && ir[0, 4096] =~ SVG_R) then :svg
|
92
92
|
when head[0, 2] =~ /\n[\0-\5]/ then :pcx
|
93
|
-
when head[0, 12] =~
|
93
|
+
when head[0, 12] =~ /\ARIFF(?m:....)WEBP\z/ then :webp
|
94
94
|
when head[0, 4] == "\0\0\1\0" then :ico
|
95
95
|
when head[0, 4] == "\0\0\2\0" then :cur
|
96
96
|
when head[0, 12] == "\0\0\0\fjP \r\n\207\n" then detect_jpeg2000_type(ir)
|
97
97
|
when head[0, 4] == "\377O\377Q" then :j2c
|
98
98
|
when head[0, 4] == "\1\0\0\0" && head[40, 4] == ' EMF' then :emf
|
99
|
+
when head[4, 8] =~ /\Aftypavi[fs]\z/ then :avif
|
100
|
+
when head[4, 8] =~ /\Aftyp(hei[cs]|mif[12]|msf1)\z/ then :heic
|
99
101
|
end
|
100
102
|
end
|
101
103
|
|
@@ -107,7 +109,7 @@ private
|
|
107
109
|
return :apng if type == 'acTL'
|
108
110
|
|
109
111
|
length = ir.unpack1(offset, 4, 'N')
|
110
|
-
offset +=
|
112
|
+
offset += length + 8 + 4
|
111
113
|
end
|
112
114
|
:png
|
113
115
|
end
|
@@ -126,7 +128,7 @@ private
|
|
126
128
|
|
127
129
|
# using xl-box would be weird, but doesn't seem to contradict specification
|
128
130
|
skip = ir[12, 4] == "\0\0\0\1" ? 16 : 8
|
129
|
-
case ir[
|
131
|
+
case ir[skip + 12, 4]
|
130
132
|
when 'jp2 ' then :jp2
|
131
133
|
when 'jpx ' then :jpx
|
132
134
|
end
|
@@ -288,7 +290,7 @@ private
|
|
288
290
|
|
289
291
|
def size_of_swf(ir)
|
290
292
|
value_bit_length = ir.unpack1(8, 1, 'B5').to_i(2)
|
291
|
-
bit_length =
|
293
|
+
bit_length = (value_bit_length * 4) + 5
|
292
294
|
rect_bits = ir.unpack1(8, (bit_length / 8) + 1, "B#{bit_length}")
|
293
295
|
values = rect_bits[5..-1].unpack("a#{value_bit_length}" * 4).map{ |bits| bits.to_i(2) }
|
294
296
|
x_min, x_max, y_min, y_max = values
|
@@ -336,38 +338,13 @@ private
|
|
336
338
|
end
|
337
339
|
end
|
338
340
|
|
341
|
+
JP2_WALKER = ImageSize::ISOBMFF.new(
|
342
|
+
:recurse => %w[jp2h],
|
343
|
+
:last => %w[jp2h]
|
344
|
+
)
|
339
345
|
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
|
346
|
+
JP2_WALKER.recurse(ir) do |box|
|
347
|
+
return ir.unpack(box.data_offset, 8, 'NN').reverse if box.type == 'ihdr'
|
371
348
|
end
|
372
349
|
end
|
373
350
|
alias_method :size_of_jpx, :size_of_jp2
|
@@ -391,4 +368,66 @@ private
|
|
391
368
|
(n.to_f * dpi / 2540).round
|
392
369
|
end
|
393
370
|
end
|
371
|
+
|
372
|
+
HEIF_WALKER = ImageSize::ISOBMFF.new(
|
373
|
+
:recurse => %w[meta iprp ipco],
|
374
|
+
:full => %w[meta hdlr pitm ipma ispe],
|
375
|
+
:last => %w[meta]
|
376
|
+
)
|
377
|
+
def size_of_heif(ir)
|
378
|
+
pitm = nil
|
379
|
+
ipma = nil
|
380
|
+
ispes = {}
|
381
|
+
claps = {}
|
382
|
+
irots = {}
|
383
|
+
|
384
|
+
HEIF_WALKER.recurse(ir) do |box, _path|
|
385
|
+
case box.type
|
386
|
+
when 'hdlr'
|
387
|
+
raise FormatError, "hdlr box too small (#{box.data_size})" if box.data_size < 8
|
388
|
+
|
389
|
+
return nil unless ir[box.data_offset + 4, 4] == 'pict'
|
390
|
+
when 'pitm'
|
391
|
+
raise FormatError, 'second pitm box encountered' if pitm
|
392
|
+
|
393
|
+
pitm = box.version == 0 ? ir.unpack1(box.data_offset, 2, 'n') : ir.unpack1(box.data_offset, 4, 'N')
|
394
|
+
when 'ipma'
|
395
|
+
stream = ir.stream(box.data_offset)
|
396
|
+
|
397
|
+
property_index_16b = (box.flags & 1) == 1
|
398
|
+
|
399
|
+
ipma ||= {}
|
400
|
+
stream.unpack1(4, 'N').times do
|
401
|
+
item_id = box.version == 0 ? stream.unpack1(2, 'n') : stream.unpack1(4, 'N')
|
402
|
+
ipma[item_id] ||= Array.new(stream.unpack1(1, 'C')) do
|
403
|
+
property_index_16b ? stream.unpack1(2, 'n') & 0x7fff : stream.unpack1(1, 'C') & 0x7f
|
404
|
+
end
|
405
|
+
end
|
406
|
+
when 'ispe'
|
407
|
+
ispes[box.index] ||= ir.unpack(box.data_offset, 8, 'NN')
|
408
|
+
when 'clap'
|
409
|
+
width_n, width_d, height_n, height_d = ir.unpack(box.data_offset, 16, 'N4')
|
410
|
+
claps[box.index] ||= [Rational(width_n, width_d).round, Rational(height_n, height_d).round]
|
411
|
+
when 'irot'
|
412
|
+
irots[box.index] ||= ir.unpack1(box.data_offset, 1, 'C') & 0b11
|
413
|
+
end
|
414
|
+
end
|
415
|
+
|
416
|
+
return unless ipma
|
417
|
+
|
418
|
+
properties = ipma[pitm || ipma.keys.min]
|
419
|
+
return unless properties
|
420
|
+
|
421
|
+
dimensions = claps.values_at(*properties).compact.first || ispes.values_at(*properties).compact.first
|
422
|
+
return unless dimensions
|
423
|
+
|
424
|
+
irot = irots.values_at(*properties).compact.first
|
425
|
+
if irot && irot.odd?
|
426
|
+
dimensions.reverse
|
427
|
+
else
|
428
|
+
dimensions
|
429
|
+
end
|
430
|
+
end
|
431
|
+
alias_method :size_of_avif, :size_of_heif
|
432
|
+
alias_method :size_of_heic, :size_of_heif
|
394
433
|
end
|
@@ -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
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
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.3.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: 2023-05-30 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,15 @@ 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
|
77
79
|
- lib/image_size/reader.rb
|
78
80
|
- lib/image_size/seekable_io_reader.rb
|
79
81
|
- lib/image_size/stream_io_reader.rb
|
80
82
|
- lib/image_size/string_reader.rb
|
81
83
|
- lib/image_size/uri_reader.rb
|
82
84
|
- spec/image_size/chunky_reader_spec.rb
|
85
|
+
- spec/image_size/isobmff_spec.rb
|
83
86
|
- spec/image_size/seekable_io_reader_spec.rb
|
84
87
|
- spec/image_size_spec.rb
|
85
88
|
- spec/images/.gitattributes
|
@@ -91,6 +94,11 @@ files:
|
|
91
94
|
- spec/images/emf/77x77.emf
|
92
95
|
- spec/images/empty
|
93
96
|
- spec/images/gif/668x481.gif
|
97
|
+
- spec/images/heif/ap_maloletka_mariupol.452x301.heic
|
98
|
+
- spec/images/heif/maxar_mariupol.400x300.avif
|
99
|
+
- spec/images/heif/multiple.169x83.heic
|
100
|
+
- spec/images/heif/rotate.73x173.avif
|
101
|
+
- spec/images/heif/sequence.7x5.avif
|
94
102
|
- spec/images/ico/32x256.ico
|
95
103
|
- spec/images/jp2/163x402.jp2
|
96
104
|
- spec/images/jp2/176x373.jpx
|
@@ -130,7 +138,7 @@ licenses:
|
|
130
138
|
metadata:
|
131
139
|
bug_tracker_uri: https://github.com/toy/image_size/issues
|
132
140
|
changelog_uri: https://github.com/toy/image_size/blob/master/CHANGELOG.markdown
|
133
|
-
documentation_uri: https://www.rubydoc.info/gems/image_size/3.
|
141
|
+
documentation_uri: https://www.rubydoc.info/gems/image_size/3.3.0
|
134
142
|
source_code_uri: https://github.com/toy/image_size
|
135
143
|
post_install_message:
|
136
144
|
rdoc_options: []
|
@@ -147,12 +155,13 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
147
155
|
- !ruby/object:Gem::Version
|
148
156
|
version: '0'
|
149
157
|
requirements: []
|
150
|
-
rubygems_version: 3.
|
158
|
+
rubygems_version: 3.4.12
|
151
159
|
signing_key:
|
152
160
|
specification_version: 4
|
153
|
-
summary: Measure image size using pure Ruby
|
161
|
+
summary: Measure image size/dimensions using pure Ruby
|
154
162
|
test_files:
|
155
163
|
- spec/image_size/chunky_reader_spec.rb
|
164
|
+
- spec/image_size/isobmff_spec.rb
|
156
165
|
- spec/image_size/seekable_io_reader_spec.rb
|
157
166
|
- spec/image_size_spec.rb
|
158
167
|
- spec/images/.gitattributes
|
@@ -164,6 +173,11 @@ test_files:
|
|
164
173
|
- spec/images/emf/77x77.emf
|
165
174
|
- spec/images/empty
|
166
175
|
- spec/images/gif/668x481.gif
|
176
|
+
- spec/images/heif/ap_maloletka_mariupol.452x301.heic
|
177
|
+
- spec/images/heif/maxar_mariupol.400x300.avif
|
178
|
+
- spec/images/heif/multiple.169x83.heic
|
179
|
+
- spec/images/heif/rotate.73x173.avif
|
180
|
+
- spec/images/heif/sequence.7x5.avif
|
167
181
|
- spec/images/ico/32x256.ico
|
168
182
|
- spec/images/jp2/163x402.jp2
|
169
183
|
- spec/images/jp2/176x373.jpx
|