fastimage 2.3.0 → 2.4.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: fa0337307f5e98ae916e46768e148812f0f6dbb93a1c67d193ca981540779336
4
- data.tar.gz: 8600c44df8eed5762ec526709d46e32841a099c0c453e7c255c2a87611dcb822
3
+ metadata.gz: f76a7f8a37bf8a8e98ea4d1e740a0df0d1248005bb143b2a0ffb5f6a27ad5ae5
4
+ data.tar.gz: 9b12393ce757e0628f298cb2919475ceeacd660dbf41f55fb98c8d0d3871e1d9
5
5
  SHA512:
6
- metadata.gz: 57b4e77535ee1022ec9b1d3209536729dd5e980c2799fc6987d3c91731f5dd4dce3d93ef29f97c8762b6021b12795554efbc3ca1db7a49713aa119bdb5df5088
7
- data.tar.gz: 83741942ce879586ae813fda60da760bee079ff1000dbbf7ee118b2513d579ae4cff8acf98de0bafe5313d429352053380e8d0293a101b0a3b1a799ce457f5a1
6
+ metadata.gz: 2bc5b3c37baafd7c2bf9bdddddc5464f2a1963c65e47a263b73e00251e3d763391e65afddc5fadf5fc87a76f6751e3c8b013c6578b32ff65a62cf885a4b2dafa
7
+ data.tar.gz: b1c2973319aa246147e80ad42776104e3317ca2f5e5ce154c512ad4157a9f1cc04d4ed1547031580f3252a14fd479c3a4cea671ff731b5739a5775565ca0af5d
data/README.md CHANGED
@@ -13,7 +13,7 @@ But the image is not locally stored - it's on another asset server, or in the cl
13
13
 
14
14
  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.
15
15
 
16
- 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).
16
+ FastImage does this minimal fetch for image types GIF, JPEG, PNG, TIFF, BMP, ICO, CUR, PSD, SVG, WEBP and JXL. And it doesn't rely on installing external libraries such as RMagick (which relies on ImageMagick or GraphicsMagick) or ImageScience (which relies on FreeImage).
17
17
 
18
18
  You only need supply the uri, and FastImage will do the rest.
19
19
 
@@ -96,38 +96,78 @@ http://sdsykes.github.io/fastimage/rdoc/FastImage.html
96
96
 
97
97
  ## Maintainer
98
98
 
99
- FastImage is maintained by Stephen Sykes (`sdsykes). Support this project by using [LibPixel](https://libpixel.com) cloud based image resizing and processing service.
99
+ FastImage is maintained by Stephen Sykes (sdsykes). SamSaffron also helps out from time to time (thanks!).
100
100
 
101
101
  ## Benchmark
102
102
 
103
- It's way faster than conventional methods (for example the image_size gem) for most types of file when fetching over the wire.
103
+ It's way faster than conventional methods for most types of file when fetching over the wire. Compared here by using OpenURI which will fetch the whole file.
104
104
 
105
105
  ```ruby
106
- irb> uri = "http://upload.wikimedia.org/wikipedia/commons/b/b4/Mardin_1350660_1350692_33_images.jpg"
107
- irb> puts Benchmark.measure {open(uri, 'rb') {|fh| p ImageSize.new(fh).size}}
106
+ require 'benchmark'
107
+ require 'fastimage'
108
+ require 'open-uri'
109
+
110
+ uri = "http://upload.wikimedia.org/wikipedia/commons/b/b4/Mardin_1350660_1350692_33_images.jpg"
111
+ puts Benchmark.measure {URI.open(uri, 'rb') {|fh| p FastImage.size(fh)}}
108
112
  [9545, 6623]
109
- 0.680000 0.250000 0.930000 ( 7.571887)
113
+ 0.059088 0.067694 0.126782 ( 0.808131)
110
114
 
111
- irb> puts Benchmark.measure {p FastImage.size(uri)}
115
+ puts Benchmark.measure {p FastImage.size(uri)}
112
116
  [9545, 6623]
113
- 0.010000 0.000000 0.010000 ( 0.090640)
117
+ 0.006198 0.001563 0.007761 ( 0.162021)
118
+ ```
119
+
120
+ The file is fetched in about 0.8 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 0.16s.
121
+
122
+ You'll see similar excellent results for the other file types.
123
+
124
+ ```ruby
125
+ require 'benchmark'
126
+ require 'fastimage'
127
+ require 'open-uri'
128
+
129
+ uri = "https://upload.wikimedia.org/wikipedia/commons/a/a9/Augustine_Herrman_1670_Map_Virginia_Maryland.tiff"
130
+ puts Benchmark.measure {URI.open(uri, 'rb') {|fh| p FastImage.size(fh)}}
131
+ [12805, 10204]
132
+ 1.332587 2.049915 3.382502 ( 19.925270)
133
+
134
+ puts Benchmark.measure {p FastImage.size(uri)}
135
+ [12805, 10204]
136
+ 0.004593 0.000924 0.005517 ( 0.100592)
114
137
  ```
115
138
 
116
- 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.
139
+ Some tiff files however do not have their metadata near the start of the file.
117
140
 
118
- You'll see similar excellent results for the other file types, except for TIFF. Unfortunately TIFFs tend to have their
119
- metadata towards the end of the file, so it makes little difference to do a minimal fetch. The result shown below is
120
- mostly dependent on the exact internet conditions during the test, and little to do with the library used.
141
+ ```ruby
142
+ require 'benchmark'
143
+ require 'fastimage'
144
+ require 'open-uri'
145
+
146
+ uri = "https://upload.wikimedia.org/wikipedia/commons/1/14/Center-Filled_LIMA.tif"
147
+ puts Benchmark.measure {URI.open(uri, 'rb') {|fh| p FastImage.size(fh)}}
148
+ [22841, 19404]
149
+ 0.350304 0.321104 0.671408 ( 3.053605)
150
+
151
+ puts Benchmark.measure {p FastImage.size(uri)}
152
+ [22841, 19404]
153
+ 0.163443 0.214301 0.377744 ( 2.880414)
154
+ ```
155
+
156
+ Note that if you want a really fast result for this file type, [image_size](https://github.com/toy/image_size?tab=readme-ov-file#experimental-fetch-image-meta-from-http-server) might be useful as it has an optimisation for this (fetching only the needed data range).
121
157
 
122
158
  ```ruby
123
- irb> uri = "http://upload.wikimedia.org/wikipedia/commons/1/11/Shinbutsureijoushuincho.tiff"
124
- irb> puts Benchmark.measure {open(uri, 'rb') {|fh| p ImageSize.new(fh).size}}
125
- [1120, 1559]
126
- 1.080000 0.370000 1.450000 ( 13.766962)
127
-
128
- irb> puts Benchmark.measure {p FastImage.size(uri)}
129
- [1120, 1559]
130
- 3.490000 3.810000 7.300000 ( 11.754315)
159
+ require 'benchmark'
160
+ require 'image_size/uri'
161
+ require 'fastimage'
162
+
163
+ uri = "https://upload.wikimedia.org/wikipedia/commons/1/14/Center-Filled_LIMA.tif"
164
+ puts Benchmark.measure {p ImageSize.url(uri).size }
165
+ [22841, 19404]
166
+ 0.008983 0.001311 0.010294 ( 0.128986)
167
+
168
+ puts Benchmark.measure {p FastImage.size(uri)}
169
+ [22841, 19404]
170
+ 0.163443 0.214301 0.377744 ( 2.880414)
131
171
  ```
132
172
 
133
173
  ## Tests
@@ -156,6 +196,10 @@ ruby test/test.rb
156
196
  - [Android by qstumn](https://github.com/qstumn/FastImageSize)
157
197
  - [Flutter by ky1vstar](https://github.com/ky1vstar/fastimage.dart)
158
198
 
199
+ ### Also of interest
200
+ - [C++ by xiaozhuai](https://github.com/xiaozhuai/imageinfo)
201
+ - [Rust by xiaozhuai](https://github.com/xiaozhuai/imageinfo-rs)
202
+
159
203
  ## Licence
160
204
 
161
205
  MIT, see file "MIT-LICENSE"
@@ -0,0 +1,471 @@
1
+ require_relative 'fastimage_parsing/image_base'
2
+ require_relative 'fastimage_parsing/stream_util'
3
+
4
+ require_relative 'fastimage_parsing/avif'
5
+ require_relative 'fastimage_parsing/bmp'
6
+ require_relative 'fastimage_parsing/exif'
7
+ require_relative 'fastimage_parsing/fiber_stream'
8
+ require_relative 'fastimage_parsing/gif'
9
+ require_relative 'fastimage_parsing/heic'
10
+ require_relative 'fastimage_parsing/ico'
11
+ require_relative 'fastimage_parsing/iso_bmff'
12
+ require_relative 'fastimage_parsing/jpeg'
13
+ require_relative 'fastimage_parsing/jxl'
14
+ require_relative 'fastimage_parsing/jxlc'
15
+ require_relative 'fastimage_parsing/png'
16
+ require_relative 'fastimage_parsing/psd'
17
+ require_relative 'fastimage_parsing/svg'
18
+ require_relative 'fastimage_parsing/tiff'
19
+ require_relative 'fastimage_parsing/type_parser'
20
+ require_relative 'fastimage_parsing/webp'
21
+
22
+ class FastImage
23
+ include FastImageParsing
24
+
25
+ attr_reader :bytes_read
26
+
27
+ class FastImageException < StandardError # :nodoc:
28
+ end
29
+ class UnknownImageType < FastImageException # :nodoc:
30
+ end
31
+ class ImageFetchFailure < FastImageException # :nodoc:
32
+ end
33
+ class SizeNotFound < FastImageException # :nodoc:
34
+ end
35
+ class CannotParseImage < FastImageException # :nodoc:
36
+ end
37
+ class BadImageURI < FastImageException # :nodoc:
38
+ end
39
+
40
+ DefaultTimeout = 2 unless const_defined?(:DefaultTimeout)
41
+
42
+ LocalFileChunkSize = 256 unless const_defined?(:LocalFileChunkSize)
43
+
44
+ private
45
+
46
+ Parsers = {
47
+ :bmp => Bmp,
48
+ :gif => Gif,
49
+ :jpeg => Jpeg,
50
+ :png => Png,
51
+ :tiff => Tiff,
52
+ :psd => Psd,
53
+ :heic => Heic,
54
+ :heif => Heic,
55
+ :webp => Webp,
56
+ :svg => Svg,
57
+ :ico => Ico,
58
+ :cur => Ico,
59
+ :jxl => Jxl,
60
+ :avif => Avif
61
+ }.freeze
62
+
63
+ public
64
+
65
+ SUPPORTED_IMAGE_TYPES = Parsers.keys.freeze
66
+
67
+ # Returns an array containing the width and height of the image.
68
+ # It will return nil if the image could not be fetched, or if the image type was not recognised.
69
+ #
70
+ # By default there is a timeout of 2 seconds for opening and reading from a remote server.
71
+ # This can be changed by passing a :timeout => number_of_seconds in the options.
72
+ #
73
+ # If you wish FastImage to raise if it cannot size the image for any reason, then pass
74
+ # :raise_on_failure => true in the options.
75
+ #
76
+ # FastImage knows about GIF, JPEG, BMP, TIFF, ICO, CUR, PNG, HEIC/HEIF, AVIF, PSD, SVG, WEBP and JXL files.
77
+ #
78
+ # === Example
79
+ #
80
+ # require 'fastimage'
81
+ #
82
+ # FastImage.size("https://switchstep.com/images/ios.gif")
83
+ # => [196, 283]
84
+ # FastImage.size("http://switchstep.com/images/ss_logo.png")
85
+ # => [300, 300]
86
+ # FastImage.size("https://upload.wikimedia.org/wikipedia/commons/0/09/Jpeg_thumb_artifacts_test.jpg")
87
+ # => [1280, 800]
88
+ # FastImage.size("https://eeweb.engineering.nyu.edu/~yao/EL5123/image/lena_gray.bmp")
89
+ # => [512, 512]
90
+ # FastImage.size("test/fixtures/test.jpg")
91
+ # => [882, 470]
92
+ # FastImage.size("http://switchstep.com/does_not_exist")
93
+ # => nil
94
+ # FastImage.size("http://switchstep.com/does_not_exist", :raise_on_failure=>true)
95
+ # => raises FastImage::ImageFetchFailure
96
+ # FastImage.size("http://switchstep.com/images/favicon.ico", :raise_on_failure=>true)
97
+ # => [16, 16]
98
+ # FastImage.size("http://switchstep.com/foo.ics", :raise_on_failure=>true)
99
+ # => raises FastImage::UnknownImageType
100
+ # FastImage.size("http://switchstep.com/images/favicon.ico", :raise_on_failure=>true, :timeout=>0.01)
101
+ # => raises FastImage::ImageFetchFailure
102
+ # FastImage.size("http://switchstep.com/images/faulty.jpg", :raise_on_failure=>true)
103
+ # => raises FastImage::SizeNotFound
104
+ #
105
+ # === Supported options
106
+ # [:timeout]
107
+ # Overrides the default timeout of 2 seconds. Applies both to reading from and opening the http connection.
108
+ # [:raise_on_failure]
109
+ # If set to true causes an exception to be raised if the image size cannot be found for any reason.
110
+ #
111
+ def self.size(uri, options={})
112
+ new(uri, options).size
113
+ end
114
+
115
+ # Returns an symbol indicating the image type fetched from a uri.
116
+ # It will return nil if the image could not be fetched, or if the image type was not recognised.
117
+ #
118
+ # By default there is a timeout of 2 seconds for opening and reading from a remote server.
119
+ # This can be changed by passing a :timeout => number_of_seconds in the options.
120
+ #
121
+ # If you wish FastImage to raise if it cannot find the type of the image for any reason, then pass
122
+ # :raise_on_failure => true in the options.
123
+ #
124
+ # === Example
125
+ #
126
+ # require 'fastimage'
127
+ #
128
+ # FastImage.type("https://switchstep.com/images/ios.gif")
129
+ # => :gif
130
+ # FastImage.type("http://switchstep.com/images/ss_logo.png")
131
+ # => :png
132
+ # FastImage.type("https://upload.wikimedia.org/wikipedia/commons/0/09/Jpeg_thumb_artifacts_test.jpg")
133
+ # => :jpeg
134
+ # FastImage.type("https://eeweb.engineering.nyu.edu/~yao/EL5123/image/lena_gray.bmp")
135
+ # => :bmp
136
+ # FastImage.type("test/fixtures/test.jpg")
137
+ # => :jpeg
138
+ # FastImage.type("http://switchstep.com/does_not_exist")
139
+ # => nil
140
+ # File.open("/some/local/file.gif", "r") {|io| FastImage.type(io)}
141
+ # => :gif
142
+ # FastImage.type("test/fixtures/test.tiff")
143
+ # => :tiff
144
+ # FastImage.type("test/fixtures/test.psd")
145
+ # => :psd
146
+ #
147
+ # === Supported options
148
+ # [:timeout]
149
+ # Overrides the default timeout of 2 seconds. Applies both to reading from and opening the http connection.
150
+ # [:raise_on_failure]
151
+ # If set to true causes an exception to be raised if the image type cannot be found for any reason.
152
+ #
153
+ def self.type(uri, options={})
154
+ new(uri, options).type
155
+ end
156
+
157
+ # Returns a boolean value indicating the image is animated.
158
+ # It will return nil if the image could not be fetched, or if the image type was not recognised.
159
+ #
160
+ # By default there is a timeout of 2 seconds for opening and reading from a remote server.
161
+ # This can be changed by passing a :timeout => number_of_seconds in the options.
162
+ #
163
+ # If you wish FastImage to raise if it cannot find the type of the image for any reason, then pass
164
+ # :raise_on_failure => true in the options.
165
+ #
166
+ # === Example
167
+ #
168
+ # require 'fastimage'
169
+ #
170
+ # FastImage.animated?("test/fixtures/test.gif")
171
+ # => false
172
+ # FastImage.animated?("test/fixtures/animated.gif")
173
+ # => true
174
+ #
175
+ # === Supported options
176
+ # [:timeout]
177
+ # Overrides the default timeout of 2 seconds. Applies both to reading from and opening the http connection.
178
+ # [:raise_on_failure]
179
+ # If set to true causes an exception to be raised if the image type cannot be found for any reason.
180
+ #
181
+ def self.animated?(uri, options={})
182
+ new(uri, options).animated
183
+ end
184
+
185
+ def initialize(uri, options={})
186
+ @uri = uri
187
+ @options = {
188
+ :timeout => DefaultTimeout,
189
+ :raise_on_failure => false,
190
+ :proxy => nil,
191
+ :http_header => {}
192
+ }.merge(options)
193
+ end
194
+
195
+ def type
196
+ @property = :type
197
+ fetch unless defined?(@type)
198
+ @type
199
+ end
200
+
201
+ def size
202
+ @property = :size
203
+ begin
204
+ fetch unless defined?(@size)
205
+ rescue CannotParseImage
206
+ end
207
+
208
+ raise SizeNotFound if @options[:raise_on_failure] && !@size
209
+
210
+ @size
211
+ end
212
+
213
+ def orientation
214
+ size unless defined?(@size)
215
+ @orientation ||= 1 if @size
216
+ end
217
+
218
+ def width
219
+ size && @size[0]
220
+ end
221
+
222
+ def height
223
+ size && @size[1]
224
+ end
225
+
226
+ def animated
227
+ @property = :animated
228
+ fetch unless defined?(@animated)
229
+ @animated
230
+ end
231
+
232
+ def content_length
233
+ @property = :content_length
234
+ fetch unless defined?(@content_length)
235
+ @content_length
236
+ end
237
+
238
+ # find an appropriate method to fetch the image according to the passed parameter
239
+ def fetch
240
+ raise BadImageURI if @uri.nil?
241
+
242
+ if @uri.respond_to?(:read)
243
+ fetch_using_read(@uri)
244
+ elsif @uri.start_with?('data:')
245
+ fetch_using_base64(@uri)
246
+ else
247
+ begin
248
+ @parsed_uri = URI.parse(@uri)
249
+ rescue URI::InvalidURIError
250
+ fetch_using_file_open
251
+ else
252
+ if @parsed_uri.scheme == "http" || @parsed_uri.scheme == "https"
253
+ fetch_using_http
254
+ else
255
+ fetch_using_file_open
256
+ end
257
+ end
258
+ end
259
+
260
+ raise SizeNotFound if @options[:raise_on_failure] && @property == :size && !@size
261
+
262
+ rescue Timeout::Error, SocketError, Errno::ECONNREFUSED, Errno::EHOSTUNREACH, Errno::ECONNRESET,
263
+ Errno::ENETUNREACH, ImageFetchFailure, Net::HTTPBadResponse, EOFError, Errno::ENOENT,
264
+ OpenSSL::SSL::SSLError
265
+ raise ImageFetchFailure if @options[:raise_on_failure]
266
+ rescue UnknownImageType, BadImageURI, CannotParseImage
267
+ raise if @options[:raise_on_failure]
268
+
269
+ ensure
270
+ @uri.rewind if @uri.respond_to?(:rewind)
271
+
272
+ end
273
+
274
+ private
275
+
276
+ def fetch_using_http
277
+ @redirect_count = 0
278
+
279
+ fetch_using_http_from_parsed_uri
280
+ end
281
+
282
+ # Some invalid locations need escaping
283
+ def escaped_location(location)
284
+ begin
285
+ URI(location)
286
+ rescue URI::InvalidURIError
287
+ ::URI::DEFAULT_PARSER.escape(location)
288
+ else
289
+ location
290
+ end
291
+ end
292
+
293
+ def fetch_using_http_from_parsed_uri
294
+ raise ImageFetchFailure unless @parsed_uri.is_a?(URI::HTTP)
295
+
296
+ http_header = {'Accept-Encoding' => 'identity'}.merge(@options[:http_header])
297
+
298
+ setup_http
299
+ @http.request_get(@parsed_uri.request_uri, http_header) do |res|
300
+ if res.is_a?(Net::HTTPRedirection) && @redirect_count < 4
301
+ @redirect_count += 1
302
+ begin
303
+ location = res['Location']
304
+ raise ImageFetchFailure if location.nil? || location.empty?
305
+
306
+ @parsed_uri = URI.join(@parsed_uri, escaped_location(location))
307
+ rescue URI::InvalidURIError
308
+ else
309
+ fetch_using_http_from_parsed_uri
310
+ break
311
+ end
312
+ end
313
+
314
+ raise ImageFetchFailure unless res.is_a?(Net::HTTPSuccess)
315
+
316
+ @content_length = res.content_length
317
+ break if @property == :content_length
318
+
319
+ read_fiber = Fiber.new do
320
+ res.read_body do |str|
321
+ Fiber.yield str
322
+ end
323
+ nil
324
+ end
325
+
326
+ case res['content-encoding']
327
+ when 'deflate', 'gzip', 'x-gzip'
328
+ begin
329
+ gzip = Zlib::GzipReader.new(FiberStream.new(read_fiber))
330
+ rescue FiberError, Zlib::GzipFile::Error
331
+ raise CannotParseImage
332
+ end
333
+
334
+ read_fiber = Fiber.new do
335
+ while data = gzip.readline
336
+ Fiber.yield data
337
+ end
338
+ nil
339
+ end
340
+ end
341
+
342
+ parse_packets FiberStream.new(read_fiber)
343
+
344
+ break # needed to actively quit out of the fetch
345
+ end
346
+ end
347
+
348
+ def protocol_relative_url?(url)
349
+ url.start_with?("//")
350
+ end
351
+
352
+ def proxy_uri
353
+ begin
354
+ if @options[:proxy]
355
+ proxy = URI.parse(@options[:proxy])
356
+ else
357
+ proxy = ENV['http_proxy'] && ENV['http_proxy'] != "" ? URI.parse(ENV['http_proxy']) : nil
358
+ end
359
+ rescue URI::InvalidURIError
360
+ proxy = nil
361
+ end
362
+ proxy
363
+ end
364
+
365
+ def setup_http
366
+ proxy = proxy_uri
367
+
368
+ if proxy
369
+ @http = Net::HTTP::Proxy(proxy.host, proxy.port, proxy.user, proxy.password).new(@parsed_uri.host, @parsed_uri.port)
370
+ else
371
+ @http = Net::HTTP.new(@parsed_uri.host, @parsed_uri.port)
372
+ end
373
+ @http.use_ssl = (@parsed_uri.scheme == "https")
374
+ @http.verify_mode = OpenSSL::SSL::VERIFY_NONE
375
+ @http.open_timeout = @options[:timeout]
376
+ @http.read_timeout = @options[:timeout]
377
+ end
378
+
379
+ def fetch_using_read(readable)
380
+ return @content_length = readable.size if @property == :content_length && readable.respond_to?(:size)
381
+
382
+ readable.rewind if readable.respond_to?(:rewind)
383
+ # Pathnames respond to read, but always return the first
384
+ # chunk of the file unlike an IO (even though the
385
+ # docuementation for it refers to IO). Need to supply
386
+ # an offset in this case.
387
+ if readable.is_a?(Pathname)
388
+ read_fiber = Fiber.new do
389
+ offset = 0
390
+ while str = readable.read(LocalFileChunkSize, offset)
391
+ Fiber.yield str
392
+ offset += LocalFileChunkSize
393
+ end
394
+ nil
395
+ end
396
+ else
397
+ read_fiber = Fiber.new do
398
+ while str = readable.read(LocalFileChunkSize)
399
+ Fiber.yield str
400
+ end
401
+ nil
402
+ end
403
+ end
404
+
405
+ parse_packets FiberStream.new(read_fiber)
406
+ end
407
+
408
+ def fetch_using_file_open
409
+ return @content_length = File.size?(@uri) if @property == :content_length
410
+
411
+ File.open(@uri) do |s|
412
+ fetch_using_read(s)
413
+ end
414
+ end
415
+
416
+ def fetch_using_base64(uri)
417
+ decoded = begin
418
+ uri.split(',')[1].unpack("m").first
419
+ rescue
420
+ raise CannotParseImage
421
+ end
422
+
423
+ fetch_using_read StringIO.new(decoded)
424
+ end
425
+
426
+ def parse_packets(stream)
427
+ @stream = stream
428
+
429
+ begin
430
+ @type = TypeParser.new(@stream).type unless defined?(@type)
431
+
432
+ result = case @property
433
+ when :type
434
+ @type
435
+ when :size
436
+ parse_size
437
+ when :animated
438
+ parse_animated
439
+ end
440
+
441
+ if result != nil
442
+ # extract exif orientation if it was found
443
+ if @property == :size && result.size == 3
444
+ @orientation = result.pop
445
+ else
446
+ @orientation = 1
447
+ end
448
+
449
+ instance_variable_set("@#{@property}", result)
450
+ else
451
+ raise CannotParseImage
452
+ end
453
+ rescue FiberError
454
+ raise CannotParseImage
455
+ end
456
+ end
457
+
458
+ def parser_class
459
+ klass = Parsers[@type]
460
+ raise UnknownImageType unless klass
461
+ klass
462
+ end
463
+
464
+ def parse_size
465
+ parser_class.new(@stream).dimensions
466
+ end
467
+
468
+ def parse_animated
469
+ parser_class.new(@stream).animated?
470
+ end
471
+ end
@@ -0,0 +1,12 @@
1
+ module FastImageParsing
2
+ class Avif < ImageBase # :nodoc:
3
+ def dimensions
4
+ bmff = IsoBmff.new(@stream)
5
+ [bmff.width, bmff.height]
6
+ end
7
+
8
+ def animated?
9
+ @stream.peek(12)[4..-1] == "ftypavis"
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,17 @@
1
+ module FastImageParsing
2
+ class Bmp < ImageBase # :nodoc:
3
+ def dimensions
4
+ d = @stream.read(32)[14..28]
5
+ header = d.unpack("C")[0]
6
+
7
+ result = if header == 12
8
+ d[4..8].unpack('SS')
9
+ else
10
+ d[4..-1].unpack('l<l<')
11
+ end
12
+
13
+ # ImageHeight is expressed in pixels. The absolute value is necessary because ImageHeight can be negative
14
+ [result.first, result.last.abs]
15
+ end
16
+ end
17
+ end