fixed hidpi for some images

This commit is contained in:
diamondburned 2020-12-25 00:32:08 -08:00
parent cda40a58ee
commit 999432a481
7 changed files with 88 additions and 30 deletions

2
go.mod
View File

@ -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

6
go.sum
View File

@ -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=

View File

@ -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 {

View File

@ -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 }

View File

@ -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

View File

@ -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 {

View File

@ -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
}