diff --git a/.debpacker.yml b/.debpacker.yml
index 24cd3c0..c32e053 100644
--- a/.debpacker.yml
+++ b/.debpacker.yml
@@ -5,6 +5,7 @@ copy-files:
- "./matrix-package/themes/*:themes"
- "./matrix-package/fonts/*:fonts"
- "./matrix-package/images/*:images"
+ - "./matrix-package/animations/*:animations"
- "./matrix-package/gamepad/public/*:gamepad/public"
- "./matrix-package/emulator/public/*:emulator/public"
version: "0.0.1"
@@ -13,7 +14,7 @@ maintainer: "richard.le.terrier@gmail.com"
systemd-configuration:
user: "root"
after: network.target
- args: ["start", "--log-level", "info", "--gamepad-port", "80", "core", "device", "gamepad", "emulator", "demo", "zigzag", "yumyum", "clock", "draw", "blocks", "getout"]
+ args: ["start", "--log-level", "info", "--gamepad-port", "80", "core", "device", "gamepad", "emulator", "demo", "zigzag", "yumyum", "clock", "draw", "blocks", "getout", "animate"]
stop-command: /bin/kill $MAINPID
restart: always
wanted-by: multi-user.target
diff --git a/Makefile b/Makefile
index c261435..48c1726 100644
--- a/Makefile
+++ b/Makefile
@@ -42,6 +42,7 @@ package:
cp -R themes matrix-package/
cp -R fonts matrix-package/
cp -R images matrix-package/
+ cp -R animations matrix-package/
cp -R gamepad/build/default/. matrix-package/gamepad/public/
cp -R emulator/client/public/. matrix-package/emulator/public/
zip -r matrix.zip matrix-package
diff --git a/README.md b/README.md
index 1ace536..a3b894c 100644
--- a/README.md
+++ b/README.md
@@ -24,6 +24,7 @@ Video game console operating system that displays on a 16*9 RGB LED matrix.
| | Blocks | A puzzle game, score a maximum of points by clearing complete lines. | |
| | Getout | A labyrinth game, try to get out if you can. | |
| | Rollup dice | Random dice generator (https://github.com/gwenker/matrix-rollup-dice). | |
+| | Animate | Player for animations generated with Glediator (http://www.solderlab.de/index.php/software/glediator). | |
## Matrix types
diff --git a/animate/daemon.go b/animate/daemon.go
new file mode 100644
index 0000000..8973b9d
--- /dev/null
+++ b/animate/daemon.go
@@ -0,0 +1,203 @@
+package animate
+
+import (
+ "context"
+ "encoding/json"
+ "fmt"
+ "io/ioutil"
+ "os"
+ "path/filepath"
+ "strings"
+ "time"
+
+ "github.com/ovh/cds/sdk"
+ "github.com/pkg/errors"
+ "github.com/sirupsen/logrus"
+
+ "github.com/richardlt/matrix/sdk-go/common"
+ "github.com/richardlt/matrix/sdk-go/software"
+)
+
+// Start the animate software.
+func Start(uri string) error {
+ logrus.Infof("Start animate for uri %s\n", uri)
+
+ a := &animate{}
+
+ // list animation headers and set index
+ if err := filepath.Walk("./animations", func(path string, info os.FileInfo, err error) error {
+ if !info.IsDir() && strings.HasSuffix(path, ".json") {
+ buf, err := ioutil.ReadFile(path)
+ if err != nil {
+ return sdk.WithStack(err)
+ }
+
+ var h header
+ if err := json.Unmarshal(buf, &h); err != nil {
+ return errors.WithStack(err)
+ }
+
+ a.headers = append(a.headers, h)
+ }
+ return nil
+ }); err != nil {
+ return errors.WithStack(err)
+ }
+
+ return software.Connect(uri, a, true)
+}
+
+type header struct {
+ Name string `json:"name"`
+ Width int `json:"width"`
+ Height int `json:"height"`
+ FPS int `json:"fps"`
+}
+
+type animation []byte
+
+func (a animation) readFrame(width, height, index int) software.Image {
+ pixels := 3 * width * height
+ start := index * pixels
+ end := start + pixels
+ buf := a[start:end]
+
+ var colors []*common.Color
+ mapColors := map[string]uint64{}
+ mask := make([]uint64, width*height)
+ var cursor int
+ for i := range mask {
+ c := common.Color{
+ R: uint64(buf[cursor]),
+ G: uint64(buf[cursor+1]),
+ B: uint64(buf[cursor+2]),
+ A: 1,
+ }
+ key := fmt.Sprintf("%d%d%d", c.R, c.G, c.B)
+ if v, ok := mapColors[key]; !ok {
+ colors = append(colors, &c)
+ mask[i] = uint64(len(colors) - 1)
+ mapColors[key] = mask[i]
+ } else {
+ mask[i] = v
+ }
+ cursor += 3
+ }
+
+ return software.Image{
+ Width: uint64(width),
+ Height: uint64(height),
+ Colors: colors,
+ Mask: mask,
+ }
+}
+
+type animate struct {
+ api software.API
+ layer software.Layer
+ imageDriver *software.ImageDriver
+ cancel context.CancelFunc
+ headers []header
+ index int
+}
+
+func (a *animate) Init(api software.API) (err error) {
+ logrus.Debug("Init animate")
+
+ a.api = api
+
+ i := api.GetImageFromLocal("animate")
+
+ api.SetConfig(software.ConnectRequest_SoftwareData_Config{
+ Logo: &i,
+ MinPlayerCount: 1,
+ MaxPlayerCount: 1,
+ })
+
+ a.layer, err = api.NewLayer()
+ if err != nil {
+ return err
+ }
+
+ a.imageDriver, err = a.layer.NewImageDriver()
+ if err != nil {
+ return err
+ }
+ a.imageDriver.OnEnd(func() { a.api.Print() })
+
+ return api.Ready()
+}
+
+func (a *animate) Start(playerCount uint64) { a.play() }
+
+func (a *animate) Close() { a.reset() }
+
+func (a *animate) ActionReceived(slot uint64, cmd common.Command) {
+ switch cmd {
+ case common.Command_LEFT_UP:
+ if a.index < 1 {
+ a.index = len(a.headers) - 1
+ } else {
+ a.index--
+ }
+ a.play()
+ case common.Command_RIGHT_UP:
+ if a.index+1 == len(a.headers) {
+ a.index = 0
+ } else {
+ a.index++
+ }
+ a.play()
+ }
+}
+
+func (a *animate) reset() {
+ if a.cancel != nil {
+ a.cancel()
+ }
+}
+
+func (a *animate) play() {
+ a.reset()
+
+ a.layer.Clean()
+ a.api.Print()
+
+ if len(a.headers) == 0 {
+ return
+ }
+
+ var anim animation
+ var err error
+ anim, err = ioutil.ReadFile(fmt.Sprintf("./animations/%s", a.headers[a.index].Name))
+ if err != nil {
+ logrus.Error(errors.WithStack(err))
+ return
+ }
+
+ ctx, cancel := context.WithCancel(context.Background())
+ a.cancel = cancel
+
+ h := a.headers[a.index]
+
+ ticker := time.NewTicker(time.Second / time.Duration(h.FPS))
+ defer ticker.Stop()
+ maxIndex := len(anim) / (h.Width * h.Height * 3)
+ index := 0
+ for {
+ select {
+ case <-ctx.Done():
+ return
+ case <-ticker.C:
+ a.imageDriver.Render(
+ anim.readFrame(h.Width, h.Height, index),
+ common.Coord{X: 8, Y: 4}, // middle of the screen
+ )
+ if index+1 == maxIndex {
+ index = 0
+ } else {
+ index++
+ }
+ }
+ }
+}
diff --git a/animations/sample-one.dat b/animations/sample-one.dat
new file mode 100644
index 0000000..2eb434b
Binary files /dev/null and b/animations/sample-one.dat differ
diff --git a/animations/sample-one.json b/animations/sample-one.json
new file mode 100644
index 0000000..7d603f1
--- /dev/null
+++ b/animations/sample-one.json
@@ -0,0 +1,6 @@
+{
+ "name": "sample-one.dat",
+ "width": 16,
+ "height": 9,
+ "fps": 25
+}
\ No newline at end of file
diff --git a/animations/sample-three.dat b/animations/sample-three.dat
new file mode 100644
index 0000000..b7111b7
Binary files /dev/null and b/animations/sample-three.dat differ
diff --git a/animations/sample-three.json b/animations/sample-three.json
new file mode 100644
index 0000000..6e5b6ff
--- /dev/null
+++ b/animations/sample-three.json
@@ -0,0 +1,6 @@
+{
+ "name": "sample-three.dat",
+ "width": 16,
+ "height": 9,
+ "fps": 25
+}
\ No newline at end of file
diff --git a/animations/sample-two.dat b/animations/sample-two.dat
new file mode 100644
index 0000000..4cec814
Binary files /dev/null and b/animations/sample-two.dat differ
diff --git a/animations/sample-two.json b/animations/sample-two.json
new file mode 100644
index 0000000..fb2a0e4
--- /dev/null
+++ b/animations/sample-two.json
@@ -0,0 +1,6 @@
+{
+ "name": "sample-two.dat",
+ "width": 16,
+ "height": 9,
+ "fps": 25
+}
\ No newline at end of file
diff --git a/docs/animate.gif b/docs/animate.gif
new file mode 100644
index 0000000..d5a881d
Binary files /dev/null and b/docs/animate.gif differ
diff --git a/docs/animate.png b/docs/animate.png
new file mode 100644
index 0000000..227393f
Binary files /dev/null and b/docs/animate.png differ
diff --git a/images/animate.json b/images/animate.json
new file mode 100644
index 0000000..cfed9e4
--- /dev/null
+++ b/images/animate.json
@@ -0,0 +1,24 @@
+{
+ "name": "animate",
+ "height": 7,
+ "width": 8,
+ "colors": [
+ { "r": 205, "g": 0, "b": 0, "a": 1 },
+ { "r": 255, "g": 0, "b": 0, "a": 1 },
+ { "r": 0, "g": 205, "b": 0, "a": 1 },
+ { "r": 0, "g": 255, "b": 0, "a": 1 },
+ { "r": 0, "g": 0, "b": 205, "a": 1 },
+ { "r": 0, "g": 0, "b": 255, "a": 1 },
+ { "r": 255, "g": 255, "b": 0, "a": 1 },
+ { "r": 205, "g": 205, "b": 0, "a": 1 }
+ ],
+ "mask": [
+ 3, 2, 3, 7, 6, 7, 5, 4,
+ 1, 3, 2, 3, 7, 6, 7, 5,
+ 0, 1, 3, 2, 3, 7, 6, 7,
+ 1, 0, 1, 3, 2, 3, 7, 6,
+ 5, 1, 0, 1, 3, 2, 3, 7,
+ 4, 5, 1, 0, 1, 3, 2, 3,
+ 5, 4, 5, 1, 0, 1, 3, 2
+ ]
+}
diff --git a/main.go b/main.go
index 2bc55d1..f7a65b5 100644
--- a/main.go
+++ b/main.go
@@ -5,6 +5,10 @@ import (
"os"
"github.com/pkg/errors"
+ "github.com/sirupsen/logrus"
+ cli "gopkg.in/urfave/cli.v1"
+
+ "github.com/richardlt/matrix/animate"
"github.com/richardlt/matrix/blocks"
"github.com/richardlt/matrix/clock"
"github.com/richardlt/matrix/core"
@@ -16,8 +20,6 @@ import (
"github.com/richardlt/matrix/getout"
"github.com/richardlt/matrix/yumyum"
"github.com/richardlt/matrix/zigzag"
- "github.com/sirupsen/logrus"
- cli "gopkg.in/urfave/cli.v1"
)
func main() {
@@ -98,6 +100,8 @@ func startAction(c *cli.Context) error {
cs = append(cs, component(func() error { return blocks.Start(c.String("core-uri")) }))
case "getout":
cs = append(cs, component(func() error { return getout.Start(c.String("core-uri")) }))
+ case "animate":
+ cs = append(cs, component(func() error { return animate.Start(c.String("core-uri")) }))
default:
return errors.New("Invalid given component name")
}