You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

158 lines
3.6 KiB

// SPDX-License-Identifier: Unlicense OR MIT
package main
// A simple Gio program. See https://gioui.org for more information.
import (
"fmt"
"image"
"image/jpeg"
_ "image/jpeg"
"image/png"
_ "image/png"
"log"
"os"
"gioui.org/app"
"gioui.org/io/system"
"gioui.org/layout"
"gioui.org/op"
"gioui.org/op/paint"
"gioui.org/widget"
"gioui.org/widget/material"
"gioui.org/font/gofont"
"gioui.org/x/explorer"
)
func main() {
go func() {
w := app.NewWindow()
if err := loop(w); err != nil {
log.Fatal(err)
}
os.Exit(0)
}()
app.Main()
}
type (
C = layout.Context
D = layout.Dimensions
)
// ImageResult is the results of trying to open an image. It may
// contain either an error or an image, but not both. The error
// should always be checked first.
type ImageResult struct {
Error error
Format string
Image image.Image
}
func loop(w *app.Window) error {
expl := explorer.NewExplorer(w)
var openBtn, saveBtn widget.Clickable
th := material.NewTheme(gofont.Collection())
imgChan := make(chan ImageResult)
saveChan := make(chan error)
var img ImageResult
var saveErr error
var ops op.Ops
for {
select {
case img = <-imgChan:
w.Invalidate()
case saveErr = <-saveChan:
w.Invalidate()
case e := <-w.Events():
expl.ListenEvents(e)
switch e := e.(type) {
case system.DestroyEvent:
return e.Err
case system.FrameEvent:
gtx := layout.NewContext(&ops, e)
if openBtn.Clicked() {
go func() {
file, err := expl.ChooseFile("png", "jpeg", "jpg")
if err != nil {
err = fmt.Errorf("failed opening image file: %w", err)
imgChan <- ImageResult{Error: err}
return
}
defer file.Close()
imgData, format, err := image.Decode(file)
if err != nil {
err = fmt.Errorf("failed decoding image data: %w", err)
imgChan <- ImageResult{Error: err}
return
}
imgChan <- ImageResult{Image: imgData, Format: format}
}()
}
if saveBtn.Clicked() {
go func(img ImageResult) {
if img.Error != nil {
saveChan <- fmt.Errorf("no image loaded, cannot save")
return
}
extension := "jpg"
switch img.Format {
case "png":
extension = "png"
}
file, err := expl.CreateFile("file." + extension)
if err != nil {
saveChan <- fmt.Errorf("failed exporting image file: %w", err)
return
}
defer func() {
saveChan <- file.Close()
}()
switch extension {
case "jpg":
if err := jpeg.Encode(file, img.Image, nil); err != nil {
saveChan <- fmt.Errorf("failed encoding image file: %w", err)
return
}
case "png":
if err := png.Encode(file, img.Image); err != nil {
saveChan <- fmt.Errorf("failed encoding image file: %w", err)
return
}
}
}(img)
}
layout.Flex{Axis: layout.Vertical}.Layout(gtx,
layout.Rigid(material.Button(th, &openBtn, "Open Image").Layout),
layout.Flexed(1, func(gtx C) D {
if img.Error == nil && img.Image == nil {
return D{}
} else if img.Error != nil {
return material.H6(th, img.Error.Error()).Layout(gtx)
}
return widget.Image{
Src: paint.NewImageOp(img.Image),
Fit: widget.Contain,
}.Layout(gtx)
}),
layout.Rigid(func(gtx C) D {
if img.Image == nil {
gtx.Queue = nil
}
return material.Button(th, &saveBtn, "Save Image").Layout(gtx)
}),
layout.Rigid(func(gtx C) D {
if saveErr == nil {
return D{}
}
return material.H6(th, saveErr.Error()).Layout(gtx)
}),
)
e.Frame(gtx.Ops)
}
}
}
}