package main import ( "os" "image" "encoding/json" "log" "image/png" "github.com/disintegration/gift" "flag" "lcthw.dev/go/jankifier/filters" ) func LoadImage(filename string) image.Image { reader, err := os.Open(filename) if err != nil { log.Fatal(err) } defer reader.Close() img, _, err := image.Decode(reader) if err != nil { log.Fatal(err) } return img } func SaveImage(filename string, img image.Image) { out, err := os.Create(filename) if err != nil { log.Fatalf("can't write file %s: %v", filename, err) } defer out.Close() err = png.Encode(out, img) if err != nil { log.Fatalf("can't png encode %s: %v", filename, err) } } type Opts struct { Config string Force bool } type Conversion struct { PixelWidth int ColorDepth int DitherType int Width int Height int } type Settings struct { Source string Target string Base Conversion Exceptions map[string]Conversion } func LoadSettings(path string) Settings { var settings Settings config, err := os.ReadFile(path) if err != nil { log.Fatalf("invalid config path: %s", path) } err = json.Unmarshal(config, &settings) if err != nil { log.Fatalf("json format error:", err) } return settings } func ParseOpts() Opts { var opts Opts flag.StringVar(&opts.Config, "config", "config.json", "config file") flag.BoolVar(&opts.Force, "force", false, "force a full convert") flag.Parse() return opts } func JankImage(settings Conversion, in_file string, out_file string) { src := LoadImage(in_file) bounds := src.Bounds() resize := gift.Resize(bounds.Max.X / settings.PixelWidth, 0, gift.NearestNeighborResampling) posterize := filters.Posterize(uint16(settings.ColorDepth), settings.DitherType) upscale := filters.Upscale(bounds, settings.PixelWidth) sharpen := gift.UnsharpMask(1, 1, 0) g := gift.New(resize, posterize, upscale, sharpen) out_img := image.NewNRGBA(g.Bounds(bounds)) g.Draw(out_img, src) SaveImage(out_file, out_img) } func main() { opts := ParseOpts() settings := LoadSettings(opts.Config) JankImage(settings.Base, settings.Source, settings.Target) }