Class: Flipbook::GIF::Writer

Inherits:
Object
  • Object
show all
Defined in:
lib/flipbook/gif/writer.rb,
sig/flipbook.rbs

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(io, width:, height:, loop: true, palette: :per_frame, colors: 256, dither: :floyd_steinberg, transparent_index: nil, optimize: true) ⇒ Writer

Returns a new instance of Writer.

Raises:

  • (ArgumentError)


10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
# File 'lib/flipbook/gif/writer.rb', line 10

def initialize(io, width:, height:, loop: true, palette: :per_frame, colors: 256, dither: :floyd_steinberg, transparent_index: nil, optimize: true)
  @io = io
  @width = Integer(width)
  @height = Integer(height)
  raise ArgumentError, "GIF dimensions must be between 1 and 65535" unless @width.between?(1, 65_535) && @height.between?(1, 65_535)
  raise ArgumentError, "colors must be between 2 and 256" unless colors.is_a?(Integer) && colors.between?(2, 256)
  raise ArgumentError, "unknown dither: #{dither}" unless %i[none ordered floyd_steinberg].include?(dither)
  raise TypeError, "optimize must be true or false" unless [true, false].include?(optimize)

  @loop = loop_value(loop)
  @colors = colors
  @dither = dither
  @optimize = optimize
  @previous_image = nil
  @elapsed = Rational(0)
  @rounded_delay = 0
  @closed = false
  @warned_short_delay = false
  @frame_count = 0
  @pending_frame = nil
  @canvas_cleared = false
  @palette_mode = palette
  if palette == :per_frame
    raise ArgumentError, "transparent_index is only valid with a global palette" unless transparent_index.nil?
    @global_palette = nil
    @global_transparent_index = 0
  else
    raise TypeError, "palette must be :per_frame or an RGB array" unless palette.is_a?(Array)
    @global_palette = validate_palette(palette)
    if transparent_index
      raise ArgumentError, "transparent_index must be the last palette entry" unless transparent_index == @global_palette.length - 1
      raise ArgumentError, "transparent_index is out of range" unless transparent_index.between?(0, @global_palette.length - 1)
      raise ArgumentError, "global palette must include opaque and transparent colors" if @global_palette.length < 2
    end
    @global_transparent_index = transparent_index
  end
  write_header
end

Class Method Details

.open(path, **options) {|arg0| ... } ⇒ String

Parameters:

  • path (String)
  • width: (Integer)
  • height: (Integer)
  • loop: (bool, Integer)
  • palette: (Symbol, Array[[Integer, Integer, Integer]])
  • colors: (Integer)
  • dither: (Symbol)
  • transparent_index: (Integer, nil)
  • optimize: (Boolean)

Yields:

Yield Parameters:

Yield Returns:

  • (void)

Returns:

  • (String)


6
7
8
# File 'lib/flipbook/gif/writer.rb', line 6

def self.open(path, **options, &block)
  Output.open(self, path, **options, &block)
end

Instance Method Details

#add(image, delay:) ⇒ Writer

Parameters:

Returns:

Raises:

  • (IOError)


49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
# File 'lib/flipbook/gif/writer.rb', line 49

def add(image, delay:)
  raise IOError, "GIF writer is closed" if @closed
  raise TypeError, "frame must be a Tessel::Image" unless image.is_a?(Tessel::Image)
  raise ArgumentError, "frame dimensions must match the logical screen" unless image.width == @width && image.height == @height

  seconds = Timing.duration(delay)
  if seconds < Rational(1, 50) && !@warned_short_delay
    warn("Flipbook: delays below 1/50 second may be clamped by GIF viewers")
    @warned_short_delay = true
  end
  transparent_pixels = image.bytes.bytes.each_slice(4).any? { |_, _, _, alpha| alpha.zero? }
  if @pending_frame
    disposal = (@pending_frame[:transparent] || transparent_pixels) ? 2 : 1
    write_pending(disposal)
    @canvas_cleared = disposal == 2
  end

  elapsed = @elapsed + seconds
  target_delay = (elapsed * 100).floor
  frame_delay = target_delay - @rounded_delay
  raise ArgumentError, "GIF frame delays cannot exceed 655.35 seconds" if frame_delay > 65_535
  rectangle = if @optimize && @previous_image && !transparent_pixels && !@canvas_cleared
    Diff.bounds(@previous_image, image) || [0, 0, 1, 1]
  else
    [0, 0, @width, @height]
  end
  @pending_frame = { image: image.dup, delay: frame_delay, rectangle:, transparent: transparent_pixels }
  @elapsed = elapsed
  @rounded_delay = target_delay
  @frame_count += 1
  @previous_image = image.dup
  self
end

#close ⇒ Writer

Returns:

Raises:



83
84
85
86
87
88
89
90
91
92
# File 'lib/flipbook/gif/writer.rb', line 83

def close
  return if @closed
  raise Error, "GIF contains no frames" if @frame_count.zero?

  write_pending(@pending_frame[:transparent] ? 2 : 1)
  @io << ";".b
  @closed = true
  Output.finish(@io, @target_path, @target_mode) if @owns_io
  self
end