unicornpaint

A web-based painting app for raspberry PI and pimoroni Unicorn Hat HD
Log | Files | Refs | README

Server2.go (6366B)


      1 package main
      2 
      3 import (
      4 	"bytes"
      5 	"image"
      6 	"image/color"
      7 	"image/gif"
      8 	"os"
      9 
     10 	"io/ioutil"
     11 	"log"
     12 	"net/http"
     13 	"path"
     14 
     15 	"github.com/gorilla/websocket"
     16 
     17 	"github.com/MFAshby/unicornpaint/unicorn"
     18 )
     19 
     20 var (
     21 	un unicorn.Unicorn2
     22 
     23 	upgrader = websocket.Upgrader{
     24 		ReadBufferSize:  1024,
     25 		WriteBufferSize: 1024,
     26 		CheckOrigin:     func(*http.Request) bool { return true },
     27 	}
     28 	delCh       = make(chan *websocket.Conn)
     29 	addCh       = make(chan *websocket.Conn)
     30 	broadcastCh = make(chan interface{})
     31 	clients     = map[*websocket.Conn]bool{}
     32 )
     33 
     34 // Types of commands
     35 type commandType string
     36 
     37 const (
     38 	noop        commandType = "NO_OP"
     39 	setPixel    commandType = "SET_PIXEL"
     40 	clear       commandType = "CLEAR"
     41 	save        commandType = "SAVE"
     42 	load        commandType = "LOAD"
     43 	addFrame    commandType = "ADD_FRAME"
     44 	removeFrame commandType = "REMOVE_FRAME"
     45 )
     46 
     47 type Command struct {
     48 	Type     commandType `json:"type"`
     49 	X        uint8       `json:"x"`
     50 	Y        uint8       `json:"y"`
     51 	R        uint8       `json:"r"`
     52 	G        uint8       `json:"g"`
     53 	B        uint8       `json:"b"`
     54 	Frame    int         `json:"frame"`
     55 	Delay    int         `json:"delay"`
     56 	SaveName string      `json:"saveName"`
     57 }
     58 
     59 const (
     60 	savesDir = "saves"
     61 )
     62 
     63 type State struct {
     64 	Saves     []string `json:"saves"`
     65 	ImageData []byte   `json:"imageData"`
     66 }
     67 
     68 func getGifBytes() ([]byte, error) {
     69 	g := un.GetGif()
     70 	buf := &bytes.Buffer{}
     71 	err := gif.EncodeAll(buf, g)
     72 	return buf.Bytes(), err
     73 }
     74 
     75 func getState() *State {
     76 	infos, err := ioutil.ReadDir(savesDir)
     77 	if err != nil {
     78 		log.Printf("Error reading saves dir %v", err)
     79 	}
     80 	saveFileNames := make([]string, len(infos))
     81 	for ix, info := range infos {
     82 		saveFileNames[ix] = info.Name()
     83 	}
     84 
     85 	// Write out as GIF file
     86 	gifBytes, err := getGifBytes()
     87 	if err != nil {
     88 		log.Printf("Error getting GIF bytes %v", err)
     89 	}
     90 
     91 	return &State{
     92 		ImageData: gifBytes,
     93 		Saves:     saveFileNames,
     94 	}
     95 }
     96 
     97 func savePic(saveFileName string) {
     98 	f, err := os.Create(path.Join(savesDir, saveFileName))
     99 	if err != nil {
    100 		log.Printf("Failed to open file for saving %v", err)
    101 		return
    102 	}
    103 	defer f.Close()
    104 
    105 	g := un.GetGif()
    106 	err = gif.EncodeAll(f, g)
    107 }
    108 
    109 func loadPic(saveFileName string) {
    110 	f, err := os.Open(path.Join(savesDir, saveFileName))
    111 	if err != nil {
    112 		log.Printf("Failed to read file %v", err)
    113 		return
    114 	}
    115 	defer f.Close()
    116 
    117 	g, err := gif.DecodeAll(f)
    118 	if err != nil {
    119 		log.Printf("Failed to decode GIF file %v", err)
    120 		return
    121 	}
    122 
    123 	un.SetGif(g)
    124 }
    125 
    126 func doSetPixel(x, y, r, g, b uint8, frame int) {
    127 	gf := un.GetGif()
    128 	if int(frame) >= len(gf.Image) {
    129 		log.Printf("Tried to set pixel in frame out of range %v", frame)
    130 		return
    131 	}
    132 	im := gf.Image[frame]
    133 	im.Set(int(x), int(y), color.RGBA{
    134 		R: uint8(r),
    135 		G: uint8(g),
    136 		B: uint8(b),
    137 		A: uint8(255),
    138 	})
    139 }
    140 
    141 func doAddFrame(frame int, delay int) {
    142 	gf := un.GetGif()
    143 	if frame < 1 || frame > len(gf.Image) {
    144 		log.Printf("Trying to add frame in invalid position %v", frame)
    145 		return
    146 	}
    147 
    148 	// Copy the image before it
    149 	sourceImage := gf.Image[frame-1]
    150 
    151 	buf := &bytes.Buffer{}
    152 	if err := gif.Encode(buf, sourceImage, nil); err != nil {
    153 		log.Printf("Failed to encode image %v", err)
    154 		return
    155 	}
    156 	im, err := gif.Decode(buf)
    157 	if err != nil {
    158 		log.Printf("Failed to decode image %v", err)
    159 		return
    160 	}
    161 
    162 	newImage, ok := im.(*image.Paletted)
    163 	if !ok {
    164 		log.Printf("Wrong image type %v", err)
    165 	}
    166 
    167 	gf.Image = append(gf.Image[:frame], append([]*image.Paletted{newImage}, gf.Image[frame:]...)...)
    168 	gf.Delay = append(gf.Delay[:frame], append([]int{delay}, gf.Delay[frame:]...)...)
    169 	gf.Disposal = append(gf.Disposal[:frame], append([]byte{gif.DisposalBackground}, gf.Disposal[frame:]...)...)
    170 }
    171 
    172 func doRemoveFrame(frame int) {
    173 	// Can't remove the first frame
    174 	gf := un.GetGif()
    175 	frameCount := len(gf.Image)
    176 	if frame < 0 || frame >= frameCount {
    177 		log.Printf("Trying to remove frame %v which is invalid", frame)
    178 		return
    179 	}
    180 
    181 	if frameCount == 1 {
    182 		log.Printf("Trying to remove the last frame, ignoring")
    183 		return
    184 	}
    185 
    186 	gf.Image = append(gf.Image[:frame], gf.Image[frame+1:]...)
    187 	gf.Delay = append(gf.Delay[:frame], gf.Delay[frame+1:]...)
    188 	gf.Disposal = append(gf.Disposal[:frame], gf.Disposal[frame+1:]...)
    189 }
    190 
    191 func doClear(frame int) {
    192 	gf := un.GetGif()
    193 	if frame >= len(gf.Image) {
    194 		log.Printf("Trying to clear invalid frame %v", frame)
    195 		return
    196 	}
    197 
    198 	im := gf.Image[frame]
    199 	b := im.Bounds()
    200 	width := b.Dx()
    201 	height := b.Dy()
    202 	for x := 0; x < width; x++ {
    203 		for y := 0; y < height; y++ {
    204 			im.SetColorIndex(x, y, 0) // 0 in WebSafe colors is black
    205 		}
    206 	}
    207 }
    208 
    209 func upgradeHandler(w http.ResponseWriter, r *http.Request) {
    210 	conn, err := upgrader.Upgrade(w, r, nil)
    211 	if err != nil {
    212 		log.Printf("Failed to upgrade someone %v", err)
    213 		return
    214 	}
    215 
    216 	// Add you to my list, remember to remove later
    217 	addCh <- conn
    218 	defer func() { delCh <- conn }()
    219 
    220 	// Get you up to speed
    221 	err = conn.WriteJSON(getState())
    222 	if err != nil {
    223 		log.Printf("Failed to send initial state %v", err)
    224 		return
    225 	}
    226 
    227 	// Read & execute commands in a loop until error
    228 	for {
    229 		cmd := Command{}
    230 		err = conn.ReadJSON(&cmd)
    231 		if err != nil {
    232 			log.Printf("Error reading from client %v", err)
    233 			break
    234 		}
    235 
    236 		switch cmd.Type {
    237 		case noop:
    238 			// Don't do anything
    239 		case setPixel:
    240 			doSetPixel(cmd.X, cmd.Y, cmd.R, cmd.G, cmd.B, cmd.Frame)
    241 		case clear:
    242 			doClear(cmd.Frame)
    243 		case save:
    244 			savePic(cmd.SaveName)
    245 		case load:
    246 			loadPic(cmd.SaveName)
    247 		case addFrame:
    248 			doAddFrame(cmd.Frame, cmd.Delay)
    249 		case removeFrame:
    250 			doRemoveFrame(cmd.Frame)
    251 		}
    252 		// Pretty much all commands demand a change of state,
    253 		// do a broadcast each time
    254 		broadcastCh <- getState()
    255 	}
    256 }
    257 
    258 func handleClients() {
    259 	for {
    260 		select {
    261 		case c := <-addCh:
    262 			clients[c] = true
    263 		case c := <-delCh:
    264 			delete(clients, c)
    265 		case msg := <-broadcastCh:
    266 			doBroadcast(msg)
    267 		}
    268 	}
    269 }
    270 
    271 func main() {
    272 	uni, err := unicorn.NewUnicorn2()
    273 	if err != nil {
    274 		log.Fatalf("Couldn't get a unicorn :( %v", err)
    275 	}
    276 	un = uni
    277 	un.StartRender()
    278 
    279 	log.Println("Starting server on port 3001")
    280 	http.Handle("/", http.FileServer(http.Dir("build")))
    281 	http.HandleFunc("/ws", upgradeHandler)
    282 	go http.ListenAndServe(":3001", nil)
    283 	go handleClients()
    284 	un.MainLoop()
    285 }
    286 
    287 func doBroadcast(obj interface{}) {
    288 	for client := range clients {
    289 		err := client.WriteJSON(obj)
    290 
    291 		if err != nil {
    292 			log.Printf("Error writing to client, closing %v", err)
    293 			client.Close()
    294 			delete(clients, client)
    295 		}
    296 	}
    297 }