package gifmeme import ( "bytes" "github.com/andybons/gogif" "github.com/rs/zerolog/log" "github.com/velour/catbase/config" "github.com/velour/catbase/plugins/meme" "image" "image/gif" "net/http" "time" ) const tpc = 5 const nColors = 64 func getGif(c *config.Config, s meme.Specification) ([]byte, int, int, error) { t0 := time.Now() resp, err := http.Get(s.ImageURL) if err != nil { return nil, 0, 0, err } g, err := gif.DecodeAll(resp.Body) if err != nil { return nil, 0, 0, err } log.Debug().Msgf("Len before adding text: %d", len(g.Image)) err = addText(c, g, s.Configs) log.Debug().Msgf("Len after adding text: %d", len(g.Image)) log.Debug().Msgf("%v elapsed adding text", time.Now().Sub(t0)) if err != nil { return nil, 0, 0, err } out := bytes.NewBuffer([]byte{}) t1 := time.Now() err = gif.EncodeAll(out, g) if err != nil { return nil, 0, 0, err } log.Debug().Msgf("%v elapsed encoding gif", time.Now().Sub(t1)) w, h := g.Image[0].Bounds().Max.X, g.Image[0].Bounds().Max.Y return out.Bytes(), w, h, nil } func addText(c *config.Config, g *gif.GIF, text []meme.Text) error { totalTime := calcTime(g) framesNeeded, avgFrames := calcTextTime(text) loops := 1 if totalTime < framesNeeded { loops = framesNeeded / totalTime totalTime = loops * totalTime } else { framesNeeded = totalTime } log.Debug().Msgf("We need %d loops for %d total time with %d frames", loops, totalTime, framesNeeded) frames := []*image.Paletted{} delays := []int{} idx := 0 toicfg := meme.MkTextOnImageConfig(c) bounds := g.Image[0].Bounds() maxSz := max(bounds.Max.X, bounds.Max.Y) if toicfg.MaxSz > float64(maxSz) { toicfg.MaxSz = float64(maxSz) } currText := 0 currTextFrames := 0 log.Debug().Msgf("Writing text configs %+v on image", text) log.Debug().Msgf("Starting text with %+v", text[currText]) for i := 0; i < framesNeeded; i++ { img := g.Image[idx] i, err := meme.TextOnImage(toicfg, img, []meme.Text{text[currText]}) if err != nil { log.Error().Err(err).Msg("error encoding gif") return err } frames = append(frames, imgToPaletted(i)) delays = append(delays, g.Delay[idx]) idx = (idx + 1) % len(g.Image) currTextFrames++ if currTextFrames > avgFrames { currText = (currText + 1) % len(text) currTextFrames = 0 log.Debug().Msgf("Switching text to %+v", text[currText]) } } g.Image = frames g.Delay = delays g.Disposal = nil g.Config = image.Config{} return nil } func calcTextTime(text []meme.Text) (int, int) { total := 1 for _, frame := range text { total += tpc * len(frame.Text) } return total, total / len(text) } func calcTime(g *gif.GIF) int { total := 0 for _, frameDelay := range g.Delay { total += frameDelay } return total } func imgToPaletted(i image.Image) *image.Paletted { bounds := i.Bounds() p := image.NewPaletted(bounds, nil) quantizer := gogif.MedianCutQuantizer{NumColor: nColors} quantizer.Quantize(p, bounds, i, image.Point{}) return p } func max(x, y int) int { if x > y { return x } return y }