A fast, fluent, pure-Go renderer and CLI for 1200×630 Open Graph images. It supports cover-cropped PNG/JPEG/WebP backgrounds, gradients, linear-time blur, Bayer dithering, overlays, grids, wrapped text, custom fonts, and circular avatars. No CGO or external image tools are required.
Launch the terminal editor:
go run ./cmd/opengraph-tuiThe TUI opens on one simple Create screen: choose an image, write the text, adjust blur/dither/overlay, and save. Press Tab only when you need the optional Advanced settings such as gradients, grids, avatars, fonts, colors, and dimensions. It uses the same renderer as the CLI.
↑ / ↓ or j / k select a field
Enter edit a field or advance a choice
← / → adjust a number or choice
Tab show/hide advanced settings
Ctrl+V or P load an image directly from the system clipboard
R refresh the preview
S save the configured PNG
O open a full-size preview
Q quit
Clipboard image paste works through native platform adapters:
- macOS: built-in AppleScript, with
pngpasteused when installed - Wayland:
wl-paste - X11:
xclip - Windows: PowerShell clipboard APIs
Dragging an image file into an active path field also works in terminals that paste dropped file paths.
Build or run the command:
go build -o opengraph ./cmd/opengraph
go build -o opengraph-tui ./cmd/opengraph-tui
./opengraph --helpCreate an image like the reference card:
./opengraph \
--image background.png \
--blur 12 \
--dither 4,32 \
--overlay '#00000099' \
--title 'this is some bigger text on the top' \
--subtitle 'ziggy zig zig zig (zig (btw)) (btw)' \
--bottom '/zigging' \
--output card.pngUse a gradient instead of an image:
./opengraph \
--gradient '#0f0f0f,#222222,left-to-right' \
--grid '40,1,#ffffff22' \
--title 'a simple gradient background' \
-o gradient.pngGenerate only the dithered texture shown in the second reference:
./opengraph --background-color '#11131a' --dither 1,24 -o texture.pngPipe PNG data to another program with --output -:
./opengraph --title 'hello' --output - > card.pngQuote colors containing # in shell commands. The CLI always applies layers in a safe order: background, blur, dither, overlay, grid, then crisp text and avatar.
| Area | Flags |
|---|---|
| Canvas | --width, --height, --padding, --output / -o |
| Background | --image, --background-color, or --gradient START,END,DIRECTION |
| Effects | --blur RADIUS, --dither DOT_SIZE,STRENGTH, --overlay COLOR, --grid SPACING,WIDTH,COLOR |
| Text | --title, --subtitle, --bottom and corresponding --*-color flags |
| Type sizes | --title-size, --minimum-title-size, --subtitle-size, --bottom-size |
| Fonts | --title-font, --body-font (supply both) |
| Avatar | --avatar, --avatar-size, --avatar-border-width, --avatar-border-color |
Gradient directions are left-to-right, right-to-left, top-to-bottom, and bottom-to-top; ltr, rtl, ttb, and btt are accepted aliases.
package main
import (
"log"
og "opengraph"
)
func main() {
card := og.Default().
BackgroundImage("background.png").
Blur(12).
Dither(4, 32).
Overlay("#000000cc").
Title("the day i fell in love with a cactus", "#ffffffff").
Subtitle("completely true story", "#ffffffff").
Bottom("#real_life", "#ffffffff").
AvatarWithBorder("avatar.jpg", 72, 3, "#fff")
if err := card.Save("out.png"); err != nil {
log.Fatal(err)
}
}Library operations execute in call order. Fluent methods retain their first error, returned by Err, Write, or Save.
og.New(width, height)
og.Default() // 1200×630
card.BackgroundColor("#101010")
card.BackgroundImage("photo.webp")
card.LinearGradient("#0f0f0f", "#222222", og.LeftToRight)
card.Grid(40, 1, "#ffffff22")
card.Blur(12)
card.Dither(2, 32)
card.Overlay("#00000099")
card.Title("Wrapped title", "#fff")
card.Subtitle("Supporting copy", "#fff")
card.Bottom("example.com", "#fff")
card.Avatar("avatar.jpg", 72)
card.AvatarWithBorder("avatar.jpg", 72, 3, "#fff")
card.Padding(40)
card.FontSizes(60, 30, 24)
card.MinimumTitleSize(32)
card.LoadFonts("heading.ttf", "body.ttf")
card.Write(writer)
card.Save("card.png")Colors accept #RGB, #RGBA, #RRGGBB, or #RRGGBBAA. Output is PNG.
Blur uses running horizontal and vertical channel sums, making it O(width × height) rather than scaling with blur radius. Gradients reuse rows/columns, dithering applies precomputed Bayer offsets, and parsed fonts are cached per card.
Run benchmarks with:
go test -bench=. -benchmem ./...The original idea was inspired by The Kind Stranger at namishh.com. This project would not have popped into my head without them.