export image functionality
This commit is contained in:
@@ -5,6 +5,7 @@ go 1.25.6
|
|||||||
require (
|
require (
|
||||||
fyne.io/fyne/v2 v2.7.2
|
fyne.io/fyne/v2 v2.7.2
|
||||||
github.com/aquilax/go-perlin v1.1.0
|
github.com/aquilax/go-perlin v1.1.0
|
||||||
|
github.com/chai2010/webp v1.4.0
|
||||||
github.com/disintegration/imaging v1.6.2
|
github.com/disintegration/imaging v1.6.2
|
||||||
github.com/ojrac/opensimplex-go v1.0.2
|
github.com/ojrac/opensimplex-go v1.0.2
|
||||||
)
|
)
|
||||||
@@ -37,9 +38,9 @@ require (
|
|||||||
github.com/srwiley/rasterx v0.0.0-20220730225603-2ab79fcdd4ef // indirect
|
github.com/srwiley/rasterx v0.0.0-20220730225603-2ab79fcdd4ef // indirect
|
||||||
github.com/stretchr/testify v1.11.1 // indirect
|
github.com/stretchr/testify v1.11.1 // indirect
|
||||||
github.com/yuin/goldmark v1.7.8 // indirect
|
github.com/yuin/goldmark v1.7.8 // indirect
|
||||||
golang.org/x/image v0.24.0 // indirect
|
golang.org/x/image v0.35.0 // indirect
|
||||||
golang.org/x/net v0.35.0 // indirect
|
golang.org/x/net v0.35.0 // indirect
|
||||||
golang.org/x/sys v0.30.0 // indirect
|
golang.org/x/sys v0.30.0 // indirect
|
||||||
golang.org/x/text v0.22.0 // indirect
|
golang.org/x/text v0.33.0 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg
|
|||||||
github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
|
github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
|
||||||
github.com/aquilax/go-perlin v1.1.0 h1:Gg+3jQ24wT4Y5GI7TCRLmYarzUG0k+n/JATFqOimb7s=
|
github.com/aquilax/go-perlin v1.1.0 h1:Gg+3jQ24wT4Y5GI7TCRLmYarzUG0k+n/JATFqOimb7s=
|
||||||
github.com/aquilax/go-perlin v1.1.0/go.mod h1:z9Rl7EM4BZY0Ikp2fEN1I5mKSOJ26HQpk0O2TBdN2HE=
|
github.com/aquilax/go-perlin v1.1.0/go.mod h1:z9Rl7EM4BZY0Ikp2fEN1I5mKSOJ26HQpk0O2TBdN2HE=
|
||||||
|
github.com/chai2010/webp v1.4.0 h1:6DA2pkkRUPnbOHvvsmGI3He1hBKf/bkRlniAiSGuEko=
|
||||||
|
github.com/chai2010/webp v1.4.0/go.mod h1:0XVwvZWdjjdxpUEIf7b9g9VkHFnInUSYujwqTLEuldU=
|
||||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
@@ -72,15 +74,15 @@ github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD
|
|||||||
github.com/yuin/goldmark v1.7.8 h1:iERMLn0/QJeHFhxSt3p6PeN9mGnvIKSpG9YYorDMnic=
|
github.com/yuin/goldmark v1.7.8 h1:iERMLn0/QJeHFhxSt3p6PeN9mGnvIKSpG9YYorDMnic=
|
||||||
github.com/yuin/goldmark v1.7.8/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E=
|
github.com/yuin/goldmark v1.7.8/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E=
|
||||||
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||||
golang.org/x/image v0.24.0 h1:AN7zRgVsbvmTfNyqIbbOraYL8mSwcKncEj8ofjgzcMQ=
|
golang.org/x/image v0.35.0 h1:LKjiHdgMtO8z7Fh18nGY6KDcoEtVfsgLDPeLyguqb7I=
|
||||||
golang.org/x/image v0.24.0/go.mod h1:4b/ITuLfqYq1hqZcjofwctIhi7sZh2WaCjvsBNjjya8=
|
golang.org/x/image v0.35.0/go.mod h1:MwPLTVgvxSASsxdLzKrl8BRFuyqMyGhLwmC+TO1Sybk=
|
||||||
golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8=
|
golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8=
|
||||||
golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk=
|
golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk=
|
||||||
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
|
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
|
||||||
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=
|
golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE=
|
||||||
golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
|
golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
|
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
|
||||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
|||||||
@@ -12,15 +12,21 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
|
"image/jpeg"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"fyne.io/fyne/v2"
|
"fyne.io/fyne/v2"
|
||||||
"fyne.io/fyne/v2/app"
|
"fyne.io/fyne/v2/app"
|
||||||
"fyne.io/fyne/v2/canvas"
|
"fyne.io/fyne/v2/canvas"
|
||||||
"fyne.io/fyne/v2/container"
|
"fyne.io/fyne/v2/container"
|
||||||
|
"fyne.io/fyne/v2/dialog"
|
||||||
"fyne.io/fyne/v2/widget"
|
"fyne.io/fyne/v2/widget"
|
||||||
|
|
||||||
|
"github.com/chai2010/webp"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
a := app.New()
|
a := app.NewWithID("com.example.rpgcitymaker")
|
||||||
a.Settings().SetTheme(&CustomTheme{a.Settings().Theme()})
|
a.Settings().SetTheme(&CustomTheme{a.Settings().Theme()})
|
||||||
w := a.NewWindow("RPG City Maker")
|
w := a.NewWindow("RPG City Maker")
|
||||||
w.Resize(fyne.NewSize(800, 600))
|
w.Resize(fyne.NewSize(800, 600))
|
||||||
@@ -254,6 +260,9 @@ func main() {
|
|||||||
|
|
||||||
var generateBtn *widget.Button
|
var generateBtn *widget.Button
|
||||||
progressBar := NewTextOverlayProgressBar()
|
progressBar := NewTextOverlayProgressBar()
|
||||||
|
exportBtn := widget.NewButton("Export", func() {
|
||||||
|
showSaveDialog(w, canvasImg.Image, settings)
|
||||||
|
})
|
||||||
|
|
||||||
generateBtn = widget.NewButton("Generate", func() {
|
generateBtn = widget.NewButton("Generate", func() {
|
||||||
go func() {
|
go func() {
|
||||||
@@ -437,6 +446,8 @@ func main() {
|
|||||||
generateBtn,
|
generateBtn,
|
||||||
errorLabel,
|
errorLabel,
|
||||||
progressBar,
|
progressBar,
|
||||||
|
widget.NewSeparator(),
|
||||||
|
exportBtn,
|
||||||
))
|
))
|
||||||
|
|
||||||
tabs := container.NewAppTabs(
|
tabs := container.NewAppTabs(
|
||||||
@@ -464,3 +475,86 @@ func main() {
|
|||||||
w.SetContent(split)
|
w.SetContent(split)
|
||||||
w.ShowAndRun()
|
w.ShowAndRun()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func showSaveDialog(win fyne.Window, img image.Image, settings *Settings) {
|
||||||
|
fileNameEntry := widget.NewEntry()
|
||||||
|
fileNameEntry.SetPlaceHolder("image")
|
||||||
|
|
||||||
|
formatSelect := widget.NewSelect([]string{"PNG", "JPG", "WEBP"}, nil)
|
||||||
|
formatSelect.SetSelected("PNG")
|
||||||
|
|
||||||
|
pathLabel := widget.NewLabel(settings.LastExportPath)
|
||||||
|
|
||||||
|
browseBtn := widget.NewButton("Browse", func() {
|
||||||
|
dialog.ShowFolderOpen(func(uri fyne.ListableURI, err error) {
|
||||||
|
if err != nil {
|
||||||
|
log.Println("Error opening folder dialog:", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if uri == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
settings.LastExportPath = uri.Path()
|
||||||
|
pathLabel.SetText(settings.LastExportPath)
|
||||||
|
}, win)
|
||||||
|
})
|
||||||
|
|
||||||
|
content := container.NewVBox(
|
||||||
|
widget.NewLabel("Save to:"),
|
||||||
|
container.NewBorder(nil, nil, nil, browseBtn, pathLabel),
|
||||||
|
widget.NewLabel("Filename:"),
|
||||||
|
fileNameEntry,
|
||||||
|
widget.NewLabel("Format:"),
|
||||||
|
formatSelect,
|
||||||
|
)
|
||||||
|
|
||||||
|
saveDialog := dialog.NewCustom("Export Image", "Cancel", content, win)
|
||||||
|
|
||||||
|
saveBtn := widget.NewButton("Save", func() {
|
||||||
|
fileName := fileNameEntry.Text
|
||||||
|
if fileName == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
filePath := filepath.Join(pathLabel.Text, fileName+"."+strings.ToLower(formatSelect.Selected))
|
||||||
|
settings.LastExportPath = filepath.Dir(filePath)
|
||||||
|
|
||||||
|
file, err := os.Create(filePath)
|
||||||
|
if err != nil {
|
||||||
|
log.Println("Error creating file:", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
switch formatSelect.Selected {
|
||||||
|
case "PNG":
|
||||||
|
err = png.Encode(file, img)
|
||||||
|
case "JPG":
|
||||||
|
err = jpeg.Encode(file, img, nil)
|
||||||
|
case "WEBP":
|
||||||
|
err = webp.Encode(file, img, &webp.Options{Lossless: true})
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
log.Println("Failed to encode image:", err)
|
||||||
|
}
|
||||||
|
saveDialog.Hide()
|
||||||
|
})
|
||||||
|
saveBtn.Disable()
|
||||||
|
|
||||||
|
fileNameEntry.OnChanged = func(text string) {
|
||||||
|
if text == "" {
|
||||||
|
saveBtn.Disable()
|
||||||
|
} else {
|
||||||
|
saveBtn.Enable()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
saveDialog.SetButtons([]fyne.CanvasObject{
|
||||||
|
widget.NewButton("Cancel", func() {
|
||||||
|
saveDialog.Hide()
|
||||||
|
}),
|
||||||
|
saveBtn,
|
||||||
|
})
|
||||||
|
|
||||||
|
saveDialog.Show()
|
||||||
|
}
|
||||||
|
|||||||
+15
@@ -24,6 +24,7 @@ type Settings struct {
|
|||||||
MinRiverWidth float64 `json:"min_river_width"`
|
MinRiverWidth float64 `json:"min_river_width"`
|
||||||
MaxRiverWidth float64 `json:"max_river_width"`
|
MaxRiverWidth float64 `json:"max_river_width"`
|
||||||
RiverCurvyness float64 `json:"river_curvyness"`
|
RiverCurvyness float64 `json:"river_curvyness"`
|
||||||
|
LastExportPath string `json:"last_export_path"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Settings) Save() error {
|
func (s *Settings) Save() error {
|
||||||
@@ -58,6 +59,10 @@ func LoadSettings() (*Settings, error) {
|
|||||||
file, err := os.Open(configFile)
|
file, err := os.Open(configFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if os.IsNotExist(err) {
|
if os.IsNotExist(err) {
|
||||||
|
homeDir, err := os.UserHomeDir()
|
||||||
|
if err != nil {
|
||||||
|
homeDir = "."
|
||||||
|
}
|
||||||
return &Settings{
|
return &Settings{
|
||||||
Detail: 1,
|
Detail: 1,
|
||||||
Roughness: 0,
|
Roughness: 0,
|
||||||
@@ -75,6 +80,7 @@ func LoadSettings() (*Settings, error) {
|
|||||||
MinRiverWidth: 1,
|
MinRiverWidth: 1,
|
||||||
MaxRiverWidth: 5,
|
MaxRiverWidth: 5,
|
||||||
RiverCurvyness: 50,
|
RiverCurvyness: 50,
|
||||||
|
LastExportPath: homeDir,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -86,5 +92,14 @@ func LoadSettings() (*Settings, error) {
|
|||||||
if err := decoder.Decode(&settings); err != nil {
|
if err := decoder.Decode(&settings); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if settings.LastExportPath == "" {
|
||||||
|
homeDir, err := os.UserHomeDir()
|
||||||
|
if err != nil {
|
||||||
|
homeDir = "."
|
||||||
|
}
|
||||||
|
settings.LastExportPath = homeDir
|
||||||
|
}
|
||||||
|
|
||||||
return &settings, nil
|
return &settings, nil
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user