-
-
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: `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?