fastimage 2.2.0 → 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: 53c0966ef5216c189e2950b1ad36c106777175f0d4dca9851abd34472fd9cb61
4
- data.tar.gz: 29f5a6bc03623e184166e7f9c5e8c4379011b301904d4c690037d5b7b0ba0b33
3
+ metadata.gz: d7667683a4bbc8ac16d583fb3ed7c93e6a58a25c19bf8df909ab7dae8d4ef782
4
+ data.tar.gz: 0baf5dcf6af104d0edd38baf65b7b35c972e6fc4271363d52855071e22b97a82
5
5
  SHA512:
6
- metadata.gz: 28e8ec4debe6c2c477da19cafb1ad41e5de4d809136bd86621ae128372c32c1259abea833856f73dfcfe8ef3444bc5e1a148f91b52ee02d3292235c4d95fc542
7
- data.tar.gz: ba8b4c6bb42146387b1a8af718fbbaf74a9a94532ee5adce082ff676095dad5d5e0eb613f1ec41d7729709847195fa81c4614f2d9d510d6c7def67f8970f168e
6
+ metadata.gz: 9261157ade925ba5be4761028b4d70a698f1cc4d54e4ef0fcb7f4b23b6120ae6f9c42516b0dc17f915c423d168a4ef66c805a686ea20e3ca311556ce3e930593
7
+ data.tar.gz: 2d319a0aa6c4e7c7a3d951b3025d6215ac999bc2efe5d054720dac9c063cb3e2939e35e30660cf360bfae5f88285b91bb8029ec594c64ca1eb9e3e4144fa6b4c
data/README.textile CHANGED
@@ -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"
@@ -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
  #
@@ -251,10 +254,8 @@ class FastImage
251
254
  Errno::ENETUNREACH, ImageFetchFailure, Net::HTTPBadResponse, EOFError, Errno::ENOENT,
252
255
  OpenSSL::SSL::SSLError
253
256
  raise ImageFetchFailure if @options[:raise_on_failure]
254
- rescue NoMethodError # 1.8.7p248 can raise this due to a net/http bug
255
- raise ImageFetchFailure if @options[:raise_on_failure]
256
257
  rescue UnknownImageType
257
- raise UnknownImageType if @options[:raise_on_failure]
258
+ raise if @options[:raise_on_failure]
258
259
  rescue CannotParseImage
259
260
  if @options[:raise_on_failure]
260
261
  if @property == :size
@@ -434,8 +435,11 @@ class FastImage
434
435
  end
435
436
 
436
437
  def fetch_using_base64(uri)
437
- data = uri.split(',')[1]
438
- decoded = Base64.decode64(data)
438
+ decoded = begin
439
+ Base64.decode64(uri.split(',')[1])
440
+ rescue
441
+ raise CannotParseImage
442
+ end
439
443
  @content_length = decoded.size
440
444
  fetch_using_read StringIO.new(decoded)
441
445
  end
@@ -471,19 +475,20 @@ class FastImage
471
475
 
472
476
  # Peeking beyond the end of the input will raise
473
477
  def peek(n)
474
- while @strpos + n - 1 >= @str.size
478
+ while @strpos + n > @str.size
475
479
  unused_str = @str[@strpos..-1]
480
+
476
481
  new_string = @read_fiber.resume
482
+ new_string = @read_fiber.resume if new_string.is_a? Net::ReadAdapter
477
483
  raise CannotParseImage if !new_string
478
-
479
484
  # we are dealing with bytes here, so force the encoding
480
- 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
481
486
 
482
487
  @str = unused_str + new_string
483
488
  @strpos = 0
484
489
  end
485
490
 
486
- @str[@strpos..(@strpos + n - 1)]
491
+ @str[@strpos, n]
487
492
  end
488
493
 
489
494
  def read(n)
@@ -501,7 +506,7 @@ class FastImage
501
506
  new_string = @read_fiber.resume
502
507
  raise CannotParseImage if !new_string
503
508
 
504
- 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
505
510
 
506
511
  fetched += new_string.size
507
512
  @str = new_string
@@ -536,8 +541,18 @@ class FastImage
536
541
  when '8B'
537
542
  :psd
538
543
  when "\0\0"
539
- # ico has either a 1 (for ico format) or 2 (for cursor) at offset 3
540
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
541
556
  when 1 then :ico
542
557
  when 2 then :cur
543
558
  end
@@ -545,7 +560,7 @@ class FastImage
545
560
  :webp if @stream.peek(12)[8..11] == "WEBP"
546
561
  when "<s"
547
562
  :svg if @stream.peek(4) == "<svg"
548
- when /<[?!]/
563
+ when /\s\s|\s<|<[?!]/, 0xef.chr + 0xbb.chr
549
564
  # Peek 10 more chars each time, and if end of file is reached just raise
550
565
  # unknown. We assume the <svg tag cannot be within 10 chars of the end of
551
566
  # the file, and is within the first 250 chars.
@@ -566,6 +581,179 @@ class FastImage
566
581
  end
567
582
  alias_method :parse_size_for_cur, :parse_size_for_ico
568
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
+
569
757
  class Gif # :nodoc:
570
758
  def initialize(stream)
571
759
  @stream = stream
@@ -578,38 +766,45 @@ class FastImage
578
766
  # Checks if a delay between frames exists and if it does, then the GIFs is
579
767
  # animated
580
768
  def animated?
581
- delay = 0
769
+ frames = 0
582
770
 
583
- @stream.read(10) # "GIF" + version (3) + width (2) + height (2)
771
+ # "GIF" + version (3) + width (2) + height (2)
772
+ @stream.skip(10)
584
773
 
585
- fields = @stream.read(3).unpack("C")[0] # fields (1) + bg color (1) + pixel ratio (1)
586
-
587
- # Skip Global Color Table if it exists
588
- if fields & 0x80
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
589
777
  # 2 * (depth + 1) colors, each occupying 3 bytes (RGB)
590
778
  @stream.skip(3 * 2 ** ((fields & 0x7) + 1))
591
779
  end
592
780
 
593
781
  loop do
594
782
  block_type = @stream.read(1).unpack("C")[0]
595
- if block_type == 0x21
596
- extension_type = @stream.read(1).unpack("C")[0]
597
- size = @stream.read(1).unpack("C")[0]
598
- if extension_type == 0xF9
599
- delay = @stream.read(4).unpack("CSC")[1] # fields (1) + delay (2) + transparent index (1)
600
- break
601
- elsif extension_type == 0xFF
602
- @stream.skip(size) # application ID (8) + version (3)
603
- else
604
- return # unrecognized extension
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))
605
798
  end
799
+
800
+ @stream.skip(1) # LZW min code size (1)
606
801
  skip_sub_blocks
607
802
  else
608
- return # unrecognized block
803
+ break # unrecognized block
609
804
  end
610
805
  end
611
806
 
612
- delay > 0
807
+ false
613
808
  end
614
809
 
615
810
  private
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.2.0
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: 2020-07-23 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
@@ -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