image_size 3.1.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: b0873a78a9fb9f84f07d0389071379dda288330bd38ee7516b442ffde85a3af8
4
- data.tar.gz: 0b9fffd2311efa1c24379686821289951a4cd2ec895c27325148a886a24ff57b
3
+ metadata.gz: 4d0b43a481f6a18e76f7cf8325240014d2a1e07a52cfb462be37444e020ff036
4
+ data.tar.gz: 21edad5bbdd1a4e4b60953ee2769bd0648e8deb389f220f0dbd120f6a9dee09d
5
5
  SHA512:
6
- metadata.gz: f6dede99002af9b9fc32f9776e42019079abcd846370c06b2aeee812a73c9ec46dcb6c703d1ae454f807d532c1d6b740aabb999892a6ef610cd17311f6a9a7f0
7
- data.tar.gz: 819d8a3b7797b1f12b59eb608ffdeda9338314a923ca54711143832685853ed36b9ae8d165fcfa242ce4e1d72aecd5a03bd11714aa7aee3b464722ea8bb4688f
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,15 @@
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
+
10
+ ## v3.2.0 (2022-11-03)
11
+
12
+ * Support `EMF` images [#21](https://github.com/toy/image_size/pull/21) [@opoudjis](https://github.com/opoudjis)
13
+
5
14
  ## v3.1.0 (2022-09-17)
6
15
 
7
16
  * Document experimental fetching from http server [#18](https://github.com/toy/image_size/issues/18) [@toy](https://github.com/toy)
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`, `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.1.0'
6
- s.summary = %q{Measure image size using pure Ruby}
7
- s.description = %q{Measure following file dimensions: apng, bmp, cur, 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,24 +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
+ 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
98
101
  end
99
102
  end
100
103
 
@@ -106,7 +109,7 @@ private
106
109
  return :apng if type == 'acTL'
107
110
 
108
111
  length = ir.unpack1(offset, 4, 'N')
109
- offset += 8 + length + 4
112
+ offset += length + 8 + 4
110
113
  end
111
114
  :png
112
115
  end
@@ -125,7 +128,7 @@ private
125
128
 
126
129
  # using xl-box would be weird, but doesn't seem to contradict specification
127
130
  skip = ir[12, 4] == "\0\0\0\1" ? 16 : 8
128
- case ir[12 + skip, 4]
131
+ case ir[skip + 12, 4]
129
132
  when 'jp2 ' then :jp2
130
133
  when 'jpx ' then :jpx
131
134
  end
@@ -287,7 +290,7 @@ private
287
290
 
288
291
  def size_of_swf(ir)
289
292
  value_bit_length = ir.unpack1(8, 1, 'B5').to_i(2)
290
- bit_length = 5 + (value_bit_length * 4)
293
+ bit_length = (value_bit_length * 4) + 5
291
294
  rect_bits = ir.unpack1(8, (bit_length / 8) + 1, "B#{bit_length}")
292
295
  values = rect_bits[5..-1].unpack("a#{value_bit_length}" * 4).map{ |bits| bits.to_i(2) }
293
296
  x_min, x_max, y_min, y_max = values
@@ -335,38 +338,13 @@ private
335
338
  end
336
339
  end
337
340
 
341
+ JP2_WALKER = ImageSize::ISOBMFF.new(
342
+ :recurse => %w[jp2h],
343
+ :last => %w[jp2h]
344
+ )
338
345
  def size_of_jp2(ir)
339
- offset = 12
340
- stop = nil
341
- in_header = false
342
- loop do
343
- break if stop && offset >= stop
344
- break if ir[offset, 4] == '' || ir[offset, 4].nil?
345
-
346
- size = ir.unpack1(offset, 4, 'N')
347
- type = ir.fetch(offset + 4, 4)
348
-
349
- data_offset = 8
350
- case size
351
- when 1
352
- size = ir.unpack1(offset, 8, 'Q>')
353
- data_offset = 16
354
- raise FormatError, "Unexpected xl-box size #{size}" if (1..15).include?(size)
355
- when 2..7
356
- raise FormatError, "Reserved box size #{size}"
357
- end
358
-
359
- if type == 'jp2h'
360
- stop = offset + size unless size.zero?
361
- offset += data_offset
362
- in_header = true
363
- elsif in_header && type == 'ihdr'
364
- return ir.unpack(offset + data_offset, 8, 'NN').reverse
365
- else
366
- break if size.zero? # box to the end of file
367
-
368
- offset += size
369
- end
346
+ JP2_WALKER.recurse(ir) do |box|
347
+ return ir.unpack(box.data_offset, 8, 'NN').reverse if box.type == 'ihdr'
370
348
  end
371
349
  end
372
350
  alias_method :size_of_jpx, :size_of_jp2
@@ -374,4 +352,82 @@ private
374
352
  def size_of_j2c(ir)
375
353
  ir.unpack(8, 8, 'NN')
376
354
  end
355
+
356
+ EMF_UMAX = 256**4
357
+ EMF_SMAX = EMF_UMAX / 2
358
+
359
+ def size_of_emf(ir)
360
+ left, top, right, bottom =
361
+ if RUBY_VERSION < '1.9'
362
+ ir.unpack(24, 16, 'V*').map{ |u| u < EMF_SMAX ? u : u - EMF_UMAX }
363
+ else
364
+ ir.unpack(24, 16, 'L<*')
365
+ end
366
+ dpi = self.class.dpi
367
+ [right - left + 1, bottom - top + 1].map do |n|
368
+ (n.to_f * dpi / 2540).round
369
+ end
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
377
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
@@ -19,6 +19,26 @@ describe ImageSize do
19
19
  @server.finish
20
20
  end
21
21
 
22
+ def supported_formats
23
+ ImageSize.private_instance_methods.map{ |name| name[/\Asize_of_(.*)\z/, 1] }.compact.sort
24
+ end
25
+
26
+ describe 'README' do
27
+ let(:readme){ File.read('README.markdown') }
28
+
29
+ it 'lists all supported formats' do
30
+ expect(readme[/^Formats: .*$/]).to eq("Formats: #{supported_formats.map{ |format| "`#{format}`" }.join(', ')}.")
31
+ end
32
+ end
33
+
34
+ describe 'gemspec' do
35
+ let(:gemspec){ Gem::Specification.load('image_size.gemspec') }
36
+
37
+ it 'lists all supported formats in description' do
38
+ expect(gemspec.description).to eq("Measure following file dimensions: #{supported_formats.join(', ')}")
39
+ end
40
+ end
41
+
22
42
  Dir['spec/**/*'].each do |path|
23
43
  next unless File.file?(path)
24
44
 
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.1.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-09-16 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,8 +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, gif, ico, j2c, jp2,
57
- jpeg, jpx, mng, pam, pbm, pcx, pgm, png, ppm, psd, svg, swf, tiff, webp, xbm, 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'
58
59
  email:
59
60
  executables: []
60
61
  extensions: []
@@ -73,12 +74,15 @@ files:
73
74
  - image_size.gemspec
74
75
  - lib/image_size.rb
75
76
  - lib/image_size/chunky_reader.rb
77
+ - lib/image_size/format_error.rb
78
+ - lib/image_size/isobmff.rb
76
79
  - lib/image_size/reader.rb
77
80
  - lib/image_size/seekable_io_reader.rb
78
81
  - lib/image_size/stream_io_reader.rb
79
82
  - lib/image_size/string_reader.rb
80
83
  - lib/image_size/uri_reader.rb
81
84
  - spec/image_size/chunky_reader_spec.rb
85
+ - spec/image_size/isobmff_spec.rb
82
86
  - spec/image_size/seekable_io_reader_spec.rb
83
87
  - spec/image_size_spec.rb
84
88
  - spec/images/.gitattributes
@@ -86,8 +90,15 @@ files:
86
90
  - spec/images/bmp/v3-bottom2top.42x50.bmp
87
91
  - spec/images/bmp/v3-top2bottom.42x50.bmp
88
92
  - spec/images/cur/32x256.cur
93
+ - spec/images/emf/74x77.emf
94
+ - spec/images/emf/77x77.emf
89
95
  - spec/images/empty
90
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
91
102
  - spec/images/ico/32x256.ico
92
103
  - spec/images/jp2/163x402.jp2
93
104
  - spec/images/jp2/176x373.jpx
@@ -127,7 +138,7 @@ licenses:
127
138
  metadata:
128
139
  bug_tracker_uri: https://github.com/toy/image_size/issues
129
140
  changelog_uri: https://github.com/toy/image_size/blob/master/CHANGELOG.markdown
130
- documentation_uri: https://www.rubydoc.info/gems/image_size/3.1.0
141
+ documentation_uri: https://www.rubydoc.info/gems/image_size/3.3.0
131
142
  source_code_uri: https://github.com/toy/image_size
132
143
  post_install_message:
133
144
  rdoc_options: []
@@ -144,12 +155,13 @@ required_rubygems_version: !ruby/object:Gem::Requirement
144
155
  - !ruby/object:Gem::Version
145
156
  version: '0'
146
157
  requirements: []
147
- rubygems_version: 3.3.11
158
+ rubygems_version: 3.4.12
148
159
  signing_key:
149
160
  specification_version: 4
150
- summary: Measure image size using pure Ruby
161
+ summary: Measure image size/dimensions using pure Ruby
151
162
  test_files:
152
163
  - spec/image_size/chunky_reader_spec.rb
164
+ - spec/image_size/isobmff_spec.rb
153
165
  - spec/image_size/seekable_io_reader_spec.rb
154
166
  - spec/image_size_spec.rb
155
167
  - spec/images/.gitattributes
@@ -157,8 +169,15 @@ test_files:
157
169
  - spec/images/bmp/v3-bottom2top.42x50.bmp
158
170
  - spec/images/bmp/v3-top2bottom.42x50.bmp
159
171
  - spec/images/cur/32x256.cur
172
+ - spec/images/emf/74x77.emf
173
+ - spec/images/emf/77x77.emf
160
174
  - spec/images/empty
161
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
162
181
  - spec/images/ico/32x256.ico
163
182
  - spec/images/jp2/163x402.jp2
164
183
  - spec/images/jp2/176x373.jpx