cchat-gtk/internal/gts/httputil/image.go

147 lines
3.3 KiB
Go
Raw Normal View History

2020-06-06 07:44:36 +00:00
package httputil
import (
"context"
2020-06-06 07:44:36 +00:00
"io"
"strings"
"github.com/diamondburned/cchat-gtk/internal/gts"
"github.com/diamondburned/cchat-gtk/internal/log"
"github.com/diamondburned/cchat-gtk/internal/ui/primitives"
2020-06-06 07:44:36 +00:00
"github.com/diamondburned/imgutil"
2020-12-25 08:32:08 +00:00
"github.com/gotk3/gotk3/cairo"
2020-06-06 07:44:36 +00:00
"github.com/gotk3/gotk3/gdk"
2020-12-25 08:32:08 +00:00
"github.com/gotk3/gotk3/gtk"
2020-06-06 07:44:36 +00:00
"github.com/pkg/errors"
)
// TODO:
2020-06-07 04:27:28 +00:00
type ImageContainer interface {
primitives.Connector
2020-06-07 04:27:28 +00:00
SetFromPixbuf(*gdk.Pixbuf)
SetFromAnimation(*gdk.PixbufAnimation)
2020-12-20 08:18:23 +00:00
GetSizeRequest() (w, h int)
}
2020-12-25 08:32:08 +00:00
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) {
2020-07-11 06:48:44 +00:00
if url == "" {
return
}
gif := strings.Contains(url, ".gif")
2020-12-25 08:32:08 +00:00
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)
2020-06-07 04:27:28 +00:00
l, err := gdk.PixbufLoaderNew()
if err != nil {
log.Error(errors.Wrap(err, "Failed to make pixbuf loader"))
return
}
2020-12-25 08:32:08 +00:00
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))
2020-12-25 08:32:08 +00:00
go downloadImage(ctx, l, url, procs, gif)
2020-06-07 04:27:28 +00:00
}
func areaPreparedFn(ctx context.Context, img ImageContainer, gif bool) func(l *gdk.PixbufLoader) {
2020-06-07 04:27:28 +00:00
return func(l *gdk.PixbufLoader) {
if !gif {
p, err := l.GetPixbuf()
if err != nil {
log.Error(errors.Wrap(err, "Failed to get pixbuf"))
return
}
execIfCtx(ctx, func() { img.SetFromPixbuf(p) })
2020-06-07 04:27:28 +00:00
} else {
p, err := l.GetAnimation()
if err != nil {
log.Error(errors.Wrap(err, "Failed to get animation"))
return
}
execIfCtx(ctx, func() { img.SetFromAnimation(p) })
2020-06-07 04:27:28 +00:00
}
}
2020-06-06 07:44:36 +00:00
}
func execIfCtx(ctx context.Context, fn func()) {
gts.ExecAsync(func() {
if ctx.Err() == nil {
fn()
}
})
}
2020-12-25 08:32:08 +00:00
func downloadImage(ctx context.Context, dst io.WriteCloser, url string, p []imgutil.Processor, gif bool) {
// Close at the end when done.
2020-12-25 08:32:08 +00:00
defer dst.Close()
2020-06-07 04:27:28 +00:00
r, err := get(ctx, url, true)
2020-06-06 07:44:36 +00:00
if err != nil {
log.Error(err)
return
}
defer r.Body.Close()
// If we have processors, then write directly in there.
if len(p) > 0 {
2020-06-06 07:44:36 +00:00
if !gif {
2020-12-25 08:32:08 +00:00
err = imgutil.ProcessStream(dst, r.Body, p)
2020-06-06 07:44:36 +00:00
} else {
2020-12-25 08:32:08 +00:00
err = imgutil.ProcessAnimationStream(dst, r.Body, p)
2020-06-06 07:44:36 +00:00
}
} else {
// Else, directly copy the body over.
2020-12-25 08:32:08 +00:00
_, err = io.Copy(dst, r.Body)
2020-06-06 07:44:36 +00:00
}
if err != nil {
log.Error(errors.Wrap(err, "Error processing image"))
return
}
}