image_size 1.4.2 → 2.1.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.
Files changed (50) hide show
  1. checksums.yaml +5 -13
  2. data/.rubocop.yml +80 -0
  3. data/.rubocop_todo.yml +28 -0
  4. data/.travis.yml +25 -8
  5. data/CHANGELOG.markdown +117 -0
  6. data/Gemfile +7 -1
  7. data/README.markdown +69 -21
  8. data/image_size.gemspec +12 -4
  9. data/lib/image_size.rb +205 -87
  10. data/spec/image_size_spec.rb +79 -71
  11. data/spec/{test2.bmp → images/bmp/v2.42x50.bmp} +0 -0
  12. data/spec/{test3b.bmp → images/bmp/v3-bottom2top.42x50.bmp} +0 -0
  13. data/spec/{test3t.bmp → images/bmp/v3-top2bottom.42x50.bmp} +0 -0
  14. data/spec/images/cur/32x256.cur +0 -0
  15. data/spec/{test.gif → images/gif/668x481.gif} +0 -0
  16. data/spec/images/ico/32x256.ico +0 -0
  17. data/spec/images/jp2/163x402.jp2 +0 -0
  18. data/spec/images/jp2/176x373.jpx +0 -0
  19. data/spec/images/jp2/224x293.j2c +0 -0
  20. data/spec/images/jpeg/436x429.jpeg +0 -0
  21. data/spec/images/jpeg/extraneous-bytes.436x429.jpeg +0 -0
  22. data/spec/images/mng/61x42.mng +0 -0
  23. data/spec/{test.pcx → images/pcx/70x60.pcx} +0 -0
  24. data/spec/images/png/192x110.apng +0 -0
  25. data/spec/{test.png → images/png/640x532.png} +0 -0
  26. data/spec/images/pnm/22x25.pam +8 -0
  27. data/spec/images/pnm/22x25.pbm +0 -0
  28. data/spec/images/pnm/22x25.pgm +4 -0
  29. data/spec/images/pnm/22x25.ppm +4 -0
  30. data/spec/images/pnm/ascii.22x25.pbm +27 -0
  31. data/spec/images/pnm/ascii.22x25.pgm +28 -0
  32. data/spec/images/pnm/ascii.22x25.ppm +28 -0
  33. data/spec/{test.psd → images/psd/16x20.psd} +0 -0
  34. data/spec/{test.svg → images/svg/72x100.svg} +0 -0
  35. data/spec/{test.swf → images/swf/450x200.swf} +0 -0
  36. data/spec/images/tiff/big-endian.68x49.tiff +0 -0
  37. data/spec/images/tiff/little-endian.40x68.tiff +0 -0
  38. data/spec/images/webp/extended.16x32.webp +0 -0
  39. data/spec/images/webp/lossless.16x32.webp +0 -0
  40. data/spec/images/webp/lossy.16x32.webp +0 -0
  41. data/spec/{test.xbm → images/xbm/16x32.xbm} +0 -0
  42. data/spec/{test.xpm → images/xpm/24x32.xpm} +0 -0
  43. metadata +108 -55
  44. data/spec/test.cur +0 -0
  45. data/spec/test.ico +0 -0
  46. data/spec/test.jpg +0 -0
  47. data/spec/test.pbm +0 -0
  48. data/spec/test.pgm +0 -5
  49. data/spec/test.tif +0 -0
  50. data/spec/test2.jpg +0 -0
checksums.yaml CHANGED
@@ -1,15 +1,7 @@
1
1
  ---
2
- !binary "U0hBMQ==":
3
- metadata.gz: !binary |-
4
- OWEzNGYyODYwNmFkZTE1ZGEzOTA0YmVlZmNiZWE1MmE0NjExZjYzOQ==
5
- data.tar.gz: !binary |-
6
- YTAzMDA3MTIzYzJjYzgxNDIyNjRjZTlhMmNlZDFlNTY3MzU3MmQyOA==
2
+ SHA256:
3
+ metadata.gz: 9ecfd6172b63860fd5e18bf96cdfc79c7ab275f52643d6069ef4386978a70187
4
+ data.tar.gz: c0be8f8fea7b00fb2ad849598fb461238942a836f89dd63edc452432637e1fd5
7
5
  SHA512:
8
- metadata.gz: !binary |-
9
- NWQ2YTNiNjFiNmY0ZGJiNWZkYjBiODQzMmE5Y2I5NjhkMGVmMzdhMGIyZmEz
10
- MjI1NjRiNDFkMTE1ZGE3YzZmYjgwYTcyYmE2NDg5ZWY2YWM2NWVkZGEwNDVj
11
- OWIyM2M0NjIwMmE5Y2Y1YzY5OWM4NTdlMjA4ZjQxMGMxYzNjMmI=
12
- data.tar.gz: !binary |-
13
- MTI2YTEwM2IzNTAwYmExNGE5MTE1NGUyNGQyNTFlNTFiMTA2NzEyYzNlZDYw
14
- MzMzYzFmMTQ4NjY0ZWI2YmUxNGNkYWJlYWVlMjZiMzZmNDczOTU0NDJlZTM2
15
- ODUzZGUxYWJkYTEyOGIwNDdlYTQzNjdmOWQyMmUzNWEzYzIyMDE=
6
+ metadata.gz: 1536d248784158f8163a3fd7cde488af4a5dbd6e5ccfde7e40e491f5679db4bdd87a0834de00c077d3312090e36c677d6df29fed85cd3b45596b1303831fb33b
7
+ data.tar.gz: 2251c45b938da475db4a03ea570bdc3e2b513309817a874c9e0b4b458b83d67f7687af691a84a6f98aaa39de6241a5e4dc8f8177552f0ac9e051b494626398e0
@@ -0,0 +1,80 @@
1
+ inherit_from: .rubocop_todo.yml
2
+
3
+ AllCops:
4
+ Exclude:
5
+ - '*.gemspec'
6
+ NewCops: enable
7
+
8
+ Layout/AccessModifierIndentation:
9
+ EnforcedStyle: outdent
10
+
11
+ Layout/CaseIndentation:
12
+ EnforcedStyle: end
13
+
14
+ Layout/EndAlignment:
15
+ EnforcedStyleAlignWith: variable
16
+
17
+ Layout/LineLength:
18
+ Max: 120
19
+
20
+ Layout/SpaceBeforeBlockBraces:
21
+ EnforcedStyle: no_space
22
+ EnforcedStyleForEmptyBraces: no_space
23
+
24
+ Lint/PercentStringArray: # broken in rubocop 0.55.0
25
+ Enabled: false
26
+
27
+ Metrics/BlockLength:
28
+ Exclude:
29
+ - 'spec/**/*.rb'
30
+
31
+ Metrics/ClassLength:
32
+ Enabled: false
33
+
34
+ Metrics/MethodLength:
35
+ Enabled: false
36
+
37
+ Naming/MethodParameterName:
38
+ Enabled: false
39
+
40
+ Style/AccessorGrouping:
41
+ Enabled: false
42
+
43
+ Style/Alias:
44
+ EnforcedStyle: prefer_alias_method
45
+
46
+ Style/EmptyCaseCondition:
47
+ Enabled: false
48
+
49
+ Style/Encoding:
50
+ Enabled: false
51
+
52
+ Style/HashEachMethods:
53
+ Enabled: true
54
+
55
+ Style/HashSyntax:
56
+ EnforcedStyle: hash_rockets
57
+
58
+ Style/HashTransformKeys:
59
+ Enabled: false
60
+
61
+ Style/HashTransformValues:
62
+ Enabled: false
63
+
64
+ Style/IfUnlessModifier:
65
+ Enabled: false
66
+
67
+ Style/ParallelAssignment:
68
+ Enabled: false
69
+
70
+ Style/SafeNavigation:
71
+ Enabled: false
72
+
73
+ Style/TrailingCommaInArrayLiteral:
74
+ EnforcedStyleForMultiline: comma
75
+
76
+ Style/TrailingCommaInHashLiteral:
77
+ EnforcedStyleForMultiline: comma
78
+
79
+ Style/UnpackFirst:
80
+ Enabled: false
@@ -0,0 +1,28 @@
1
+ # This configuration was generated by
2
+ # `rubocop --auto-gen-config --no-auto-gen-timestamp`
3
+ # using RuboCop version 0.89.0.
4
+ # The point is for the user to remove these configuration records
5
+ # one by one as the offenses are removed from the code base.
6
+ # Note that changes in the inspected code, or installation of new
7
+ # versions of RuboCop, may require this file to be generated again.
8
+
9
+ # Offense count: 8
10
+ # Configuration parameters: IgnoredMethods.
11
+ Metrics/AbcSize:
12
+ Max: 43
13
+
14
+ # Offense count: 5
15
+ # Configuration parameters: IgnoredMethods.
16
+ Metrics/CyclomaticComplexity:
17
+ Max: 21
18
+
19
+ # Offense count: 2
20
+ # Configuration parameters: IgnoredMethods.
21
+ Metrics/PerceivedComplexity:
22
+ Max: 21
23
+
24
+ # Offense count: 8
25
+ # Cop supports --auto-correct.
26
+ Style/PerlBackrefs:
27
+ Exclude:
28
+ - 'lib/image_size.rb'
@@ -1,10 +1,27 @@
1
+ sudo: false
2
+ dist: trusty
1
3
  language: ruby
4
+ before_install:
5
+ - gem install bundler || gem install bundler --version '< 2'
2
6
  rvm:
3
- - 1.8.7
4
- - 1.9.2
5
- - 1.9.3
6
- - 2.0.0
7
- - jruby-18mode
8
- - jruby-19mode
9
- - ree
10
- script: "bundle exec rspec"
7
+ - '1.8.7-p374'
8
+ - '1.9.3-p551'
9
+ - '2.0.0-p648'
10
+ - '2.1.10'
11
+ - '2.2.10'
12
+ - '2.3.8'
13
+ - '2.4.10'
14
+ - '2.5.8'
15
+ - '2.6.6'
16
+ - '2.7.1'
17
+ - 'jruby-9.1.17.0'
18
+ - 'jruby-9.2.11.1'
19
+ script: bundle exec rspec
20
+ matrix:
21
+ include:
22
+ - env: RUBOCOP=1
23
+ rvm: '2.6.6'
24
+ script: bundle exec rubocop
25
+ - env: CHECK_RUBIES=1
26
+ rvm: '2.6.6'
27
+ script: bundle exec travis_check_rubies
@@ -0,0 +1,117 @@
1
+ # ChangeLog
2
+
3
+ ## unreleased
4
+
5
+ ## v2.1.0 (2020-08-09)
6
+
7
+ * Add handling of JPEG 2000: part 1 (jp2), part 2 (jpx) and codestream (j2c) [#13](https://github.com/toy/image_size/issues/13) [@toy](https://github.com/toy)
8
+ * Correct handling of pam images and cleanup handling of Netpbm images (pbm, pgm, pnm) [@toy](https://github.com/toy)
9
+
10
+ ## v2.0.2 (2019-07-14)
11
+
12
+ * Remove deprecated `rubyforge_project` attribute from gemspec [rubygems/rubygems#2436](https://github.com/rubygems/rubygems/pull/2436) [@toy](https://github.com/toy)
13
+
14
+ ## v2.0.1 (2019-05-17)
15
+
16
+ * Adapt to frozen string literals [@toy](https://github.com/toy)
17
+
18
+ ## v2.0.0 (2018-05-01)
19
+
20
+ * Allow any class responding to `read` and `eof?` to be passed to `ImageSize` [@toy](https://github.com/toy)
21
+ * Introduce `rubocop` [@toy](https://github.com/toy)
22
+ * Use `File.open` instead of `Kernel#open` [@toy](https://github.com/toy)
23
+ * Don’t rewind `IO` before or after usage [@toy](https://github.com/toy)
24
+ * Enhance readme [@toy](https://github.com/toy)
25
+
26
+ ## v1.5.0 (2016-11-20)
27
+
28
+ * Support `WEBP` images [@toy](https://github.com/toy)
29
+ * Cleanup `GIF`, `PPM` and `SWF` magic number matching [@toy](https://github.com/toy)
30
+ * Fix `GIF` magic number (matched `GIF8,a`) [@toy](https://github.com/toy)
31
+ * Detect `APNG` images by `acTL` chunk [@toy](https://github.com/toy)
32
+ * Support `MNG` images [@toy](https://github.com/toy)
33
+
34
+ ## v1.4.2 (2016-02-18)
35
+
36
+ * Fixed license in gemspec to be Ruby [#10](https://github.com/toy/image_size/issues/10) [@toy](https://github.com/toy)
37
+
38
+ ## v1.4.1 (2014-11-19)
39
+
40
+ * Missed `ICO` and `CUR` in description [@toy](https://github.com/toy)
41
+
42
+ ## v1.4.0 (2014-11-19)
43
+
44
+ * Detecting `ICO` and `CUR` images [@toy](https://github.com/toy)
45
+
46
+ ## v1.3.1 (2014-06-24)
47
+
48
+ * Fix reading `JPEGs` with extraneous bytes [@toy](https://github.com/toy)
49
+
50
+ ## v1.3.0 (2014-04-06)
51
+
52
+ * Raise `FormatError` instead of `RuntimeError` [@toy](https://github.com/toy)
53
+
54
+ ## v1.2.0 (2014-02-01)
55
+
56
+ * Basic handling of `SVG` (only width and height attributes) [@toy](https://github.com/toy)
57
+ * Enhance matching `PCX` [@toy](https://github.com/toy)
58
+
59
+ ## v1.1.5 (2013-12-23)
60
+
61
+ * Fix reading dimensions of `BMP v2` and `BMP v3` [@toy](https://github.com/toy)
62
+ * Fix swapped `PSD` width (columns) and height (rows) [#9](https://github.com/toy/image_size/issues/9) [@toy](https://github.com/toy)
63
+ * Replace square test images with rectangle ones [@toy](https://github.com/toy)
64
+
65
+ ## v1.1.4 (2013-11-05)
66
+
67
+ * Close instead of only rewinding `IO` instances [@toy](https://github.com/toy)
68
+ * Add `.travis.yml` and supporting files [#8](https://github.com/toy/image_size/pull/8) [@petergoldstein](https://github.com/petergoldstein)
69
+
70
+ ## v1.1.3 (2013-07-24)
71
+
72
+ * Enforce binary encoding of data returned by `ImageReader#[]` [#6](https://github.com/toy/image_size/issues/6) [@toy](https://github.com/toy)
73
+
74
+ ## v1.1.2 (2013-02-24)
75
+
76
+ * Explicitly set encoding to `ASCII-8BIT` as for `ruby2.0.0-p0` it will be `UTF-8` by default [#5](https://github.com/toy/image_size/pull/5) [@walf443](https://github.com/walf443)
77
+
78
+ ## v1.1.1 (2012-06-19)
79
+
80
+ * Fix exception in message for exception [#3](https://github.com/toy/image_size/pull/3) [@yachi](https://github.com/yachi)
81
+
82
+ ## v1.1.0 (2012-02-25)
83
+
84
+ * Rework most code [@toy](https://github.com/toy)
85
+ * `Size` class instead of dynamically adding `to_s` method to size array [@toy](https://github.com/toy)
86
+ * `ImageSize.path`, more examples [@toy](https://github.com/toy)
87
+ * Added support for `Tempfile`, as well as fixed bug when running `ImageSize` on the same `IO` stream twice [#2](https://github.com/toy/image_size/pull/2) [@kanevski](https://github.com/kanevski)
88
+
89
+ ## v1.0.6 (2012-02-08)
90
+
91
+ * Internal gem changes [@toy](https://github.com/toy)
92
+
93
+ ## v1.0.5 (2012-02-04)
94
+
95
+ * Fix getting `SWF` dimensions for ruby 1.9 [@toy](https://github.com/toy)
96
+ * Fix determining `PCX` for ruby1.9 [@toy](https://github.com/toy)
97
+ * Enhance `SWF` checking [@toy](https://github.com/toy)
98
+
99
+ ## v1.0.4 (2011-12-16)
100
+
101
+ * Internal gem changes [@toy](https://github.com/toy)
102
+
103
+ ## v1.0.3 (2011-02-21)
104
+
105
+ * Fix permissions on `lib/image_size.rb` from `700` to `644` [#1](https://github.com/toy/image_size/issues/1) [@toy](https://github.com/toy)
106
+
107
+ ## v1.0.2 (2010-12-15)
108
+
109
+ * Internal gem changes [@toy](https://github.com/toy)
110
+
111
+ ## v1.0.1 (2010-12-15)
112
+
113
+ * Internal gem changes [@toy](https://github.com/toy)
114
+
115
+ ## v1.0.0 (2010-11-01)
116
+
117
+ * Initial commit [@toy](https://github.com/toy)
data/Gemfile CHANGED
@@ -1,3 +1,9 @@
1
- source "https://rubygems.org"
1
+ # frozen_string_literal: true
2
+
3
+ source 'https://rubygems.org'
2
4
 
3
5
  gemspec
6
+
7
+ if RUBY_VERSION >= '2.0'
8
+ gem 'travis_check_rubies', '~> 0.2'
9
+ end
@@ -1,40 +1,87 @@
1
+ [![Gem Version](https://img.shields.io/gem/v/image_size.svg?style=flat)](https://rubygems.org/gems/image_size)
2
+ [![Build Status](https://img.shields.io/travis/toy/image_size/master.svg?style=flat)](https://travis-ci.org/toy/image_size)
3
+
1
4
  # image_size
2
5
 
3
6
  measure image size using pure Ruby
4
- formats: `bmp`, `cur`, `gif`, `jpeg`, `ico`, `pbm`, `pcx`, `pgm`, `png`, `ppm`, `psd`, `swf`, `tiff`, `xbm`, `xpm`
7
+ formats: `apng`, `bmp`, `cur`, `gif`, `ico`, `j2c`, `jp2`, `jpeg`, `jpx`, `mng`, `pam`, `pbm`, `pcx`, `pgm`, `png`, `ppm`, `psd`, `svg`, `swf`, `tiff`, `webp`, `xbm`, `xpm`
8
+
9
+ ## Installation
5
10
 
6
- [![Build Status](https://travis-ci.org/toy/image_size.png?branch=master)](https://travis-ci.org/toy/image_size)
11
+ ```sh
12
+ gem install image_size
13
+ ```
7
14
 
8
- ## Download
15
+ ### Bundler
9
16
 
10
- The latest version of image\_size can be found at http://github.com/toy/image_size
17
+ Add to your `Gemfile`:
11
18
 
12
- ## Installation
19
+ ```ruby
20
+ gem 'image_size', '~> 2.0'
21
+ ```
22
+
23
+ ## Usage
24
+
25
+ ```ruby
26
+ image_size = ImageSize.path('spec/test.jpg')
27
+
28
+ image_size.format #=> :jpec
29
+ image_size.width #=> 320
30
+ image_size.height #=> 240
31
+ image_size.w #=> 320
32
+ image_size.h #=> 240
33
+ image_size.size #=> [320, 240]
34
+ ```
35
+
36
+ Or using `IO` object:
37
+
38
+ ```ruby
39
+ image_size = File.open('spec/test.jpg', 'rb'){ |fh| ImageSize.new(fh) }
40
+ ```
41
+
42
+ Any object responding to `read` and `eof?`:
43
+
44
+ ```ruby
45
+ require 'image_size'
46
+
47
+ image_size = ImageSize.new(ARGF)
48
+ ```
49
+
50
+ Works with `open-uri` if needed:
13
51
 
14
- gem install image_size
52
+ ```ruby
53
+ require 'image_size'
54
+ require 'open-uri'
15
55
 
16
- ## Examples
56
+ image_size = URI.parse('http://www.rubycgi.org/image/ruby_gtk_book_title.jpg').open('rb') do |fh|
57
+ ImageSize.new(fh)
58
+ end
17
59
 
18
- require 'image_size'
60
+ image_size = open('http://www.rubycgi.org/image/ruby_gtk_book_title.jpg', 'rb') do |fh|
61
+ ImageSize.new(fh)
62
+ end
63
+ ```
19
64
 
20
- p ImageSize.path('spec/test.jpg').size
65
+ Note that starting with version `2.0.0` the object given to `ImageSize` will not be rewound before or after use.
66
+ So rewind if needed before passing to `ImageSize` and/or rewind after passing to `ImageSize` before reading data.
21
67
 
22
- open('spec/test.jpg', 'rb') do |fh|
23
- p ImageSize.new(fh).size
24
- end
68
+ ```ruby
69
+ require 'image_size'
25
70
 
71
+ File.open('spec/test.jpg', 'rb') do |fh|
72
+ image_size = ImageSize.new(fh)
26
73
 
27
- require 'image_size'
28
- require 'open-uri'
74
+ fh.rewind
75
+ data = fh.read
76
+ end
29
77
 
30
- open('http://www.rubycgi.org/image/ruby_gtk_book_title.jpg', 'rb') do |fh|
31
- p ImageSize.new(fh).size
32
- end
78
+ File.open('spec/test.jpg', 'rb') do |fh|
79
+ data = fh.read
80
+ fh.rewind
33
81
 
34
- open('http://www.rubycgi.org/image/ruby_gtk_book_title.jpg', 'rb') do |fh|
35
- data = fh.read
36
- p ImageSize.new(data).size
37
- end
82
+ image_size = ImageSize.new(fh)
83
+ end
84
+ ```
38
85
 
39
86
  ## Licence
40
87
 
@@ -43,3 +90,4 @@ This code is free to use under the terms of the Ruby's licence.
43
90
  ## Contact
44
91
 
45
92
  Original author: "Keisuke Minami": mailto:keisuke@rccn.com
93
+ Further development by Ivan Kuchin https://github.com/toy/image_size
@@ -2,14 +2,19 @@
2
2
 
3
3
  Gem::Specification.new do |s|
4
4
  s.name = 'image_size'
5
- s.version = '1.4.2'
5
+ s.version = '2.1.0'
6
6
  s.summary = %q{Measure image size using pure Ruby}
7
- s.description = %q{Measure following file dimensions: bmp, cur, gif, jpeg, ico, pbm, pcx, pgm, png, ppm, psd, swf, tiff, xbm, xpm}
8
- s.homepage = "http://github.com/toy/#{s.name}"
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}
8
+ s.homepage = "https://github.com/toy/#{s.name}"
9
9
  s.authors = ['Keisuke Minami', 'Ivan Kuchin']
10
10
  s.license = 'Ruby'
11
11
 
12
- s.rubyforge_project = s.name
12
+ s.metadata = {
13
+ 'bug_tracker_uri' => "https://github.com/toy/#{s.name}/issues",
14
+ 'changelog_uri' => "https://github.com/toy/#{s.name}/blob/master/CHANGELOG.markdown",
15
+ 'documentation_uri' => "https://www.rubydoc.info/gems/#{s.name}/#{s.version}",
16
+ 'source_code_uri' => "https://github.com/toy/#{s.name}",
17
+ }
13
18
 
14
19
  s.files = `git ls-files`.split("\n")
15
20
  s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
@@ -17,4 +22,7 @@ Gem::Specification.new do |s|
17
22
  s.require_paths = %w[lib]
18
23
 
19
24
  s.add_development_dependency 'rspec', '~> 3.0'
25
+ if RUBY_VERSION >= '2.2'
26
+ s.add_development_dependency 'rubocop', '~> 0.59', '!= 0.78.0'
27
+ end
20
28
  end
@@ -1,11 +1,13 @@
1
- # -- coding: ASCII-8BIT --
2
- #
1
+ # encoding: BINARY
2
+ # frozen_string_literal: true
3
+
3
4
  require 'stringio'
4
- require 'tempfile'
5
5
 
6
+ # Determine image format and size
6
7
  class ImageSize
7
8
  class FormatError < StandardError; end
8
9
 
10
+ # Array joining with 'x'
9
11
  class Size < Array
10
12
  # join using 'x'
11
13
  def to_s
@@ -15,34 +17,26 @@ class ImageSize
15
17
 
16
18
  class ImageReader # :nodoc:
17
19
  attr_reader :data
20
+
18
21
  def initialize(data_or_io)
19
- @io = case data_or_io
20
- when IO, StringIO, Tempfile
21
- data_or_io.dup.tap(&:rewind)
22
- when String
22
+ @io = if data_or_io.is_a?(String)
23
23
  StringIO.new(data_or_io)
24
+ elsif data_or_io.respond_to?(:read) && data_or_io.respond_to?(:eof?)
25
+ data_or_io
24
26
  else
25
- raise ArgumentError.new("expected instance of IO, StringIO, Tempfile or String, got #{data_or_io.class}")
27
+ raise ArgumentError, "expected data as String or an object responding to read and eof?, got #{data_or_io.class}"
26
28
  end
27
- @read = 0
28
- @data = ''
29
- end
30
-
31
- def close
32
- @io.rewind
33
- @io.close if IO === @io
29
+ @data = String.new # not frozen
34
30
  end
35
31
 
36
32
  CHUNK = 1024
37
33
  def [](offset, length)
38
- while offset + length > @read
39
- @read += CHUNK
40
- if data = @io.read(CHUNK)
41
- if data.respond_to?(:encoding)
42
- data.force_encoding(@data.encoding)
43
- end
44
- @data << data
45
- end
34
+ while !@io.eof? && @data.length < offset + length
35
+ data = @io.read(CHUNK)
36
+ break unless data
37
+
38
+ data.force_encoding(@data.encoding) if data.respond_to?(:encoding)
39
+ @data << data
46
40
  end
47
41
  @data[offset, length]
48
42
  end
@@ -50,7 +44,7 @@ class ImageSize
50
44
 
51
45
  # Given path to image finds its format, width and height
52
46
  def self.path(path)
53
- open(path, 'rb'){ |f| new(f) }
47
+ File.open(path, 'rb'){ |f| new(f) }
54
48
  end
55
49
 
56
50
  # Used for svg
@@ -63,13 +57,13 @@ class ImageSize
63
57
  @dpi = dpi.to_f
64
58
  end
65
59
 
66
- # Given image as IO, StringIO, Tempfile or String finds its format and dimensions
60
+ # Given image as any class responding to read and eof? or data as String, finds its format and dimensions
67
61
  def initialize(data)
68
62
  ir = ImageReader.new(data)
69
- if @format = detect_format(ir)
70
- @width, @height = self.send("size_of_#{@format}", ir)
71
- end
72
- ir.close
63
+ @format = detect_format(ir)
64
+ return unless @format
65
+
66
+ @width, @height = send("size_of_#{@format}", ir)
73
67
  end
74
68
 
75
69
  # Image format
@@ -77,42 +71,75 @@ class ImageSize
77
71
 
78
72
  # Image width
79
73
  attr_reader :width
80
- alias :w :width
74
+ alias_method :w, :width
81
75
 
82
76
  # Image height
83
77
  attr_reader :height
84
- alias :h :height
78
+ alias_method :h, :height
85
79
 
86
80
  # get image width and height as an array which to_s method returns "#{width}x#{height}"
87
81
  def size
88
- if format
89
- Size.new([width, height])
90
- end
82
+ Size.new([width, height]) if format
91
83
  end
92
84
 
93
85
  private
94
86
 
95
- SVG_R = /<svg\b([^>]*)>/
87
+ SVG_R = /<svg\b([^>]*)>/.freeze
88
+ XML_R = /<\?xml|<!--/.freeze
96
89
  def detect_format(ir)
97
90
  head = ir[0, 1024]
98
91
  case
99
- when head =~ /^GIF8[7,9]a/ then :gif
100
- when head[0, 8] == "\211PNG\r\n\032\n" then :png
101
- when head[0, 2] == "\377\330" then :jpeg
102
- when head[0, 2] == 'BM' then :bmp
103
- when head =~ /^P[1-7]/ then :ppm
104
- when head =~ /\#define\s+\S+\s+\d+/ then :xbm
105
- when head[0, 4] == "II*\000" then :tiff
106
- when head[0, 4] == "MM\000*" then :tiff
107
- when head =~ /\/\* XPM \*\// then :xpm
108
- when head[0, 4] == '8BPS' then :psd
109
- when head =~ /^[FC]WS/ then :swf
110
- when head[SVG_R] ||
111
- head =~ /<\?xml|<!--/ && ir[0, 4096][SVG_R]
112
- then :svg
113
- when head[0, 2] =~ /\n[\000-\005]/ then :pcx
114
- when head[0, 4] == "\000\000\001\000" then :ico
115
- when head[0, 4] == "\000\000\002\000" then :cur
92
+ when head[0, 6] =~ /GIF8[79]a/ then :gif
93
+ when head[0, 8] == "\211PNG\r\n\032\n" then detect_png_type(ir)
94
+ when head[0, 8] == "\212MNG\r\n\032\n" then :mng
95
+ when head[0, 2] == "\377\330" then :jpeg
96
+ when head[0, 2] == 'BM' then :bmp
97
+ when head[0, 3] =~ /P[1-6]\s|P7\n/ then detect_pnm_type(ir)
98
+ when head =~ /\#define\s+\S+\s+\d+/ then :xbm
99
+ when %W[II*\0 MM\0*].include?(head[0, 4]) then :tiff
100
+ when head =~ %r{/\* XPM \*/} then :xpm
101
+ when head[0, 4] == '8BPS' then :psd
102
+ when head[0, 3] =~ /[FC]WS/ then :swf
103
+ when head =~ SVG_R || (head =~ XML_R && ir[0, 4096][SVG_R]) then :svg
104
+ when head[0, 2] =~ /\n[\0-\5]/ then :pcx
105
+ when head[0, 12] =~ /RIFF(?m:....)WEBP/ then :webp
106
+ when head[0, 4] == "\0\0\1\0" then :ico
107
+ when head[0, 4] == "\0\0\2\0" then :cur
108
+ when head[0, 12] == "\0\0\0\fjP \r\n\207\n" then detect_jpeg2000_type(ir)
109
+ when head[0, 4] == "\377O\377Q" then :j2c
110
+ end
111
+ end
112
+
113
+ def detect_png_type(ir)
114
+ offset = 8
115
+ loop do
116
+ type = ir[offset + 4, 4]
117
+ break if ['IDAT', 'IEND', nil].include?(type)
118
+ return :apng if type == 'acTL'
119
+
120
+ length = ir[offset, 4].unpack('N')[0]
121
+ offset += 8 + length + 4
122
+ end
123
+ :png
124
+ end
125
+
126
+ def detect_pnm_type(ir)
127
+ case ir[0, 2]
128
+ when 'P1', 'P4' then :pbm
129
+ when 'P2', 'P5' then :pgm
130
+ when 'P3', 'P6' then :ppm
131
+ when 'P7' then :pam
132
+ end
133
+ end
134
+
135
+ def detect_jpeg2000_type(ir)
136
+ return unless ir[16, 4] == 'ftyp'
137
+
138
+ # using xl-box would be weird, but doesn't seem to contradict specification
139
+ skip = ir[12, 4] == "\0\0\0\1" ? 16 : 8
140
+ case ir[12 + skip, 4]
141
+ when 'jp2 ' then :jp2
142
+ when 'jpx ' then :jpx
116
143
  end
117
144
  end
118
145
 
@@ -120,19 +147,29 @@ private
120
147
  ir[6, 4].unpack('vv')
121
148
  end
122
149
 
150
+ def size_of_mng(ir)
151
+ unless ir[12, 4] == 'MHDR'
152
+ raise FormatError, 'MHDR not in place for MNG'
153
+ end
154
+
155
+ ir[16, 8].unpack('NN')
156
+ end
157
+
123
158
  def size_of_png(ir)
124
159
  unless ir[12, 4] == 'IHDR'
125
160
  raise FormatError, 'IHDR not in place for PNG'
126
161
  end
162
+
127
163
  ir[16, 8].unpack('NN')
128
164
  end
129
-
130
- JpegCodeCheck = [
131
- "\xc0", "\xc1", "\xc2", "\xc3",
132
- "\xc5", "\xc6", "\xc7",
133
- "\xc9", "\xca", "\xcb",
134
- "\xcd", "\xce", "\xcf",
135
- ] # :nodoc:
165
+ alias_method :size_of_apng, :size_of_png
166
+
167
+ JPEG_CODE_CHECK = %W[
168
+ \xC0 \xC1 \xC2 \xC3
169
+ \xC5 \xC6 \xC7
170
+ \xC9 \xCA \xCB
171
+ \xCD \xCE \xCF
172
+ ].freeze
136
173
  def size_of_jpeg(ir)
137
174
  section_marker = "\xFF"
138
175
  offset = 2
@@ -141,12 +178,13 @@ private
141
178
  offset += 1 until section_marker != ir[offset + 1, 1]
142
179
  raise FormatError, 'EOF in JPEG' if ir[offset, 1].nil?
143
180
 
144
- marker, code, length = ir[offset, 4].unpack('aan')
181
+ _marker, code, length = ir[offset, 4].unpack('aan')
145
182
  offset += 4
146
183
 
147
- if JpegCodeCheck.include?(code)
184
+ if JPEG_CODE_CHECK.include?(code)
148
185
  return ir[offset + 1, 4].unpack('nn').reverse
149
186
  end
187
+
150
188
  offset += length - 2
151
189
  end
152
190
  end
@@ -170,12 +208,38 @@ private
170
208
  header = ir[0, 1024]
171
209
  header.gsub!(/^\#[^\n\r]*/m, '')
172
210
  header =~ /^(P[1-6])\s+?(\d+)\s+?(\d+)/m
173
- case $1
174
- when 'P1', 'P4' then @format = :pbm
175
- when 'P2', 'P5' then @format = :pgm
176
- end
177
211
  [$2.to_i, $3.to_i]
178
212
  end
213
+ alias_method :size_of_pbm, :size_of_ppm
214
+ alias_method :size_of_pgm, :size_of_ppm
215
+
216
+ def size_of_pam(ir)
217
+ width = height = nil
218
+ offset = 3
219
+ loop do
220
+ if ir[offset, 1] == '#'
221
+ offset += 1 until ["\n", '', nil].include?(ir[offset, 1])
222
+ offset += 1
223
+ else
224
+ chunk = ir[offset, 32]
225
+ case chunk
226
+ when /\AWIDTH (\d+)\n/
227
+ width = $1.to_i
228
+ when /\AHEIGHT (\d+)\n/
229
+ height = $1.to_i
230
+ when /\AENDHDR\n/
231
+ break
232
+ when /\A(?:DEPTH|MAXVAL) \d+\n/, /\ATUPLTYPE \S+\n/
233
+ # ignore
234
+ else
235
+ raise FormatError, "Unexpected data in PAM header: #{chunk.inspect}"
236
+ end
237
+ offset += $&.length
238
+ break if width && height
239
+ end
240
+ end
241
+ [width, height]
242
+ end
179
243
 
180
244
  def size_of_xbm(ir)
181
245
  ir[0, 1024] =~ /^\#define\s*\S*\s*(\d+)\s*\n\#define\s*\S*\s*(\d+)/mi
@@ -188,6 +252,7 @@ private
188
252
  if data.length != length
189
253
  raise FormatError, 'XPM size not found'
190
254
  end
255
+
191
256
  length += 1024
192
257
  end
193
258
  [$1.to_i, $2.to_i]
@@ -198,7 +263,7 @@ private
198
263
  end
199
264
 
200
265
  def size_of_tiff(ir)
201
- endian2b = (ir[0, 4] == "II*\000") ? 'v' : 'n'
266
+ endian2b = ir[0, 4] == "II*\000" ? 'v' : 'n'
202
267
  endian4b = endian2b.upcase
203
268
  packspec = [nil, 'C', nil, endian2b, endian4b, nil, 'c', nil, endian2b, endian4b]
204
269
 
@@ -211,17 +276,18 @@ private
211
276
  until width && height
212
277
  ifd = ir[offset, 12]
213
278
  raise FormatError, 'Reached end of directory entries in TIFF' if ifd.nil? || offset > num_dirent
279
+
214
280
  tag, type = ifd.unpack(endian2b * 2)
215
281
  offset += 12
216
282
 
217
- unless packspec[type].nil?
218
- value = ifd[8, 4].unpack(packspec[type])[0]
219
- case tag
220
- when 0x0100
221
- width = value
222
- when 0x0101
223
- height = value
224
- end
283
+ next if packspec[type].nil?
284
+
285
+ value = ifd[8, 4].unpack(packspec[type])[0]
286
+ case tag
287
+ when 0x0100
288
+ width = value
289
+ when 0x0101
290
+ height = value
225
291
  end
226
292
  end
227
293
  [width, height]
@@ -236,12 +302,11 @@ private
236
302
  value_bit_length = ir[8, 1].unpack('B5').first.to_i(2)
237
303
  bit_length = 5 + value_bit_length * 4
238
304
  rect_bits = ir[8, bit_length / 8 + 1].unpack("B#{bit_length}").first
239
- values = rect_bits.unpack('@5' + "a#{value_bit_length}" * 4).map{ |bits| bits.to_i(2) }
305
+ values = rect_bits[5..-1].unpack("a#{value_bit_length}" * 4).map{ |bits| bits.to_i(2) }
240
306
  x_min, x_max, y_min, y_max = values
241
307
  [(x_max - x_min) / 20, (y_max - y_min) / 20]
242
308
  end
243
309
 
244
- DPI = 72
245
310
  def size_of_svg(ir)
246
311
  attributes = {}
247
312
  ir.data[SVG_R, 1].scan(/(\S+)=(?:'([^']*)'|"([^"]*)"|([^'"\s]*))/) do |name, v0, v1, v2|
@@ -249,18 +314,18 @@ private
249
314
  end
250
315
  dpi = self.class.dpi
251
316
  [attributes['width'], attributes['height']].map do |length|
252
- if length
253
- pixels = case length.downcase.strip[/(?:em|ex|px|in|cm|mm|pt|pc|%)\z/]
254
- when 'em', 'ex', '%' then nil
255
- when 'in' then length.to_f * dpi
256
- when 'cm' then length.to_f * dpi / 2.54
257
- when 'mm' then length.to_f * dpi / 25.4
258
- when 'pt' then length.to_f * dpi / 72
259
- when 'pc' then length.to_f * dpi / 6
260
- else length.to_f
261
- end
262
- pixels.round if pixels
317
+ next unless length
318
+
319
+ pixels = case length.downcase.strip[/(?:em|ex|px|in|cm|mm|pt|pc|%)\z/]
320
+ when 'em', 'ex', '%' then nil
321
+ when 'in' then length.to_f * dpi
322
+ when 'cm' then length.to_f * dpi / 2.54
323
+ when 'mm' then length.to_f * dpi / 25.4
324
+ when 'pt' then length.to_f * dpi / 72
325
+ when 'pc' then length.to_f * dpi / 6
326
+ else length.to_f
263
327
  end
328
+ pixels.round if pixels
264
329
  end
265
330
  end
266
331
 
@@ -268,4 +333,57 @@ private
268
333
  ir[6, 2].unpack('CC').map{ |v| v.zero? ? 256 : v }
269
334
  end
270
335
  alias_method :size_of_cur, :size_of_ico
336
+
337
+ def size_of_webp(ir)
338
+ case ir[12, 4]
339
+ when 'VP8 '
340
+ ir[26, 4].unpack('vv').map{ |v| v & 0x3fff }
341
+ when 'VP8L'
342
+ n = ir[21, 4].unpack('V')[0]
343
+ [(n & 0x3fff) + 1, (n >> 14 & 0x3fff) + 1]
344
+ when 'VP8X'
345
+ w16, w8, h16, h8 = ir[24, 6].unpack('vCvC')
346
+ [(w16 | w8 << 16) + 1, (h16 | h8 << 16) + 1]
347
+ end
348
+ end
349
+
350
+ def size_of_jp2(ir)
351
+ offset = 12
352
+ stop = nil
353
+ in_header = false
354
+ loop do
355
+ break if stop && offset >= stop
356
+ break if ir[offset, 4] == '' || ir[offset, 4].nil?
357
+
358
+ size = ir[offset, 4].unpack('N')[0]
359
+ type = ir[offset + 4, 4]
360
+
361
+ data_offset = 8
362
+ case size
363
+ when 1
364
+ size = ir[offset, 8].unpack('Q>')[0]
365
+ data_offset = 16
366
+ raise FormatError, "Unexpected xl-box size #{size}" if (1..15).include?(size)
367
+ when 2..7
368
+ raise FormatError, "Reserved box size #{size}"
369
+ end
370
+
371
+ if type == 'jp2h'
372
+ stop = offset + size unless size.zero?
373
+ offset += data_offset
374
+ in_header = true
375
+ elsif in_header && type == 'ihdr'
376
+ return ir[offset + data_offset, 8].unpack('NN').reverse
377
+ else
378
+ break if size.zero? # box to the end of file
379
+
380
+ offset += size
381
+ end
382
+ end
383
+ end
384
+ alias_method :size_of_jpx, :size_of_jp2
385
+
386
+ def size_of_j2c(ir)
387
+ ir[8, 8].unpack('NN')
388
+ end
271
389
  end