em-fastimage 0.1

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.
@@ -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.
@@ -0,0 +1,132 @@
1
+ !https://travis-ci.org/sdsykes/fastimage.png?branch=master!:https://travis-ci.org/sdsykes/fastimage
2
+
3
+ h1. FastImage
4
+
5
+ h4. FastImage finds the size or type of an image given its uri by fetching as little as needed
6
+
7
+ h2. The problem
8
+
9
+ 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.
10
+
11
+ But the image is not locally stored - it's on another asset server, or in the cloud - at Amazon S3 for example.
12
+
13
+ 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), 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.
14
+
15
+ FastImage does this minimal fetch for image types GIF, JPEG, PNG, TIFF, BMP and PSD. And it doesn't rely on installing external libraries such as RMagick (which relies on ImageMagick or GraphicsMagick) or ImageScience (which relies on FreeImage).
16
+
17
+ You only need supply the uri, and FastImage will do the rest.
18
+
19
+ h2. Features
20
+
21
+ Fastimage can also read local (and other) files, and uses the Addressable library to do so.
22
+
23
+ FastImage will automatically read from any object that responds to :read - for
24
+ instance an IO object if that is passed instead of a URI.
25
+
26
+ FastImage will follow up to 4 HTTP redirects to get the image.
27
+
28
+ FastImage will obey the http_proxy setting in your environment to route requests via a proxy.
29
+
30
+ You can add a timeout to the request which will limit the request time by passing :timeout => number_of_seconds.
31
+
32
+ FastImage normally replies will nil if it encounters an error, but you can pass :raise_on_failure => true to get an exception.
33
+
34
+ h2. Examples
35
+
36
+ <pre lang="ruby"><code>
37
+ require 'fastimage'
38
+
39
+ FastImage.size("http://stephensykes.com/images/ss.com_x.gif")
40
+ => [266, 56] # width, height
41
+ FastImage.type("http://stephensykes.com/images/pngimage")
42
+ => :png
43
+ FastImage.type("/some/local/file.gif")
44
+ => :gif
45
+ FastImage.size("http://upload.wikimedia.org/wikipedia/commons/b/b4/Mardin_1350660_1350692_33_images.jpg", :raise_on_failure=>true, :timeout=>0.1)
46
+ => FastImage::ImageFetchFailure: FastImage::ImageFetchFailure
47
+ FastImage.size("http://upload.wikimedia.org/wikipedia/commons/b/b4/Mardin_1350660_1350692_33_images.jpg", :raise_on_failure=>true, :timeout=>2.0)
48
+ => [9545, 6623]
49
+ </code></pre>
50
+
51
+ h2. Installation
52
+
53
+ h4. Gem
54
+
55
+ bc. gem install fastimage
56
+
57
+ h4. Rails
58
+
59
+ Add fastimage to your Gemfile, and bundle.
60
+
61
+ Then you're off - just use @FastImage.size()@ and @FastImage.type()@ in your code as in the examples.
62
+
63
+ h2. Documentation
64
+
65
+ "http://sdsykes.github.io/fastimage/rdoc/FastImage.html":http://sdsykes.github.io/fastimage/rdoc/FastImage.html
66
+
67
+ h2. Benchmark
68
+
69
+ It's way faster than conventional methods (for example the image_size gem) for most types of file when fetching over the wire.
70
+
71
+ <pre lang="ruby"><code>
72
+ irb> uri = "http://upload.wikimedia.org/wikipedia/commons/b/b4/Mardin_1350660_1350692_33_images.jpg"
73
+ irb> puts Benchmark.measure {open(uri, 'rb') {|fh| p ImageSize.new(fh).size}}
74
+ [9545, 6623]
75
+ 0.680000 0.250000 0.930000 ( 7.571887)
76
+
77
+ irb> puts Benchmark.measure {p FastImage.size(uri)}
78
+ [9545, 6623]
79
+ 0.010000 0.000000 0.010000 ( 0.090640)
80
+ </code></pre>
81
+
82
+ 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.
83
+
84
+ You'll see similar excellent results for the other file types, except for TIFF. Unfortunately TIFFs tend to have their
85
+ metadata towards the end of the file, so it makes little difference to do a minimal fetch. The result shown below is
86
+ mostly dependent on the exact internet conditions during the test, and little to do with the library used.
87
+
88
+ <pre lang="ruby"><code>
89
+ irb> uri = "http://upload.wikimedia.org/wikipedia/commons/1/11/Shinbutsureijoushuincho.tiff"
90
+ irb> puts Benchmark.measure {open(uri, 'rb') {|fh| p ImageSize.new(fh).size}}
91
+ [1120, 1559]
92
+ 1.080000 0.370000 1.450000 ( 13.766962)
93
+
94
+ irb> puts Benchmark.measure {p FastImage.size(uri)}
95
+ [1120, 1559]
96
+ 3.490000 3.810000 7.300000 ( 11.754315)
97
+ </code></pre>
98
+
99
+ h2. Tests
100
+
101
+ You'll need to @gem install fakeweb@ to be able to run the tests.
102
+
103
+ bc.. $ ruby test.rb
104
+ Run options:
105
+
106
+ # Running tests:
107
+
108
+ Finished tests in 1.033640s, 23.2189 tests/s, 82.2337 assertions/s.
109
+ 24 tests, 85 assertions, 0 failures, 0 errors, 0 skips
110
+
111
+ h2. References
112
+
113
+ * "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/
114
+ * "DZone - Determine Image Size":http://snippets.dzone.com/posts/show/805
115
+ * "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/
116
+ * "imagesize gem documentation":http://imagesize.rubyforge.org/
117
+ * "EXIF Reader":https://github.com/remvee/exifr
118
+
119
+ h2. Licence
120
+
121
+ MIT, see file "MIT-LICENSE":MIT-LICENSE
122
+
123
+ h2. Contributors
124
+
125
+ Pull requests and suggestions are always welcome. Thanks to all the contributors!
126
+
127
+ * @felixbuenemann
128
+ * @speedmax
129
+ * @sebastianludwig
130
+ * @benjaminjackson
131
+ * @muffinista
132
+ * @marcandre
@@ -0,0 +1,523 @@
1
+ # coding: ASCII-8BIT
2
+
3
+ # FastImage finds the size or type of an image given its uri.
4
+ # It is careful to only fetch and parse as much of the image as is needed to determine the result.
5
+ # It does this by using a feature of Net::HTTP that yields strings from the resource being fetched
6
+ # as soon as the packets arrive.
7
+ #
8
+ # No external libraries such as ImageMagick are used here, this is a very lightweight solution to
9
+ # finding image information.
10
+ #
11
+ # FastImage knows about GIF, JPEG, BMP, TIFF, PNG and PSD files.
12
+ #
13
+ # FastImage can also read files from the local filesystem by supplying the path instead of a uri.
14
+ # In this case FastImage uses the Addressable library to read the file in chunks of 256 bytes until
15
+ # it has enough. This is possibly a useful bandwidth-saving feature if the file is on a network
16
+ # attached disk rather than truly local.
17
+ #
18
+ # FastImage will automatically read from any object that responds to :read - for
19
+ # instance an IO object if that is passed instead of a URI.
20
+ #
21
+ # FastImage will follow up to 4 HTTP redirects to get the image.
22
+ #
23
+ # === Examples
24
+ # require 'fastimage'
25
+ #
26
+ # FastImage.size("http://stephensykes.com/images/ss.com_x.gif")
27
+ # => [266, 56]
28
+ # FastImage.type("http://stephensykes.com/images/pngimage")
29
+ # => :png
30
+ # FastImage.type("/some/local/file.gif")
31
+ # => :gif
32
+ # File.open("/some/local/file.gif", "r") {|io| FastImage.type(io)}
33
+ # => :gif
34
+ #
35
+ # === References
36
+ # * http://snippets.dzone.com/posts/show/805
37
+ # * http://www.anttikupila.com/flash/getting-jpg-dimensions-with-as3-without-loading-the-entire-file/
38
+ # * http://pennysmalls.wordpress.com/2008/08/19/find-jpeg-dimensions-fast-in-pure-ruby-no-ima/
39
+ # * http://imagesize.rubyforge.org/
40
+ # * https://github.com/remvee/exifr
41
+ #
42
+
43
+ # require 'net/https'
44
+ require 'em-http-request'
45
+ require 'addressable/uri'
46
+ require 'fastimage/fbr.rb'
47
+ require 'delegate'
48
+ require 'pathname'
49
+
50
+ class EM::FastImage
51
+ include EM::Deferrable
52
+
53
+ attr_reader :size, :type
54
+
55
+ attr_reader :bytes_read
56
+
57
+ class FastImageException < StandardError # :nodoc:
58
+ end
59
+ class MoreCharsNeeded < FastImageException # :nodoc:
60
+ end
61
+ class UnknownImageType < FastImageException # :nodoc:
62
+ end
63
+ class ImageFetchFailure < FastImageException # :nodoc:
64
+ end
65
+ class SizeNotFound < FastImageException # :nodoc:
66
+ end
67
+ class CannotParseImage < FastImageException # :nodoc:
68
+ end
69
+
70
+ DefaultTimeout = 2
71
+
72
+ LocalFileChunkSize = 256
73
+
74
+ # Returns an array containing the width and height of the image.
75
+ # It will return nil if the image could not be fetched, or if the image type was not recognised.
76
+ #
77
+ # By default there is a timeout of 2 seconds for opening and reading from a remote server.
78
+ # This can be changed by passing a :timeout => number_of_seconds in the options.
79
+ #
80
+ # If you wish FastImage to raise if it cannot size the image for any reason, then pass
81
+ # :raise_on_failure => true in the options.
82
+ #
83
+ # FastImage knows about GIF, JPEG, BMP, TIFF, PNG and PSD files.
84
+ #
85
+ # === Example
86
+ #
87
+ # require 'fastimage'
88
+ #
89
+ # FastImage.size("http://stephensykes.com/images/ss.com_x.gif")
90
+ # => [266, 56]
91
+ # FastImage.size("http://stephensykes.com/images/pngimage")
92
+ # => [16, 16]
93
+ # FastImage.size("http://farm4.static.flickr.com/3023/3047236863_9dce98b836.jpg")
94
+ # => [500, 375]
95
+ # FastImage.size("http://www-ece.rice.edu/~wakin/images/lena512.bmp")
96
+ # => [512, 512]
97
+ # FastImage.size("test/fixtures/test.jpg")
98
+ # => [882, 470]
99
+ # FastImage.size("http://pennysmalls.com/does_not_exist")
100
+ # => nil
101
+ # FastImage.size("http://pennysmalls.com/does_not_exist", :raise_on_failure=>true)
102
+ # => raises FastImage::ImageFetchFailure
103
+ # FastImage.size("http://stephensykes.com/favicon.ico", :raise_on_failure=>true)
104
+ # => raises FastImage::UnknownImageType
105
+ # FastImage.size("http://stephensykes.com/favicon.ico", :raise_on_failure=>true, :timeout=>0.01)
106
+ # => raises FastImage::ImageFetchFailure
107
+ # FastImage.size("http://stephensykes.com/images/faulty.jpg", :raise_on_failure=>true)
108
+ # => raises FastImage::SizeNotFound
109
+ #
110
+ # === Supported options
111
+ # [:timeout]
112
+ # Overrides the default timeout of 2 seconds. Applies both to reading from and opening the http connection.
113
+ # [:raise_on_failure]
114
+ # If set to true causes an exception to be raised if the image size cannot be found for any reason.
115
+ #
116
+ def self.size(uri, options={})
117
+ (o = new(uri, options)).size
118
+ o
119
+ end
120
+
121
+ # Returns an symbol indicating the image type fetched from a uri.
122
+ # It will return nil if the image could not be fetched, or if the image type was not recognised.
123
+ #
124
+ # By default there is a timeout of 2 seconds for opening and reading from a remote server.
125
+ # This can be changed by passing a :timeout => number_of_seconds in the options.
126
+ #
127
+ # If you wish FastImage to raise if it cannot find the type of the image for any reason, then pass
128
+ # :raise_on_failure => true in the options.
129
+ #
130
+ # === Example
131
+ #
132
+ # require 'fastimage'
133
+ #
134
+ # FastImage.type("http://stephensykes.com/images/ss.com_x.gif")
135
+ # => :gif
136
+ # FastImage.type("http://stephensykes.com/images/pngimage")
137
+ # => :png
138
+ # FastImage.type("http://farm4.static.flickr.com/3023/3047236863_9dce98b836.jpg")
139
+ # => :jpeg
140
+ # FastImage.type("http://www-ece.rice.edu/~wakin/images/lena512.bmp")
141
+ # => :bmp
142
+ # FastImage.type("test/fixtures/test.jpg")
143
+ # => :jpeg
144
+ # FastImage.type("http://pennysmalls.com/does_not_exist")
145
+ # => nil
146
+ # File.open("/some/local/file.gif", "r") {|io| FastImage.type(io)}
147
+ # => :gif
148
+ # FastImage.type("test/fixtures/test.tiff")
149
+ # => :tiff
150
+ # FastImage.type("test/fixtures/test.psd")
151
+ # => :psd
152
+ #
153
+ # === Supported options
154
+ # [:timeout]
155
+ # Overrides the default timeout of 2 seconds. Applies both to reading from and opening the http connection.
156
+ # [:raise_on_failure]
157
+ # If set to true causes an exception to be raised if the image type cannot be found for any reason.
158
+ #
159
+ def self.type(uri, options={})
160
+ new(uri, options.merge(:type_only=>true)).type
161
+ end
162
+
163
+ def initialize(uri, options={})
164
+ @property = options[:type_only] ? :type : :size
165
+ @timeout = options[:timeout] || DefaultTimeout
166
+ @uri = uri
167
+
168
+ if uri.respond_to?(:read)
169
+ fetch_using_read(uri)
170
+ else
171
+ begin
172
+ @parsed_uri = Addressable::URI.parse(uri)
173
+ rescue Addressable::URI::InvalidURIError
174
+ # fetch_using_open_uri
175
+ else
176
+ if @parsed_uri.scheme == "http" || @parsed_uri.scheme == "https"
177
+ fetch_using_http
178
+ else
179
+ # fetch_using_open_uri
180
+ end
181
+ end
182
+ end
183
+
184
+ uri.rewind if uri.respond_to?(:rewind)
185
+
186
+ raise SizeNotFound if options[:raise_on_failure] && @property == :size && !@size
187
+
188
+ rescue Timeout::Error, SocketError, Errno::ECONNREFUSED, Errno::EHOSTUNREACH, Errno::ECONNRESET,
189
+ ImageFetchFailure, EOFError, Errno::ENOENT
190
+ raise ImageFetchFailure if options[:raise_on_failure]
191
+ rescue NoMethodError # 1.8.7p248 can raise this due to a net/http bug
192
+ raise ImageFetchFailure if options[:raise_on_failure]
193
+ rescue UnknownImageType
194
+ raise UnknownImageType if options[:raise_on_failure]
195
+ rescue CannotParseImage
196
+ if options[:raise_on_failure]
197
+ if @property == :size
198
+ raise SizeNotFound
199
+ else
200
+ raise ImageFetchFailure
201
+ end
202
+ end
203
+
204
+ end
205
+
206
+ private
207
+
208
+ def fetch_using_http
209
+ @redirect_count = 0
210
+
211
+ fetch_using_http_from_parsed_uri
212
+ end
213
+
214
+ def fetch_using_http_from_parsed_uri
215
+ setup_http
216
+ @http.get.callback do |res|
217
+ # begin
218
+ # newly_parsed_uri = Addressable::URI.parse(res['Location'])
219
+ # # The new location may be relative - check for that
220
+ # if newly_parsed_uri.scheme != "http" && newly_parsed_uri.scheme != "https"
221
+ # @parsed_uri.path = res['Location']
222
+ # else
223
+ # @parsed_uri = newly_parsed_uri
224
+ # end
225
+ # rescue Addressable::URI::InvalidURIError
226
+ # else
227
+ # fetch_using_http_from_parsed_uri
228
+ # break
229
+ # end
230
+ #
231
+
232
+ read_fiber = Fiber.new do
233
+ res.response do |str|
234
+ Fiber.yield str
235
+ end
236
+ end
237
+ succeed(parse_packets FiberStream.new(read_fiber))
238
+
239
+ end.errback do |err|
240
+ fail(err)
241
+ end
242
+ end
243
+
244
+ def proxy_uri
245
+ begin
246
+ proxy = ENV['http_proxy'] && ENV['http_proxy'] != "" ? Addressable::URI.parse(ENV['http_proxy']) : nil
247
+ rescue Addressable::URI::InvalidURIError
248
+ proxy = nil
249
+ end
250
+ proxy
251
+ end
252
+
253
+ def setup_http
254
+ proxy = proxy_uri
255
+ real_uri = "#{@parsed_uri.host}"# :#{@parsed_uri.inferred_port}"
256
+ conn_opts = {}
257
+ if proxy
258
+ conn_opts[:proxy] = { host: proxy.host, port: proxy.port }
259
+ end
260
+ if @parsed_uri.scheme == 'https'
261
+ conn_opts[:ssl] = {} # I don't know how to put the keys in here
262
+ end
263
+
264
+ @http = EM::HttpRequest.new(@parsed_uri, conn_opts)
265
+
266
+ end
267
+
268
+ def fetch_using_read(readable)
269
+ # Pathnames respond to read, but always return the first
270
+ # chunk of the file unlike an IO (even though the
271
+ # docuementation for it refers to IO). Need to supply
272
+ # an offset in this case.
273
+ if readable.is_a?(Pathname)
274
+ read_fiber = Fiber.new do
275
+ offset = 0
276
+ while str = readable.read(LocalFileChunkSize, offset)
277
+ Fiber.yield str
278
+ offset += LocalFileChunkSize
279
+ end
280
+ end
281
+ else
282
+ read_fiber = Fiber.new do
283
+ while str = readable.read(LocalFileChunkSize)
284
+ Fiber.yield str
285
+ end
286
+ end
287
+ end
288
+
289
+ succeed(parse_packets FiberStream.new(read_fiber))
290
+ end
291
+
292
+ def fetch_using_open_uri
293
+ open(@uri) do |s|
294
+ fetch_using_read(s)
295
+ end
296
+ end
297
+
298
+ def parse_packets(stream)
299
+ @stream = stream
300
+
301
+ begin
302
+ result = send("parse_#{@property}")
303
+ if result
304
+ instance_variable_set("@#{@property}", result)
305
+ else
306
+ raise CannotParseImage
307
+ end
308
+ rescue FiberError
309
+ raise CannotParseImage
310
+ end
311
+ end
312
+
313
+ def parse_size
314
+ @type = parse_type unless @type
315
+ send("parse_size_for_#{@type}")
316
+ end
317
+
318
+ module StreamUtil # :nodoc:
319
+ def read_byte
320
+ read(1)[0].ord
321
+ end
322
+
323
+ def read_int
324
+ read(2).unpack('n')[0]
325
+ end
326
+ end
327
+
328
+ class FiberStream # :nodoc:
329
+ include StreamUtil
330
+ attr_reader :pos
331
+
332
+ def initialize(read_fiber)
333
+ @read_fiber = read_fiber
334
+ @pos = 0
335
+ @strpos = 0
336
+ @str = ''
337
+ end
338
+
339
+ def peek(n)
340
+ while @strpos + n - 1 >= @str.size
341
+ unused_str = @str[@strpos..-1]
342
+ new_string = @read_fiber.resume
343
+ raise CannotParseImage if !new_string
344
+
345
+ # we are dealing with bytes here, so force the encoding
346
+ new_string.force_encoding("ASCII-8BIT") if String.method_defined? :force_encoding
347
+
348
+ @str = unused_str + new_string
349
+ @strpos = 0
350
+ end
351
+
352
+ result = @str[@strpos..(@strpos + n - 1)]
353
+ end
354
+
355
+ def read(n)
356
+ result = peek(n)
357
+ @strpos += n
358
+ @pos += n
359
+ result
360
+ end
361
+ end
362
+
363
+ class IOStream < SimpleDelegator # :nodoc:
364
+ include StreamUtil
365
+ end
366
+
367
+ def parse_type
368
+ case @stream.peek(2)
369
+ when "BM"
370
+ :bmp
371
+ when "GI"
372
+ :gif
373
+ when 0xff.chr + 0xd8.chr
374
+ :jpeg
375
+ when 0x89.chr + "P"
376
+ :png
377
+ when "II", "MM"
378
+ :tiff
379
+ when '8B'
380
+ :psd
381
+ else
382
+ raise UnknownImageType
383
+ end
384
+ end
385
+
386
+ def parse_size_for_gif
387
+ @stream.read(11)[6..10].unpack('SS')
388
+ end
389
+
390
+ def parse_size_for_png
391
+ @stream.read(25)[16..24].unpack('NN')
392
+ end
393
+
394
+ def parse_size_for_jpeg
395
+ loop do
396
+ @state = case @state
397
+ when nil
398
+ @stream.read(2)
399
+ :started
400
+ when :started
401
+ @stream.read_byte == 0xFF ? :sof : :started
402
+ when :sof
403
+ case @stream.read_byte
404
+ when 0xe1 # APP1
405
+ skip_chars = @stream.read_int - 2
406
+ data = @stream.read(skip_chars)
407
+ io = StringIO.new(data)
408
+ if io.read(4) == "Exif"
409
+ io.read(2)
410
+ @exif = Exif.new(IOStream.new(io)) rescue nil
411
+ end
412
+ :started
413
+ when 0xe0..0xef
414
+ :skipframe
415
+ when 0xC0..0xC3, 0xC5..0xC7, 0xC9..0xCB, 0xCD..0xCF
416
+ :readsize
417
+ when 0xFF
418
+ :sof
419
+ else
420
+ :skipframe
421
+ end
422
+ when :skipframe
423
+ skip_chars = @stream.read_int - 2
424
+ @stream.read(skip_chars)
425
+ :started
426
+ when :readsize
427
+ s = @stream.read(3)
428
+ height = @stream.read_int
429
+ width = @stream.read_int
430
+ width, height = height, width if @exif && @exif.rotated?
431
+ return [width, height]
432
+ end
433
+ end
434
+ end
435
+
436
+ def parse_size_for_bmp
437
+ d = @stream.read(32)[14..28]
438
+ header = d.unpack("C")[0]
439
+
440
+ result = if header == 40
441
+ d[4..-1].unpack('l<l<')
442
+ else
443
+ d[4..8].unpack('SS')
444
+ end
445
+
446
+ # ImageHeight is expressed in pixels. The absolute value is necessary because ImageHeight can be negative
447
+ [result.first, result.last.abs]
448
+ end
449
+
450
+ class Exif # :nodoc:
451
+ attr_reader :width, :height
452
+ def initialize(stream)
453
+ @stream = stream
454
+ parse_exif
455
+ end
456
+
457
+ def rotated?
458
+ @orientation && @orientation >= 5
459
+ end
460
+
461
+ private
462
+
463
+ def get_exif_byte_order
464
+ byte_order = @stream.read(2)
465
+ case byte_order
466
+ when 'II'
467
+ @short, @long = 'v', 'V'
468
+ when 'MM'
469
+ @short, @long = 'n', 'N'
470
+ else
471
+ raise CannotParseImage
472
+ end
473
+ end
474
+
475
+ def parse_exif_ifd
476
+ tag_count = @stream.read(2).unpack(@short)[0]
477
+ tag_count.downto(1) do
478
+ type = @stream.read(2).unpack(@short)[0]
479
+ @stream.read(6)
480
+ data = @stream.read(2).unpack(@short)[0]
481
+ case type
482
+ when 0x0100 # image width
483
+ @width = data
484
+ when 0x0101 # image height
485
+ @height = data
486
+ when 0x0112 # orientation
487
+ @orientation = data
488
+ end
489
+ if @width && @height && @orientation
490
+ return # no need to parse more
491
+ end
492
+ @stream.read(2)
493
+ end
494
+ end
495
+
496
+ def parse_exif
497
+ @start_byte = @stream.pos
498
+
499
+ get_exif_byte_order
500
+
501
+ @stream.read(2) # 42
502
+
503
+ offset = @stream.read(4).unpack(@long)[0]
504
+ @stream.read(offset - 8)
505
+
506
+ parse_exif_ifd
507
+ end
508
+
509
+ end
510
+
511
+ def parse_size_for_tiff
512
+ exif = Exif.new(@stream)
513
+ if exif.rotated?
514
+ [exif.height, exif.width]
515
+ else
516
+ [exif.width, exif.height]
517
+ end
518
+ end
519
+
520
+ def parse_size_for_psd
521
+ @stream.read(26).unpack("x14NN").reverse
522
+ end
523
+ end
@@ -0,0 +1,67 @@
1
+ # Poor Man's Fiber (API compatible Thread based Fiber implementation for Ruby 1.8)
2
+ # (c) 2008 Aman Gupta (tmm1)
3
+
4
+ unless defined? Fiber
5
+ require 'thread'
6
+
7
+ class FiberError < StandardError; # :nodoc:
8
+ end
9
+
10
+ class Fiber # :nodoc:
11
+ def initialize
12
+ raise ArgumentError, 'new Fiber requires a block' unless block_given?
13
+
14
+ @yield = Queue.new
15
+ @resume = Queue.new
16
+
17
+ @thread = Thread.new{ @yield.push [yield(*@resume.pop)] }
18
+ @thread.abort_on_exception = true
19
+ @thread[:fiber] = self
20
+ end
21
+ attr_reader :thread
22
+
23
+ def resume *args
24
+ raise FiberError, 'dead fiber called' unless @thread.alive?
25
+ @resume.push(args)
26
+ result = @yield.pop
27
+ result.size > 1 ? result : result.first
28
+ end
29
+
30
+ def yield *args
31
+ @yield.push(args)
32
+ result = @resume.pop
33
+ result.size > 1 ? result : result.first
34
+ end
35
+
36
+ def self.yield *args
37
+ if fiber = Thread.current[:fiber]
38
+ fiber.yield(*args)
39
+ else
40
+ raise FiberError, 'not inside a fiber'
41
+ end
42
+ end
43
+
44
+ def self.current
45
+ if Thread.current == Thread.main
46
+ return Thread.main[:fiber] ||= RootFiber.new
47
+ end
48
+
49
+ Thread.current[:fiber] or raise FiberError, 'not inside a fiber'
50
+ end
51
+
52
+ def inspect
53
+ "#<#{self.class}:0x#{self.object_id.to_s(16)}>"
54
+ end
55
+ end
56
+
57
+ class RootFiber < Fiber # :nodoc:
58
+ def initialize
59
+ # XXX: what is a root fiber anyway?
60
+ end
61
+
62
+ def self.yield *args
63
+ raise FiberError, "can't yield from root fiber"
64
+ end
65
+ end
66
+ end
67
+
@@ -0,0 +1,9 @@
1
+ PathHere = File.dirname(__FILE__)
2
+ $LOAD_PATH.unshift File.join(PathHere, "..", "lib")
3
+
4
+ require 'fastimage'
5
+ require 'eventmachine'
6
+
7
+ EM.run do
8
+ FastImage.size('http://i.imgur.com/DEjv4DX.png').callback { |size| p size }
9
+ end
metadata ADDED
@@ -0,0 +1,157 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: em-fastimage
3
+ version: !ruby/object:Gem::Version
4
+ version: '0.1'
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Stephen Sykes
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2014-03-08 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: eventmachine
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: '0'
30
+ - !ruby/object:Gem::Dependency
31
+ name: em-http-request
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ type: :runtime
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ - !ruby/object:Gem::Dependency
47
+ name: addressable
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ~>
52
+ - !ruby/object:Gem::Version
53
+ version: '2.3'
54
+ - - ! '>='
55
+ - !ruby/object:Gem::Version
56
+ version: 2.3.5
57
+ type: :runtime
58
+ prerelease: false
59
+ version_requirements: !ruby/object:Gem::Requirement
60
+ none: false
61
+ requirements:
62
+ - - ~>
63
+ - !ruby/object:Gem::Version
64
+ version: '2.3'
65
+ - - ! '>='
66
+ - !ruby/object:Gem::Version
67
+ version: 2.3.5
68
+ - !ruby/object:Gem::Dependency
69
+ name: fakeweb
70
+ requirement: !ruby/object:Gem::Requirement
71
+ none: false
72
+ requirements:
73
+ - - ~>
74
+ - !ruby/object:Gem::Version
75
+ version: '1.3'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ none: false
80
+ requirements:
81
+ - - ~>
82
+ - !ruby/object:Gem::Version
83
+ version: '1.3'
84
+ - !ruby/object:Gem::Dependency
85
+ name: rake
86
+ requirement: !ruby/object:Gem::Requirement
87
+ none: false
88
+ requirements:
89
+ - - ! '>='
90
+ - !ruby/object:Gem::Version
91
+ version: '0'
92
+ type: :development
93
+ prerelease: false
94
+ version_requirements: !ruby/object:Gem::Requirement
95
+ none: false
96
+ requirements:
97
+ - - ! '>='
98
+ - !ruby/object:Gem::Version
99
+ version: '0'
100
+ - !ruby/object:Gem::Dependency
101
+ name: rdoc
102
+ requirement: !ruby/object:Gem::Requirement
103
+ none: false
104
+ requirements:
105
+ - - ! '>='
106
+ - !ruby/object:Gem::Version
107
+ version: '0'
108
+ type: :development
109
+ prerelease: false
110
+ version_requirements: !ruby/object:Gem::Requirement
111
+ none: false
112
+ requirements:
113
+ - - ! '>='
114
+ - !ruby/object:Gem::Version
115
+ version: '0'
116
+ description: FastImage finds the size or type of an image given its uri by fetching
117
+ as little as needed.
118
+ email: sdsykes@gmail.com
119
+ executables: []
120
+ extensions: []
121
+ extra_rdoc_files:
122
+ - README.textile
123
+ files:
124
+ - MIT-LICENSE
125
+ - README.textile
126
+ - lib/em-fastimage.rb
127
+ - lib/fastimage/fbr.rb
128
+ - test/e-test.rb
129
+ homepage:
130
+ licenses:
131
+ - MIT
132
+ post_install_message:
133
+ rdoc_options:
134
+ - --charset=UTF-8
135
+ require_paths:
136
+ - lib
137
+ required_ruby_version: !ruby/object:Gem::Requirement
138
+ none: false
139
+ requirements:
140
+ - - ! '>='
141
+ - !ruby/object:Gem::Version
142
+ version: '0'
143
+ required_rubygems_version: !ruby/object:Gem::Requirement
144
+ none: false
145
+ requirements:
146
+ - - ! '>='
147
+ - !ruby/object:Gem::Version
148
+ version: '0'
149
+ requirements: []
150
+ rubyforge_project:
151
+ rubygems_version: 1.8.24
152
+ signing_key:
153
+ specification_version: 3
154
+ summary: em-fastimage - async FastImage
155
+ test_files:
156
+ - test/e-test.rb
157
+ has_rdoc: