Skip to content

Commit

Permalink
Fix the flicker on originalFill image load
Browse files Browse the repository at this point in the history
This is not pretty but what we have to do is return the visible portion pixel for pixel.
The image is immediately refreshed so the correct, full, image (with aspect applied) is then used.

Fixes fyne-io#1432
  • Loading branch information
andydotxyz committed Oct 21, 2020
1 parent d07083a commit d1b64d2
Show file tree
Hide file tree
Showing 2 changed files with 47 additions and 18 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Expand Up @@ -56,6 +56,7 @@ More detailed release notes can be found on the [releases page](https://github.c
* Respect SelectEntry datta changes on refresh (#1462)
* Incorrect SelectEntry dropdown button position (#1361)
* don't allow both single and double tap events to fire (#1381)
* Fix issue where long or tall images could jump on load (#1266, #1432)
* Add missing raster support in software render
* Respect GOOS/GOARCH in fyne command utilities
* BSD support in build tools
Expand Down
64 changes: 46 additions & 18 deletions internal/painter/image.go
Expand Up @@ -55,21 +55,26 @@ func PaintImage(img *canvas.Image, c fyne.Canvas, width, height int) image.Image
aspect := float32(origW) / float32(origH)
viewAspect := float32(width) / float32(height)

texW, texH := width, height
if viewAspect > aspect {
texW = int(float32(height) * aspect)
} else if viewAspect < aspect {
texH = int(float32(width) / aspect)
}

icon.SetTarget(0, 0, float64(texW), float64(texH))
// this is used by our render code, so let's set it to the file aspect
aspects[img.Resource] = aspect
firstPassOriginal := false
// if the image specifies it should be original size we need at least that many pixels on screen
if img.FillMode == canvas.ImageFillOriginal {
checkImageMinSize(img, c, origW, origH)
if !checkImageMinSize(img, c, origW, origH) {
firstPassOriginal = true
}
}

texW, texH := width, height
if firstPassOriginal { // we draw pixel 1:1 so we can subimage it below to fit in the current bounds
texW, texH = origW, origH
} else {
if viewAspect > aspect {
texW = int(float32(height) * aspect)
} else if viewAspect < aspect {
texH = int(float32(width) / aspect)
}
}

icon.SetTarget(0, 0, float64(texW), float64(texH))
tex = image.NewNRGBA(image.Rect(0, 0, texW, texH))
scanner := rasterx.NewScannerGV(origW, origH, tex, tex.Bounds())
raster := rasterx.NewDasher(width, height, scanner)
Expand All @@ -79,6 +84,13 @@ func PaintImage(img *canvas.Image, c fyne.Canvas, width, height int) image.Image
fyne.LogError("SVG Render error:", err)
return nil
}

if firstPassOriginal { // crop out the currently visible portion to avoid flicker when it sizes later
return imageSubset(tex, width, height, img.ScaleMode)
}

// this is used by our render code, so let's set it to the file aspect
aspects[img.Resource] = aspect
svgCachePut(img.Resource, tex, width, height)
}

Expand All @@ -93,29 +105,42 @@ func PaintImage(img *canvas.Image, c fyne.Canvas, width, height int) image.Image
return nil
}
origSize := pixels.Bounds().Size()
// this is used by our render code, so let's set it to the file aspect
aspects[img] = float32(origSize.X) / float32(origSize.Y)
// if the image specifies it should be original size we need at least that many pixels on screen
if img.FillMode == canvas.ImageFillOriginal {
checkImageMinSize(img, c, origSize.X, origSize.Y)
if !checkImageMinSize(img, c, origSize.X, origSize.Y) {
return imageSubset(pixels, width, height, img.ScaleMode)
}
}
// this is used by our render code, so let's set it to the file aspect
aspects[img] = float32(origSize.X) / float32(origSize.Y)

return scaleImage(pixels, width, height, img.ScaleMode)
case img.Image != nil:
origSize := img.Image.Bounds().Size()
// this is used by our render code, so let's set it to the file aspect
aspects[img] = float32(origSize.X) / float32(origSize.Y)
// if the image specifies it should be original size we need at least that many pixels on screen
if img.FillMode == canvas.ImageFillOriginal {
checkImageMinSize(img, c, origSize.X, origSize.Y)
if !checkImageMinSize(img, c, origSize.X, origSize.Y) {
return imageSubset(img.Image, width, height, img.ScaleMode)
}
}

// this is used by our render code, so let's set it to the file aspect
aspects[img] = float32(origSize.X) / float32(origSize.Y)
return scaleImage(img.Image, width, height, img.ScaleMode)
default:
return image.NewNRGBA(image.Rect(0, 0, 1, 1))
}
}

// imageSubset returns a portion of an image based on pixel dimensions.
// This is needed when we initially display an image with original dimensions that are still being calculated.
func imageSubset(img image.Image, width, height int, scale canvas.ImageScale) image.Image {
sub := img.(interface {
SubImage(r image.Rectangle) image.Image
}).SubImage(image.Rect(0, 0, width, height))
return scaleImage(sub, width, height, scale)
}

func scaleImage(pixels image.Image, scaledW, scaledH int, scale canvas.ImageScale) image.Image {
pixW := fyne.Min(scaledW, pixels.Bounds().Dx()) // don't push more pixels than we have to
pixH := fyne.Min(scaledH, pixels.Bounds().Dy()) // the GL calls will scale this up on GPU.
Expand Down Expand Up @@ -145,11 +170,14 @@ func drawSVGSafely(icon *oksvg.SvgIcon, raster *rasterx.Dasher) error {
return err
}

func checkImageMinSize(img *canvas.Image, c fyne.Canvas, pixX, pixY int) {
func checkImageMinSize(img *canvas.Image, c fyne.Canvas, pixX, pixY int) bool {
dpSize := fyne.NewSize(internal.UnscaleInt(c, pixX), internal.UnscaleInt(c, pixY))

if img.MinSize() != dpSize {
img.SetMinSize(dpSize)
canvas.Refresh(img) // force the initial size to be respected
return false
}

return true
}

0 comments on commit d1b64d2

Please sign in to comment.