local-fastimage 3.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: afae3838ec65db1a4b956aca4771b119eeda4146
4
+ data.tar.gz: a37f9cd24560a853cb8d6b31187279cf3d7f3f6d
5
+ SHA512:
6
+ metadata.gz: 2f9edc6900c3d9d6057c783504b4650071863510aef858778b6ef42128f629942a273ba3629f90431663f83067d805c728934bf9d1c26cf07a4c8c54b5f1670e
7
+ data.tar.gz: 9fc7f86dcf068a435b33bb663b1da7900b97d2783d6d6c8bab677c2a3f2a6ce264ed2d0456f049f1cc5c0958cdc85e2e2d641b983aa442a6e3daa5614719d6ab
data/.gitignore ADDED
@@ -0,0 +1,2 @@
1
+ pkg/
2
+ Gemfile.lock
data/.travis.yml ADDED
@@ -0,0 +1,12 @@
1
+ language: ruby
2
+ rvm:
3
+ - 1.9.2
4
+ - 1.9.3
5
+ - 2.0.0
6
+ - 2.1.8
7
+ - 2.2.4
8
+ - 2.3.0
9
+
10
+ sudo: false
11
+ # uncomment this line if your project needs to run something other than `rake`:
12
+ # script: bundle exec rspec spec
data/CHANGELOG.md ADDED
@@ -0,0 +1,8 @@
1
+ # 3.0.0 - 2016-06-02
2
+
3
+ Removing support for remote images.
4
+
5
+ This way we can drop the addressable and fakeweb dependencies and eliminate a
6
+ whole class of security problems.
7
+
8
+ Also renaming to local-fastimage.
data/Gemfile ADDED
@@ -0,0 +1,2 @@
1
+ source 'https://rubygems.org'
2
+ gemspec
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2008-2013 Stephen Sykes
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,43 @@
1
+ # Local FastImage
2
+
3
+ This is a fork of the [FastImage](https://github.com/sdsykes/fastimage) gem.
4
+
5
+ It features the following differences:
6
+
7
+ * Removal of all remote image handling code
8
+ * Minor changes to code organization
9
+
10
+ ## Installation
11
+
12
+ Add this line to your application's Gemfile:
13
+
14
+ ```ruby
15
+ gem 'local-fastimage', require: 'fastimage'
16
+ ```
17
+
18
+ And then execute:
19
+
20
+ $ bundle
21
+
22
+
23
+ If you are using Bundler's autorequire, your good to go. Otherwise make sure to
24
+ `require "fastimage"`.
25
+
26
+ Or install it yourself as:
27
+
28
+ $ gem install local-fastimage
29
+
30
+ Again, make sure to `require "fastimage"`.
31
+
32
+
33
+
34
+ ## Usage
35
+
36
+ See [README.textile](README.textile) for more documentation. Everything should
37
+ work as advertised, expect for remote images of course.
38
+
39
+
40
+
41
+ ## License
42
+
43
+ MIT, see file [MIT-LICENSE](MIT-LICENSE)
data/README.textile ADDED
@@ -0,0 +1,158 @@
1
+ !https://img.shields.io/gem/dt/fastimage.svg!:https://rubygems.org/gems/fastimage
2
+ !https://travis-ci.org/sdsykes/fastimage.png?branch=master!:https://travis-ci.org/sdsykes/fastimage
3
+
4
+ h1. FastImage
5
+
6
+ > This fork of the FastImage Gem removes remote URL support to avoid a whole
7
+ > class of security-related problems. See "README.md":README.md for more details.
8
+
9
+ h4. FastImage finds the size or type of an image given its uri by fetching as little as needed
10
+
11
+ h2. The problem
12
+
13
+ Your app needs to find the size or type of an image. This could be for adding width and height attributes to an image tag, for adjusting layouts or overlays to fit an image or any other of dozens of reasons.
14
+
15
+ But the image is not locally stored - it's on another asset server, or in the cloud - at Amazon S3 for example.
16
+
17
+ You don't want to download the entire image to your app server - it could be many tens of kilobytes, or even megabytes just to get this information. For most common image types (GIF, PNG, BMP etc.), the size of the image is simply stored at the start of the file. For JPEG files it's a little bit more complex, but even so you do not need to fetch much of the image to find the size.
18
+
19
+ FastImage does this minimal fetch for image types GIF, JPEG, PNG, TIFF, BMP, ICO, CUR, PSD, SVG and WEBP. And it doesn't rely on installing external libraries such as RMagick (which relies on ImageMagick or GraphicsMagick) or ImageScience (which relies on FreeImage).
20
+
21
+ You only need supply the uri, and FastImage will do the rest.
22
+
23
+ h2. Features
24
+
25
+ Fastimage can also read local (and other) files - anything that is not parseable as a URI will be
26
+ interpreted as a filename, and FastImage will attempt to open it with File#open.
27
+
28
+ FastImage will also automatically read from any object that responds to :read - for
29
+ instance an IO object if that is passed instead of a URI.
30
+
31
+ FastImage will follow up to 4 HTTP redirects to get the image.
32
+
33
+ FastImage will obey the http_proxy setting in your environment to route requests via a proxy. You can also pass a :proxy argument if you want to specify the proxy address in the call.
34
+
35
+ You can add a timeout to the request which will limit the request time by passing :timeout => number_of_seconds.
36
+
37
+ FastImage normally replies will nil if it encounters an error, but you can pass :raise_on_failure => true to get an exception.
38
+
39
+ FastImage also provides a reader for the content length header provided in HTTP. This may be useful to assess the file size of an image, but do not rely on it exclusively - it will not be present in chunked responses for instance.
40
+
41
+ FastImage accepts additional HTTP headers. This can be used to set a user agent or referrer which some servers require. Pass an :http_header argument to specify headers, e.g., :http_header => {'User-Agent' => 'Fake Browser'}.
42
+
43
+ FastImage can give you information about the parsed display orientation of an image with Exif data (jpeg or tiff).
44
+
45
+ h2. Security
46
+
47
+ As of v1.6.7 FastImage no longer uses openuri to open files, but directly calls File.open. But take care to sanitise the strings passed to FastImage; it will try to read from whatever is passed.
48
+
49
+ h2. Examples
50
+
51
+ <pre lang="ruby"><code>
52
+ require 'fastimage'
53
+
54
+ FastImage.size("http://stephensykes.com/images/ss.com_x.gif")
55
+ => [266, 56] # width, height
56
+ FastImage.type("http://stephensykes.com/images/pngimage")
57
+ => :png
58
+ FastImage.type("/some/local/file.gif")
59
+ => :gif
60
+ FastImage.size("http://upload.wikimedia.org/wikipedia/commons/b/b4/Mardin_1350660_1350692_33_images.jpg", :raise_on_failure=>true, :timeout=>0.1)
61
+ => FastImage::ImageFetchFailure: FastImage::ImageFetchFailure
62
+ FastImage.size("http://upload.wikimedia.org/wikipedia/commons/b/b4/Mardin_1350660_1350692_33_images.jpg", :raise_on_failure=>true, :timeout=>2.0)
63
+ => [9545, 6623]
64
+ FastImage.new("http://stephensykes.com/images/pngimage").content_length
65
+ => 432
66
+ FastImage.size("http://stephensykes.com/images/ss.com_x.gif", :http_header => {'User-Agent' => 'Fake Browser'})
67
+ => [266, 56]
68
+ FastImage.new("http://stephensykes.com/images/ExifOrientation3.jpg").orientation
69
+ => 3
70
+ </code></pre>
71
+
72
+ h2. Installation
73
+
74
+ h4. Required Ruby version
75
+
76
+ FastImage version 2.0.0 and above work with Ruby 1.9.2 and above.
77
+
78
+ FastImage version 1.9.0 was the last version that supported Ruby 1.8.7.
79
+
80
+ h4. Gem
81
+
82
+ bc. gem install fastimage
83
+
84
+ h4. Rails
85
+
86
+ Add fastimage to your Gemfile, and bundle.
87
+
88
+ bc. gem 'fastimage'
89
+
90
+ Then you're off - just use @FastImage.size()@ and @FastImage.type()@ in your code as in the examples.
91
+
92
+ h2. Documentation
93
+
94
+ "http://sdsykes.github.io/fastimage/rdoc/FastImage.html":http://sdsykes.github.io/fastimage/rdoc/FastImage.html
95
+
96
+ h2. Benchmark
97
+
98
+ It's way faster than conventional methods (for example the image_size gem) for most types of file when fetching over the wire.
99
+
100
+ <pre lang="ruby"><code>
101
+ irb> uri = "http://upload.wikimedia.org/wikipedia/commons/b/b4/Mardin_1350660_1350692_33_images.jpg"
102
+ irb> puts Benchmark.measure {open(uri, 'rb') {|fh| p ImageSize.new(fh).size}}
103
+ [9545, 6623]
104
+ 0.680000 0.250000 0.930000 ( 7.571887)
105
+
106
+ irb> puts Benchmark.measure {p FastImage.size(uri)}
107
+ [9545, 6623]
108
+ 0.010000 0.000000 0.010000 ( 0.090640)
109
+ </code></pre>
110
+
111
+ The file is fetched in about 7.5 seconds in this test (the number in brackets is the total time taken), but as FastImage doesn't need to fetch the whole thing, it completes in less than 0.1s.
112
+
113
+ You'll see similar excellent results for the other file types, except for TIFF. Unfortunately TIFFs tend to have their
114
+ metadata towards the end of the file, so it makes little difference to do a minimal fetch. The result shown below is
115
+ mostly dependent on the exact internet conditions during the test, and little to do with the library used.
116
+
117
+ <pre lang="ruby"><code>
118
+ irb> uri = "http://upload.wikimedia.org/wikipedia/commons/1/11/Shinbutsureijoushuincho.tiff"
119
+ irb> puts Benchmark.measure {open(uri, 'rb') {|fh| p ImageSize.new(fh).size}}
120
+ [1120, 1559]
121
+ 1.080000 0.370000 1.450000 ( 13.766962)
122
+
123
+ irb> puts Benchmark.measure {p FastImage.size(uri)}
124
+ [1120, 1559]
125
+ 3.490000 3.810000 7.300000 ( 11.754315)
126
+ </code></pre>
127
+
128
+ h2. Tests
129
+
130
+ You'll need to @gem install fakeweb@ and possibly also @gem install test-unit@ to be able to run the tests.
131
+
132
+ bc.. $ ruby test.rb
133
+ Run options:
134
+
135
+ # Running tests:
136
+
137
+ Finished tests in 1.033640s, 23.2189 tests/s, 82.2337 assertions/s.
138
+ 24 tests, 85 assertions, 0 failures, 0 errors, 0 skips
139
+
140
+ h2. References
141
+
142
+ * "Pennysmalls - Find jpeg dimensions fast in pure Ruby, no image library needed":http://pennysmalls.wordpress.com/2008/08/19/find-jpeg-dimensions-fast-in-pure-ruby-no-ima/
143
+ * "Antti Kupila - Getting JPG dimensions with AS3 without loading the entire file":http://www.anttikupila.com/flash/getting-jpg-dimensions-with-as3-without-loading-the-entire-file/
144
+ * "imagesize gem":https://rubygems.org/gems/imagesize
145
+ * "EXIF Reader":https://github.com/remvee/exifr
146
+
147
+ h2. FastImage in other languages
148
+
149
+ * "Python by bmuller":https://github.com/bmuller/fastimage
150
+ * "Swift by kaishin":https://github.com/kaishin/ImageScout
151
+ * "Go by rubenfonseca":https://github.com/rubenfonseca/fastimage
152
+ * "PHP by tommoor":https://github.com/tommoor/fastimage
153
+ * "Node.js by ShogunPanda":https://github.com/ShogunPanda/fastimage
154
+ * "Objective C by kylehickinson":https://github.com/kylehickinson/FastImage
155
+
156
+ h2. Licence
157
+
158
+ MIT, see file "MIT-LICENSE":MIT-LICENSE
data/Rakefile ADDED
@@ -0,0 +1,10 @@
1
+ require "bundler/gem_tasks"
2
+ require "rake/testtask"
3
+
4
+ Rake::TestTask.new(:test) do |t|
5
+ t.libs << "test"
6
+ t.libs << "lib"
7
+ t.test_files = FileList['test/**/*_test.rb']
8
+ end
9
+
10
+ task :default => :test
@@ -0,0 +1,3 @@
1
+ class FastImage
2
+ VERSION = "3.0.0"
3
+ end
data/lib/fastimage.rb ADDED
@@ -0,0 +1,573 @@
1
+ # coding: ASCII-8BIT
2
+
3
+ # FastImage finds the size or type of an image given its path. FastImage knows
4
+ # about GIF, JPEG, BMP, TIFF, ICO, CUR, PNG, PSD, SVG and WEBP files.
5
+ #
6
+ # No external libraries such as ImageMagick are used here, this is a very
7
+ # lightweight solution to finding image information.
8
+ #
9
+ # FastImage reads the file in chunks of 256 bytes until it has enough. This is
10
+ # possibly a useful bandwidth-saving feature if the file is on a network
11
+ # attached disk rather than truly local.
12
+ #
13
+ # FastImage will automatically read from any object that responds to :read - for
14
+ # instance an IO object if that is passed instead of a path.
15
+ #
16
+ # FastImage can give you information about the parsed display orientation of an image with Exif
17
+ # data (jpeg or tiff).
18
+ #
19
+ # === Examples
20
+ # require 'fastimage'
21
+ #
22
+ # FastImage.size("image.gif")
23
+ # => [266, 56]
24
+ # FastImage.type("/some/local/file.png")
25
+ # => :png
26
+ # File.open("/some/local/file.gif", "r") {|io| FastImage.type(io)}
27
+ # => :gif
28
+ # FastImage.new("ExifOrientation3.jpg").orientation
29
+ # => 3
30
+ #
31
+ # === References
32
+ # * http://www.anttikupila.com/flash/getting-jpg-dimensions-with-as3-without-loading-the-entire-file/
33
+ # * http://pennysmalls.wordpress.com/2008/08/19/find-jpeg-dimensions-fast-in-pure-ruby-no-ima/
34
+ # * https://rubygems.org/gems/imagesize
35
+ # * https://github.com/remvee/exifr
36
+ #
37
+
38
+ require 'delegate'
39
+ require 'pathname'
40
+ require 'stringio'
41
+
42
+ class FastImage
43
+ attr_reader :size, :type, :orientation, :source, :path
44
+
45
+ attr_reader :bytes_read
46
+
47
+ class FastImageException < StandardError # :nodoc:
48
+ end
49
+ class UnknownImageType < FastImageException # :nodoc:
50
+ end
51
+ class ImageFetchFailure < FastImageException # :nodoc:
52
+ end
53
+ class SizeNotFound < FastImageException # :nodoc:
54
+ end
55
+ class CannotParseImage < FastImageException # :nodoc:
56
+ end
57
+
58
+ LocalFileChunkSize = 256 unless const_defined?(:LocalFileChunkSize)
59
+
60
+ # Returns an array containing the width and height of the image. It will
61
+ # return nil if the image could not be fetched, or if the image type was not
62
+ # recognised.
63
+ #
64
+ # If you wish FastImage to raise if it cannot size the image for any reason,
65
+ # then pass :raise_on_failure => true in the options.
66
+ #
67
+ # FastImage knows about GIF, JPEG, BMP, TIFF, ICO, CUR, PNG, PSD, SVG and WEBP
68
+ # files.
69
+ #
70
+ # === Example
71
+ #
72
+ # require 'fastimage'
73
+ #
74
+ # FastImage.size("example.gif")
75
+ # => [266, 56]
76
+ # FastImage.size("does_not_exist")
77
+ # => nil
78
+ # FastImage.size("does_not_exist", :raise_on_failure => true)
79
+ # => raises FastImage::ImageFetchFailure
80
+ # FastImage.size("example.png", :raise_on_failure => true)
81
+ # => [16, 16]
82
+ # FastImage.size("app.icns", :raise_on_failure=>true)
83
+ # => raises FastImage::UnknownImageType
84
+ # FastImage.size("faulty.jpg", :raise_on_failure=>true)
85
+ # => raises FastImage::SizeNotFound
86
+ #
87
+ # === Supported options
88
+ # [:raise_on_failure]
89
+ # If set to true causes an exception to be raised if the image size cannot be found for any reason.
90
+ #
91
+ def self.size(source, options={})
92
+ new(source, options).size
93
+ end
94
+
95
+ # Returns an symbol indicating the image type located at source. It will
96
+ # return nil if the image could not be fetched, or if the image type was not
97
+ # recognised.
98
+ #
99
+ # If you wish FastImage to raise if it cannot find the type of the image for
100
+ # any reason, then pass :raise_on_failure => true in the options.
101
+ #
102
+ # === Example
103
+ #
104
+ # require 'fastimage'
105
+ #
106
+ # FastImage.type("example.gif")
107
+ # => :gif
108
+ # FastImage.type("image.png")
109
+ # => :png
110
+ # FastImage.type("photo.jpg")
111
+ # => :jpeg
112
+ # FastImage.type("lena512.bmp")
113
+ # => :bmp
114
+ # FastImage.type("does_not_exist")
115
+ # => nil
116
+ # File.open("file.gif", "r") {|io| FastImage.type(io)}
117
+ # => :gif
118
+ # FastImage.type("test/fixtures/test.tiff")
119
+ # => :tiff
120
+ # FastImage.type("test/fixtures/test.psd")
121
+ # => :psd
122
+ #
123
+ # === Supported options
124
+ # [:raise_on_failure]
125
+ # If set to true causes an exception to be raised if the image type cannot be found for any reason.
126
+ #
127
+ def self.type(source, options={})
128
+ new(source, options.merge(:type_only=>true)).type
129
+ end
130
+
131
+ def initialize(source, options={})
132
+ @source = source
133
+ @options = {
134
+ :type_only => false,
135
+ :raise_on_failure => false,
136
+ :proxy => nil,
137
+ :http_header => {}
138
+ }.merge(options)
139
+
140
+ @property = @options[:type_only] ? :type : :size
141
+
142
+ if @source.respond_to?(:read)
143
+ @path = @source.path if @source.respond_to? :path
144
+ fetch_using_read
145
+ else
146
+ @path = @source
147
+ fetch_using_file_open
148
+ end
149
+
150
+ raise SizeNotFound if @options[:raise_on_failure] && @property == :size && !@size
151
+
152
+ rescue ImageFetchFailure, EOFError, Errno::ENOENT
153
+ raise ImageFetchFailure if @options[:raise_on_failure]
154
+ rescue UnknownImageType
155
+ raise UnknownImageType if @options[:raise_on_failure]
156
+ rescue CannotParseImage
157
+ if @options[:raise_on_failure]
158
+ if @property == :size
159
+ raise SizeNotFound
160
+ else
161
+ raise ImageFetchFailure
162
+ end
163
+ end
164
+
165
+ ensure
166
+ source.rewind if source.respond_to?(:rewind)
167
+ end
168
+
169
+ private
170
+
171
+ def fetch_using_read(readable = @source)
172
+ # Pathnames respond to read, but always return the first
173
+ # chunk of the file unlike an IO (even though the
174
+ # docuementation for it refers to IO). Need to supply
175
+ # an offset in this case.
176
+ if readable.is_a?(Pathname)
177
+ read_fiber = Fiber.new do
178
+ offset = 0
179
+ while str = readable.read(LocalFileChunkSize, offset)
180
+ Fiber.yield str
181
+ offset += LocalFileChunkSize
182
+ end
183
+ end
184
+ else
185
+ read_fiber = Fiber.new do
186
+ while str = readable.read(LocalFileChunkSize)
187
+ Fiber.yield str
188
+ end
189
+ end
190
+ end
191
+
192
+ parse_packets FiberStream.new(read_fiber)
193
+ end
194
+
195
+ def fetch_using_file_open
196
+ File.open(@source) do |file|
197
+ fetch_using_read(file)
198
+ end
199
+ end
200
+
201
+ def parse_packets(stream)
202
+ @stream = stream
203
+
204
+ begin
205
+ result = send("parse_#{@property}")
206
+ if result
207
+ # extract exif orientation if it was found
208
+ if @property == :size && result.size == 3
209
+ @orientation = result.pop
210
+ else
211
+ @orientation = 1
212
+ end
213
+
214
+ instance_variable_set("@#{@property}", result)
215
+ else
216
+ raise CannotParseImage
217
+ end
218
+ rescue FiberError
219
+ raise CannotParseImage
220
+ end
221
+ end
222
+
223
+ def parse_size
224
+ @type = parse_type unless @type
225
+ send("parse_size_for_#{@type}")
226
+ end
227
+
228
+ module StreamUtil # :nodoc:
229
+ def read_byte
230
+ read(1)[0].ord
231
+ end
232
+
233
+ def read_int
234
+ read(2).unpack('n')[0]
235
+ end
236
+
237
+ def read_string_int
238
+ value = []
239
+ while read(1) =~ /(\d)/
240
+ value << $1
241
+ end
242
+ value.join.to_i
243
+ end
244
+ end
245
+
246
+ class FiberStream # :nodoc:
247
+ include StreamUtil
248
+ attr_reader :pos
249
+
250
+ def initialize(read_fiber)
251
+ @read_fiber = read_fiber
252
+ @pos = 0
253
+ @strpos = 0
254
+ @str = ''
255
+ end
256
+
257
+ def peek(n)
258
+ while @strpos + n - 1 >= @str.size
259
+ unused_str = @str[@strpos..-1]
260
+ new_string = @read_fiber.resume
261
+ raise CannotParseImage if !new_string
262
+
263
+ # we are dealing with bytes here, so force the encoding
264
+ new_string.force_encoding("ASCII-8BIT") if String.method_defined? :force_encoding
265
+
266
+ @str = unused_str + new_string
267
+ @strpos = 0
268
+ end
269
+
270
+ @str[@strpos..(@strpos + n - 1)]
271
+ end
272
+
273
+ def read(n)
274
+ result = peek(n)
275
+ @strpos += n
276
+ @pos += n
277
+ result
278
+ end
279
+ end
280
+
281
+ class IOStream < SimpleDelegator # :nodoc:
282
+ include StreamUtil
283
+ end
284
+
285
+ def parse_type
286
+ case @stream.peek(2)
287
+ when "BM"
288
+ :bmp
289
+ when "GI"
290
+ :gif
291
+ when 0xff.chr + 0xd8.chr
292
+ :jpeg
293
+ when 0x89.chr + "P"
294
+ :png
295
+ when "II", "MM"
296
+ :tiff
297
+ when '8B'
298
+ :psd
299
+ when "\0\0"
300
+ # ico has either a 1 (for ico format) or 2 (for cursor) at offset 3
301
+ case @stream.peek(3).bytes.to_a.last
302
+ when 1 then :ico
303
+ when 2 then :cur
304
+ end
305
+ when "RI"
306
+ if @stream.peek(12)[8..11] == "WEBP"
307
+ :webp
308
+ else
309
+ raise UnknownImageType
310
+ end
311
+ when "<s", "<?"
312
+ # Does not handle UTF-16 or other multi-byte encodings
313
+ if @stream.peek(64).include?("<svg")
314
+ :svg
315
+ else
316
+ raise UnknownImageType
317
+ end
318
+ else
319
+ raise UnknownImageType
320
+ end
321
+ end
322
+
323
+ def parse_size_for_ico
324
+ icons = @stream.read(6)[4..5].unpack('v').first
325
+ sizes = icons.times.map { @stream.read(16).unpack('C2').map { |x| x == 0 ? 256 : x } }.sort_by { |w,h| w * h }
326
+ sizes.last
327
+ end
328
+ alias_method :parse_size_for_cur, :parse_size_for_ico
329
+
330
+ def parse_size_for_gif
331
+ @stream.read(11)[6..10].unpack('SS')
332
+ end
333
+
334
+ def parse_size_for_png
335
+ @stream.read(25)[16..24].unpack('NN')
336
+ end
337
+
338
+ def parse_size_for_jpeg
339
+ loop do
340
+ @state = case @state
341
+ when nil
342
+ @stream.read(2)
343
+ :started
344
+ when :started
345
+ @stream.read_byte == 0xFF ? :sof : :started
346
+ when :sof
347
+ case @stream.read_byte
348
+ when 0xe1 # APP1
349
+ skip_chars = @stream.read_int - 2
350
+ data = @stream.read(skip_chars)
351
+ io = StringIO.new(data)
352
+ if io.read(4) == "Exif"
353
+ io.read(2)
354
+ @exif = Exif.new(IOStream.new(io)) rescue nil
355
+ end
356
+ :started
357
+ when 0xe0..0xef
358
+ :skipframe
359
+ when 0xC0..0xC3, 0xC5..0xC7, 0xC9..0xCB, 0xCD..0xCF
360
+ :readsize
361
+ when 0xFF
362
+ :sof
363
+ else
364
+ :skipframe
365
+ end
366
+ when :skipframe
367
+ skip_chars = @stream.read_int - 2
368
+ @stream.read(skip_chars)
369
+ :started
370
+ when :readsize
371
+ @stream.read(3)
372
+ height = @stream.read_int
373
+ width = @stream.read_int
374
+ width, height = height, width if @exif && @exif.rotated?
375
+ return [width, height, @exif ? @exif.orientation : 1]
376
+ end
377
+ end
378
+ end
379
+
380
+ def parse_size_for_bmp
381
+ d = @stream.read(32)[14..28]
382
+ header = d.unpack("C")[0]
383
+
384
+ result = if header == 40
385
+ d[4..-1].unpack('l<l<')
386
+ else
387
+ d[4..8].unpack('SS')
388
+ end
389
+
390
+ # ImageHeight is expressed in pixels. The absolute value is necessary because ImageHeight can be negative
391
+ [result.first, result.last.abs]
392
+ end
393
+
394
+ def parse_size_for_webp
395
+ vp8 = @stream.read(16)[12..15]
396
+ @stream.read(4).unpack("V") # len
397
+ case vp8
398
+ when "VP8 "
399
+ parse_size_vp8
400
+ when "VP8L"
401
+ parse_size_vp8l
402
+ when "VP8X"
403
+ parse_size_vp8x
404
+ else
405
+ nil
406
+ end
407
+ end
408
+
409
+ def parse_size_vp8
410
+ w, h = @stream.read(10).unpack("@6vv")
411
+ [w & 0x3fff, h & 0x3fff]
412
+ end
413
+
414
+ def parse_size_vp8l
415
+ @stream.read(1) # 0x2f
416
+ b1, b2, b3, b4 = @stream.read(4).bytes.to_a
417
+ [1 + (((b2 & 0x3f) << 8) | b1), 1 + (((b4 & 0xF) << 10) | (b3 << 2) | ((b2 & 0xC0) >> 6))]
418
+ end
419
+
420
+ def parse_size_vp8x
421
+ flags = @stream.read(4).unpack("C")[0]
422
+ b1, b2, b3, b4, b5, b6 = @stream.read(6).unpack("CCCCCC")
423
+ width, height = 1 + b1 + (b2 << 8) + (b3 << 16), 1 + b4 + (b5 << 8) + (b6 << 16)
424
+
425
+ if flags & 8 > 0 # exif
426
+ # parse exif for orientation
427
+ # TODO: find or create test images for this
428
+ end
429
+
430
+ return [width, height]
431
+ end
432
+
433
+ class Exif # :nodoc:
434
+ attr_reader :width, :height, :orientation
435
+
436
+ def initialize(stream)
437
+ @stream = stream
438
+ parse_exif
439
+ end
440
+
441
+ def rotated?
442
+ @orientation >= 5
443
+ end
444
+
445
+ private
446
+
447
+ def get_exif_byte_order
448
+ byte_order = @stream.read(2)
449
+ case byte_order
450
+ when 'II'
451
+ @short, @long = 'v', 'V'
452
+ when 'MM'
453
+ @short, @long = 'n', 'N'
454
+ else
455
+ raise CannotParseImage
456
+ end
457
+ end
458
+
459
+ def parse_exif_ifd
460
+ tag_count = @stream.read(2).unpack(@short)[0]
461
+ tag_count.downto(1) do
462
+ type = @stream.read(2).unpack(@short)[0]
463
+ @stream.read(6)
464
+ data = @stream.read(2).unpack(@short)[0]
465
+ case type
466
+ when 0x0100 # image width
467
+ @width = data
468
+ when 0x0101 # image height
469
+ @height = data
470
+ when 0x0112 # orientation
471
+ @orientation = data
472
+ end
473
+ if @width && @height && @orientation
474
+ return # no need to parse more
475
+ end
476
+ @stream.read(2)
477
+ end
478
+ end
479
+
480
+ def parse_exif
481
+ @start_byte = @stream.pos
482
+
483
+ get_exif_byte_order
484
+
485
+ @stream.read(2) # 42
486
+
487
+ offset = @stream.read(4).unpack(@long)[0]
488
+ @stream.read(offset - 8)
489
+
490
+ parse_exif_ifd
491
+
492
+ @orientation ||= 1
493
+ end
494
+
495
+ end
496
+
497
+ def parse_size_for_tiff
498
+ exif = Exif.new(@stream)
499
+ if exif.rotated?
500
+ [exif.height, exif.width, exif.orientation]
501
+ else
502
+ [exif.width, exif.height, exif.orientation]
503
+ end
504
+ end
505
+
506
+ def parse_size_for_psd
507
+ @stream.read(26).unpack("x14NN").reverse
508
+ end
509
+
510
+ class Svg # :nodoc:
511
+ def initialize(stream)
512
+ @stream = stream
513
+ parse_svg
514
+ end
515
+
516
+ def width_and_height
517
+ if @width && @height
518
+ [@width, @height]
519
+ elsif @width && @ratio
520
+ [@width, @width / @ratio]
521
+ elsif @height && @ratio
522
+ [@height * @ratio, @height]
523
+ end
524
+ end
525
+
526
+ private
527
+
528
+ def parse_svg
529
+ attr_name = []
530
+ state = nil
531
+
532
+ while (char = @stream.read(1)) && state != :stop do
533
+ case char
534
+ when "="
535
+ if attr_name.join =~ /width/i
536
+ @stream.read(1)
537
+ @width = @stream.read_string_int
538
+ return if @height
539
+ elsif attr_name.join =~ /height/i
540
+ @stream.read(1)
541
+ @height = @stream.read_string_int
542
+ return if @width
543
+ elsif attr_name.join =~ /viewbox/i
544
+ values = attr_value.split(/\s/)
545
+ @ratio = values[2].to_f / values[3].to_f
546
+ end
547
+ when /\w/
548
+ attr_name << char
549
+ when ">"
550
+ state = :stop if state == :started
551
+ else
552
+ state = :started if attr_name.join == "svg"
553
+ attr_name.clear
554
+ end
555
+ end
556
+ end
557
+
558
+ def attr_value
559
+ @stream.read(1)
560
+
561
+ value = []
562
+ while @stream.read(1) =~ /([^"])/
563
+ value << $1
564
+ end
565
+ value.join
566
+ end
567
+ end
568
+
569
+ def parse_size_for_svg
570
+ svg = Svg.new(@stream)
571
+ svg.width_and_height
572
+ end
573
+ end
@@ -0,0 +1,24 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path("../lib", __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require "fastimage/version"
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{local-fastimage}
8
+ s.version = FastImage::VERSION
9
+ s.summary = %q{FastImage - Image info fast}
10
+ s.description = %q{FastImage finds the size or type of an image given its uri by fetching as little as needed.}
11
+
12
+ s.authors = ["Stephen Sykes"]
13
+ s.email = %q{sdsykes@gmail.com}
14
+ s.homepage = %q{http://github.com/sdsykes/fastimage}
15
+
16
+ s.license = "MIT"
17
+
18
+ s.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
19
+ s.require_paths = ["lib"]
20
+
21
+ s.add_development_dependency "bundler", "~> 1.12"
22
+ s.add_development_dependency "rake", "~> 10.0"
23
+ s.add_development_dependency "minitest", "~> 5.0"
24
+ end
metadata ADDED
@@ -0,0 +1,97 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: local-fastimage
3
+ version: !ruby/object:Gem::Version
4
+ version: 3.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Stephen Sykes
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2016-06-02 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.12'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.12'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '10.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '10.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: minitest
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '5.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '5.0'
55
+ description: FastImage finds the size or type of an image given its uri by fetching
56
+ as little as needed.
57
+ email: sdsykes@gmail.com
58
+ executables: []
59
+ extensions: []
60
+ extra_rdoc_files: []
61
+ files:
62
+ - ".gitignore"
63
+ - ".travis.yml"
64
+ - CHANGELOG.md
65
+ - Gemfile
66
+ - MIT-LICENSE
67
+ - README.md
68
+ - README.textile
69
+ - Rakefile
70
+ - lib/fastimage.rb
71
+ - lib/fastimage/version.rb
72
+ - local-fastimage.gemspec
73
+ homepage: http://github.com/sdsykes/fastimage
74
+ licenses:
75
+ - MIT
76
+ metadata: {}
77
+ post_install_message:
78
+ rdoc_options: []
79
+ require_paths:
80
+ - lib
81
+ required_ruby_version: !ruby/object:Gem::Requirement
82
+ requirements:
83
+ - - ">="
84
+ - !ruby/object:Gem::Version
85
+ version: '0'
86
+ required_rubygems_version: !ruby/object:Gem::Requirement
87
+ requirements:
88
+ - - ">="
89
+ - !ruby/object:Gem::Version
90
+ version: '0'
91
+ requirements: []
92
+ rubyforge_project:
93
+ rubygems_version: 2.4.8
94
+ signing_key:
95
+ specification_version: 4
96
+ summary: FastImage - Image info fast
97
+ test_files: []