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 +4 -4
- data/README.textile +2 -1
- data/lib/fastimage/version.rb +5 -0
- data/lib/fastimage.rb +225 -30
- metadata +4 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d7667683a4bbc8ac16d583fb3ed7c93e6a58a25c19bf8df909ab7dae8d4ef782
|
4
|
+
data.tar.gz: 0baf5dcf6af104d0edd38baf65b7b35c972e6fc4271363d52855071e22b97a82
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
|
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
|
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
|
-
|
438
|
-
|
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
|
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
|
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
|
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
|
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
|
-
|
769
|
+
frames = 0
|
582
770
|
|
583
|
-
|
771
|
+
# "GIF" + version (3) + width (2) + height (2)
|
772
|
+
@stream.skip(10)
|
584
773
|
|
585
|
-
|
586
|
-
|
587
|
-
#
|
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
|
-
|
596
|
-
|
597
|
-
|
598
|
-
|
599
|
-
|
600
|
-
|
601
|
-
|
602
|
-
|
603
|
-
|
604
|
-
|
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
|
-
|
803
|
+
break # unrecognized block
|
609
804
|
end
|
610
805
|
end
|
611
806
|
|
612
|
-
|
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.
|
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:
|
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.
|
101
|
+
rubygems_version: 3.1.6
|
101
102
|
signing_key:
|
102
103
|
specification_version: 4
|
103
104
|
summary: FastImage - Image info fast
|