flipbook 0.3.0 → 0.4.0
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/CHANGELOG.md +17 -4
- data/README.md +38 -15
- data/docs/assets/gradient.gif +0 -0
- data/docs/assets/spinning-square.gif +0 -0
- data/docs/assets/transparent.gif +0 -0
- data/docs/assets/transparent.png +0 -0
- data/docs/index.html +32 -0
- data/docs/verification.md +6 -4
- data/examples/generate.rb +49 -0
- data/lib/flipbook/apng/writer.rb +99 -56
- data/lib/flipbook/diff.rb +28 -17
- data/lib/flipbook/gif/lzw.rb +0 -66
- data/lib/flipbook/gif/reader.rb +205 -126
- data/lib/flipbook/gif/writer.rb +50 -52
- data/lib/flipbook/timing.rb +2 -13
- data/lib/flipbook/version.rb +1 -1
- data/lib/flipbook.rb +18 -20
- data/sig/flipbook.rbs +30 -4
- data/sig/tessel.rbs +4 -0
- metadata +11 -3
data/lib/flipbook.rb
CHANGED
|
@@ -1,51 +1,48 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "tessel"
|
|
4
|
-
require "zlib"
|
|
5
4
|
|
|
6
5
|
require_relative "flipbook/version"
|
|
7
6
|
require_relative "flipbook/timing"
|
|
8
|
-
require_relative "flipbook/diff"
|
|
9
7
|
require_relative "flipbook/gif/bit_writer"
|
|
10
8
|
require_relative "flipbook/gif/lzw"
|
|
11
|
-
require_relative "flipbook/gif/writer"
|
|
12
9
|
require_relative "flipbook/gif/reader"
|
|
10
|
+
require_relative "flipbook/gif/writer"
|
|
13
11
|
require_relative "flipbook/apng/writer"
|
|
12
|
+
require_relative "flipbook/diff"
|
|
14
13
|
|
|
15
14
|
module Flipbook
|
|
16
15
|
class Error < StandardError; end
|
|
17
16
|
|
|
18
17
|
module_function
|
|
19
18
|
|
|
20
|
-
def write(path, frames, fps: nil, delay: nil, loop: true,
|
|
19
|
+
def write(path, frames, fps: nil, delay: nil, loop: true, colors: 256, palette: :global, dither: :floyd_steinberg, optimize: true)
|
|
21
20
|
raise TypeError, "frames must be an array of Tessel::Image values" unless frames.is_a?(Array) && frames.all? { |frame| frame.is_a?(Tessel::Image) }
|
|
22
21
|
raise ArgumentError, "at least one frame is required" if frames.empty?
|
|
23
22
|
raise ArgumentError, "frame dimensions must match" unless frames.all? { |frame| frame.width == frames.first.width && frame.height == frames.first.height }
|
|
24
23
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
24
|
+
extension = File.extname(String(path)).downcase
|
|
25
|
+
raise ArgumentError, "output path must end in .gif, .png, or .apng" unless %w[.gif .png .apng].include?(extension)
|
|
26
|
+
|
|
27
|
+
delays = Timing.delays(frames.length, fps:, delay:, warn_short: extension == ".gif")
|
|
28
|
+
if extension == ".gif"
|
|
29
29
|
write_gif(path, frames, delays, loop:, colors:, palette:, dither:, optimize:)
|
|
30
|
-
when "png", "apng"
|
|
31
|
-
APNG::Writer.open(path, width: frames.first.width, height: frames.first.height, loop:, optimize:, **options) do |writer|
|
|
32
|
-
frames.zip(delays).each { |frame, frame_delay| writer.add(frame, delay: frame_delay) }
|
|
33
|
-
end
|
|
34
30
|
else
|
|
35
|
-
|
|
31
|
+
APNG::Writer.open(path, width: frames.first.width, height: frames.first.height, loop:, optimize:) do |writer|
|
|
32
|
+
frames.zip(delays).each do |frame, frame_delay|
|
|
33
|
+
writer.add(frame, delay: frame_delay)
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
36
|
end
|
|
37
37
|
end
|
|
38
38
|
|
|
39
|
-
def read(path)
|
|
40
|
-
|
|
41
|
-
raise Error, "not a GIF file" unless bytes.start_with?("GIF87a", "GIF89a")
|
|
42
|
-
|
|
43
|
-
GIF::Reader.new(bytes).frames
|
|
39
|
+
def read(path, **options)
|
|
40
|
+
GIF::Reader.read(path, **options)
|
|
44
41
|
end
|
|
45
42
|
|
|
46
43
|
def write_gif(path, frames, delays, loop:, colors:, palette:, dither:, optimize:)
|
|
47
44
|
has_transparency = frames.any? { |frame| frame.bytes.bytes.each_slice(4).any? { |_, _, _, alpha| alpha.zero? } }
|
|
48
|
-
transparent =
|
|
45
|
+
transparent = has_transparency
|
|
49
46
|
global_palette = if palette == :per_frame
|
|
50
47
|
raise ArgumentError, "per-frame palettes cannot preserve transparency across frames" if has_transparency
|
|
51
48
|
:per_frame
|
|
@@ -65,9 +62,10 @@ module Flipbook
|
|
|
65
62
|
end
|
|
66
63
|
transparent_index = global_palette.is_a?(Array) && transparent ? global_palette.length - 1 : nil
|
|
67
64
|
|
|
68
|
-
GIF::Writer.open(path, width: frames.first.width, height: frames.first.height, loop:, palette: global_palette, colors:, dither:,
|
|
65
|
+
GIF::Writer.open(path, width: frames.first.width, height: frames.first.height, loop:, palette: global_palette, colors:, dither:, transparent_index:, optimize:) do |writer|
|
|
69
66
|
frames.zip(delays).each { |frame, frame_delay| writer.add(frame, delay: frame_delay) }
|
|
70
67
|
end
|
|
71
68
|
end
|
|
72
69
|
private_class_method :write_gif
|
|
70
|
+
|
|
73
71
|
end
|
data/sig/flipbook.rbs
CHANGED
|
@@ -1,12 +1,38 @@
|
|
|
1
1
|
module Flipbook
|
|
2
2
|
VERSION: String
|
|
3
3
|
|
|
4
|
-
def self.write: (String path, Array[Tessel::Image] frames, ?fps: Numeric?, ?delay: Numeric | Array[Numeric]?, ?loop: bool | Integer, ?
|
|
5
|
-
def self.read: (String path) -> Array[Tessel::Image]
|
|
4
|
+
def self.write: (String path, Array[Tessel::Image] frames, ?fps: Numeric?, ?delay: Numeric | Array[Numeric]?, ?loop: bool | Integer, ?colors: Integer, ?palette: Symbol | Array[[Integer, Integer, Integer]], ?dither: Symbol, ?optimize: bool) -> String
|
|
5
|
+
def self.read: (String path, ?max_pixels: Integer, ?max_frames: Integer, ?max_total_pixels: Integer) -> Array[Tessel::Image]
|
|
6
6
|
|
|
7
7
|
module Timing
|
|
8
|
-
def self.delays: (Integer count, ?fps: Numeric?, ?delay: Numeric | Array[Numeric]?) -> Array[Rational]
|
|
8
|
+
def self.delays: (Integer count, ?fps: Numeric?, ?delay: Numeric | Array[Numeric]?, ?warn_short: bool) -> Array[Rational]
|
|
9
9
|
def self.centiseconds: (Array[Numeric] delays) -> Array[Integer]
|
|
10
|
-
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
module APNG
|
|
13
|
+
class Writer
|
|
14
|
+
def self.open: (String path, width: Integer, height: Integer, ?loop: bool | Integer, ?optimize: bool) { (Writer) -> void } -> String
|
|
15
|
+
def add: (Tessel::Image image, delay: Numeric) -> Writer
|
|
16
|
+
def close: () -> Writer
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
module GIF
|
|
21
|
+
class Writer
|
|
22
|
+
def self.open: (String path, width: Integer, height: Integer, ?loop: bool | Integer, ?palette: Symbol | Array[[Integer, Integer, Integer]], ?colors: Integer, ?dither: Symbol, ?transparent_index: Integer?, ?optimize: bool) { (Writer) -> void } -> String
|
|
23
|
+
def add: (Tessel::Image image, delay: Numeric) -> Writer
|
|
24
|
+
def close: () -> Writer
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
class Reader
|
|
28
|
+
def self.read: (String path, ?max_pixels: Integer, ?max_frames: Integer, ?max_total_pixels: Integer) -> Array[Tessel::Image]
|
|
29
|
+
def read: () -> Array[Tessel::Image]
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def self.read: (String path, ?max_pixels: Integer, ?max_frames: Integer, ?max_total_pixels: Integer) -> Array[Tessel::Image]
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
module Diff
|
|
36
|
+
def self.bounds: (Tessel::Image previous, Tessel::Image current) -> [Integer, Integer, Integer, Integer]?
|
|
11
37
|
end
|
|
12
38
|
end
|
data/sig/tessel.rbs
ADDED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: flipbook
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.4.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Yudai Takada
|
|
@@ -58,7 +58,8 @@ dependencies:
|
|
|
58
58
|
- - "~>"
|
|
59
59
|
- !ruby/object:Gem::Version
|
|
60
60
|
version: '3.6'
|
|
61
|
-
description: Flipbook encodes
|
|
61
|
+
description: Flipbook encodes optimized GIF and APNG animations and reads composited
|
|
62
|
+
GIF frames from Tessel images.
|
|
62
63
|
email:
|
|
63
64
|
- t.yudai92@gmail.com
|
|
64
65
|
executables: []
|
|
@@ -69,7 +70,13 @@ files:
|
|
|
69
70
|
- LICENSE.txt
|
|
70
71
|
- README.md
|
|
71
72
|
- Rakefile
|
|
73
|
+
- docs/assets/gradient.gif
|
|
74
|
+
- docs/assets/spinning-square.gif
|
|
75
|
+
- docs/assets/transparent.gif
|
|
76
|
+
- docs/assets/transparent.png
|
|
77
|
+
- docs/index.html
|
|
72
78
|
- docs/verification.md
|
|
79
|
+
- examples/generate.rb
|
|
73
80
|
- lib/flipbook.rb
|
|
74
81
|
- lib/flipbook/apng/writer.rb
|
|
75
82
|
- lib/flipbook/diff.rb
|
|
@@ -80,6 +87,7 @@ files:
|
|
|
80
87
|
- lib/flipbook/timing.rb
|
|
81
88
|
- lib/flipbook/version.rb
|
|
82
89
|
- sig/flipbook.rbs
|
|
90
|
+
- sig/tessel.rbs
|
|
83
91
|
homepage: https://github.com/rbgfx/flipbook
|
|
84
92
|
licenses:
|
|
85
93
|
- MIT
|
|
@@ -104,5 +112,5 @@ requirements: []
|
|
|
104
112
|
rubygems_version: 3.4.19
|
|
105
113
|
signing_key:
|
|
106
114
|
specification_version: 4
|
|
107
|
-
summary: Write and read GIF and APNG
|
|
115
|
+
summary: Write and read animated GIF and APNG files in Ruby
|
|
108
116
|
test_files: []
|