From e8ae07d2d6e0cb8a4f6c3de64b3a499c7206108c Mon Sep 17 00:00:00 2001 From: "Colin J. Brigato" Date: Wed, 25 Sep 2024 04:34:31 +0200 Subject: [PATCH] We are nearly done there. Fix Onclick widget with Size consideration, Add Scale operation in imageWidget, implement SurfaceLoaders for easy extension of loading images, change loading image example for advanced drawings and controls, updates URL downloaded images. Last Step: URL With OnError/OnLoading to mimic old behavior with better extension. Linting, Documentation. --- ImageWidgets.go | 14 +++- ReflectiveBoundTexture.go | 55 +++++++------- SurfaceLoaders.go | 124 ++++++++++++++++++++++++++++++++ examples/loadimage/loadimage.go | 102 +++++++++++++------------- go.mod | 2 + 5 files changed, 212 insertions(+), 85 deletions(-) create mode 100644 SurfaceLoaders.go diff --git a/ImageWidgets.go b/ImageWidgets.go index 2b553795..4d46b503 100644 --- a/ImageWidgets.go +++ b/ImageWidgets.go @@ -24,6 +24,7 @@ type ImageWidget struct { texture *Texture width float32 height float32 + scale imgui.Vec2 uv0, uv1 imgui.Vec2 tintColor, borderColor color.Color onClick func() @@ -35,6 +36,7 @@ func Image(texture *Texture) *ImageWidget { texture: texture, width: 0, height: 0, + scale: imgui.Vec2{X: 1, Y: 1}, uv0: imgui.Vec2{X: 0, Y: 0}, uv1: imgui.Vec2{X: 1, Y: 1}, tintColor: color.RGBA{255, 255, 255, 255}, @@ -74,6 +76,13 @@ func (i *ImageWidget) Size(width, height float32) *ImageWidget { return i } +// Scale multiply dimensions after size +func (i *ImageWidget) Scale(scaleX, ScaleY float32) *ImageWidget { + // Size image with DPI scaling + i.scale = imgui.Vec2{scaleX, ScaleY} + return i +} + // Build implements Widget interface. func (i *ImageWidget) Build() { if i.width == 0 && i.height == 0 { @@ -96,6 +105,9 @@ func (i *ImageWidget) Build() { size.Y = rect.Y } + size.X = size.X * i.scale.X + size.Y = size.Y * i.scale.Y + if i.texture == nil || i.texture.tex == nil { Dummy(size.X, size.Y).Build() return @@ -107,7 +119,7 @@ func (i *ImageWidget) Build() { mousePos := GetMousePos() if cursorPos.X <= mousePos.X && cursorPos.Y <= mousePos.Y && - cursorPos.X+int(i.width) >= mousePos.X && cursorPos.Y+int(i.height) >= mousePos.Y { + cursorPos.X+int(size.X) >= mousePos.X && cursorPos.Y+int(size.Y) >= mousePos.Y { i.onClick() } } diff --git a/ReflectiveBoundTexture.go b/ReflectiveBoundTexture.go index e8ca83fa..5d6f8024 100644 --- a/ReflectiveBoundTexture.go +++ b/ReflectiveBoundTexture.go @@ -10,20 +10,19 @@ import ( imgui "github.com/AllenDang/cimgui-go" ) -func defaultCanvas() *image.RGBA { - white := color.RGBA{255, 255, 255, 255} - return newImage(800, 600, white) +func defaultSurface() *image.RGBA { + surface, _ := UniformLoader(REFLECTIVE_SURFACE_DEFAULT_WIDTH, REFLECTIVE_SURFACE_DEFAULT_HEIGHT, REFLECTIVE_SURFACE_DEFAULT_COLOR).ServeRGBA() + return surface } -func newImage(width, height int, bgColor color.Color) *image.RGBA { - img := image.NewRGBA(image.Rect(0, 0, width, height)) - for y := 0; y < height; y++ { - for x := 0; x < width; x++ { - img.Set(x, y, bgColor) - } - } - return img -} +const ( + REFLECTIVE_SURFACE_DEFAULT_WIDTH = 128 + REFLECTIVE_SURFACE_DEFAULT_HEIGHT = 128 +) + +var ( + REFLECTIVE_SURFACE_DEFAULT_COLOR = color.RGBA{255, 255, 255, 255} +) type ReflectiveBoundTexture struct { Surface *image.RGBA @@ -41,7 +40,7 @@ func (i *ReflectiveBoundTexture) commit() (*ReflectiveBoundTexture, bool) { i.mu.Lock() defer i.mu.Unlock() if i.Surface == nil { - i.Surface = defaultCanvas() + i.Surface = defaultSurface() } var has_changed bool @@ -54,24 +53,6 @@ func (i *ReflectiveBoundTexture) commit() (*ReflectiveBoundTexture, bool) { return i, has_changed } -func (i *ReflectiveBoundTexture) SetSurfaceFn(fn func() *image.RGBA, commit bool) error { - img := fn() - return i.SetSurfaceFromRGBA(img, commit) -} - -func (i *ReflectiveBoundTexture) SetSurfaceFromFillRect(width int, height int, c color.Color, commit bool) error { - img := newImage(width, height, c) - return i.SetSurfaceFromRGBA(img, commit) -} - -func (i *ReflectiveBoundTexture) SetSurfaceFromFile(path string, commit bool) error { - img, err := LoadImage(path) - if err != nil { - return err - } - return i.SetSurfaceFromRGBA(img, commit) -} - func (i *ReflectiveBoundTexture) SetSurfaceFromRGBA(img *image.RGBA, commit bool) error { if img != nil { i.Surface = img @@ -164,6 +145,18 @@ func (i *ReflectiveBoundTexture) bind() { }) } +func (i *ReflectiveBoundTexture) GetSurfaceWidth() int { + return i.Surface.Bounds().Dx() +} + +func (i *ReflectiveBoundTexture) GetSurfaceHeight() int { + return i.Surface.Bounds().Dy() +} + +func (i *ReflectiveBoundTexture) GetSurfaceSize() image.Point { + return i.Surface.Bounds().Size() +} + func (i *ReflectiveBoundTexture) Texture() *Texture { i.commit() return i.tex diff --git a/SurfaceLoaders.go b/SurfaceLoaders.go new file mode 100644 index 00000000..c82277ac --- /dev/null +++ b/SurfaceLoaders.go @@ -0,0 +1,124 @@ +package giu + +import ( + go_ctx "context" + "image" + "image/color" + "image/draw" + "net/http" + "time" +) + +// SurfaceLoader interface +type SurfaceLoader interface { + ServeRGBA() (*image.RGBA, error) +} +type SurfaceLoaderFunc func() (*image.RGBA, error) + +func (i *ReflectiveBoundTexture) LoadSurfaceFunc(fn SurfaceLoaderFunc, commit bool) error { + img, err := fn() + if err != nil { + return err + } + return i.SetSurfaceFromRGBA(img, commit) +} + +func (i *ReflectiveBoundTexture) LoadSurface(loader SurfaceLoader, commit bool) error { + img, err := loader.ServeRGBA() + if err != nil { + return err + } + return i.SetSurfaceFromRGBA(img, commit) +} + +// FileLoader + +type fileLoader struct { + path string +} + +func (f *fileLoader) ServeRGBA() (*image.RGBA, error) { + img, err := LoadImage(f.path) + if err != nil { + return nil, err + } + return img, nil +} + +func FileLoader(path string) SurfaceLoader { + return &fileLoader{ + path: path, + } +} + +func (i *ReflectiveBoundTexture) SetSurfaceFromFile(path string, commit bool) error { + return i.LoadSurface(FileLoader(path), commit) +} + +// UrlLoader + +type urlLoader struct { + url string + timeout time.Duration +} + +func (u *urlLoader) ServeRGBA() (*image.RGBA, error) { + client := &http.Client{Timeout: u.timeout} + req, err := http.NewRequestWithContext(go_ctx.Background(), "GET", u.url, http.NoBody) + if err != nil { + //errorFn(err) + return nil, err + } + resp, err := client.Do(req) + if err != nil { + //errorFn(err) + return nil, err + } + defer resp.Body.Close() + /*defer func() { + if closeErr := resp.Body.Close(); closeErr != nil { + errorFn(closeErr) + } + }()*/ + img, _, err := image.Decode(resp.Body) + if err != nil { + //errorFn(err) + return nil, err + } + return ImageToRgba(img), nil +} + +func URLLoader(url string, timeout time.Duration) SurfaceLoader { + return &urlLoader{ + url: url, + timeout: timeout, + } +} + +func (i *ReflectiveBoundTexture) SetSurfaceFromURL(url string, timeout time.Duration, commit bool) error { + return i.LoadSurface(URLLoader(url, timeout), commit) +} + +// UniformLoader +type uniformLoader struct { + width, height int + color color.Color +} + +func (u *uniformLoader) ServeRGBA() (*image.RGBA, error) { + img := image.NewRGBA(image.Rect(0, 0, u.width, u.height)) + draw.Draw(img, img.Bounds(), &image.Uniform{u.color}, image.ZP, draw.Src) + return img, nil +} + +func UniformLoader(width, height int, color color.Color) SurfaceLoader { + return &uniformLoader{ + width: width, + height: height, + color: color, + } +} + +func (i *ReflectiveBoundTexture) SetSurfaceUniform(width int, height int, c color.Color, commit bool) error { + return i.LoadSurface(UniformLoader(width, height, c), commit) +} diff --git a/examples/loadimage/loadimage.go b/examples/loadimage/loadimage.go index 55c7a0dd..d8f62c00 100644 --- a/examples/loadimage/loadimage.go +++ b/examples/loadimage/loadimage.go @@ -1,45 +1,70 @@ package main import ( - "context" "fmt" "image" _ "image/jpeg" _ "image/png" "log" - "net/http" "time" + imgui "github.com/AllenDang/cimgui-go" g "github.com/AllenDang/giu" ) var ( - fallback = &g.ReflectiveBoundTexture{} - gopher = &g.ReflectiveBoundTexture{} - urlWidgetLike = &g.ReflectiveBoundTexture{} - rgba *image.RGBA + fromrgba = &g.ReflectiveBoundTexture{} + fromfile = &g.ReflectiveBoundTexture{} + fromurl = &g.ReflectiveBoundTexture{} + rgba *image.RGBA + sonicOffsetX = int32(1180) + sonicOffsetY = int32(580) ) func loop() { - - urlWidgetLike.SetSurfaceFn(downloadUrlFn("https://static.wikia.nocookie.net/smashbros/images/0/0e/Art_Sonic_TSR.png/revision/latest?cb=20200210122913&path-prefix=fr", time.Second*10), true) + var start_pos image.Point + var window_size imgui.Vec2 g.SingleWindow().Layout( - g.Label("Display image from texture"), - gopher.ToImageWidget(), - g.Label("Display image from fallback"), - fallback.ToImageWidget().OnClick(func() { + g.Custom(func() { + start_pos = g.GetCursorScreenPos() + window_size = imgui.WindowSize() + }), + g.Label("Display wich has size of contentAvaiable (stretch)"), + fromfile.ToImageWidget().OnClick(func() { + fmt.Println("contentAvailable image was clicked") + }).Size(-1, -1), + + g.Label("Display image from preloaded rgba"), + fromrgba.ToImageWidget().OnClick(func() { fmt.Println("rgba image was clicked") - }).Size(600, 400), - g.Label("Display image from gopher"), - gopher.ToImageWidget().OnClick(func() { + }), + + g.Label("Display image from file"), + fromfile.ToImageWidget().OnClick(func() { fmt.Println("image from file was clicked") - }).Size(860, 561), + }), - // TODO Rewrite via func (i *ReflectiveBoundTexture) SetSurfaceFn(fn func() *image.RGBA, commit bool) error { - g.Label("Display image from url (wait few seconds to download)"), - g.ImageWithURL("https://png.pngitem.com/pimgs/s/3-36108_gopher-golang-hd-png-download.png").OnClick(func() { + g.Label("Display image from url + 0.25 scale"), + fromurl.ToImageWidget().OnClick(func() { fmt.Println("image from url clicked") - }).Size(300, 200), + }).Scale(0.25, 0.25), + + g.Label("Advanced Drawing manipulation"), + g.DragInt("Sonic Offset X", &sonicOffsetX, 0, 1280), + g.DragInt("Sonic Offset Y", &sonicOffsetY, 0, 720), + g.Custom(func() { + size := fromurl.GetSurfaceSize() + sonicOffset := image.Point{int(sonicOffsetX), int(sonicOffsetY)} + pos_with_offset := start_pos.Add(sonicOffset) + computed_posX := (float32(pos_with_offset.X)) + imgui.ScrollX() + computed_posY := (float32(pos_with_offset.Y)) + imgui.ScrollY() + //cur_pos := g.GetCursorPos() + scale := imgui.Vec2{0.10, 0.10} + p_min := imgui.Vec2{computed_posX, computed_posY} + p_max := imgui.Vec2{computed_posX + float32(size.X)*scale.X, computed_posY + float32(size.Y)*scale.Y} + imgui.ForegroundDrawList().AddImage(fromurl.Texture().ID(), p_min, p_max) + }), + /* g.Label("Display images from url with loading and fallback"), g.Label("Display image from url without placeholder (no size when loading)"), @@ -76,39 +101,10 @@ func main() { if err != nil { log.Fatalf("Cannot loadIamge fallback.png") } - gopher.SetSurfaceFromFile("gopher.png", false) - fallback.SetSurfaceFromRGBA(rgba, false) + fromfile.SetSurfaceFromFile("gopher.png", false) + fromrgba.SetSurfaceFromRGBA(rgba, false) + fromurl.SetSurfaceFromURL("https://static.wikia.nocookie.net/smashbros/images/0/0e/Art_Sonic_TSR.png/revision/latest?cb=20200210122913&path-prefix=fr", time.Second*5, false) - wnd := g.NewMasterWindow("Load Image", 600, 500, g.MasterWindowFlagsNotResizable) - //wnd.SetIcon(rgba) + wnd := g.NewMasterWindow("Load Image", 1280, 720, 0) wnd.Run(loop) } - -func downloadUrlFn(imgUrl string, timeout time.Duration) func() *image.RGBA { - return func() *image.RGBA { - client := &http.Client{Timeout: timeout} - req, err := http.NewRequestWithContext(context.Background(), "GET", imgUrl, http.NoBody) - if err != nil { - //errorFn(err) - return nil - } - resp, err := client.Do(req) - if err != nil { - //errorFn(err) - return nil - } - defer resp.Body.Close() - /*defer func() { - if closeErr := resp.Body.Close(); closeErr != nil { - errorFn(closeErr) - } - }()*/ - img, _, err := image.Decode(resp.Body) - if err != nil { - //errorFn(err) - return nil - } - return g.ImageToRgba(img) - - } -} diff --git a/go.mod b/go.mod index 57974585..3ff545a4 100644 --- a/go.mod +++ b/go.mod @@ -24,3 +24,5 @@ require ( golang.org/x/sys v0.5.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) + +