// Command crop trims the uniform background border around each VHS screenshot so the // content fills the frame instead of floating in a large empty terminal. Sparse views // (alerts, detail, nodes) would otherwise sit in a sea of dead space. // // Usage: crop [dir] (dir defaults to vhs/screenshots) package main import ( "fmt" "image" "image/color" "image/png" "os" "path/filepath" ) // pad is the margin (px) left around the detected content. tol is the per-channel // colour distance (summed) above which a pixel counts as content rather than background. const ( pad = 24 tol = 28 ) func main() { dir := "vhs/screenshots" if len(os.Args) > 1 { dir = os.Args[1] } paths, err := filepath.Glob(filepath.Join(dir, "*.png")) if err != nil { fmt.Fprintf(os.Stderr, "glob: %v\n", err) os.Exit(1) } if len(paths) == 0 { fmt.Fprintf(os.Stderr, "no PNGs in %s\n", dir) os.Exit(1) } for _, p := range paths { w, h, err := cropFile(p) if err != nil { fmt.Fprintf(os.Stderr, "crop %s: %v\n", p, err) os.Exit(1) } fmt.Printf("cropped %s -> %dx%d\n", filepath.Base(p), w, h) } } func cropFile(path string) (int, int, error) { f, err := os.Open(path) //nolint:gosec // dev tool: paths come from a trusted local glob if err != nil { return 0, 0, err } src, err := png.Decode(f) _ = f.Close() if err != nil { return 0, 0, err } b := src.Bounds() // Background colour sampled from a corner — always inside VHS's blank padding. bgR, bgG, bgB := rgb(src.At(b.Min.X+2, b.Min.Y+2)) minX, minY := b.Max.X, b.Max.Y maxX, maxY := b.Min.X, b.Min.Y found := false for y := b.Min.Y; y < b.Max.Y; y++ { for x := b.Min.X; x < b.Max.X; x++ { r, g, bl := rgb(src.At(x, y)) if abs(r-bgR)+abs(g-bgG)+abs(bl-bgB) > tol { found = true minX, minY = min(minX, x), min(minY, y) maxX, maxY = max(maxX, x), max(maxY, y) } } } if !found { return b.Dx(), b.Dy(), nil // blank frame — leave untouched } minX = clamp(minX-pad, b.Min.X, b.Max.X) minY = clamp(minY-pad, b.Min.Y, b.Max.Y) maxX = clamp(maxX+pad+1, b.Min.X, b.Max.X) maxY = clamp(maxY+pad+1, b.Min.Y, b.Max.Y) dst := image.NewRGBA(image.Rect(0, 0, maxX-minX, maxY-minY)) for y := minY; y < maxY; y++ { for x := minX; x < maxX; x++ { dst.Set(x-minX, y-minY, src.At(x, y)) } } out, err := os.Create(path) //nolint:gosec // dev tool: paths come from a trusted local glob if err != nil { return 0, 0, err } defer out.Close() //nolint:errcheck // best-effort close on write path if err := png.Encode(out, dst); err != nil { return 0, 0, err } return dst.Bounds().Dx(), dst.Bounds().Dy(), nil } func rgb(c color.Color) (int, int, int) { r, g, b, _ := c.RGBA() return int(r >> 8), int(g >> 8), int(b >> 8) } func abs(x int) int { if x < 0 { return -x } return x } func clamp(v, lo, hi int) int { if v < lo { return lo } if v > hi { return hi } return v }