fastimage 2.2.0 → 2.2.6

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.
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