aboutsummaryrefslogtreecommitdiff
path: root/unicorn/Unicorn2.go
blob: 5dcc6c2d6483fe179fd9575551f9d49f830f5aa0 (plain)
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
// Version 2 of unicorn, uses gif.GIF as store of pixels
// & a separate goroutine to render it so it can do
// animated GIFs
package unicorn

import (
	"image"
	"image/color/palette"
	"image/gif"
	"time"
)

// Unicorn2 ...
// Interface for interacting with the Unicorn HAT HD
// Implemented by both real & fake unicorns.
type Unicorn2 interface {
	// Change the image
	GetGif() *gif.GIF
	SetGif(*gif.GIF)

	// Starts the rendering goroutine
	StartRender() chan bool

	// Must be implemented to actually render the image to device
	renderImage(image.Image)

	// Required for os to not think we're stuck
	MainLoop()
}

// BaseUnicorn2 ...
// Common to both real & fake unicorns!
// timing code for rendering & stopping rendering
type BaseUnicorn2 struct {
	g *gif.GIF
}

func (u *BaseUnicorn2) GetGif() *gif.GIF {
	return u.g
}

func (u *BaseUnicorn2) SetGif(g *gif.GIF) {
	u.g = g
}

func NewBaseUnicorn2() *BaseUnicorn2 {
	im := image.NewPaletted(
		image.Rect(0, 0, 16, 16),
		palette.WebSafe)

	gf := &gif.GIF{
		Image:           []*image.Paletted{im},
		Delay:           []int{50},
		Disposal:        []byte{gif.DisposalBackground},
		BackgroundIndex: 0, // This is black in the websafe palette
	}

	return &BaseUnicorn2{
		g: gf,
	}
}

// StartRenderBase ...
// Deals with the timing aspect of animated GIFs
func (u *BaseUnicorn2) StartRenderBase(renderImage func(image.Image)) chan bool {
	stopChan := make(chan bool)
	go func() {
		timer := time.NewTimer(0)
		imageIndex := 0
		running := true
		for running {
			select {
			case <-stopChan:
				timer.Stop()
				running = false
			case <-timer.C:
				gf := u.GetGif()

				// Image could change underneath us, but there should always be 1 image at least.
				if imageIndex >= len(gf.Image) {
					imageIndex = 0
				}

				im := gf.Image[imageIndex]
				delay := gf.Delay[imageIndex] //100ths of a second, 10^-2
				renderImage(im)

				timer.Reset(time.Duration(delay * 10000000)) // nanoseconds 10^-9 sec
				imageIndex = (imageIndex + 1) % len(gf.Image)
			}
		}
	}()
	return stopChan
}