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.
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, format: nil, colors: 256, palette: :global, dither: :floyd_steinberg, optimize: true, **options)
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
- delays = Timing.delays(frames.length, fps:, delay:)
26
- extension = format&.to_s&.downcase || File.extname(String(path)).delete_prefix(".").downcase
27
- case extension
28
- when "gif"
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
- raise ArgumentError, "format must be GIF or APNG"
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
- bytes = File.binread(path)
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 = optimize || has_transparency
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:, optimize:, transparent_index:, clear_background: has_transparency) do |writer|
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, ?format: String?, ?colors: Integer, ?palette: Symbol | Array[[Integer, Integer, Integer]], ?dither: Symbol, ?optimize: bool, **untyped) -> String
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
- def self.apng_fraction: (Numeric delay) -> [Integer, Integer]
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
@@ -0,0 +1,4 @@
1
+ module Tessel
2
+ class Image
3
+ end
4
+ end
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.3.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 animated GIF and APNG files from Tessel RGBA images.
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 animations in Ruby
115
+ summary: Write and read animated GIF and APNG files in Ruby
108
116
  test_files: []