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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f086d46fca698649fed0a23adca9dc67e1fa3317d9f3c209f1fe7f07fcd2cb6b
4
- data.tar.gz: ff8f5243aadd37691eb7dabaa7e4b0d3b6fb212aba286a10d86165dc174eabff
3
+ metadata.gz: 4d0b43a481f6a18e76f7cf8325240014d2a1e07a52cfb462be37444e020ff036
4
+ data.tar.gz: 21edad5bbdd1a4e4b60953ee2769bd0648e8deb389f220f0dbd120f6a9dee09d
5
5
  SHA512:
6
- metadata.gz: 6ff23aa12846de6eaeb9a8ae02cde4a152f891e863056cf901732ef1c6cd1a580befab1cb62037feed500906901018cf8cdb64b63dd6538c07f0b84c5e8ce80e
7
- data.tar.gz: fe6d488afbac72d4a8c16e3d1457c9ae3acf82b87ac9bd6df34e4077f0a0166c47862eac796f71e34084bb7c1314e49f79963eba41ad73de935232b2f2c35bd1
6
+ metadata.gz: 78e9a671390fc80747273ccd6555e2faa915c4186261f473e86aa274f3e93e75c5e26f4674f3fea307d8a94ca21e352dfeb44a2046f20fb453d7e394f56c13b5
7
+ data.tar.gz: 3dd7259403bb9aebc0d75834330f8db1b21d68bc619ec9204475e46e4c46b818ce28af2712d57a0c7ef3d7bb1a5922f3d91ba3a6b6779c7421e2fd2d9eaf4182
@@ -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
- - jruby-9.1
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@v2
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@v2
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@v2
60
+ - uses: actions/checkout@v3
59
61
  - uses: ruby/setup-ruby@v1
60
62
  with:
61
63
  ruby-version: "${{ matrix.ruby }}"
@@ -8,9 +8,9 @@ jobs:
8
8
  rubocop:
9
9
  runs-on: ubuntu-latest
10
10
  steps:
11
- - uses: actions/checkout@v2
11
+ - uses: actions/checkout@v3
12
12
  - uses: ruby/setup-ruby@v1
13
13
  with:
14
- ruby-version: '3.1'
14
+ ruby-version: '3'
15
15
  bundler-cache: true
16
16
  - run: bundle exec rubocop
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/master?logo=github)](https://github.com/toy/image_size/actions/workflows/check.yml)
3
- [![Rubocop](https://img.shields.io/github/workflow/status/toy/image_size/rubocop/master?label=rubocop&logo=rubocop)](https://github.com/toy/image_size/actions/workflows/rubocop.yml)
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-2022 Ivan Kuchin https://github.com/toy/image_size
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.2.0'
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,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ class ImageSize
4
+ class FormatError < StandardError; end
5
+ end
@@ -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
@@ -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] =~ /GIF8[79]a/ then :gif
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] =~ /P[1-6]\s|P7\n/ then detect_pnm_type(ir)
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] =~ /[FC]WS/ then :swf
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] =~ /RIFF(?m:....)WEBP/ then :webp
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 += 8 + length + 4
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[12 + skip, 4]
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 = 5 + (value_bit_length * 4)
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
- offset = 12
341
- stop = nil
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
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.2.0
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: 2022-11-02 00:00:00.000000000 Z
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, ico, j2c,
57
- jp2, jpeg, jpx, mng, pam, pbm, pcx, pgm, png, ppm, psd, svg, swf, tiff, webp, xbm,
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.2.0
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.3.11
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