fastimage 2.1.7 → 2.2.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (4) hide show
  1. checksums.yaml +4 -4
  2. data/README.textile +3 -2
  3. data/lib/fastimage.rb +297 -19
  4. metadata +8 -8
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2aaa7bc9e7b596326f39fc933d836e8d94a0c19601670c0b55c839ba8d0e9ed5
4
- data.tar.gz: b807e4cb3ac6ac16b10c026d15ac6a6440a65a75036482518b1a337475bd723b
3
+ metadata.gz: 074e776db55bfd4766292bca262afc562d4d38d07d86f56f1299a666b4dc3e30
4
+ data.tar.gz: 7e079013cbd7802773119a5a6b7f25377e2878c1afbeb610c68d8a8a069ad0a1
5
5
  SHA512:
6
- metadata.gz: 8a87fd1e5eb5a209da0c2a2af1d7c0d6cead3f14af8cc591810e0e0d7b853cd519e236f9ec0ed70fdea0c39569631f5b41f88f5fea8942aa55fbe018390102ef
7
- data.tar.gz: 002d20c88557352e4b3ebb80975d2f26cffa221cc3c510a0865ffed55fe251a63f53dfeff53843f351574cff1aa9c9864d5df1a2e5fc9a4550b5e03fd798880a
6
+ metadata.gz: 4c82693c20723bc22b41e9839104097b0cb435c392e8ddd19edff93aeaa1802f3319c6ad49f1b82ba3926bd1bdcc5e04870d02401ff20e745e61d26a81473c6b
7
+ data.tar.gz: 230fbfd667aab5c59ce7bc53909454d2d9134fdb3c6705173d5df264a3c8a36df2d879c959df1ab4662d5e216a554ada7ba482ba3e0872b9c000d087c91e5b29
data/README.textile CHANGED
@@ -1,5 +1,5 @@
1
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
2
+ !https://travis-ci.org/sdsykes/fastimage.svg?branch=master!:https://travis-ci.org/sdsykes/fastimage
3
3
 
4
4
  h1. FastImage
5
5
 
@@ -132,7 +132,7 @@ h2. Tests
132
132
 
133
133
  You'll need to @gem install fakeweb@ and possibly also @gem install test-unit@ to be able to run the tests.
134
134
 
135
- bc.. $ ruby test.rb
135
+ bc.. $ ruby test/test.rb
136
136
  Run options:
137
137
 
138
138
  # Running tests:
@@ -156,6 +156,7 @@ h2. FastImage in other languages
156
156
  * "Node.js by ShogunPanda":https://github.com/ShogunPanda/fastimage
157
157
  * "Objective C by kylehickinson":https://github.com/kylehickinson/FastImage
158
158
  * "Android by qstumn":https://github.com/qstumn/FastImageSize
159
+ * "Flutter by ky1vstar":https://github.com/ky1vstar/fastimage.dart
159
160
 
160
161
  h2. Licence
161
162
 
data/lib/fastimage.rb CHANGED
@@ -70,7 +70,7 @@ if RUBY_VERSION < "2.2"
70
70
  end
71
71
 
72
72
  class FastImage
73
- attr_reader :size, :type, :content_length, :orientation
73
+ attr_reader :size, :type, :content_length, :orientation, :animated
74
74
 
75
75
  attr_reader :bytes_read
76
76
 
@@ -89,6 +89,8 @@ class FastImage
89
89
 
90
90
  LocalFileChunkSize = 256 unless const_defined?(:LocalFileChunkSize)
91
91
 
92
+ SUPPORTED_IMAGE_TYPES = [:bmp, :gif, :jpeg, :png, :tiff, :psd, :heic, :heif, :webp, :svg, :ico, :cur].freeze
93
+
92
94
  # Returns an array containing the width and height of the image.
93
95
  # It will return nil if the image could not be fetched, or if the image type was not recognised.
94
96
  #
@@ -179,6 +181,34 @@ class FastImage
179
181
  new(uri, options.merge(:type_only=>true)).type
180
182
  end
181
183
 
184
+ # Returns a boolean value indicating the image is animated.
185
+ # It will return nil if the image could not be fetched, or if the image type was not recognised.
186
+ #
187
+ # By default there is a timeout of 2 seconds for opening and reading from a remote server.
188
+ # This can be changed by passing a :timeout => number_of_seconds in the options.
189
+ #
190
+ # If you wish FastImage to raise if it cannot find the type of the image for any reason, then pass
191
+ # :raise_on_failure => true in the options.
192
+ #
193
+ # === Example
194
+ #
195
+ # require 'fastimage'
196
+ #
197
+ # FastImage.animated?("test/fixtures/test.gif")
198
+ # => false
199
+ # FastImage.animated?("test/fixtures/animated.gif")
200
+ # => true
201
+ #
202
+ # === Supported options
203
+ # [:timeout]
204
+ # Overrides the default timeout of 2 seconds. Applies both to reading from and opening the http connection.
205
+ # [:raise_on_failure]
206
+ # If set to true causes an exception to be raised if the image type cannot be found for any reason.
207
+ #
208
+ def self.animated?(uri, options={})
209
+ new(uri, options.merge(:animated_only=>true)).animated
210
+ end
211
+
182
212
  def initialize(uri, options={})
183
213
  @uri = uri
184
214
  @options = {
@@ -189,7 +219,13 @@ class FastImage
189
219
  :http_header => {}
190
220
  }.merge(options)
191
221
 
192
- @property = @options[:type_only] ? :type : :size
222
+ @property = if @options[:animated_only]
223
+ :animated
224
+ elsif @options[:type_only]
225
+ :type
226
+ else
227
+ :size
228
+ end
193
229
 
194
230
  @type, @state = nil
195
231
 
@@ -217,10 +253,8 @@ class FastImage
217
253
  Errno::ENETUNREACH, ImageFetchFailure, Net::HTTPBadResponse, EOFError, Errno::ENOENT,
218
254
  OpenSSL::SSL::SSLError
219
255
  raise ImageFetchFailure if @options[:raise_on_failure]
220
- rescue NoMethodError # 1.8.7p248 can raise this due to a net/http bug
221
- raise ImageFetchFailure if @options[:raise_on_failure]
222
256
  rescue UnknownImageType
223
- raise UnknownImageType if @options[:raise_on_failure]
257
+ raise if @options[:raise_on_failure]
224
258
  rescue CannotParseImage
225
259
  if @options[:raise_on_failure]
226
260
  if @property == :size
@@ -248,7 +282,7 @@ class FastImage
248
282
  begin
249
283
  URI(location)
250
284
  rescue URI::InvalidURIError
251
- URI.escape(location)
285
+ ::URI::DEFAULT_PARSER.escape(location)
252
286
  else
253
287
  location
254
288
  end
@@ -262,7 +296,10 @@ class FastImage
262
296
  if res.is_a?(Net::HTTPRedirection) && @redirect_count < 4
263
297
  @redirect_count += 1
264
298
  begin
265
- @parsed_uri = URI.join(@parsed_uri, escaped_location(res['Location']))
299
+ location = res['Location']
300
+ raise ImageFetchFailure if location.nil? || location.empty?
301
+
302
+ @parsed_uri = URI.join(@parsed_uri, escaped_location(location))
266
303
  rescue URI::InvalidURIError
267
304
  else
268
305
  fetch_using_http_from_parsed_uri
@@ -369,7 +406,7 @@ class FastImage
369
406
 
370
407
  begin
371
408
  result = send("parse_#{@property}")
372
- if result
409
+ if result != nil
373
410
  # extract exif orientation if it was found
374
411
  if @property == :size && result.size == 3
375
412
  @orientation = result.pop
@@ -391,9 +428,17 @@ class FastImage
391
428
  send("parse_size_for_#{@type}")
392
429
  end
393
430
 
431
+ def parse_animated
432
+ @type = parse_type unless @type
433
+ @type == :gif ? send("parse_animated_for_#{@type}") : nil
434
+ end
435
+
394
436
  def fetch_using_base64(uri)
395
- data = uri.split(',')[1]
396
- decoded = Base64.decode64(data)
437
+ decoded = begin
438
+ Base64.decode64(uri.split(',')[1])
439
+ rescue
440
+ raise CannotParseImage
441
+ end
397
442
  @content_length = decoded.size
398
443
  fetch_using_read StringIO.new(decoded)
399
444
  end
@@ -429,19 +474,20 @@ class FastImage
429
474
 
430
475
  # Peeking beyond the end of the input will raise
431
476
  def peek(n)
432
- while @strpos + n - 1 >= @str.size
477
+ while @strpos + n > @str.size
433
478
  unused_str = @str[@strpos..-1]
479
+
434
480
  new_string = @read_fiber.resume
481
+ new_string = @read_fiber.resume if new_string.is_a? Net::ReadAdapter
435
482
  raise CannotParseImage if !new_string
436
-
437
483
  # we are dealing with bytes here, so force the encoding
438
- new_string.force_encoding("ASCII-8BIT") if String.method_defined? :force_encoding
484
+ new_string.force_encoding("ASCII-8BIT") if new_string.respond_to? :force_encoding
439
485
 
440
486
  @str = unused_str + new_string
441
487
  @strpos = 0
442
488
  end
443
489
 
444
- @str[@strpos..(@strpos + n - 1)]
490
+ @str[@strpos, n]
445
491
  end
446
492
 
447
493
  def read(n)
@@ -459,7 +505,7 @@ class FastImage
459
505
  new_string = @read_fiber.resume
460
506
  raise CannotParseImage if !new_string
461
507
 
462
- new_string.force_encoding("ASCII-8BIT") if String.method_defined? :force_encoding
508
+ new_string.force_encoding("ASCII-8BIT") if new_string.respond_to? :force_encoding
463
509
 
464
510
  fetched += new_string.size
465
511
  @str = new_string
@@ -494,16 +540,29 @@ class FastImage
494
540
  when '8B'
495
541
  :psd
496
542
  when "\0\0"
497
- # ico has either a 1 (for ico format) or 2 (for cursor) at offset 3
498
543
  case @stream.peek(3).bytes.to_a.last
544
+ when 0
545
+ # http://www.ftyps.com/what.html
546
+ # HEIC is composed of nested "boxes". Each box has a header composed of
547
+ # - Size (32 bit integer)
548
+ # - Box type (4 chars)
549
+ # - Extended size: only if size === 1, the type field is followed by 64 bit integer of extended size
550
+ # - Payload: Type-dependent
551
+ case @stream.peek(12)[4..-1]
552
+ when "ftypheic"
553
+ :heic
554
+ when "ftypmif1"
555
+ :heif
556
+ end
557
+ # ico has either a 1 (for ico format) or 2 (for cursor) at offset 3
499
558
  when 1 then :ico
500
559
  when 2 then :cur
501
560
  end
502
561
  when "RI"
503
562
  :webp if @stream.peek(12)[8..11] == "WEBP"
504
563
  when "<s"
505
- :svg
506
- when /<[?!]/
564
+ :svg if @stream.peek(4) == "<svg"
565
+ when /\s\s|\s<|<[?!]/, 0xef.chr + 0xbb.chr
507
566
  # Peek 10 more chars each time, and if end of file is reached just raise
508
567
  # unknown. We assume the <svg tag cannot be within 10 chars of the end of
509
568
  # the file, and is within the first 250 chars.
@@ -524,8 +583,222 @@ class FastImage
524
583
  end
525
584
  alias_method :parse_size_for_cur, :parse_size_for_ico
526
585
 
586
+ class Heic # :nodoc:
587
+ def initialize(stream)
588
+ @stream = stream
589
+ end
590
+
591
+ def width_and_height
592
+ @max_size = nil
593
+ @primary_box = nil
594
+ @ipma_boxes = []
595
+ @ispe_boxes = []
596
+ @final_size = nil
597
+
598
+ catch :finish do
599
+ read_boxes!
600
+ end
601
+
602
+ @final_size
603
+ end
604
+
605
+ private
606
+
607
+ def read_boxes!(max_read_bytes = nil)
608
+ end_pos = max_read_bytes.nil? ? nil : @stream.pos + max_read_bytes
609
+ index = 0
610
+
611
+ loop do
612
+ return if end_pos && @stream.pos >= end_pos
613
+
614
+ box_type, box_size = read_box_header!
615
+
616
+ case box_type
617
+ when "meta"
618
+ handle_meta_box(box_size)
619
+ when "pitm"
620
+ handle_pitm_box(box_size)
621
+ when "ipma"
622
+ handle_ipma_box(box_size)
623
+ when "hdlr"
624
+ handle_hdlr_box(box_size)
625
+ when "iprp", "ipco"
626
+ read_boxes!(box_size)
627
+ when "ispe"
628
+ handle_ispe_box(box_size, index)
629
+ when "mdat"
630
+ throw :finish
631
+ else
632
+ @stream.read(box_size)
633
+ end
634
+
635
+ index += 1
636
+ end
637
+ end
638
+
639
+ def handle_ispe_box(box_size, index)
640
+ throw :finish if box_size < 12
641
+
642
+ data = @stream.read(box_size)
643
+ width, height = data[4...12].unpack("N2")
644
+ @ispe_boxes << { index: index, size: [width, height] }
645
+ end
646
+
647
+ def handle_hdlr_box(box_size)
648
+ throw :finish if box_size < 12
649
+
650
+ data = @stream.read(box_size)
651
+ throw :finish if data[8...12] != "pict"
652
+ end
653
+
654
+ def handle_ipma_box(box_size)
655
+ @stream.read(3)
656
+ flags3 = read_uint8!
657
+ entries_count = read_uint32!
658
+
659
+ entries_count.times do
660
+ id = read_uint16!
661
+ essen_count = read_uint8!
662
+
663
+ essen_count.times do
664
+ property_index = read_uint8! & 0x7F
665
+
666
+ if flags3 & 1 == 1
667
+ property_index = (property_index << 7) + read_uint8!
668
+ end
669
+
670
+ @ipma_boxes << { id: id, property_index: property_index - 1 }
671
+ end
672
+ end
673
+ end
674
+
675
+ def handle_pitm_box(box_size)
676
+ data = @stream.read(box_size)
677
+ @primary_box = data[4...6].unpack("S>")[0]
678
+ end
679
+
680
+ def handle_meta_box(box_size)
681
+ throw :finish if box_size < 4
682
+
683
+ @stream.read(4)
684
+ read_boxes!(box_size - 4)
685
+
686
+ throw :finish if !@primary_box
687
+
688
+ primary_indices = @ipma_boxes
689
+ .select { |box| box[:id] == @primary_box }
690
+ .map { |box| box[:property_index] }
691
+
692
+ ispe_box = @ispe_boxes.find do |box|
693
+ primary_indices.include?(box[:index])
694
+ end
695
+
696
+ if ispe_box
697
+ @final_size = ispe_box[:size]
698
+ end
699
+
700
+ throw :finish
701
+ end
702
+
703
+ def read_box_header!
704
+ size = read_uint32!
705
+ type = @stream.read(4)
706
+ [type, size - 8]
707
+ end
708
+
709
+ def read_uint8!
710
+ @stream.read(1).unpack("C")[0]
711
+ end
712
+
713
+ def read_uint16!
714
+ @stream.read(2).unpack("S>")[0]
715
+ end
716
+
717
+ def read_uint32!
718
+ @stream.read(4).unpack("N")[0]
719
+ end
720
+ end
721
+
722
+ def parse_size_for_heic
723
+ heic = Heic.new(@stream)
724
+ heic.width_and_height
725
+ end
726
+
727
+ def parse_size_for_heif
728
+ heic = Heic.new(@stream)
729
+ heic.width_and_height
730
+ end
731
+
732
+ class Gif # :nodoc:
733
+ def initialize(stream)
734
+ @stream = stream
735
+ end
736
+
737
+ def width_and_height
738
+ @stream.read(11)[6..10].unpack('SS')
739
+ end
740
+
741
+ # Checks if a delay between frames exists and if it does, then the GIFs is
742
+ # animated
743
+ def animated?
744
+ frames = 0
745
+
746
+ # "GIF" + version (3) + width (2) + height (2)
747
+ @stream.skip(10)
748
+
749
+ # fields (1) + bg color (1) + pixel ratio (1)
750
+ fields = @stream.read(3).unpack("CCC")[0]
751
+ if fields & 0x80 != 0 # Global Color Table
752
+ # 2 * (depth + 1) colors, each occupying 3 bytes (RGB)
753
+ @stream.skip(3 * 2 ** ((fields & 0x7) + 1))
754
+ end
755
+
756
+ loop do
757
+ block_type = @stream.read(1).unpack("C")[0]
758
+
759
+ if block_type == 0x21 # Graphic Control Extension
760
+ # extension type (1) + size (1)
761
+ size = @stream.read(2).unpack("CC")[1]
762
+ @stream.skip(size)
763
+ skip_sub_blocks
764
+ elsif block_type == 0x2C # Image Descriptor
765
+ frames += 1
766
+ return true if frames > 1
767
+
768
+ # left position (2) + top position (2) + width (2) + height (2) + fields (1)
769
+ fields = @stream.read(9).unpack("SSSSC")[4]
770
+ if fields & 0x80 != 0 # Local Color Table
771
+ # 2 * (depth + 1) colors, each occupying 3 bytes (RGB)
772
+ @stream.skip(3 * 2 ** ((fields & 0x7) + 1))
773
+ end
774
+
775
+ @stream.skip(1) # LZW min code size (1)
776
+ skip_sub_blocks
777
+ else
778
+ break # unrecognized block
779
+ end
780
+ end
781
+
782
+ false
783
+ end
784
+
785
+ private
786
+
787
+ def skip_sub_blocks
788
+ loop do
789
+ size = @stream.read(1).unpack("C")[0]
790
+ if size == 0
791
+ break
792
+ else
793
+ @stream.skip(size)
794
+ end
795
+ end
796
+ end
797
+ end
798
+
527
799
  def parse_size_for_gif
528
- @stream.read(11)[6..10].unpack('SS')
800
+ gif = Gif.new(@stream)
801
+ gif.width_and_height
529
802
  end
530
803
 
531
804
  def parse_size_for_png
@@ -785,4 +1058,9 @@ class FastImage
785
1058
  svg = Svg.new(@stream)
786
1059
  svg.width_and_height
787
1060
  end
1061
+
1062
+ def parse_animated_for_gif
1063
+ gif = Gif.new(@stream)
1064
+ gif.animated?
1065
+ end
788
1066
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: fastimage
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.1.7
4
+ version: 2.2.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Stephen Sykes
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-09-05 00:00:00.000000000 Z
11
+ date: 2021-02-28 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: fakeweb-fi
@@ -28,14 +28,14 @@ dependencies:
28
28
  name: rake
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
- - - "~>"
31
+ - - ">="
32
32
  - !ruby/object:Gem::Version
33
33
  version: '10.5'
34
34
  type: :development
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
- - - "~>"
38
+ - - ">="
39
39
  - !ruby/object:Gem::Version
40
40
  version: '10.5'
41
41
  - !ruby/object:Gem::Dependency
@@ -81,7 +81,7 @@ homepage: http://github.com/sdsykes/fastimage
81
81
  licenses:
82
82
  - MIT
83
83
  metadata: {}
84
- post_install_message:
84
+ post_install_message:
85
85
  rdoc_options:
86
86
  - "--charset=UTF-8"
87
87
  require_paths:
@@ -97,8 +97,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
97
97
  - !ruby/object:Gem::Version
98
98
  version: '0'
99
99
  requirements: []
100
- rubygems_version: 3.0.6
101
- signing_key:
100
+ rubygems_version: 3.1.6
101
+ signing_key:
102
102
  specification_version: 4
103
103
  summary: FastImage - Image info fast
104
104
  test_files: []