mirror of
https://github.com/esimov/caire.git
synced 2025-09-27 04:45:53 +08:00
239 lines
5.6 KiB
Go
239 lines
5.6 KiB
Go
package caire
|
|
|
|
import (
|
|
"image"
|
|
"image/color"
|
|
"math"
|
|
|
|
"gioui.org/app"
|
|
"gioui.org/f32"
|
|
"gioui.org/font/gofont"
|
|
"gioui.org/io/key"
|
|
"gioui.org/io/system"
|
|
"gioui.org/layout"
|
|
"gioui.org/op"
|
|
"gioui.org/op/clip"
|
|
"gioui.org/op/paint"
|
|
"gioui.org/unit"
|
|
"gioui.org/widget"
|
|
"gioui.org/widget/material"
|
|
)
|
|
|
|
const (
|
|
maxScreenX = 1366
|
|
maxScreenY = 768
|
|
)
|
|
|
|
var (
|
|
defaultBkgColor = color.Transparent
|
|
defaultFillColor = color.Black
|
|
)
|
|
|
|
type interval struct {
|
|
min, max float64
|
|
}
|
|
|
|
// Gui is the basic struct containing all of the information needed for the UI operation.
|
|
// It receives the resized image transferred through a channel which is called in a separate goroutine.
|
|
type Gui struct {
|
|
cfg struct {
|
|
x interval
|
|
y interval
|
|
window struct {
|
|
w float64
|
|
h float64
|
|
title string
|
|
}
|
|
color struct {
|
|
background color.Color
|
|
fill color.Color
|
|
}
|
|
}
|
|
proc struct {
|
|
img image.Image
|
|
seams []Seam
|
|
|
|
wrk <-chan worker
|
|
err chan<- error
|
|
}
|
|
cp *Processor
|
|
ctx layout.Context
|
|
}
|
|
|
|
// newGui initializes the Gio interface.
|
|
func newGui(w, h int) *Gui {
|
|
gui := &Gui{
|
|
ctx: layout.Context{
|
|
Ops: new(op.Ops),
|
|
Constraints: layout.Constraints{
|
|
Max: image.Pt(w, h),
|
|
},
|
|
},
|
|
}
|
|
gui.initWindow(w, h)
|
|
|
|
return gui
|
|
}
|
|
|
|
// initWindow creates and initializes the GUI window.
|
|
func (g *Gui) initWindow(w, h int) {
|
|
g.cfg.window.w, g.cfg.window.h = float64(w), float64(h)
|
|
g.cfg.x = interval{min: 0, max: float64(w)}
|
|
g.cfg.y = interval{min: 0, max: float64(h)}
|
|
|
|
g.cfg.color.background = defaultBkgColor
|
|
g.cfg.color.fill = defaultFillColor
|
|
|
|
g.cfg.window.w, g.cfg.window.h = g.getWindowSize()
|
|
g.cfg.window.title = "Image resize in progress..."
|
|
}
|
|
|
|
// getWindowSize returns the resized image dimmension.
|
|
func (g *Gui) getWindowSize() (float64, float64) {
|
|
w, h := g.cfg.window.w, g.cfg.window.h
|
|
|
|
// retains the aspect ratio in case the image width and height
|
|
// is greater than the predefined window.
|
|
r := getRatio(w, h)
|
|
if w > maxScreenX && h > maxScreenY {
|
|
w = float64(w) * r
|
|
h = float64(h) * r
|
|
}
|
|
return w, h
|
|
}
|
|
|
|
// Run is the core method of the Gio GUI application.
|
|
// This updates the window with the resized image obtained from a channel
|
|
// and terminates when the image resizing operation completes.
|
|
func (g *Gui) Run() error {
|
|
w := app.NewWindow(app.Title(g.cfg.window.title), app.Size(
|
|
unit.Px(float32(g.cfg.window.w)),
|
|
unit.Px(float32(g.cfg.window.h)),
|
|
))
|
|
|
|
for {
|
|
select {
|
|
case e := <-w.Events():
|
|
switch e := e.(type) {
|
|
case system.FrameEvent:
|
|
g.draw(w, e)
|
|
case key.Event:
|
|
switch e.Name {
|
|
case key.NameEscape:
|
|
g.cp.Spinner.RestoreCursor()
|
|
w.Close()
|
|
}
|
|
case system.DestroyEvent:
|
|
return e.Err
|
|
}
|
|
case res := <-g.proc.wrk:
|
|
if resizeBothSide {
|
|
continue
|
|
}
|
|
g.proc.img = res.img
|
|
g.proc.seams = res.carver.Seams
|
|
if g.cp.vRes {
|
|
g.proc.img = res.carver.RotateImage270(g.proc.img.(*image.NRGBA))
|
|
}
|
|
w.Invalidate()
|
|
}
|
|
}
|
|
}
|
|
|
|
// draw display the resized image received from a channel
|
|
// and prints the seams in case the debug mode is activated.
|
|
func (g *Gui) draw(win *app.Window, e system.FrameEvent) {
|
|
g.ctx = layout.NewContext(g.ctx.Ops, e)
|
|
win.Invalidate()
|
|
|
|
c := g.setColor(g.cfg.color.background)
|
|
paint.Fill(g.ctx.Ops, c)
|
|
|
|
type (
|
|
C = layout.Context
|
|
D = layout.Dimensions
|
|
)
|
|
|
|
if g.proc.img != nil {
|
|
src := paint.NewImageOp(g.proc.img)
|
|
src.Add(g.ctx.Ops)
|
|
|
|
layout.Flex{
|
|
Axis: layout.Horizontal,
|
|
}.Layout(g.ctx,
|
|
layout.Flexed(1, func(gtx C) D {
|
|
paint.FillShape(gtx.Ops, c,
|
|
clip.Rect{Max: g.ctx.Constraints.Max}.Op(),
|
|
)
|
|
return layout.UniformInset(unit.Px(0)).Layout(gtx,
|
|
func(gtx C) D {
|
|
widget.Image{
|
|
Src: src,
|
|
Scale: 1 / float32(g.ctx.Px(unit.Dp(1))),
|
|
Fit: widget.Contain,
|
|
}.Layout(gtx)
|
|
|
|
if g.cp.Debug {
|
|
var ratio float32
|
|
tr := f32.Affine2D{}
|
|
screen := layout.FPt(g.ctx.Constraints.Max)
|
|
width, height := float32(g.proc.img.Bounds().Dx()), float32(g.proc.img.Bounds().Dy())
|
|
sw, sh := float32(screen.X), float32(screen.Y)
|
|
|
|
if sw > width {
|
|
ratio = sw / width
|
|
tr = tr.Scale(f32.Pt(sw/2, sh/2), f32.Pt(1, ratio))
|
|
} else if sh > height {
|
|
ratio = sh / height
|
|
tr = tr.Scale(f32.Pt(sw/2, sh/2), f32.Pt(ratio, 1))
|
|
}
|
|
|
|
if g.cp.vRes {
|
|
angle := float32(270 * math.Pi / 180)
|
|
half := float32(math.Round(float64(sh*0.5-height*0.5) * 0.5))
|
|
|
|
ox := math.Abs(float64(sw - (sw - (sw/2 - sh/2))))
|
|
oy := math.Abs(float64(sh - (sh - (sw/2 - height/2 + half))))
|
|
tr = tr.Rotate(f32.Pt(sw/2, sh/2), -angle)
|
|
|
|
if screen.X > screen.Y {
|
|
tr = tr.Offset(f32.Pt(float32(ox), float32(oy)))
|
|
} else {
|
|
tr = tr.Offset(f32.Pt(float32(-ox), float32(-oy)))
|
|
}
|
|
}
|
|
op.Affine(tr).Add(gtx.Ops)
|
|
|
|
for _, s := range g.proc.seams {
|
|
g.DrawSeam(g.cp.ShapeType, float64(s.X), float64(s.Y), 1)
|
|
}
|
|
}
|
|
return layout.Dimensions{Size: gtx.Constraints.Max}
|
|
})
|
|
}),
|
|
)
|
|
|
|
}
|
|
|
|
// Disable the preview mode and warn the user in case the image is resized both horizontally and vertically.
|
|
if resizeBothSide {
|
|
var th = material.NewTheme(gofont.Collection())
|
|
th.Palette.Fg = color.NRGBA{R: 0x00, A: 0xFF}
|
|
|
|
layout.Flex{
|
|
Axis: layout.Horizontal,
|
|
Alignment: layout.Middle,
|
|
}.Layout(g.ctx,
|
|
layout.Flexed(1, func(gtx C) D {
|
|
msg := "Preview is not available when the image is resized horizontally and vertically at the same time!"
|
|
return layout.UniformInset(unit.Dp(4)).Layout(g.ctx, func(gtx C) D {
|
|
return layout.Center.Layout(g.ctx, func(gtx C) D {
|
|
return material.Label(th, unit.Sp(40), msg).Layout(gtx)
|
|
})
|
|
})
|
|
},
|
|
))
|
|
}
|
|
e.Frame(g.ctx.Ops)
|
|
}
|