image_size 1.4.2 → 2.1.0

Sign up to get free protection for your applications and to get access to all the features.
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