Skip to content

Instantly share code, notes, and snippets.

@JokerCatz JokerCatz/gif.go
Created Dec 1, 2018

Embed
What would you like to do?
rpi-rgb-led-matrix unix socket client by Golang (animation gif reader)
package main
import (
"bufio"
"flag"
"fmt"
"image"
"image/draw"
"image/gif"
"log"
"net"
"os"
"strconv"
"strings"
"time"
)
func helpGetEnvStr(key string, defaultStr string) string {
var tempStr string
tempStr = os.Getenv(key)
if tempStr == "" {
return defaultStr
}
return tempStr
}
func helpGetEnvUint(key string, defaultUint uint) uint {
var tempStr string
tempStr = os.Getenv(key)
if tempStr == "" {
return defaultUint
}
tempInt, err := strconv.Atoi(tempStr)
if err != nil || tempInt < 0 || tempInt > 65535 {
return defaultUint
}
return uint(tempInt)
}
func helperIntAryJoin(input []int, delim string) string {
return strings.Trim(strings.Join(strings.Fields(fmt.Sprint(input)), delim), "[]")
}
func helperFillColor(frameBuffer *[frameIndexMax]byte, byteIndex *int, r byte, g byte, b byte) {
frameBuffer[*byteIndex] = byte(0x20) | byte(r>>4)
*byteIndex++
frameBuffer[*byteIndex] = byte(0x30) | byte(r&0xF)
*byteIndex++
// G
frameBuffer[*byteIndex] = byte(0x40) | byte(g>>4)
*byteIndex++
frameBuffer[*byteIndex] = byte(0x50) | byte(g&0xF)
*byteIndex++
// B
frameBuffer[*byteIndex] = byte(0x60) | byte(b>>4)
*byteIndex++
frameBuffer[*byteIndex] = byte(0x70) | byte(b&0xF)
*byteIndex++
}
const squareWidth = 64 // LED panel size
const squareHeight = 64
const frameIndexMax = squareWidth*squareHeight*3*2 + 2 // width * height * 3color * 2byte + head & tail
var unixDomainSocketClient net.Conn
// flag
var pathGif = helpGetEnvStr("PATH_GIF", "./vendor/2.gif")
var pathSocket = helpGetEnvStr("PATH_SOCKET", "./tmp/socket")
var backgroundLight = helpGetEnvUint("BACKGROUND_LIGHT", 72)
var brightnessDown = helpGetEnvUint("BRIGHTNESS_DOWN", 2)
func init() {
flag.StringVar(&pathGif, "path_gif", pathGif, "gif file path")
flag.StringVar(&pathSocket, "path_socket", pathSocket, "socket path")
flag.UintVar(&backgroundLight, "background_light", backgroundLight, "background_light")
flag.UintVar(&brightnessDown, "brightness_down", brightnessDown, "brightness_down / N")
flag.Parse()
var err error
unixDomainSocketClient, err = net.Dial("unix", pathSocket)
if err != nil {
log.Fatal("Dial error", err)
}
}
func main() {
defer unixDomainSocketClient.Close()
inputFile, err := os.Open(pathGif)
defer inputFile.Close()
if err != nil {
panic(err)
}
gifImg, err := gif.DecodeAll(bufio.NewReader(inputFile))
if err != nil {
panic(err)
}
brightnessDownByte := byte(brightnessDown)
fmt.Printf("loop count : %d \n", gifImg.LoopCount)
fmt.Printf("frame count : %d \n", len(gifImg.Delay))
fmt.Printf("config.width : %d \n", gifImg.Config.Width)
fmt.Printf("config.height : %d \n", gifImg.Config.Height)
fmt.Printf("delay : (%s) \n", helperIntAryJoin(gifImg.Delay, ","))
fmt.Println("Disposal : ", gifImg.Disposal)
fmt.Println("BackgroundIndex : ", gifImg.BackgroundIndex)
disposal := gifImg.Disposal
backgroundR := byte(backgroundLight) // bg R
backgroundG := byte(backgroundLight) // bg G
backgroundB := byte(backgroundLight) // bg B
imgWidth := gifImg.Config.Width // img size
imgHeight := gifImg.Config.Height
offsetLeft := (squareWidth - imgWidth) / 2 // border single side size
offsetTop := (squareHeight - imgHeight) / 2
frameCount := len(gifImg.Image)
var loopCount int
if gifImg.LoopCount == 0 {
loopCount = -1
} else {
loopCount = gifImg.LoopCount
}
delayAry := gifImg.Delay
// init default content : background color ... then skip
frameBuffer := [frameIndexMax]byte{}
/////////start
byteIndex := 0
frameBuffer[byteIndex] = byte(0x80) // head cmd 100_0_0000
byteIndex++
/////////background content
for t := 0; t < squareWidth*squareHeight; t++ {
helperFillColor(&frameBuffer, &byteIndex, backgroundR, backgroundG, backgroundB)
}
frameBuffer[byteIndex] = byte(0xA0) // tail cmd 101_0_0000
/////////end
frameIndex := 0
t := 0
x := 0
y := 0
sourceFrame := gifImg.Image[frameIndex]
outputFrame := image.NewRGBA(image.Rect(0, 0, imgWidth, imgHeight))
for { // frame loop
// clone frame first
if disposal[frameIndex] == 2 { //full frame redraw
outputFrame = image.NewRGBA(image.Rect(0, 0, imgWidth, imgHeight))
draw.Draw(outputFrame, outputFrame.Bounds(), sourceFrame, image.ZP, draw.Over)
} else if disposal[frameIndex] == 1 { //mask background
draw.Draw(outputFrame, outputFrame.Bounds(), sourceFrame, image.ZP, draw.Over)
}
byteIndex = 0
byteIndex++ // skip head cmd 100_0_0000
// top border offset
if offsetTop > 0 {
for t = 0; t < squareWidth*offsetTop; t++ {
byteIndex = byteIndex + 6 // skip RGB x 2byte
}
}
// middle content
for y = 0; y < imgHeight; y++ {
// left border offset
for t = 0; t < offsetLeft; t++ {
byteIndex = byteIndex + 6 // skip RGB x 2byte
}
// image content
for x = 0; x < imgWidth; x++ {
r, g, b, a := outputFrame.At(x, y).RGBA()
if a == 0 { // alpha background
helperFillColor(
&frameBuffer,
&byteIndex,
backgroundR,
backgroundG,
backgroundB,
)
} else {
helperFillColor(
&frameBuffer,
&byteIndex,
byte(r)/brightnessDownByte,
byte(g)/brightnessDownByte,
byte(b)/brightnessDownByte,
)
}
}
// right border offset
for t = 0; t < squareHeight-offsetLeft-imgWidth; t++ {
byteIndex = byteIndex + 6 // skip RGB x 2byte
}
}
// bottom border offset // do nothing
// tail cmd 101_0_0000 // do nothing
_, err := unixDomainSocketClient.Write(frameBuffer[:])
if err != nil {
panic(err)
}
// delayAry[frameIndex] 100 = 1sec , so * 10 => 1000 (GIF spec)
time.Sleep(time.Duration(delayAry[frameIndex]*10) * time.Millisecond) //delay timer
// next round init
frameIndex = (frameIndex + 1) % frameCount
if frameIndex == 0 {
if loopCount > 0 {
loopCount--
} else if loopCount == 0 { // count down break
break
} else { // -1 loop forever
// do nothing
}
}
sourceFrame = gifImg.Image[frameIndex]
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.