-
Notifications
You must be signed in to change notification settings - Fork 7
/
Copy pathgif.go
118 lines (101 loc) · 2.82 KB
/
gif.go
1
2
3
4
5
6
7
8
9
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
48
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
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
package dotmatrix
import (
"context"
"image"
"image/color"
"image/gif"
"io"
"time"
)
type GIFPrinter struct {
w io.Writer
c Config
}
func NewGIFPrinter(w io.Writer, c *Config) *GIFPrinter {
return &GIFPrinter{
w: w,
c: mergeConfig(c),
}
}
/*
Print animates a gif
*/
func (p *GIFPrinter) Print(ctx context.Context, giff *gif.GIF) error {
if len(giff.Image) < 1 {
return nil
}
// Only used if we see background disposal methods
bgPallette := []color.Color{color.Transparent}
if giff.Config.ColorModel != nil {
bgPallette = giff.Config.ColorModel.(color.Palette)
}
// The screen is what we flush to the writer on each iteration
screen := redraw(image.NewPaletted(giff.Image[0].Bounds(), bgPallette), p.c.Filter, p.c.Drawer)
rows := screen.Bounds().Dy() / 4
if screen.Bounds().Dy()%4 != 0 {
rows++
}
for c := 0; giff.LoopCount == 0 || c < giff.LoopCount; c++ {
for i := 0; i < len(giff.Image); i++ {
select {
case <-ctx.Done():
return ctx.Err()
default:
}
delay := time.After(time.Duration(giff.Delay[i]) * time.Second / 100)
frame := redraw(giff.Image[i], p.c.Filter, p.c.Drawer)
switch giff.Disposal[i] {
case gif.DisposalPrevious: // Dispose previous essentially means draw then undo
temp := image.NewPaletted(screen.Bounds(), screen.Palette)
copy(temp.Pix, screen.Pix)
p.drawOver(screen, frame)
if err := flush(p.w, screen, p.c.Flusher); err != nil {
return err
}
<-delay
screen = temp
case gif.DisposalBackground: // Dispose background replaces everything just drawn with the background canvas
background := redraw(image.NewPaletted(frame.Bounds(), bgPallette), p.c.Filter, p.c.Drawer)
p.drawExact(screen, background)
temp := image.NewPaletted(screen.Bounds(), screen.Palette)
copy(temp.Pix, screen.Pix)
p.drawOver(screen, frame)
if err := flush(p.w, screen, p.c.Flusher); err != nil {
return err
}
<-delay
screen = temp
default: // Dispose none or undefined means we just draw what we got over top
p.drawOver(screen, frame)
if err := flush(p.w, screen, p.c.Flusher); err != nil {
return err
}
<-delay
}
p.c.Reset(p.w, rows)
}
}
return nil
}
// Draws any non-transparent pixels into target
func (p *GIFPrinter) drawOver(target *image.Paletted, source image.Image) {
bounds := source.Bounds()
for y := bounds.Min.Y; y < bounds.Max.Y; y++ {
for x := bounds.Min.X; x < bounds.Max.X; x++ {
c := source.At(x, y)
if c == color.Transparent {
continue
}
target.Set(x, y, c)
}
}
}
// Draws pixels into target, including transparent ones.
func (p *GIFPrinter) drawExact(target *image.Paletted, source image.Image) {
bounds := source.Bounds()
for y := bounds.Min.Y; y < bounds.Max.Y; y++ {
for x := bounds.Min.X; x < bounds.Max.X; x++ {
target.Set(x, y, source.At(x, y))
}
}
}