package main import ( "bytes" _ "embed" "fmt" "image" "image/draw" "image/jpeg" "image/png" "io" "log" "os" "slices" "strings" "time" tk "modernc.org/tk9.0" ) //go:embed gopher.png var gopher []byte var validFileTypes = []tk.FileType{ { TypeName: "JPEG", Extensions: []string{".jpg", ".jpeg"}, }, { TypeName: "GIF", Extensions: []string{".gif"}, }, { TypeName: "PNG", Extensions: []string{".png"}, }, { TypeName: "SVG", Extensions: []string{".svg"}, }, } var metaPressed bool func isType(filename string, desired string) bool { splode := strings.Split(filename, ".") if len(splode) < 2 { return false } ext := fmt.Sprintf(".%s", splode[len(splode)-1]) for _, ft := range validFileTypes { if slices.Contains(ft.Extensions, ext) && ft.TypeName == desired { return true } } return false } func jpegToPng(in io.Reader) (*bytes.Buffer, error) { start := time.Now() buf := new(bytes.Buffer) img, err := jpeg.Decode(in) if err != nil { return nil, fmt.Errorf("unable to decode jpeg: %w", err) } b := img.Bounds() dst := image.NewNRGBA(image.Rect(0, 0, b.Dx(), b.Dy())) draw.Draw(dst, dst.Bounds(), img, b.Min, draw.Src) if err := (&png.Encoder{ CompressionLevel: -2, }).Encode(buf, dst); err != nil { return nil, fmt.Errorf("unable to encode png: %w", err) } end := time.Now() log.Printf("jpeg to png took %v", end.Sub(start)) return buf, nil } func loadImage(img *tk.LabelWidget) func() { return func() { files := tk.GetOpenFile(tk.Filetypes(validFileTypes)) if len(files) > 0 { f, err := os.Open(files[0]) if err != nil { log.Println(err.Error()) return } var r io.Reader if isType(files[0], "JPEG") { r, err = jpegToPng(f) if err != nil { log.Println(err.Error()) return } } else { r = f } i, err := io.ReadAll(r) if err != nil { log.Println(err.Error()) return } img.Configure(tk.Image(tk.NewPhoto(tk.Data(i)))) } } } func main() { img := tk.Label(tk.Image(tk.NewPhoto(tk.Data(gopher)))) menubar := tk.Menu() fileMenu := menubar.Menu() fileMenu.AddCommand(tk.Lbl("Open"), tk.Underline(0), tk.Accelerator("Meta+O"), tk.Command(loadImage(img))) menubar.AddCascade(tk.Lbl("File"), tk.Underline(0), tk.Mnu(fileMenu)) tk.Bind(tk.App, "", tk.Command(func(e *tk.Event) { if e.Keysym == "Meta_L" || e.Keysym == "Meta_R" { metaPressed = true } if e.Keysym == "o" && metaPressed { loadImage(img)() } })) tk.Bind(tk.App, "", tk.Command(func(e *tk.Event) { if e.Keysym == "Meta_L" || e.Keysym == "Meta_R" { metaPressed = false } })) tk.Bind(tk.App, "", tk.Command(func(e *tk.Event) { // TODO: this event doesn't contain deltas, which means I can't tell if I'm scrolling "in" or "out" })) tk.Pack(img) tk.App.Configure(tk.Mnu(menubar)).Center().Wait() }