Created
August 2, 2020 08:41
-
-
Save diamondburned/70e2b1d8341e22abd7cd80a5e5590bc7 to your computer and use it in GitHub Desktop.
Shitty 2-bit meme text writer on GIF, optimized for speed
This file contains hidden or 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 | |
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