Skip to content

Fyne Thumbnail Proposal

Stephen M Houston edited this page Aug 13, 2020 · 9 revisions

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?

Welcome to the Fyne wiki.

Project documentation

Getting involved

Platform details

Clone this wiki locally