-
-
Notifications
You must be signed in to change notification settings - Fork 1.4k
Fyne Thumbnail Proposal
At some point in the near future Fyne will need the ability to create thumbnails and load them as well as load them from cache. I am not familiar with thumbnailing outside of Linux using the xdg/fdo thumbnail manager spec. At a brief glance it would seem to use Window's system we would need to do it natively using syscalls. I would imagine we would need to do thumbnailing natively on ios/android as well due to storage locations and permissions. For now I have a working solution in FyFoto that follows the xdg/fdo spec and adapts to fallbacks for cross platform. Thumbnailing is done in a go routine and this is what the current code looks like:
package main
import (
"crypto/md5"
"encoding/hex"
"fmt"
"image"
"image/png"
"math"
"os"
"os/user"
"time"
`"fyne.io/fyne"`
`"fyne.io/fyne/canvas"`
`"golang.org/x/image/draw"`
)
func thumbnail(ff *FyFoto, thumbQueue <-chan gridImage, quitQueue <-chan string) {
for {
select {
case <-quitQueue:
return
case gi := <-thumbQueue:
file := gi.imageFile
base := ""
xdgcache := os.Getenv("XDG_CACHE_HOME")
if xdgcache == "" {
usr, err := user.Current()
if err != nil {
fmt.Println("Could not find the current user")
return
}
base = usr.HomeDir + "/.cache"
} else {
base = xdgcache
}
os.Mkdir(base, 0700)
thumbDir := base + "/thumbnails"
os.Mkdir(thumbDir, 0700)
thumbDir += "/normal"
os.Mkdir(thumbDir, 0700)
`uri := []byte("file://" + file)`
`newfileMD5 := md5.New()`
`newfileMD5.Write(uri)`
`newfile := hex.EncodeToString(newfileMD5.Sum(nil))`
`destfile := thumbDir + "/" + newfile + ".png"`
`needThumb := 1`
`th, err := os.Stat(destfile)`
`if !os.IsNotExist(err) {`
`orig, _ := os.Stat(file)`
`modThumb := th.ModTime()`
`modOrig := orig.ModTime()`
`diff := modThumb.Sub(modOrig)`
`if diff > (time.Duration(0) * time.Second) {`
`needThumb = 0`
`}`
`}`
`if needThumb == 1 {`
`img, err := os.Open(file)`
`if err != nil {`
`fmt.Println("Could not open image to thumbnail")`
`img.Close()`
`break`
`}`
`src, _, err := image.Decode(img)`
`if err != nil {`
`fmt.Println("Could not decode source image for thumbnail")`
`img.Close()`
`break`
`}`
`img.Close()`
`img, err = os.Open(file)`
`if err != nil {`
`fmt.Println("Could not open image to thumbnail")`
`img.Close()`
`break`
`}`
`cfg, _, err := image.DecodeConfig(img)`
`if err != nil {`
`fmt.Println("Could not get original image size")`
`img.Close()`
`break`
`}`
`img.Close()`
`newWidth, newHeight := 128, 128`
`if cfg.Width > cfg.Height {`
`scale := float64(cfg.Width) / float64(cfg.Height)`
`newHeight = int(math.Round(float64(newWidth) / float64(scale)))`
`} else if cfg.Height > cfg.Width {`
`scale := float64(cfg.Height) / float64(cfg.Width)`
`newWidth = int(math.Round(float64(newHeight) / float64(scale)))`
`}`
`dest := image.NewRGBA(image.Rect(0, 0, newWidth, newHeight))`
`draw.NearestNeighbor.Scale(dest, dest.Bounds(), src, src.Bounds(), draw.Src, nil)`
`out, err := os.Create(destfile)`
`if err != nil {`
`fmt.Println("Could not create thumbnail destination file")`
`}`
`err = png.Encode(out, dest)`
`if err != nil {`
`fmt.Println("Could not encode png for thumbnail")`
`}`
`}`
`if &gi != nil && gi.imageDir == ff.currentDir {`
`gi.imageObject = canvas.NewImageFromFile(destfile)`
`gi.imageObject.FillMode = canvas.ImageFillContain`
`size := int(math.Floor(float64(128 * ff.window.Canvas().Scale())))`
`gi.imageObject.SetMinSize(fyne.NewSize(size, size))`
`gi.Append(gi.imageObject)`
`ff.images.AddObject(&gi)`
`canvas.Refresh(ff.images)`
`}`
`}`
`}`
}
This would not be hard to adapt into canvas/image or widget/icon. What would be needed:
- A new API such as SetThumbnail(thumbnail bool) that if true would take the image resource and thumbnail it/get thumbnail from cache and use that as the displayed imaged.
Considerations:
- Is this cross platform enough where we take the trade off of potentially adding additional storage/duplication of thumbnails on platforms other than linux by adapting the fdo/xdg spec cross platform?
- Should we consider doing the thumbnails natively on platforms that require them be done that way including syscalls?
- Should we add in an API to switch the thumbnail size between the specs NORMAL and LARGE choices or should we just stick to NORMAL?
- What milestone would this need to be a part of?