image_size 3.2.0 → 3.3.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
[![Gem Version](https://img.shields.io/gem/v/image_size?logo=rubygems)](https://rubygems.org/gems/image_size)
|
2
|
-
[![Build Status](https://img.shields.io/github/workflow/status/toy/image_size/check
|
3
|
-
[![Rubocop](https://img.shields.io/github/workflow/status/toy/image_size/rubocop
|
2
|
+
[![Build Status](https://img.shields.io/github/actions/workflow/status/toy/image_size/check.yml?logo=github)](https://github.com/toy/image_size/actions/workflows/check.yml)
|
3
|
+
[![Rubocop](https://img.shields.io/github/actions/workflow/status/toy/image_size/rubocop.yml?label=rubocop&logo=rubocop)](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
|