fastimage 2.1.7 → 2.2.6

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: 2aaa7bc9e7b596326f39fc933d836e8d94a0c19601670c0b55c839ba8d0e9ed5
4
- data.tar.gz: b807e4cb3ac6ac16b10c026d15ac6a6440a65a75036482518b1a337475bd723b
3
+ metadata.gz: d7667683a4bbc8ac16d583fb3ed7c93e6a58a25c19bf8df909ab7dae8d4ef782
4
+ data.tar.gz: 0baf5dcf6af104d0edd38baf65b7b35c972e6fc4271363d52855071e22b97a82
5
5
  SHA512:
6
- metadata.gz: 8a87fd1e5eb5a209da0c2a2af1d7c0d6cead3f14af8cc591810e0e0d7b853cd519e236f9ec0ed70fdea0c39569631f5b41f88f5fea8942aa55fbe018390102ef
7
- data.tar.gz: 002d20c88557352e4b3ebb80975d2f26cffa221cc3c510a0865ffed55fe251a63f53dfeff53843f351574cff1aa9c9864d5df1a2e5fc9a4550b5e03fd798880a
6
+ metadata.gz: 9261157ade925ba5be4761028b4d70a698f1cc4d54e4ef0fcb7f4b23b6120ae6f9c42516b0dc17f915c423d168a4ef66c805a686ea20e3ca311556ce3e930593
7
+ data.tar.gz: 2d319a0aa6c4e7c7a3d951b3025d6215ac999bc2efe5d054720dac9c063cb3e2939e35e30660cf360bfae5f88285b91bb8029ec594c64ca1eb9e3e4144fa6b4c
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
 
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ class FastImage
4
+ VERSION = '2.2.6'
5
+ end
data/lib/fastimage.rb CHANGED
@@ -61,6 +61,7 @@ require 'pathname'
61
61
  require 'zlib'
62
62
  require 'base64'
63
63
  require 'uri'
64
+ require_relative 'fastimage/version'
64
65
 
65
66
  # see http://stackoverflow.com/questions/5208851/i/41048816#41048816
66
67
  if RUBY_VERSION < "2.2"
@@ -70,7 +71,7 @@ if RUBY_VERSION < "2.2"
70
71
  end
71
72
 
72
73
  class FastImage
73
- attr_reader :size, :type, :content_length, :orientation
74
+ attr_reader :size, :type, :content_length, :orientation, :animated
74
75
 
75
76
  attr_reader :bytes_read
76
77
 
@@ -89,6 +90,8 @@ class FastImage
89
90
 
90
91
  LocalFileChunkSize = 256 unless const_defined?(:LocalFileChunkSize)
91
92
 
93
+ SUPPORTED_IMAGE_TYPES = [:bmp, :gif, :jpeg, :png, :tiff, :psd, :heic, :heif, :webp, :svg, :ico, :cur].freeze
94
+
92
95
  # Returns an array containing the width and height of the image.
93
96
  # It will return nil if the image could not be fetched, or if the image type was not recognised.
94
97
  #
@@ -179,6 +182,34 @@ class FastImage
179
182
  new(uri, options.merge(:type_only=>true)).type
180
183
  end
181
184
 
185
+ # Returns a boolean value indicating the image is animated.
186
+ # It will return nil if the image could not be fetched, or if the image type was not recognised.
187
+ #
188
+ # By default there is a timeout of 2 seconds for opening and reading from a remote server.
189
+ # This can be changed by passing a :timeout => number_of_seconds in the options.
190
+ #
191
+ # If you wish FastImage to raise if it cannot find the type of the image for any reason, then pass
192
+ # :raise_on_failure => true in the options.
193
+ #
194
+ # === Example
195
+ #
196
+ # require 'fastimage'
197
+ #
198
+ # FastImage.animated?("test/fixtures/test.gif")
199
+ # => false
200
+ # FastImage.animated?("test/fixtures/animated.gif")
201
+ # => true
202
+ #
203
+ # === Supported options
204
+ # [:timeout]
205
+ # Overrides the default timeout of 2 seconds. Applies both to reading from and opening the http connection.
206
+ # [:raise_on_failure]
207
+ # If set to true causes an exception to be raised if the image type cannot be found for any reason.
208
+ #
209
+ def self.animated?(uri, options={})
210
+ new(uri, options.merge(:animated_only=>true)).animated
211
+ end
212
+
182
213
  def initialize(uri, options={})
183
214
  @uri = uri
184
215
  @options = {
@@ -189,7 +220,13 @@ class FastImage
189
220
  :http_header => {}
190
221
  }.merge(options)
191
222
 
192
- @property = @options[:type_only] ? :type : :size
223
+ @property = if @options[:animated_only]
224
+ :animated
225
+ elsif @options[:type_only]
226
+ :type
227
+ else
228
+ :size
229
+ end
193
230
 
194
231
  @type, @state = nil
195
232
 
@@ -217,10 +254,8 @@ class FastImage
217
254
  Errno::ENETUNREACH, ImageFetchFailure, Net::HTTPBadResponse, EOFError, Errno::ENOENT,
218
255
  OpenSSL::SSL::SSLError
219
256
  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
257
  rescue UnknownImageType
223
- raise UnknownImageType if @options[:raise_on_failure]
258
+ raise if @options[:raise_on_failure]
224
259
  rescue CannotParseImage
225
260
  if @options[:raise_on_failure]
226
261
  if @property == :size
@@ -248,7 +283,7 @@ class FastImage
248
283
  begin
249
284
  URI(location)
250
285
  rescue URI::InvalidURIError
251
- URI.escape(location)
286
+ ::URI::DEFAULT_PARSER.escape(location)
252
287
  else
253
288
  location
254
289
  end
@@ -262,7 +297,10 @@ class FastImage
262
297
  if res.is_a?(Net::HTTPRedirection) && @redirect_count < 4
263
298
  @redirect_count += 1
264
299
  begin
265
- @parsed_uri = URI.join(@parsed_uri, escaped_location(res['Location']))
300
+ location = res['Location']
301
+ raise ImageFetchFailure if location.nil? || location.empty?
302
+
303
+ @parsed_uri = URI.join(@parsed_uri, escaped_location(location))
266
304
  rescue URI::InvalidURIError
267
305
  else
268
306
  fetch_using_http_from_parsed_uri
@@ -369,7 +407,7 @@ class FastImage
369
407
 
370
408
  begin
371
409
  result = send("parse_#{@property}")
372
- if result
410
+ if result != nil
373
411
  # extract exif orientation if it was found
374
412
  if @property == :size && result.size == 3
375
413
  @orientation = result.pop
@@ -391,9 +429,17 @@ class FastImage
391
429
  send("parse_size_for_#{@type}")
392
430
  end
393
431
 
432
+ def parse_animated
433
+ @type = parse_type unless @type
434
+ @type == :gif ? send("parse_animated_for_#{@type}") : nil
435
+ end
436
+
394
437
  def fetch_using_base64(uri)
395
- data = uri.split(',')[1]
396
- decoded = Base64.decode64(data)
438
+ decoded = begin
439
+ Base64.decode64(uri.split(',')[1])
440
+ rescue
441
+ raise CannotParseImage
442
+ end
397
443
  @content_length = decoded.size
398
444
  fetch_using_read StringIO.new(decoded)
399
445
  end
@@ -429,19 +475,20 @@ class FastImage
429
475
 
430
476
  # Peeking beyond the end of the input will raise
431
477
  def peek(n)
432
- while @strpos + n - 1 >= @str.size
478
+ while @strpos + n > @str.size
433
479
  unused_str = @str[@strpos..-1]
480
+
434
481
  new_string = @read_fiber.resume
482
+ new_string = @read_fiber.resume if new_string.is_a? Net::ReadAdapter
435
483
  raise CannotParseImage if !new_string
436
-
437
484
  # we are dealing with bytes here, so force the encoding
438
- new_string.force_encoding("ASCII-8BIT") if String.method_defined? :force_encoding
485
+ new_string.force_encoding("ASCII-8BIT") if new_string.respond_to? :force_encoding
439
486
 
440
487
  @str = unused_str + new_string
441
488
  @strpos = 0
442
489
  end
443
490
 
444
- @str[@strpos..(@strpos + n - 1)]
491
+ @str[@strpos, n]
445
492
  end
446
493
 
447
494
  def read(n)
@@ -459,7 +506,7 @@ class FastImage
459
506
  new_string = @read_fiber.resume
460
507
  raise CannotParseImage if !new_string
461
508
 
462
- new_string.force_encoding("ASCII-8BIT") if String.method_defined? :force_encoding
509
+ new_string.force_encoding("ASCII-8BIT") if new_string.respond_to? :force_encoding
463
510
 
464
511
  fetched += new_string.size
465
512
  @str = new_string
@@ -494,16 +541,26 @@ class FastImage
494
541
  when '8B'
495
542
  :psd
496
543
  when "\0\0"
497
- # ico has either a 1 (for ico format) or 2 (for cursor) at offset 3
498
544
  case @stream.peek(3).bytes.to_a.last
545
+ when 0
546
+ # http://www.ftyps.com/what.html
547
+ case @stream.peek(12)[4..-1]
548
+ when "ftypavif"
549
+ :avif
550
+ when "ftypheic"
551
+ :heic
552
+ when "ftypmif1"
553
+ :heif
554
+ end
555
+ # ico has either a 1 (for ico format) or 2 (for cursor) at offset 3
499
556
  when 1 then :ico
500
557
  when 2 then :cur
501
558
  end
502
559
  when "RI"
503
560
  :webp if @stream.peek(12)[8..11] == "WEBP"
504
561
  when "<s"
505
- :svg
506
- when /<[?!]/
562
+ :svg if @stream.peek(4) == "<svg"
563
+ when /\s\s|\s<|<[?!]/, 0xef.chr + 0xbb.chr
507
564
  # Peek 10 more chars each time, and if end of file is reached just raise
508
565
  # unknown. We assume the <svg tag cannot be within 10 chars of the end of
509
566
  # the file, and is within the first 250 chars.
@@ -524,8 +581,249 @@ class FastImage
524
581
  end
525
582
  alias_method :parse_size_for_cur, :parse_size_for_ico
526
583
 
584
+ # HEIC/AVIF are a special case of the general ISO_BMFF format, in which all data is encapsulated in typed boxes,
585
+ # with a mandatory ftyp box that is used to indicate particular file types. Is composed of nested "boxes". Each
586
+ # box has a header composed of
587
+ # - Size (32 bit integer)
588
+ # - Box type (4 chars)
589
+ # - Extended size: only if size === 1, the type field is followed by 64 bit integer of extended size
590
+ # - Payload: Type-dependent
591
+ class IsoBmff # :nodoc:
592
+ def initialize(stream)
593
+ @stream = stream
594
+ end
595
+
596
+ def width_and_height
597
+ @rotation = 0
598
+ @max_size = nil
599
+ @primary_box = nil
600
+ @ipma_boxes = []
601
+ @ispe_boxes = []
602
+ @final_size = nil
603
+
604
+ catch :finish do
605
+ read_boxes!
606
+ end
607
+
608
+ if [90, 270].include?(@rotation)
609
+ @final_size.reverse
610
+ else
611
+ @final_size
612
+ end
613
+ end
614
+
615
+ private
616
+
617
+ # Format specs: https://www.loc.gov/preservation/digital/formats/fdd/fdd000525.shtml
618
+
619
+ # If you need to inspect a heic/heif file, use
620
+ # https://gpac.github.io/mp4box.js/test/filereader.html
621
+ def read_boxes!(max_read_bytes = nil)
622
+ end_pos = max_read_bytes.nil? ? nil : @stream.pos + max_read_bytes
623
+ index = 0
624
+
625
+ loop do
626
+ return if end_pos && @stream.pos >= end_pos
627
+
628
+ box_type, box_size = read_box_header!
629
+
630
+ case box_type
631
+ when "meta"
632
+ handle_meta_box(box_size)
633
+ when "pitm"
634
+ handle_pitm_box(box_size)
635
+ when "ipma"
636
+ handle_ipma_box(box_size)
637
+ when "hdlr"
638
+ handle_hdlr_box(box_size)
639
+ when "iprp", "ipco"
640
+ read_boxes!(box_size)
641
+ when "irot"
642
+ handle_irot_box
643
+ when "ispe"
644
+ handle_ispe_box(box_size, index)
645
+ when "mdat"
646
+ throw :finish
647
+ else
648
+ @stream.read(box_size)
649
+ end
650
+
651
+ index += 1
652
+ end
653
+ end
654
+
655
+ def handle_irot_box
656
+ @rotation = (read_uint8! & 0x3) * 90
657
+ end
658
+
659
+ def handle_ispe_box(box_size, index)
660
+ throw :finish if box_size < 12
661
+
662
+ data = @stream.read(box_size)
663
+ width, height = data[4...12].unpack("N2")
664
+ @ispe_boxes << { index: index, size: [width, height] }
665
+ end
666
+
667
+ def handle_hdlr_box(box_size)
668
+ throw :finish if box_size < 12
669
+
670
+ data = @stream.read(box_size)
671
+ throw :finish if data[8...12] != "pict"
672
+ end
673
+
674
+ def handle_ipma_box(box_size)
675
+ @stream.read(3)
676
+ flags3 = read_uint8!
677
+ entries_count = read_uint32!
678
+
679
+ entries_count.times do
680
+ id = read_uint16!
681
+ essen_count = read_uint8!
682
+
683
+ essen_count.times do
684
+ property_index = read_uint8! & 0x7F
685
+
686
+ if flags3 & 1 == 1
687
+ property_index = (property_index << 7) + read_uint8!
688
+ end
689
+
690
+ @ipma_boxes << { id: id, property_index: property_index - 1 }
691
+ end
692
+ end
693
+ end
694
+
695
+ def handle_pitm_box(box_size)
696
+ data = @stream.read(box_size)
697
+ @primary_box = data[4...6].unpack("S>")[0]
698
+ end
699
+
700
+ def handle_meta_box(box_size)
701
+ throw :finish if box_size < 4
702
+
703
+ @stream.read(4)
704
+ read_boxes!(box_size - 4)
705
+
706
+ throw :finish if !@primary_box
707
+
708
+ primary_indices = @ipma_boxes
709
+ .select { |box| box[:id] == @primary_box }
710
+ .map { |box| box[:property_index] }
711
+
712
+ ispe_box = @ispe_boxes.find do |box|
713
+ primary_indices.include?(box[:index])
714
+ end
715
+
716
+ if ispe_box
717
+ @final_size = ispe_box[:size]
718
+ end
719
+
720
+ throw :finish
721
+ end
722
+
723
+ def read_box_header!
724
+ size = read_uint32!
725
+ type = @stream.read(4)
726
+ [type, size - 8]
727
+ end
728
+
729
+ def read_uint8!
730
+ @stream.read(1).unpack("C")[0]
731
+ end
732
+
733
+ def read_uint16!
734
+ @stream.read(2).unpack("S>")[0]
735
+ end
736
+
737
+ def read_uint32!
738
+ @stream.read(4).unpack("N")[0]
739
+ end
740
+ end
741
+
742
+ def parse_size_for_avif
743
+ bmff = IsoBmff.new(@stream)
744
+ bmff.width_and_height
745
+ end
746
+
747
+ def parse_size_for_heic
748
+ bmff = IsoBmff.new(@stream)
749
+ bmff.width_and_height
750
+ end
751
+
752
+ def parse_size_for_heif
753
+ bmff = IsoBmff.new(@stream)
754
+ bmff.width_and_height
755
+ end
756
+
757
+ class Gif # :nodoc:
758
+ def initialize(stream)
759
+ @stream = stream
760
+ end
761
+
762
+ def width_and_height
763
+ @stream.read(11)[6..10].unpack('SS')
764
+ end
765
+
766
+ # Checks if a delay between frames exists and if it does, then the GIFs is
767
+ # animated
768
+ def animated?
769
+ frames = 0
770
+
771
+ # "GIF" + version (3) + width (2) + height (2)
772
+ @stream.skip(10)
773
+
774
+ # fields (1) + bg color (1) + pixel ratio (1)
775
+ fields = @stream.read(3).unpack("CCC")[0]
776
+ if fields & 0x80 != 0 # Global Color Table
777
+ # 2 * (depth + 1) colors, each occupying 3 bytes (RGB)
778
+ @stream.skip(3 * 2 ** ((fields & 0x7) + 1))
779
+ end
780
+
781
+ loop do
782
+ block_type = @stream.read(1).unpack("C")[0]
783
+
784
+ if block_type == 0x21 # Graphic Control Extension
785
+ # extension type (1) + size (1)
786
+ size = @stream.read(2).unpack("CC")[1]
787
+ @stream.skip(size)
788
+ skip_sub_blocks
789
+ elsif block_type == 0x2C # Image Descriptor
790
+ frames += 1
791
+ return true if frames > 1
792
+
793
+ # left position (2) + top position (2) + width (2) + height (2) + fields (1)
794
+ fields = @stream.read(9).unpack("SSSSC")[4]
795
+ if fields & 0x80 != 0 # Local Color Table
796
+ # 2 * (depth + 1) colors, each occupying 3 bytes (RGB)
797
+ @stream.skip(3 * 2 ** ((fields & 0x7) + 1))
798
+ end
799
+
800
+ @stream.skip(1) # LZW min code size (1)
801
+ skip_sub_blocks
802
+ else
803
+ break # unrecognized block
804
+ end
805
+ end
806
+
807
+ false
808
+ end
809
+
810
+ private
811
+
812
+ def skip_sub_blocks
813
+ loop do
814
+ size = @stream.read(1).unpack("C")[0]
815
+ if size == 0
816
+ break
817
+ else
818
+ @stream.skip(size)
819
+ end
820
+ end
821
+ end
822
+ end
823
+
527
824
  def parse_size_for_gif
528
- @stream.read(11)[6..10].unpack('SS')
825
+ gif = Gif.new(@stream)
826
+ gif.width_and_height
529
827
  end
530
828
 
531
829
  def parse_size_for_png
@@ -785,4 +1083,9 @@ class FastImage
785
1083
  svg = Svg.new(@stream)
786
1084
  svg.width_and_height
787
1085
  end
1086
+
1087
+ def parse_animated_for_gif
1088
+ gif = Gif.new(@stream)
1089
+ gif.animated?
1090
+ end
788
1091
  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.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Stephen Sykes
8
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
@@ -77,6 +77,7 @@ files:
77
77
  - MIT-LICENSE
78
78
  - README.textile
79
79
  - lib/fastimage.rb
80
+ - lib/fastimage/version.rb
80
81
  homepage: http://github.com/sdsykes/fastimage
81
82
  licenses:
82
83
  - MIT
@@ -97,7 +98,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
97
98
  - !ruby/object:Gem::Version
98
99
  version: '0'
99
100
  requirements: []
100
- rubygems_version: 3.0.6
101
+ rubygems_version: 3.1.6
101
102
  signing_key:
102
103
  specification_version: 4
103
104
  summary: FastImage - Image info fast