| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192 |
- package painter
- import (
- "bytes"
- "fmt"
- "image"
- _ "image/jpeg" // avoid users having to import when using image widget
- _ "image/png" // avoid the same for PNG images
- "io"
- "os"
- "path/filepath"
- "strings"
- "golang.org/x/image/draw"
- "fyne.io/fyne/v2"
- "fyne.io/fyne/v2/canvas"
- "fyne.io/fyne/v2/internal"
- "fyne.io/fyne/v2/internal/cache"
- "fyne.io/fyne/v2/internal/svg"
- )
- var aspects = make(map[interface{}]float32, 16)
- // GetAspect looks up an aspect ratio of an image
- func GetAspect(img *canvas.Image) float32 {
- aspect := float32(0.0)
- if img.Resource != nil {
- aspect = aspects[img.Resource.Name()]
- } else if img.File != "" {
- aspect = aspects[img.File]
- } else if img.Image != nil {
- // HOTFIX until Fyne 2.4 proper fix:
- // we are not storing the aspect ratio in the map for the image.Image case
- size := img.Image.Bounds().Size()
- return float32(size.X) / float32(size.Y)
- }
- if aspect == 0 {
- aspect = aspects[img]
- }
- return aspect
- }
- // PaintImage renders a given fyne Image to a Go standard image
- // If a fyne.Canvas is given and the image’s fill mode is “fill original” the image’s min size has
- // to fit its original size. If it doesn’t, PaintImage does not paint the image but adjusts its min size.
- // The image will then be painted on the next frame because of the min size change.
- func PaintImage(img *canvas.Image, c fyne.Canvas, width, height int) image.Image {
- var wantOrigW, wantOrigH int
- wantOrigSize := false
- if img.FillMode == canvas.ImageFillOriginal && c != nil {
- wantOrigW = internal.ScaleInt(c, img.MinSize().Width)
- wantOrigH = internal.ScaleInt(c, img.MinSize().Height)
- wantOrigSize = true
- }
- dst, origW, origH, err := paintImage(img, width, height, wantOrigSize, wantOrigW, wantOrigH)
- if err != nil {
- fyne.LogError("failed to paint image", err)
- return nil
- }
- if wantOrigSize && dst == nil {
- dpSize := fyne.NewSize(internal.UnscaleInt(c, origW), internal.UnscaleInt(c, origH))
- img.SetMinSize(dpSize)
- canvas.Refresh(img) // force the initial size to be respected
- }
- return dst
- }
- func paintImage(img *canvas.Image, width, height int, wantOrigSize bool, wantOrigW, wantOrigH int) (dst image.Image, origW, origH int, err error) {
- if (width <= 0 || height <= 0) && !wantOrigSize {
- return
- }
- var aspectCacheKey interface{} = img
- checkSize := func(origW, origH int) bool {
- aspect := float32(origW) / float32(origH)
- // this is used by our render code, so let's set it to the file aspect
- aspects[aspectCacheKey] = aspect
- return !wantOrigSize || (wantOrigW == origW && wantOrigH == origH)
- }
- switch {
- case img.File != "" || img.Resource != nil:
- var (
- file io.Reader
- name string
- isSVG bool
- )
- if img.Resource != nil {
- name = img.Resource.Name()
- file = bytes.NewReader(img.Resource.Content())
- isSVG = IsResourceSVG(img.Resource)
- } else {
- name = img.File
- var handle *os.File
- handle, err = os.Open(img.File)
- if err != nil {
- err = fmt.Errorf("image load error: %w", err)
- return
- }
- defer handle.Close()
- file = handle
- isSVG = isFileSVG(img.File)
- }
- aspectCacheKey = name
- if isSVG {
- tex := cache.GetSvg(name, width, height)
- if tex == nil {
- // Not in cache, so load the item and add to cache
- tex, err = svg.ToImage(file, width, height, checkSize)
- if err != nil {
- return
- }
- cache.SetSvg(name, tex, width, height)
- }
- dst = tex
- } else {
- var pixels image.Image
- pixels, _, err = image.Decode(file)
- if err != nil {
- err = fmt.Errorf("failed to decode image: %w", err)
- return
- }
- origSize := pixels.Bounds().Size()
- origW, origH = origSize.X, origSize.Y
- if checkSize(origSize.X, origSize.Y) {
- dst = scaleImage(pixels, width, height, img.ScaleMode)
- }
- }
- case img.Image != nil:
- origSize := img.Image.Bounds().Size()
- origW, origH = origSize.X, origSize.Y
- // HOTFIX until Fyne 2.4: don't store aspect ratio in map, as checkSize(x, y) does.
- // Doing so leaks a reference to the image.Image data
- if !wantOrigSize || (wantOrigW == origW && wantOrigH == origH) {
- dst = scaleImage(img.Image, width, height, img.ScaleMode)
- }
- default:
- dst = image.NewNRGBA(image.Rect(0, 0, 1, 1))
- }
- return
- }
- func scaleImage(pixels image.Image, scaledW, scaledH int, scale canvas.ImageScale) image.Image {
- if scale == canvas.ImageScaleFastest || scale == canvas.ImageScalePixels {
- // do not perform software scaling
- return pixels
- }
- pixW := int(fyne.Min(float32(scaledW), float32(pixels.Bounds().Dx()))) // don't push more pixels than we have to
- pixH := int(fyne.Min(float32(scaledH), float32(pixels.Bounds().Dy()))) // the GL calls will scale this up on GPU.
- scaledBounds := image.Rect(0, 0, pixW, pixH)
- tex := image.NewNRGBA(scaledBounds)
- switch scale {
- case canvas.ImageScalePixels:
- draw.NearestNeighbor.Scale(tex, scaledBounds, pixels, pixels.Bounds(), draw.Over, nil)
- default:
- if scale != canvas.ImageScaleSmooth {
- fyne.LogError("Invalid canvas.ImageScale value, using canvas.ImageScaleSmooth", nil)
- }
- draw.CatmullRom.Scale(tex, scaledBounds, pixels, pixels.Bounds(), draw.Over, nil)
- }
- return tex
- }
- func isFileSVG(path string) bool {
- return strings.ToLower(filepath.Ext(path)) == ".svg"
- }
- // IsResourceSVG checks if the resource is an SVG or not.
- func IsResourceSVG(res fyne.Resource) bool {
- if strings.ToLower(filepath.Ext(res.Name())) == ".svg" {
- return true
- }
- if len(res.Content()) < 5 {
- return false
- }
- switch strings.ToLower(string(res.Content()[:5])) {
- case "<!doc", "<?xml", "<svg ":
- return true
- }
- return false
- }
|