Skip to content

Instantly share code, notes, and snippets.

@diamondburned
Created August 2, 2020 08:41
Show Gist options
  • Save diamondburned/70e2b1d8341e22abd7cd80a5e5590bc7 to your computer and use it in GitHub Desktop.
Save diamondburned/70e2b1d8341e22abd7cd80a5e5590bc7 to your computer and use it in GitHub Desktop.
Shitty 2-bit meme text writer on GIF, optimized for speed
package main
import (
"bytes"
"image"
"image/color"
"image/gif"
"io"
"io/ioutil"
"os"
"runtime"
"sync"
"github.com/fogleman/gg"
"github.com/golang/freetype/truetype"
"golang.org/x/image/font"
)
var nproc = runtime.NumCPU() / 2
var fontface = loadFont("./impact.ttf")
func loadFont(path string) *truetype.Font {
b, err := ioutil.ReadFile(path)
if err != nil {
panic(err)
}
f, err := truetype.Parse(b)
if err != nil {
panic(err)
}
return f
}
func main() {
f, err := ioutil.ReadFile(os.Args[1])
if err != nil {
panic(err)
}
mkMeme(f, os.Stdout, "when the f", "funny is found")
}
func mkMeme(b []byte, dst io.Writer, topText, bottomText string) {
g, err := gif.DecodeAll(bytes.NewReader(b))
if err != nil {
panic(err)
}
dc := gg.NewContext(g.Config.Width, g.Config.Height)
dc.SetRGB(1, 1, 1)
w := float64(g.Config.Width)
h := float64(g.Config.Height)
dc.SetFontFace(truetype.NewFace(fontface, &truetype.Options{
Size: h / 10,
Hinting: font.HintingNone,
GlyphCacheEntries: 1,
}))
drawOutlinedText(dc, topText, w/2, dc.FontHeight()*0.3, 0.5, 0, w)
drawOutlinedText(dc, bottomText, w/2, h-dc.FontHeight()*0.3, 0.5, 1, w)
var step = len(g.Image) / nproc
var text = dc.Image().(*image.RGBA)
var bnds = text.Rect
var wait = sync.WaitGroup{}
const mid = 255 / 2
for i := 0; i < len(g.Image); i += step {
j := i + step
wait.Add(1)
go func(start, end int) {
defer wait.Done()
for i := start; i < end && i < len(g.Image); i++ {
var img = g.Image[i]
b, w := ensurePalette(&img.Palette)
for x := 0; x < bnds.Dx(); x++ {
for y := 0; y < bnds.Dy(); y++ {
o := text.PixOffset(x, y)
// Lazy black-and-white drawing.
if a := text.Pix[o+3]; a > 50 {
if r := text.Pix[o]; r > mid {
img.SetColorIndex(x, y, uint8(w))
} else {
img.SetColorIndex(x, y, uint8(b))
}
}
}
}
}
}(i, j)
}
wait.Wait()
if err := gif.EncodeAll(dst, g); err != nil {
panic(err)
}
}
func ensurePalette(p *color.Palette) (iblack, iwhite int) {
cpy := *p
// Fast path.
if len(cpy) == 0 {
iblack = len(cpy)
iwhite = iblack + 1
*p = append(cpy,
color.Gray{0},
color.Gray{255},
)
return
}
const max = 0xffff
var black, white bool
for _, c := range cpy {
r, g, b, a := c.RGBA()
if !black && (r == 0 && g == 0 && b == 0 && a == max) {
// BUG: this check mistakenly treats a grey as a black.
// black = true
continue
}
if !white && (r == max && g == max && b == max && a == max) {
white = true
continue
}
if black && white {
break
}
}
if !black {
iblack = len(cpy)
cpy = append(cpy, color.Gray{0})
}
if !white {
iwhite = len(cpy)
cpy = append(cpy, color.Gray{255})
}
if len(cpy) > 256 {
sh := len(cpy) - 256
iblack -= sh
iwhite -= sh
cpy = cpy[sh:]
}
*p = cpy
return
}
func drawOutlinedText(dc *gg.Context, s string, x, y, ax, ay, width float64) {
dc.SetRGB(0, 0, 0)
n := width / 150
const sp float64 = 1.2 // line spacing
w := width * .95
dc.DrawStringWrapped(s, x+n, y+n, ax, ay, w, sp, gg.AlignCenter)
dc.DrawStringWrapped(s, x-n, y-n, ax, ay, w, sp, gg.AlignCenter)
dc.DrawStringWrapped(s, x+n, y-n, ax, ay, w, sp, gg.AlignCenter)
dc.DrawStringWrapped(s, x-n, y+n, ax, ay, w, sp, gg.AlignCenter)
dc.SetRGB(1, 1, 1)
dc.DrawStringWrapped(s, x, y, ax, ay, w, sp, gg.AlignCenter)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment