image_size 3.5.0 → 3.6.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: 2a128281e6674bd684b8485151f3938a5d34d8fdf146e293e908e8cfb5c762d5
4
- data.tar.gz: 0615d7750931da9cf45ec6003fd1f20dba7638e393a6d18bbec53901c078e864
3
+ metadata.gz: e7880ff3fdbcfdbbc6e3248c79ef59adea338f21c63c285323c5b01bdf699477
4
+ data.tar.gz: b8ce40e97bc91a2f7e91a9ce3f47d17f06aa38a6a28f7e534837c1f668cc51e2
5
5
  SHA512:
6
- metadata.gz: d2d3cf28085954077a3016735c15ede7827dd1a72afd0617182d4278f0890f8ef41cbc8e84fe7d2661a270a8ce570b6e3cfeaa148c8be4d141c3a233cb712a59
7
- data.tar.gz: a6dd7107a1c344a4963cf527d80e76c08e6b64cf22ae2edb6d2c7dad022f3452505a369fc9a4b4c665136e34ee2379a41b235e5d89881a32721c49ae14c6b6aa
6
+ metadata.gz: 027e0f32e86acf19b92cb00404dfb2b26f0989ed13d07247512ff024d058a245d62b3b252e2605ff1d79a41636cb87874f2f556f3e2cdcaf2727efe3fc91efe2
7
+ data.tar.gz: b8a5074adaa1d08d6b3b8d9d1a5da5c5239e1ea46b345fc13ffbf5ba75736d826beaeceb38b09aec7267f6c934856c1237c9c514ecad3fb6e9701b0846352f0d
@@ -4,6 +4,8 @@ on:
4
4
  pull_request:
5
5
  schedule:
6
6
  - cron: 45 4 * * 3
7
+ permissions:
8
+ contents: read
7
9
  jobs:
8
10
  check:
9
11
  runs-on: ubuntu-22.04
@@ -4,6 +4,8 @@ on:
4
4
  pull_request:
5
5
  schedule:
6
6
  - cron: 45 4 * * 3
7
+ permissions:
8
+ contents: read
7
9
  jobs:
8
10
  rubocop:
9
11
  runs-on: ubuntu-latest
data/CHANGELOG.markdown CHANGED
@@ -2,6 +2,10 @@
2
2
 
3
3
  ## unreleased
4
4
 
5
+ ## v3.6.0 (2026-05-26)
6
+
7
+ * Support `.icns` Apple Icon Image format [@toy](https://github.com/toy)
8
+
5
9
  ## v3.5.0 (2026-05-03)
6
10
 
7
11
  * Add `byte_size` method to expose the size of the image data in bytes [#27](https://github.com/toy/image_size/pull/27) [@dgodd](https://github.com/dgodd)
data/README.markdown CHANGED
@@ -5,7 +5,7 @@
5
5
  # image_size
6
6
 
7
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`.
8
+ Formats: `apng`, `avif`, `bmp`, `cur`, `emf`, `gif`, `heic`, `heif`, `icns`, `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
 
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.5.0'
5
+ s.version = '3.6.0'
6
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}
7
+ s.description = %q{Measure following file dimensions: apng, avif, bmp, cur, emf, gif, heic, heif, icns, 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'
@@ -9,6 +9,7 @@ class ImageSize
9
9
  emf: %w[image/emf],
10
10
  gif: %w[image/gif],
11
11
  heic: %w[image/heic image/heif],
12
+ icns: %w[image/x-icns],
12
13
  ico: %w[image/x-icon image/vnd.microsoft.icon],
13
14
  j2c: %w[image/j2c],
14
15
  jp2: %w[image/jp2],
data/lib/image_size.rb CHANGED
@@ -31,35 +31,40 @@ class ImageSize
31
31
  alias_method :h, :height
32
32
  end
33
33
 
34
- # Given path to image finds its format, width and height
35
- def self.path(path)
36
- new(Pathname.new(path))
37
- end
34
+ class << self
35
+ # Given path to image finds its format, width and height
36
+ def path(path)
37
+ new(Pathname.new(path))
38
+ end
38
39
 
39
- # Used for svg and emf
40
- def self.dpi
41
- @dpi || 72.0
42
- end
40
+ # DPI used for svg and emf
41
+ def dpi
42
+ @dpi || 72.0
43
+ end
43
44
 
44
- # Used for svg and emf
45
- def self.dpi=(dpi)
46
- fail ArgumentError, "dpi should be nil or positive, got #{dpi}" unless dpi.nil? || dpi > 0
45
+ # Set DPI used for svg and emf
46
+ def dpi=(dpi)
47
+ fail ArgumentError, "dpi should be nil or positive, got #{dpi}" unless dpi.nil? || dpi > 0
47
48
 
48
- @dpi = dpi ? dpi.to_f : nil
49
- end
49
+ @dpi = dpi ? dpi.to_f : nil
50
+ end
50
51
 
51
- # Size of chunk to use by IO and URI readers
52
- def self.chunk_size
53
- @chunk_size || 4096
54
- end
52
+ # Use display pixels instead of physical/image pixels for icns
53
+ attr_accessor :use_display_pixels
55
54
 
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}"
55
+ # Size of chunk to use by IO and URI readers
56
+ def chunk_size
57
+ @chunk_size || 4096
60
58
  end
61
59
 
62
- @chunk_size = chunk_size
60
+ # Set size of chunk to use by IO and URI readers
61
+ def chunk_size=(chunk_size)
62
+ unless chunk_size.nil? || (chunk_size.is_a?(Integer) && chunk_size > 0)
63
+ fail ArgumentError, "chunk_size should be a positive Integer or nil, got #{chunk_size}"
64
+ end
65
+
66
+ @chunk_size = chunk_size
67
+ end
63
68
  end
64
69
 
65
70
  # Given image as any class responding to read and eof? or data as String, finds its format and dimensions
@@ -106,6 +111,8 @@ private
106
111
 
107
112
  SVG_R = /<svg\b([^>]*)>/.freeze
108
113
  XML_R = /<\?xml|<!--/.freeze
114
+ private_constant :SVG_R, :XML_R
115
+
109
116
  def detect_format(ir)
110
117
  head = ir[0, 1024]
111
118
  case
@@ -126,6 +133,7 @@ private
126
133
  when head[0, 12] =~ /\ARIFF(?m:....)WEBP\z/ then :webp
127
134
  when head[0, 4] == "\0\0\1\0" then :ico
128
135
  when head[0, 4] == "\0\0\2\0" then :cur
136
+ when head[0, 4] == 'icns' then :icns
129
137
  when head[0, 12] == "\0\0\0\fjP \r\n\207\n" then detect_jpeg2000_type(ir)
130
138
  when head[0, 4] == "\377O\377Q" then :j2c
131
139
  when head[0, 4] == "\1\0\0\0" && head[40, 4] == ' EMF' then :emf
@@ -190,6 +198,8 @@ private
190
198
  0xC9, 0xCA, 0xCB,
191
199
  0xCD, 0xCE, 0xCF
192
200
  ].freeze
201
+ private_constant :JPEG_CODE_CHECK
202
+
193
203
  def size_of_jpeg(ir)
194
204
  section_marker = "\xFF"
195
205
  offset = 2
@@ -351,6 +361,60 @@ private
351
361
  end
352
362
  alias_method :size_of_cur, :size_of_ico
353
363
 
364
+ ICNS_16X12 = %w[icm# icm4 icm8].freeze
365
+ ICNS_SQUARE = {
366
+ [16, 1] => %w[ic04 icp4 ics# ics4 ics8 is32 s8mk],
367
+ [16, 2] => %w[ic11],
368
+ [18, 1] => %w[icsb],
369
+ [18, 2] => %w[icsB],
370
+ [24, 1] => %w[sb24],
371
+ [24, 2] => %w[SB24],
372
+ [32, 1] => %w[ICN# ICON ic05 icl4 icl8 icp5 il32 l8mk],
373
+ [32, 2] => %w[ic12],
374
+ [48, 1] => %w[h8mk ich# ich4 ich8 icp6 ih32],
375
+ [128, 1] => %w[ic07 it32 t8mk],
376
+ [128, 2] => %w[ic13],
377
+ [256, 1] => %w[ic08],
378
+ [256, 2] => %w[ic14],
379
+ [512, 1] => %w[ic09],
380
+ [512, 2] => %w[ic10],
381
+ }.each_with_object({}){ |(spec, types), h| types.each{ |type| h[type] = spec } }.freeze
382
+ private_constant :ICNS_16X12, :ICNS_SQUARE
383
+
384
+ def size_of_icns(ir)
385
+ file_length = ir.unpack1(4, 4, 'N')
386
+ offset = 8
387
+
388
+ types = []
389
+ while offset < file_length
390
+ type = ir[offset, 4]
391
+ length = ir.unpack1(offset + 4, 4, 'N')
392
+
393
+ case type
394
+ when 'TOC ' # rely on table of contents
395
+ fail FormatError, "TOC length #{length} is not divisible by 8" unless length % 8 == 0
396
+
397
+ types = (1...(length / 8)).map{ |i| ir[offset + (8 * i), 4] }
398
+ break
399
+ when 'icnV', 'info', 'name' # not icons
400
+ when 'slct', 'sbtp', "\375\331/\250" # nested icns
401
+ else
402
+ types << type
403
+ end
404
+
405
+ offset += length
406
+ end
407
+
408
+ types.map do |type|
409
+ next [16, 12] if ICNS_16X12.include?(type)
410
+
411
+ side, scale = ICNS_SQUARE[type]
412
+ fail FormatError, "unknown icon type: #{type.inspect}" unless side
413
+
414
+ (self.class.use_display_pixels ? [side] : [side * scale]) * 2
415
+ end.max
416
+ end
417
+
354
418
  def size_of_webp(ir)
355
419
  case ir.fetch(12, 4)
356
420
  when 'VP8 '
@@ -368,6 +432,8 @@ private
368
432
  recurse: %w[jp2h],
369
433
  last: %w[jp2h]
370
434
  )
435
+ private_constant :JP2_WALKER
436
+
371
437
  def size_of_jp2(ir)
372
438
  JP2_WALKER.recurse(ir) do |box|
373
439
  return ir.unpack(box.data_offset, 8, 'NN').reverse if box.type == 'ihdr'
@@ -381,6 +447,7 @@ private
381
447
 
382
448
  EMF_UMAX = 256**4
383
449
  EMF_SMAX = EMF_UMAX / 2
450
+ private_constant :EMF_UMAX, :EMF_SMAX
384
451
 
385
452
  def size_of_emf(ir)
386
453
  left, top, right, bottom =
@@ -400,6 +467,8 @@ private
400
467
  full: %w[meta hdlr pitm ipma ispe],
401
468
  last: %w[meta]
402
469
  )
470
+ private_constant :HEIF_WALKER
471
+
403
472
  def size_of_heif(ir)
404
473
  pitm = nil
405
474
  ipma = nil
@@ -456,6 +525,4 @@ private
456
525
  end
457
526
  alias_method :size_of_avif, :size_of_heif
458
527
  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
461
528
  end
@@ -2,7 +2,7 @@
2
2
 
3
3
  require 'rspec'
4
4
 
5
- require 'image_size/chunky_reader'
5
+ require 'image_size'
6
6
 
7
7
  describe ImageSize::ChunkyReader do
8
8
  context :[] do
@@ -2,7 +2,7 @@
2
2
 
3
3
  require 'rspec'
4
4
 
5
- require 'image_size/seekable_io_reader'
5
+ require 'image_size'
6
6
 
7
7
  describe ImageSize::SeekableIOReader do
8
8
  context :[] do
@@ -70,16 +70,20 @@ describe ImageSize do
70
70
  end
71
71
  end
72
72
 
73
- Dir['spec/**/*'].each do |path|
74
- next unless File.file?(path)
73
+ def self.test_image_size(path, &block)
74
+ file_size = File.size(path)
75
+ max_file_size = 16_384
76
+ fail "reduce resulting gem size, #{path} is too big (#{file_size} > #{max_file_size})" if file_size > max_file_size
75
77
 
76
78
  describe "for #{path}" do
77
79
  let(:name){ File.basename(path) }
80
+ let(:match){ name.match(/(?<width>\d+)x(?<height>\d+)(?:@(?<scale>\d)x)?\.(?<format>[^.]+)$/) }
81
+ let(:scale){ (match[:scale] || 1).to_i if match }
78
82
  let(:attributes) do
79
- if (match = /(\d+)x(\d+)\.([^.]+)$/.match(name))
80
- width = match[1].to_i
81
- height = match[2].to_i
82
- format = match[3].to_sym
83
+ if match
84
+ width = match[:width].to_i * scale
85
+ height = match[:height].to_i * scale
86
+ format = match[:format].to_sym
83
87
  end
84
88
  size = [width, height] if format
85
89
  media_types = ImageSize::MEDIA_TYPES[format] || []
@@ -97,15 +101,8 @@ describe ImageSize do
97
101
  }
98
102
  end
99
103
  let(:file_data){ File.binread(path) }
100
- let(:file_size){ file_data.length }
101
104
 
102
- before do
103
- max_file_size = 16_384
104
-
105
- if file_size > max_file_size
106
- fail "reduce resulting gem size, #{path} is too big (#{file_size} > #{max_file_size})"
107
- end
108
- end
105
+ instance_exec(&block) if block
109
106
 
110
107
  context 'given as data' do
111
108
  it 'gets format and dimensions' do
@@ -269,6 +266,32 @@ describe ImageSize do
269
266
  end
270
267
  end
271
268
 
269
+ context 'for non images' do
270
+ test_image_size 'spec/test_server.rb'
271
+ test_image_size 'spec/images/empty'
272
+ end
273
+
274
+ context 'for images' do
275
+ Dir['spec/images/*/*.*'].each do |path|
276
+ if path.end_with?('.icns')
277
+ context 'without use_display_pixels' do
278
+ test_image_size path
279
+ end
280
+
281
+ context 'with use_display_pixels' do
282
+ before{ ImageSize.use_display_pixels = true }
283
+ after{ ImageSize.use_display_pixels = nil }
284
+
285
+ test_image_size path do
286
+ let(:scale){ 1 }
287
+ end
288
+ end
289
+ else
290
+ test_image_size path
291
+ end
292
+ end
293
+ end
294
+
272
295
  it 'raises ArgumentError if argument is not valid' do
273
296
  expect do
274
297
  ImageSize.new(Object)
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.5.0
4
+ version: 3.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Keisuke Minami
@@ -53,8 +53,8 @@ dependencies:
53
53
  - !ruby/object:Gem::Version
54
54
  version: '2.0'
55
55
  description: 'Measure following file dimensions: apng, avif, bmp, cur, emf, gif, heic,
56
- heif, ico, j2c, jp2, jpeg, jpx, mng, pam, pbm, pcx, pgm, png, ppm, psd, svg, swf,
57
- tiff, webp, xbm, xpm'
56
+ heif, icns, ico, j2c, jp2, jpeg, jpx, mng, pam, pbm, pcx, pgm, png, ppm, psd, svg,
57
+ swf, tiff, webp, xbm, xpm'
58
58
  executables: []
59
59
  extensions: []
60
60
  extra_rdoc_files: []
@@ -100,6 +100,11 @@ files:
100
100
  - spec/images/heif/multiple.169x83.heic
101
101
  - spec/images/heif/rotate.73x173.avif
102
102
  - spec/images/heif/sequence.7x5.avif
103
+ - spec/images/icns/16x12.icns
104
+ - spec/images/icns/afp_scanpix_leta_pilipey_kyiv.256x256@1x.icns
105
+ - spec/images/icns/reuters_scanpix_leta_garanich_kyiv.256x256@2x.icns
106
+ - spec/images/icns/toc.512x512@1x.icns
107
+ - spec/images/icns/toc.512x512@2x.icns
103
108
  - spec/images/ico/32x256.ico
104
109
  - spec/images/jp2/163x402.jp2
105
110
  - spec/images/jp2/176x373.jpx
@@ -140,7 +145,7 @@ licenses:
140
145
  metadata:
141
146
  bug_tracker_uri: https://github.com/toy/image_size/issues
142
147
  changelog_uri: https://github.com/toy/image_size/blob/master/CHANGELOG.markdown
143
- documentation_uri: https://www.rubydoc.info/gems/image_size/3.5.0
148
+ documentation_uri: https://www.rubydoc.info/gems/image_size/3.6.0
144
149
  source_code_uri: https://github.com/toy/image_size
145
150
  rdoc_options: []
146
151
  require_paths:
@@ -178,6 +183,11 @@ test_files:
178
183
  - spec/images/heif/multiple.169x83.heic
179
184
  - spec/images/heif/rotate.73x173.avif
180
185
  - spec/images/heif/sequence.7x5.avif
186
+ - spec/images/icns/16x12.icns
187
+ - spec/images/icns/afp_scanpix_leta_pilipey_kyiv.256x256@1x.icns
188
+ - spec/images/icns/reuters_scanpix_leta_garanich_kyiv.256x256@2x.icns
189
+ - spec/images/icns/toc.512x512@1x.icns
190
+ - spec/images/icns/toc.512x512@2x.icns
181
191
  - spec/images/ico/32x256.ico
182
192
  - spec/images/jp2/163x402.jp2
183
193
  - spec/images/jp2/176x373.jpx