image_size 3.3.0 → 3.5.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.
data/lib/image_size.rb CHANGED
@@ -3,6 +3,7 @@
3
3
 
4
4
  require 'image_size/isobmff'
5
5
  require 'image_size/format_error'
6
+ require 'image_size/media_types'
6
7
  require 'image_size/reader'
7
8
  require 'image_size/seekable_io_reader'
8
9
  require 'image_size/stream_io_reader'
@@ -35,14 +36,30 @@ class ImageSize
35
36
  new(Pathname.new(path))
36
37
  end
37
38
 
38
- # Used for svg
39
+ # Used for svg and emf
39
40
  def self.dpi
40
- @dpi || 72
41
+ @dpi || 72.0
41
42
  end
42
43
 
43
- # Used for svg
44
+ # Used for svg and emf
44
45
  def self.dpi=(dpi)
45
- @dpi = dpi.to_f
46
+ fail ArgumentError, "dpi should be nil or positive, got #{dpi}" unless dpi.nil? || dpi > 0
47
+
48
+ @dpi = dpi ? dpi.to_f : nil
49
+ end
50
+
51
+ # Size of chunk to use by IO and URI readers
52
+ def self.chunk_size
53
+ @chunk_size || 4096
54
+ end
55
+
56
+ # Set size of chunk to use by IO and URI readers
57
+ def self.chunk_size=(chunk_size)
58
+ unless chunk_size.nil? || (chunk_size.is_a?(Integer) && chunk_size > 0)
59
+ fail ArgumentError, "chunk_size should be a positive Integer or nil, got #{chunk_size}"
60
+ end
61
+
62
+ @chunk_size = chunk_size
46
63
  end
47
64
 
48
65
  # Given image as any class responding to read and eof? or data as String, finds its format and dimensions
@@ -50,6 +67,7 @@ class ImageSize
50
67
  Reader.open(data) do |ir|
51
68
  @format = detect_format(ir)
52
69
  @width, @height = send("size_of_#{@format}", ir) if @format
70
+ @byte_size = ir.byte_size
53
71
  end
54
72
  end
55
73
 
@@ -64,11 +82,26 @@ class ImageSize
64
82
  attr_reader :height
65
83
  alias_method :h, :height
66
84
 
85
+ attr_reader :byte_size
86
+
67
87
  # get image width and height as an array which to_s method returns "#{width}x#{height}"
68
88
  def size
69
89
  Size.new([width, height]) if format
70
90
  end
71
91
 
92
+ # Media type (formerly known as a MIME type)
93
+ def media_type
94
+ media_types.first
95
+ end
96
+
97
+ # All media types:
98
+ # * commonly used and official like for apng and ico
99
+ # * main and compatible like for heic and pnm (pbm, pgm, ppm)
100
+ # * multiple unregistered like for mng
101
+ def media_types
102
+ MEDIA_TYPES.fetch(format, [])
103
+ end
104
+
72
105
  private
73
106
 
74
107
  SVG_R = /<svg\b([^>]*)>/.freeze
@@ -139,17 +172,13 @@ private
139
172
  end
140
173
 
141
174
  def size_of_mng(ir)
142
- unless ir[12, 4] == 'MHDR'
143
- raise FormatError, 'MHDR not in place for MNG'
144
- end
175
+ fail FormatError, 'MHDR not in place for MNG' unless ir[12, 4] == 'MHDR'
145
176
 
146
177
  ir.unpack(16, 8, 'NN')
147
178
  end
148
179
 
149
180
  def size_of_png(ir)
150
- unless ir[12, 4] == 'IHDR'
151
- raise FormatError, 'IHDR not in place for PNG'
152
- end
181
+ fail FormatError, 'IHDR not in place for PNG' unless ir[12, 4] == 'IHDR'
153
182
 
154
183
  ir.unpack(16, 8, 'NN')
155
184
  end
@@ -167,14 +196,12 @@ private
167
196
  loop do
168
197
  offset += 1 until [nil, section_marker].include? ir[offset, 1]
169
198
  offset += 1 until section_marker != ir[offset + 1, 1]
170
- raise FormatError, 'EOF in JPEG' if ir[offset, 1].nil?
199
+ fail FormatError, 'EOF in JPEG' unless ir[offset, 1]
171
200
 
172
201
  code, length = ir.unpack(offset, 4, 'xCn')
173
202
  offset += 4
174
203
 
175
- if JPEG_CODE_CHECK.include?(code)
176
- return ir.unpack(offset + 1, 4, 'nn').reverse
177
- end
204
+ return ir.unpack(offset + 1, 4, 'nn').reverse if JPEG_CODE_CHECK.include?(code)
178
205
 
179
206
  offset += length - 2
180
207
  end
@@ -198,8 +225,7 @@ private
198
225
  def size_of_ppm(ir)
199
226
  header = ir[0, 1024]
200
227
  header.gsub!(/^\#[^\n\r]*/m, '')
201
- header =~ /^(P[1-6])\s+?(\d+)\s+?(\d+)/m
202
- [$2.to_i, $3.to_i]
228
+ header.match(/^(?:P[1-6])\s+?(\d+)\s+?(\d+)/m)[1..2].map(&:to_i)
203
229
  end
204
230
  alias_method :size_of_pbm, :size_of_ppm
205
231
  alias_method :size_of_pgm, :size_of_ppm
@@ -215,37 +241,37 @@ private
215
241
  chunk = ir[offset, 32]
216
242
  case chunk
217
243
  when /\AWIDTH (\d+)\n/
218
- width = $1.to_i
244
+ width = Regexp.last_match[1].to_i
219
245
  when /\AHEIGHT (\d+)\n/
220
- height = $1.to_i
246
+ height = Regexp.last_match[1].to_i
221
247
  when /\AENDHDR\n/
222
248
  break
223
249
  when /\A(?:DEPTH|MAXVAL) \d+\n/, /\ATUPLTYPE \S+\n/
224
250
  # ignore
225
251
  else
226
- raise FormatError, "Unexpected data in PAM header: #{chunk.inspect}"
252
+ fail FormatError, "Unexpected data in PAM header: #{chunk.inspect}"
227
253
  end
228
- offset += $&.length
254
+ offset += Regexp.last_match[0].length
229
255
  end
230
256
  end
231
257
  [width, height]
232
258
  end
233
259
 
234
260
  def size_of_xbm(ir)
235
- ir[0, 1024] =~ /^\#define\s*\S*\s*(\d+)\s*\n\#define\s*\S*\s*(\d+)/mi
236
- [$1.to_i, $2.to_i]
261
+ ir[0, 1024].match(/^\#define\s*\S*\s*(\d+)\s*\n\#define\s*\S*\s*(\d+)/mi)[1..2].map(&:to_i)
237
262
  end
238
263
 
239
264
  def size_of_xpm(ir)
240
265
  length = 1024
241
- until (data = ir[0, length]) =~ /"\s*(\d+)\s+(\d+)(\s+\d+\s+\d+){1,2}\s*"/m
242
- if data.length != length
243
- raise FormatError, 'XPM size not found'
244
- end
266
+ loop do
267
+ data = ir[0, length]
268
+ m = data.match(/"\s*(\d+)\s+(\d+)(?:\s+\d+\s+\d+){1,2}\s*"/m)
269
+ return m[1..2].map(&:to_i) if m
270
+
271
+ fail FormatError, 'XPM size not found' if data.length != length
245
272
 
246
273
  length += 1024
247
274
  end
248
- [$1.to_i, $2.to_i]
249
275
  end
250
276
 
251
277
  def size_of_psd(ir)
@@ -265,12 +291,12 @@ private
265
291
  width = height = nil
266
292
  until width && height
267
293
  ifd = ir.fetch(offset, 12)
268
- raise FormatError, 'Reached end of directory entries in TIFF' if offset > num_dirent
294
+ fail FormatError, 'Reached end of directory entries in TIFF' if offset > num_dirent
269
295
 
270
296
  tag, type = ifd.unpack(endian2b * 2)
271
297
  offset += 12
272
298
 
273
- next if packspec[type].nil?
299
+ next unless packspec[type]
274
300
 
275
301
  value = ifd[8, 4].unpack(packspec[type])[0]
276
302
  case tag
@@ -339,8 +365,8 @@ private
339
365
  end
340
366
 
341
367
  JP2_WALKER = ImageSize::ISOBMFF.new(
342
- :recurse => %w[jp2h],
343
- :last => %w[jp2h]
368
+ recurse: %w[jp2h],
369
+ last: %w[jp2h]
344
370
  )
345
371
  def size_of_jp2(ir)
346
372
  JP2_WALKER.recurse(ir) do |box|
@@ -370,9 +396,9 @@ private
370
396
  end
371
397
 
372
398
  HEIF_WALKER = ImageSize::ISOBMFF.new(
373
- :recurse => %w[meta iprp ipco],
374
- :full => %w[meta hdlr pitm ipma ispe],
375
- :last => %w[meta]
399
+ recurse: %w[meta iprp ipco],
400
+ full: %w[meta hdlr pitm ipma ispe],
401
+ last: %w[meta]
376
402
  )
377
403
  def size_of_heif(ir)
378
404
  pitm = nil
@@ -384,11 +410,11 @@ private
384
410
  HEIF_WALKER.recurse(ir) do |box, _path|
385
411
  case box.type
386
412
  when 'hdlr'
387
- raise FormatError, "hdlr box too small (#{box.data_size})" if box.data_size < 8
413
+ fail FormatError, "hdlr box too small (#{box.data_size})" if box.data_size < 8
388
414
 
389
415
  return nil unless ir[box.data_offset + 4, 4] == 'pict'
390
416
  when 'pitm'
391
- raise FormatError, 'second pitm box encountered' if pitm
417
+ fail FormatError, 'second pitm box encountered' if pitm
392
418
 
393
419
  pitm = box.version == 0 ? ir.unpack1(box.data_offset, 2, 'n') : ir.unpack1(box.data_offset, 4, 'N')
394
420
  when 'ipma'
@@ -430,4 +456,6 @@ private
430
456
  end
431
457
  alias_method :size_of_avif, :size_of_heif
432
458
  alias_method :size_of_heic, :size_of_heif
459
+
460
+ private_constant :SVG_R, :XML_R, :JPEG_CODE_CHECK, :JP2_WALKER, :EMF_UMAX, :EMF_SMAX, :HEIF_WALKER
433
461
  end
@@ -29,7 +29,7 @@ describe ImageSize::ChunkyReader do
29
29
  {
30
30
  'empty string' => '',
31
31
  'a bit of data' => 'foo bar baz',
32
- 'a lot of data' => File.open('GPL', 'rb', &:read),
32
+ 'a lot of data' => File.binread('GPL'),
33
33
  }.each do |data_description, data|
34
34
  {
35
35
  'default' => test_reader.new(data),
@@ -53,8 +53,8 @@ describe ImageSize::ISOBMFF do
53
53
 
54
54
  it do
55
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)
56
+ having_attributes(type: 'abcd', data_offset: 8, data_size: 42),
57
+ having_attributes(type: 'efgh', data_offset: 58, data_size: 10)
58
58
  )
59
59
  end
60
60
  end
@@ -74,14 +74,14 @@ describe ImageSize::ISOBMFF do
74
74
  describe 'for a box without content' do
75
75
  let(:data){ boxes.build{ box('test', 8) } }
76
76
 
77
- it{ is_expected.to yield_successive_args(having_attributes(:type => 'test', :data_offset => 8, :data_size => 0)) }
77
+ it{ is_expected.to yield_successive_args(having_attributes(type: 'test', data_offset: 8, data_size: 0)) }
78
78
  end
79
79
 
80
80
  describe 'for a box with content' do
81
81
  let(:data){ boxes.build{ box('test', 8 + 42) } }
82
82
 
83
83
  it do
84
- is_expected.to yield_successive_args(having_attributes(:type => 'test', :data_offset => 8, :data_size => 42))
84
+ is_expected.to yield_successive_args(having_attributes(type: 'test', data_offset: 8, data_size: 42))
85
85
  end
86
86
  end
87
87
 
@@ -100,7 +100,7 @@ describe ImageSize::ISOBMFF do
100
100
  let(:data){ boxes.build{ box('test', 0) } }
101
101
 
102
102
  it do
103
- is_expected.to yield_successive_args(having_attributes(:type => 'test', :data_offset => 8, :data_size => nil))
103
+ is_expected.to yield_successive_args(having_attributes(type: 'test', data_offset: 8, data_size: nil))
104
104
  end
105
105
  end
106
106
 
@@ -116,7 +116,7 @@ describe ImageSize::ISOBMFF do
116
116
  let(:data){ boxes.build{ box('test', 1, 16) } }
117
117
 
118
118
  it do
119
- is_expected.to yield_successive_args(having_attributes(:type => 'test', :data_offset => 16, :data_size => 0))
119
+ is_expected.to yield_successive_args(having_attributes(type: 'test', data_offset: 16, data_size: 0))
120
120
  end
121
121
  end
122
122
 
@@ -124,12 +124,12 @@ describe ImageSize::ISOBMFF do
124
124
  let(:data){ boxes.build{ box('test', 1, 16 + 42) } }
125
125
 
126
126
  it do
127
- is_expected.to yield_successive_args(having_attributes(:type => 'test', :data_offset => 16, :data_size => 42))
127
+ is_expected.to yield_successive_args(having_attributes(type: 'test', data_offset: 16, data_size: 42))
128
128
  end
129
129
  end
130
130
 
131
131
  describe 'for a full box' do
132
- let(:options){ { :full => %w[test] } }
132
+ let(:options){ { full: %w[test] } }
133
133
 
134
134
  let(:data) do
135
135
  boxes.build do
@@ -141,18 +141,18 @@ describe ImageSize::ISOBMFF do
141
141
  it do
142
142
  is_expected.to yield_successive_args(
143
143
  having_attributes(
144
- :type => 'test',
145
- :data_offset => 12,
146
- :data_size => 38,
147
- :version => 0x61,
148
- :flags => 0x626364
144
+ type: 'test',
145
+ data_offset: 12,
146
+ data_size: 38,
147
+ version: 0x61,
148
+ flags: 0x626364
149
149
  )
150
150
  )
151
151
  end
152
152
  end
153
153
 
154
154
  describe 'for a big full box' do
155
- let(:options){ { :full => %w[test] } }
155
+ let(:options){ { full: %w[test] } }
156
156
 
157
157
  let(:data) do
158
158
  boxes.build do
@@ -164,11 +164,11 @@ describe ImageSize::ISOBMFF do
164
164
  it do
165
165
  is_expected.to yield_successive_args(
166
166
  having_attributes(
167
- :type => 'test',
168
- :data_offset => 20,
169
- :data_size => 38,
170
- :version => 0x61,
171
- :flags => 0x626364
167
+ type: 'test',
168
+ data_offset: 20,
169
+ data_size: 38,
170
+ version: 0x61,
171
+ flags: 0x626364
172
172
  )
173
173
  )
174
174
  end
@@ -206,8 +206,8 @@ describe ImageSize::ISOBMFF do
206
206
 
207
207
  it do
208
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)
209
+ having_attributes(type: 'fooo', data_offset: 16, data_size: 0),
210
+ having_attributes(type: 'barr', data_offset: 24, data_size: 0)
211
211
  )
212
212
  end
213
213
  end
@@ -230,7 +230,7 @@ describe ImageSize::ISOBMFF do
230
230
  let(:length){ 8 }
231
231
 
232
232
  it do
233
- is_expected.to yield_successive_args(having_attributes(:type => 'fooo', :data_offset => 16, :data_size => 0))
233
+ is_expected.to yield_successive_args(having_attributes(type: 'fooo', data_offset: 16, data_size: 0))
234
234
  end
235
235
  end
236
236
  end
@@ -256,24 +256,24 @@ describe ImageSize::ISOBMFF do
256
256
  end
257
257
 
258
258
  context 'when configured to recures all' do
259
- let(:options){ { :recurse => %w[fooA barA barB bazA] } }
259
+ let(:options){ { recurse: %w[fooA barA barB bazA] } }
260
260
 
261
261
  it 'recurses complete tree' do
262
262
  enum = instance.to_enum(:recurse, string_reader)
263
263
 
264
- expect(enum.next).to have_attributes(:type => 'fooA', :data_offset => 8, :data_size => 10)
264
+ expect(enum.next).to have_attributes(type: 'fooA', data_offset: 8, data_size: 10)
265
265
 
266
- expect(enum.next).to have_attributes(:type => 'fooB', :data_offset => 16, :data_size => 2)
266
+ expect(enum.next).to have_attributes(type: 'fooB', data_offset: 16, data_size: 2)
267
267
 
268
- expect(enum.next).to have_attributes(:type => 'barA', :data_offset => 26, :data_size => 18)
268
+ expect(enum.next).to have_attributes(type: 'barA', data_offset: 26, data_size: 18)
269
269
 
270
- expect(enum.next).to have_attributes(:type => 'barB', :data_offset => 34, :data_size => 10)
270
+ expect(enum.next).to have_attributes(type: 'barB', data_offset: 34, data_size: 10)
271
271
 
272
- expect(enum.next).to have_attributes(:type => 'barC', :data_offset => 42, :data_size => 2)
272
+ expect(enum.next).to have_attributes(type: 'barC', data_offset: 42, data_size: 2)
273
273
 
274
- expect(enum.next).to have_attributes(:type => 'bazA', :data_offset => 52, :data_size => 10)
274
+ expect(enum.next).to have_attributes(type: 'bazA', data_offset: 52, data_size: 10)
275
275
 
276
- expect(enum.next).to have_attributes(:type => 'bazB', :data_offset => 60, :data_size => 2)
276
+ expect(enum.next).to have_attributes(type: 'bazB', data_offset: 60, data_size: 2)
277
277
 
278
278
  expect{ enum.next }.to raise_exception(StopIteration)
279
279
  end
@@ -284,18 +284,18 @@ describe ImageSize::ISOBMFF do
284
284
  end
285
285
 
286
286
  context 'when configured to recurse part' do
287
- let(:options){ { :recurse => %w[barA] } }
287
+ let(:options){ { recurse: %w[barA] } }
288
288
 
289
289
  it 'recurses requested part' do
290
290
  enum = instance.to_enum(:recurse, string_reader)
291
291
 
292
- expect(enum.next).to have_attributes(:type => 'fooA', :data_offset => 8, :data_size => 10)
292
+ expect(enum.next).to have_attributes(type: 'fooA', data_offset: 8, data_size: 10)
293
293
 
294
- expect(enum.next).to have_attributes(:type => 'barA', :data_offset => 26, :data_size => 18)
294
+ expect(enum.next).to have_attributes(type: 'barA', data_offset: 26, data_size: 18)
295
295
 
296
- expect(enum.next).to have_attributes(:type => 'barB', :data_offset => 34, :data_size => 10)
296
+ expect(enum.next).to have_attributes(type: 'barB', data_offset: 34, data_size: 10)
297
297
 
298
- expect(enum.next).to have_attributes(:type => 'bazA', :data_offset => 52, :data_size => 10)
298
+ expect(enum.next).to have_attributes(type: 'bazA', data_offset: 52, data_size: 10)
299
299
 
300
300
  expect{ enum.next }.to raise_exception(StopIteration)
301
301
  end
@@ -311,11 +311,11 @@ describe ImageSize::ISOBMFF do
311
311
  it 'does not recurse' do
312
312
  enum = instance.to_enum(:recurse, string_reader)
313
313
 
314
- expect(enum.next).to have_attributes(:type => 'fooA', :data_offset => 8, :data_size => 10)
314
+ expect(enum.next).to have_attributes(type: 'fooA', data_offset: 8, data_size: 10)
315
315
 
316
- expect(enum.next).to have_attributes(:type => 'barA', :data_offset => 26, :data_size => 18)
316
+ expect(enum.next).to have_attributes(type: 'barA', data_offset: 26, data_size: 18)
317
317
 
318
- expect(enum.next).to have_attributes(:type => 'bazA', :data_offset => 52, :data_size => 10)
318
+ expect(enum.next).to have_attributes(type: 'bazA', data_offset: 52, data_size: 10)
319
319
 
320
320
  expect{ enum.next }.to raise_exception(StopIteration)
321
321
  end
@@ -326,20 +326,20 @@ describe ImageSize::ISOBMFF do
326
326
  end
327
327
 
328
328
  context 'when configured to stop' do
329
- let(:options){ { :recurse => %w[fooA barA barB bazA], :last => %w[barA] } }
329
+ let(:options){ { recurse: %w[fooA barA barB bazA], last: %w[barA] } }
330
330
 
331
331
  it 'recurses complete tree' do
332
332
  enum = instance.to_enum(:recurse, string_reader)
333
333
 
334
- expect(enum.next).to have_attributes(:type => 'fooA', :data_offset => 8, :data_size => 10)
334
+ expect(enum.next).to have_attributes(type: 'fooA', data_offset: 8, data_size: 10)
335
335
 
336
- expect(enum.next).to have_attributes(:type => 'fooB', :data_offset => 16, :data_size => 2)
336
+ expect(enum.next).to have_attributes(type: 'fooB', data_offset: 16, data_size: 2)
337
337
 
338
- expect(enum.next).to have_attributes(:type => 'barA', :data_offset => 26, :data_size => 18)
338
+ expect(enum.next).to have_attributes(type: 'barA', data_offset: 26, data_size: 18)
339
339
 
340
- expect(enum.next).to have_attributes(:type => 'barB', :data_offset => 34, :data_size => 10)
340
+ expect(enum.next).to have_attributes(type: 'barB', data_offset: 34, data_size: 10)
341
341
 
342
- expect(enum.next).to have_attributes(:type => 'barC', :data_offset => 42, :data_size => 2)
342
+ expect(enum.next).to have_attributes(type: 'barC', data_offset: 42, data_size: 2)
343
343
 
344
344
  expect{ enum.next }.to raise_exception(StopIteration)
345
345
  end
@@ -6,45 +6,40 @@ require 'image_size/seekable_io_reader'
6
6
 
7
7
  describe ImageSize::SeekableIOReader do
8
8
  context :[] do
9
- def ios
10
- @ios ||= []
11
- end
12
-
13
- def io
14
- File.open('GPL', 'rb').tap do |io|
15
- ios << io
16
- end
17
- end
18
-
19
- after do
20
- ios.pop.close until ios.empty?
9
+ def new_io(&block)
10
+ File.open('GPL', 'rb', &block)
21
11
  end
22
12
 
23
13
  def new_reader
24
- ImageSize::SeekableIOReader.new(io)
14
+ new_io do |io|
15
+ yield ImageSize::SeekableIOReader.new(io)
16
+ end
25
17
  end
26
18
 
27
- let(:content){ io.read }
19
+ let(:content){ new_io(&:read) }
28
20
 
29
21
  it 'reads as expected when pieces are read consecutively' do
30
- reader = new_reader
31
- 0.step(content.length + 4096, 100) do |offset|
32
- expect(reader[offset, 100]).to eq(content[offset, 100])
22
+ new_reader do |reader|
23
+ 0.step(content.length + 4096, 100) do |offset|
24
+ expect(reader[offset, 100]).to eq(content[offset, 100])
25
+ end
33
26
  end
34
27
  end
35
28
 
36
29
  it 'reads as expected when pieces are read backwards' do
37
- reader = new_reader
38
- (content.length + 4096).step(0, -100) do |offset|
39
- expect(reader[offset, 100]).to eq(content[offset, 100])
30
+ new_reader do |reader|
31
+ (content.length + 4096).step(0, -100) do |offset|
32
+ expect(reader[offset, 100]).to eq(content[offset, 100])
33
+ end
40
34
  end
41
35
  end
42
36
 
43
37
  it 'reads as expected when pieces are read in random order' do
44
38
  100.times do
45
- reader = new_reader
46
- 0.step(content.length + 4096, 100).to_a.shuffle.each do |offset|
47
- expect(reader[offset, 100]).to eq(content[offset, 100])
39
+ new_reader do |reader|
40
+ 0.step(content.length + 4096, 100).to_a.shuffle.each do |offset|
41
+ expect(reader[offset, 100]).to eq(content[offset, 100])
42
+ end
48
43
  end
49
44
  end
50
45
  end