package main import ( "flag" "fmt" "image" "image/draw" "image/gif" "log" "net/http" "os" "path" "strings" ) var cacheDirectory string func handleUpload(w http.ResponseWriter, r *http.Request) { inf, header, err := r.FormFile("image") if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } defer inf.Close() if contentType := header.Header.Get("Content-Type"); contentType != "image/gif" { http.Error( w, fmt.Sprintf("Invalid content-type: %s", contentType), http.StatusBadRequest, ) } if !strings.HasSuffix(header.Filename, ".gif") { http.Error(w, "Invalid file type", http.StatusBadRequest) return } inputGif, err := gif.DecodeAll(inf) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } if inputGif.Config.Width != 8 || inputGif.Config.Height != 8 { http.Error( w, fmt.Sprintf("Invalid image size: %d×%d", inputGif.Config.Width, inputGif.Config.Height), http.StatusBadRequest, ) return } outRaw, err := os.Create(path.Join(cacheDirectory, "wallart.bin")) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } defer outRaw.Close() outputGifFile, err := os.Create(path.Join(cacheDirectory, "wallart.gif")) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } defer outputGifFile.Close() // You only get 8 frames max numFrames := len(inputGif.Image) if numFrames > 8 { numFrames = 8 } if numFrames == 0 { http.Error(w, "GIF must have at least one frame", http.StatusBadRequest) } outputGif := gif.GIF{ Image: make([]*image.Paletted, numFrames), Delay: make([]int, numFrames), LoopCount: 0, Disposal: make([]byte, numFrames), Config: inputGif.Config, BackgroundIndex: 0, } imgRect := image.Rectangle{ image.Point{}, image.Point{inputGif.Config.Width, inputGif.Config.Height}, } // Draw the background backgroundImg := image.NewPaletted(imgRect, inputGif.Image[0].Palette) draw.Src.Draw( backgroundImg, imgRect, image.NewUniform(inputGif.Image[0].Palette[inputGif.BackgroundIndex]), image.Point{}, ) previousImg := backgroundImg undisposedImg := backgroundImg for i := 0; i < numFrames; i++ { img := inputGif.Image[i] dst := image.NewPaletted(imgRect, img.Palette) outputGif.Image[i] = dst if img.Bounds().Max.X > 8 || img.Bounds().Max.Y > 8 { http.Error( w, fmt.Sprintf("Invalid frame size: %d×%d", img.Bounds().Max.X, img.Bounds().Max.Y), http.StatusBadRequest, ) return } // Implement GIF disposal modes // Normally you woud want to use draw.Src instead of draw.Over for the first of each of these. // But in this particular application, we want transparency to become black. draw.Src.Draw(dst, imgRect, image.Black, image.Point{}) switch inputGif.Disposal[i] { case gif.DisposalNone: draw.Over.Draw(dst, previousImg.Rect, previousImg, image.Point{}) draw.Over.Draw(dst, img.Rect, img, img.Rect.Min) undisposedImg = dst case gif.DisposalBackground: draw.Over.Draw(dst, backgroundImg.Rect, backgroundImg, image.Point{}) draw.Over.Draw(dst, img.Rect, img, img.Rect.Min) case gif.DisposalPrevious: draw.Over.Draw(dst, undisposedImg.Rect, undisposedImg, image.Point{}) draw.Over.Draw(dst, img.Rect, img, img.Rect.Min) default: draw.Over.Draw(dst, img.Rect, img, img.Rect.Min) } // Dump it to the raw file for y := 0; y < 8; y++ { for x := 0; x < 8; x++ { r, g, b, _ := dst.At(x, y).RGBA() outRaw.Write([]byte{ byte(r >> 8), byte(g >> 8), byte(b >> 8), }) } } outputGif.Delay[i] = 50 outputGif.Disposal[i] = 0 previousImg = dst } if err := gif.EncodeAll(outputGifFile, &outputGif); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } log.Printf("New image successfully uploaded: %d frames", len(outputGif.Image)) } func logRequestHandler(h http.Handler) http.Handler { fn := func(w http.ResponseWriter, r *http.Request) { log.Printf("%s %s %s", r.RemoteAddr, r.Method, r.URL) h.ServeHTTP(w, r) } return http.HandlerFunc(fn) } func main() { listen := flag.String("listen", ":8080", "listen address") web := flag.String("web", "web", "web directory") flag.StringVar(&cacheDirectory, "cache", "cache", "cache directory") flag.Parse() cacheDir := http.Dir(cacheDirectory) http.HandleFunc("/upload", handleUpload) http.Handle("/wallart.bin", http.FileServer(cacheDir)) http.Handle("/wallart.gif", http.FileServer(cacheDir)) http.Handle("/", http.FileServer(http.Dir(*web))) if err := http.ListenAndServe(*listen, logRequestHandler(http.DefaultServeMux)); err != nil { log.Fatal(err) } }