From 999432a48163fe1d13bab21d359297494ea75789 Mon Sep 17 00:00:00 2001 From: diamondburned Date: Fri, 25 Dec 2020 00:32:08 -0800 Subject: [PATCH] fixed hidpi for some images --- go.mod | 2 +- go.sum | 6 ++ internal/gts/httputil/image.go | 74 +++++++++++++------ internal/gts/httputil/nopwriter.go | 8 ++ .../messages/container/cozy/message_full.go | 14 ++-- internal/ui/primitives/primitives.go | 1 + .../ui/primitives/roundimage/roundimage.go | 13 ++++ 7 files changed, 88 insertions(+), 30 deletions(-) create mode 100644 internal/gts/httputil/nopwriter.go diff --git a/go.mod b/go.mod index 4af8557..21fdd62 100644 --- a/go.mod +++ b/go.mod @@ -2,7 +2,7 @@ module github.com/diamondburned/cchat-gtk go 1.14 -replace github.com/gotk3/gotk3 => github.com/diamondburned/gotk3 v0.0.0-20201209182406-e7291341a091 +replace github.com/gotk3/gotk3 => github.com/diamondburned/gotk3 v0.0.0-20201225074909-7bf1378bcba4 //replace github.com/diamondburned/cchat-discord => ../cchat-discord diff --git a/go.sum b/go.sum index 31b5a03..0b93150 100644 --- a/go.sum +++ b/go.sum @@ -64,6 +64,12 @@ github.com/diamondburned/cchat-mock v0.0.0-20201115033644-df8d1b10f9db h1:VQI2Pd github.com/diamondburned/cchat-mock v0.0.0-20201115033644-df8d1b10f9db/go.mod h1:M87kjNzWVPlkZycFNzpGPKQXzkHNnZphuwMf3E9ckgc= github.com/diamondburned/gotk3 v0.0.0-20201209182406-e7291341a091 h1:lQpSWzbi3rQf66aMSip/rIypasIFwqCqF0Wfn5og6gw= github.com/diamondburned/gotk3 v0.0.0-20201209182406-e7291341a091/go.mod h1:/hqFpkNa9T3JgNAE2fLvCdov7c5bw//FHNZrZ3Uv9/Q= +github.com/diamondburned/gotk3 v0.0.0-20201221055621-194e73aef9d5 h1:3mjkpXdjWoPN9sIRJeoNfJ/d8TbAzu4pVArO/0vRVZQ= +github.com/diamondburned/gotk3 v0.0.0-20201221055621-194e73aef9d5/go.mod h1:/hqFpkNa9T3JgNAE2fLvCdov7c5bw//FHNZrZ3Uv9/Q= +github.com/diamondburned/gotk3 v0.0.0-20201221091325-c5152a10909f h1:Lnrq+vXBgzbdiptxglDHhPf3kVLgLqcCGOJ7Ai3n/tc= +github.com/diamondburned/gotk3 v0.0.0-20201221091325-c5152a10909f/go.mod h1:/hqFpkNa9T3JgNAE2fLvCdov7c5bw//FHNZrZ3Uv9/Q= +github.com/diamondburned/gotk3 v0.0.0-20201225074909-7bf1378bcba4 h1:KvlmpqxLoXKg+j5uiJWZXhacfgPg4fi/8wLecWX+XlE= +github.com/diamondburned/gotk3 v0.0.0-20201225074909-7bf1378bcba4/go.mod h1:/hqFpkNa9T3JgNAE2fLvCdov7c5bw//FHNZrZ3Uv9/Q= github.com/diamondburned/gspell v0.0.0-20200830182722-77e5d27d6894 h1:QgI21deaQbCUMnxKkQQUXzQolnAe1dMIXAWwqAyOp2g= github.com/diamondburned/gspell v0.0.0-20200830182722-77e5d27d6894/go.mod h1:IoyMxPKSJOMoP0BiBuFwf2RDMeA4Uqx0HPKN5BzqTtA= github.com/diamondburned/handy v0.0.0-20200829011954-4667e7a918f4 h1:qF5VHC35+GyCjUmKz+1O94xpFc0JQd4Ui3h+I955pJw= diff --git a/internal/gts/httputil/image.go b/internal/gts/httputil/image.go index bb9fa56..058ac49 100644 --- a/internal/gts/httputil/image.go +++ b/internal/gts/httputil/image.go @@ -9,7 +9,9 @@ import ( "github.com/diamondburned/cchat-gtk/internal/log" "github.com/diamondburned/cchat-gtk/internal/ui/primitives" "github.com/diamondburned/imgutil" + "github.com/gotk3/gotk3/cairo" "github.com/gotk3/gotk3/gdk" + "github.com/gotk3/gotk3/gtk" "github.com/pkg/errors" ) @@ -23,7 +25,29 @@ type ImageContainer interface { GetSizeRequest() (w, h int) } -// AsyncImage loads an image. This method uses the cache. +type SurfaceContainer interface { + ImageContainer + GetScaleFactor() int + SetFromSurface(*cairo.Surface) +} + +var ( + _ ImageContainer = (*gtk.Image)(nil) + _ SurfaceContainer = (*gtk.Image)(nil) +) + +type surfaceWrapper struct { + SurfaceContainer + scale int +} + +func (wrapper surfaceWrapper) SetFromPixbuf(pb *gdk.Pixbuf) { + surface, _ := gdk.CairoSurfaceCreateFromPixbuf(pb, wrapper.scale, nil) + wrapper.SetFromSurface(surface) +} + +// AsyncImage loads an image. This method uses the cache. It prefers loading +// SetFromSurface over SetFromPixbuf, but will fallback if needed be. func AsyncImage(ctx context.Context, img ImageContainer, url string, procs ...imgutil.Processor) { @@ -31,9 +55,19 @@ func AsyncImage(ctx context.Context, return } - ctx = primitives.HandleDestroyCtx(ctx, img) - gif := strings.Contains(url, ".gif") + scale := 1 + + surfaceContainer, canSurface := img.(SurfaceContainer) + + if canSurface = canSurface && !gif; canSurface { + // Only bother with this API if we even have HiDPI. + if scale = surfaceContainer.GetScaleFactor(); scale > 1 { + img = surfaceWrapper{surfaceContainer, scale} + } + } + + ctx = primitives.HandleDestroyCtx(ctx, img) l, err := gdk.PixbufLoaderNew() if err != nil { @@ -41,27 +75,19 @@ func AsyncImage(ctx context.Context, return } - if w, h := img.GetSizeRequest(); w > 0 && h > 0 { - l.Connect("size-prepared", func(l *gdk.PixbufLoader, imgW, imgH int) { - w, h = imgutil.MaxSize(imgW, imgH, w, h) - if w != imgW || h != imgH { - l.SetSize(w, h) - } - }) - } + w, h := img.GetSizeRequest() + l.Connect("size-prepared", func(l *gdk.PixbufLoader, imgW, imgH int) { + w, h = imgutil.MaxSize(imgW, imgH, w, h) + if w != imgW || h != imgH || scale > 1 { + l.SetSize(w*scale, h*scale) + } + }) l.Connect("area-prepared", areaPreparedFn(ctx, img, gif)) - go syncImage(ctx, l, url, procs, gif) + go downloadImage(ctx, l, url, procs, gif) } -// func connectDestroyer(img ImageContainer, cancel func()) { -// img.Connect("destroy", func() { -// cancel() -// img.SetFromPixbuf(nil) -// }) -// } - func areaPreparedFn(ctx context.Context, img ImageContainer, gif bool) func(l *gdk.PixbufLoader) { return func(l *gdk.PixbufLoader) { if !gif { @@ -90,9 +116,9 @@ func execIfCtx(ctx context.Context, fn func()) { }) } -func syncImage(ctx context.Context, l io.WriteCloser, url string, p []imgutil.Processor, gif bool) { +func downloadImage(ctx context.Context, dst io.WriteCloser, url string, p []imgutil.Processor, gif bool) { // Close at the end when done. - defer l.Close() + defer dst.Close() r, err := get(ctx, url, true) if err != nil { @@ -104,13 +130,13 @@ func syncImage(ctx context.Context, l io.WriteCloser, url string, p []imgutil.Pr // If we have processors, then write directly in there. if len(p) > 0 { if !gif { - err = imgutil.ProcessStream(l, r.Body, p) + err = imgutil.ProcessStream(dst, r.Body, p) } else { - err = imgutil.ProcessAnimationStream(l, r.Body, p) + err = imgutil.ProcessAnimationStream(dst, r.Body, p) } } else { // Else, directly copy the body over. - _, err = io.Copy(l, r.Body) + _, err = io.Copy(dst, r.Body) } if err != nil { diff --git a/internal/gts/httputil/nopwriter.go b/internal/gts/httputil/nopwriter.go new file mode 100644 index 0000000..8e7c1d2 --- /dev/null +++ b/internal/gts/httputil/nopwriter.go @@ -0,0 +1,8 @@ +package httputil + +type nopWriterImpl struct{} + +var nopWriter = nopWriterImpl{} + +func (nopWriterImpl) Write(b []byte) (int, error) { return len(b), nil } +func (nopWriterImpl) Close() error { return nil } diff --git a/internal/ui/messages/container/cozy/message_full.go b/internal/ui/messages/container/cozy/message_full.go index 4e395c0..4ee4d50 100644 --- a/internal/ui/messages/container/cozy/message_full.go +++ b/internal/ui/messages/container/cozy/message_full.go @@ -13,6 +13,7 @@ import ( "github.com/diamondburned/cchat-gtk/internal/ui/primitives/menu" "github.com/diamondburned/cchat-gtk/internal/ui/primitives/roundimage" "github.com/diamondburned/cchat-gtk/internal/ui/rich/labeluri" + "github.com/gotk3/gotk3/cairo" "github.com/gotk3/gotk3/gtk" ) @@ -31,7 +32,7 @@ type FullMessage struct { } type AvatarPixbufCopier interface { - CopyAvatarPixbuf(img httputil.ImageContainer) + CopyAvatarPixbuf(img httputil.SurfaceContainer) } var ( @@ -148,12 +149,15 @@ func (m *FullMessage) UpdateAuthor(author cchat.Author) { // CopyAvatarPixbuf sets the pixbuf into the given container. This shares the // same pixbuf, but gtk.Image should take its own reference from the pixbuf. -func (m *FullMessage) CopyAvatarPixbuf(dst httputil.ImageContainer) { - switch img := m.Avatar.Image; img.GetImage().GetStorageType() { +func (m *FullMessage) CopyAvatarPixbuf(dst httputil.SurfaceContainer) { + switch img := m.Avatar.Image.GetImage(); img.GetStorageType() { case gtk.IMAGE_PIXBUF: dst.SetFromPixbuf(img.GetPixbuf()) case gtk.IMAGE_ANIMATION: dst.SetFromAnimation(img.GetAnimation()) + case gtk.IMAGE_SURFACE: + v, _ := img.GetProperty("surface") + dst.SetFromSurface(v.(*cairo.Surface)) } } @@ -187,7 +191,7 @@ func NewFullSendingMessage(msg input.PresendMessage) *FullSendingMessage { type Avatar struct { roundimage.Button - image *roundimage.StaticImage + Image *roundimage.StaticImage url string } @@ -216,7 +220,7 @@ func (a *Avatar) SetURL(url string) { } a.url = url - a.image.SetImageURL(url) + a.Image.SetImageURL(url) } // ManuallySetURL sets the URL without downloading the image. It assumes the diff --git a/internal/ui/primitives/primitives.go b/internal/ui/primitives/primitives.go index 5a0281d..e89665b 100644 --- a/internal/ui/primitives/primitives.go +++ b/internal/ui/primitives/primitives.go @@ -167,6 +167,7 @@ func MenuItem(label string, fn interface{}) *gtk.MenuItem { type Connector interface { Connect(string, interface{}, ...interface{}) (glib.SignalHandle, error) + ConnectAfter(string, interface{}, ...interface{}) (glib.SignalHandle, error) } func HandleDestroyCtx(ctx context.Context, connector Connector) context.Context { diff --git a/internal/ui/primitives/roundimage/roundimage.go b/internal/ui/primitives/roundimage/roundimage.go index 9509890..8cc4b86 100644 --- a/internal/ui/primitives/roundimage/roundimage.go +++ b/internal/ui/primitives/roundimage/roundimage.go @@ -60,6 +60,19 @@ func NewImage(radius float64) (*Image, error) { // Connect to the draw callback and clip the context. i.Connect("draw", image.drawer) + // Backup plan if Cairo's Surface is weird. + + // var width, height int + // i.Connect("size-allocate", func() { + // w := i.GetAllocatedWidth() + // h := i.GetAllocatedHeight() + + // if width != w || height != h { + // log.Println("Image allocate", width, height, i.GetScaleFactor()) + // width, height = w, h + // } + // }) + return image, nil }