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 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