1
0
Fork 0
mirror of https://github.com/diamondburned/cchat-gtk.git synced 2024-12-23 12:46:45 +00:00
cchat-gtk/internal/gts/httputil/image.go

138 lines
3.1 KiB
Go

package httputil
import (
"context"
"io"
"strings"
"github.com/diamondburned/cchat-gtk/internal/gts"
"github.com/diamondburned/cchat-gtk/internal/log"
"github.com/diamondburned/imgutil"
"github.com/gotk3/gotk3/gdk"
"github.com/gotk3/gotk3/glib"
"github.com/pkg/errors"
)
// TODO:
type ImageContainer interface {
SetFromPixbuf(*gdk.Pixbuf)
SetFromAnimation(*gdk.PixbufAnimation)
Connect(string, interface{}, ...interface{}) (glib.SignalHandle, error)
}
type ImageContainerSizer interface {
ImageContainer
SetSizeRequest(w, h int)
}
// AsyncImage loads an image. This method uses the cache.
func AsyncImage(img ImageContainer, url string, procs ...imgutil.Processor) {
asyncImage(img, url, 0, 0, procs)
}
// AsyncImageSized resizes using GdkPixbuf. This method uses the cache.
func AsyncImageSized(img ImageContainerSizer, url string, w, h int, procs ...imgutil.Processor) {
asyncImage(img, url, w, h, procs)
}
func asyncImage(img ImageContainer, url string, w, h int, procs []imgutil.Processor) {
if url == "" {
return
}
// // Add a processor to resize.
// procs = append(procs, imgutil.Resize(w, h))
ctx, cancel := context.WithCancel(context.Background())
connectDestroyer(img, cancel)
gif := strings.Contains(url, ".gif")
l, err := gdk.PixbufLoaderNew()
if err != nil {
log.Error(errors.Wrap(err, "Failed to make pixbuf loader"))
return
}
if 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)
execIfCtx(ctx, func() {
img.(ImageContainerSizer).SetSizeRequest(w, h)
})
}
})
}
l.Connect("area-prepared", areaPreparedFn(ctx, img, gif))
go syncImage(ctx, l, url, procs, gif)
}
func connectDestroyer(img ImageContainer, cancel func()) {
img.Connect("destroy", func(img ImageContainer) {
cancel()
img.SetFromPixbuf(nil)
})
}
func areaPreparedFn(ctx context.Context, img ImageContainer, gif bool) func(l *gdk.PixbufLoader) {
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) })
} else {
p, err := l.GetAnimation()
if err != nil {
log.Error(errors.Wrap(err, "Failed to get animation"))
return
}
execIfCtx(ctx, func() { img.SetFromAnimation(p) })
}
}
}
func execIfCtx(ctx context.Context, fn func()) {
gts.ExecAsync(func() {
if ctx.Err() == nil {
fn()
}
})
}
func syncImage(ctx context.Context, l io.WriteCloser, url string, p []imgutil.Processor, gif bool) {
// Close at the end when done.
defer l.Close()
r, err := get(ctx, url, true)
if err != nil {
log.Error(err)
return
}
defer r.Body.Close()
// If we have processors, then write directly in there.
if len(p) > 0 {
if !gif {
err = imgutil.ProcessStream(l, r.Body, p)
} else {
err = imgutil.ProcessAnimationStream(l, r.Body, p)
}
} else {
// Else, directly copy the body over.
_, err = io.Copy(l, r.Body)
}
if err != nil {
log.Error(errors.Wrap(err, "Error processing image"))
return
}
}