Last active
February 16, 2019 23:54
-
-
Save jphastings/6d664eeea6a5d98b36316bc4cf118b96 to your computer and use it in GitHub Desktop.
Animation gif looper
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package main | |
/* | |
Some really shonky code that looks for looping moments within a set of frames from animated TV shows. | |
Find out more here: https://twitter.com/jphastings/status/1096915446702489600 | |
*/ | |
import ( | |
"fmt" | |
"github.com/Nr90/imgsim" | |
"github.com/ericpauley/go-quantize/quantize" | |
"image" | |
"image/color" | |
"image/draw" | |
"image/gif" | |
"image/png" | |
_ "image/png" | |
"log" | |
"math" | |
"os" | |
"path/filepath" | |
"sort" | |
) | |
const ( | |
colours = 256 | |
minFrames = 61 | |
) | |
func main() { | |
fps := 23.976 | |
images, err := filepath.Glob("*.png") | |
if err != nil { | |
log.Fatal(err) | |
} | |
sort.Strings(images) | |
loop, err := findLoop(images, minFrames) | |
if err != nil { | |
log.Fatal(err) | |
} | |
fmt.Printf("%d frames; including %s to %s\n", len(loop), loop[0], loop[len(loop) - 1]) | |
if err := createGif(loop, fps); err != nil { | |
log.Fatal(err) | |
} | |
} | |
func createGif(images []string, fps float64) error { | |
delay := int(math.Round(100 / fps)) | |
f, err := os.Create("out.gif") | |
if err != nil { | |
return err | |
} | |
defer f.Close() | |
// newImage := resize.Resize(160, 0, original_image, resize.Lanczos3) | |
first, err := openPNG(images[0]) | |
if err != nil { | |
return err | |
} | |
q := quantize.MedianCutQuantizer{ | |
Aggregation: quantize.Mean, | |
} | |
pal := q.Quantize(make([]color.Color, 0, colours), first) | |
g := &gif.GIF{ | |
LoopCount: 0, | |
} | |
for _, imagePath := range images { | |
img, err := openPNG(imagePath) | |
if err != nil { | |
return err | |
} | |
b := img.Bounds() | |
pm := image.NewPaletted(b, pal) | |
draw.FloydSteinberg.Draw(pm, b, img, image.ZP) | |
g.Image = append(g.Image, pm) | |
g.Delay = append(g.Delay, delay) | |
} | |
return gif.EncodeAll(f, g) | |
} | |
func findLoop(images []string, skipFrames int) ([]string, error) { | |
hashes := make(map[imgsim.Hash]int) | |
for i, imagePath := range images { | |
hash, err := hash(imagePath) | |
if err != nil { | |
log.Fatal(err) | |
} | |
prevI, ok := hashes[hash] | |
if ok { | |
if i - prevI >= skipFrames { | |
return images[prevI:i], nil | |
} | |
} else { | |
hashes[hash] = i | |
} | |
} | |
fmt.Println("no loop :( using all images") | |
return images, nil | |
} | |
func findLoopFromFirst(images []string, skipFrames int) ([]string, error) { | |
firstHash, err := hash(images[0]) | |
if err != nil { | |
log.Fatal(err) | |
} | |
var diffs []int64 | |
for i, img := range images[skipFrames:] { | |
h, _ := hash(img) | |
diff := diff(firstHash, h) | |
if diff == 0 { | |
// +1 because it's exclusive, and the range starts at index 1 of images | |
return images[0 : i+skipFrames], nil | |
} | |
diffs = append(diffs, diff) | |
} | |
fmt.Println("no exact match :(") | |
// +1 because it's exclusive, and diffs starts at index 1 of images | |
return images[0:minIndex(diffs) + minFrames + 1], nil | |
} | |
func minIndex(vals []int64) int { | |
idx := len(vals) - 1 | |
min := vals[idx] | |
for i, val := range vals { | |
if val <= min { | |
idx = i | |
min = val | |
} | |
} | |
return idx | |
} | |
func hash(imagePath string) (imgsim.Hash, error) { | |
img, err := openPNG(imagePath) | |
if err != nil { | |
return 0, err | |
} | |
return imgsim.AverageHash(img), nil | |
} | |
func diff(master, copy imgsim.Hash) int64 { | |
diff := int64(master) - int64(copy) | |
if diff < 0 { | |
diff *= -1 | |
} | |
return diff | |
} | |
func openPNG(imagePath string) (image.Image, error) { | |
f, err := os.Open(imagePath) | |
defer f.Close() | |
if err != nil { | |
return nil, err | |
} | |
img, err := png.Decode(f) | |
return img, err | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment