From 6faed7e7245c2622b00852ce3e7e7e8cf98b6134 Mon Sep 17 00:00:00 2001 From: Chris Waldon Date: Sat, 27 Feb 2021 22:18:09 -0500 Subject: [PATCH] widget: fix image scaling Commit 94d242d broke the widget.Image's Scale field so that it no longer had any effect on the actual size of the displayed image. This commit fixes that, as well as adding tests to confirm that the widget.Image type scales appropriately with DPI changes and its own Scale field. Signed-off-by: Chris Waldon --- widget/image.go | 7 ++++- widget/image_test.go | 66 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 72 insertions(+), 1 deletion(-) create mode 100644 widget/image_test.go diff --git a/widget/image.go b/widget/image.go index 77062651..54ab62f2 100644 --- a/widget/image.go +++ b/widget/image.go @@ -5,6 +5,7 @@ package widget import ( "image" + "gioui.org/f32" "gioui.org/layout" "gioui.org/op" "gioui.org/op/clip" @@ -22,10 +23,12 @@ type Image struct { Scale float32 } +const defaultScale = float32(160.0 / 72.0) + func (im Image) Layout(gtx layout.Context) layout.Dimensions { scale := im.Scale if scale == 0 { - scale = 160.0 / 72.0 + scale = defaultScale } size := im.Src.Size() wf, hf := float32(size.X), float32(size.Y) @@ -33,6 +36,8 @@ func (im Image) Layout(gtx layout.Context) layout.Dimensions { cs := gtx.Constraints d := cs.Constrain(image.Pt(w, h)) stack := op.Save(gtx.Ops) + pixelScale := scale * gtx.Metric.PxPerDp + op.Affine(f32.Affine2D{}.Scale(f32.Point{}, f32.Pt(pixelScale, pixelScale))).Add(gtx.Ops) clip.Rect(image.Rectangle{Max: d}).Add(gtx.Ops) im.Src.Add(gtx.Ops) paint.PaintOp{}.Add(gtx.Ops) diff --git a/widget/image_test.go b/widget/image_test.go new file mode 100644 index 00000000..edc183ca --- /dev/null +++ b/widget/image_test.go @@ -0,0 +1,66 @@ +// SPDX-License-Identifier: Unlicense OR MIT + +package widget + +import ( + "image" + "testing" + + "gioui.org/layout" + "gioui.org/op" + "gioui.org/op/paint" +) + +func TestImageScale(t *testing.T) { + var ops op.Ops + gtx := layout.Context{ + Ops: &ops, + Constraints: layout.Constraints{ + Max: image.Pt(50, 50), + }, + } + imgSize := image.Pt(10, 10) + img := image.NewNRGBA(image.Rectangle{Max: imgSize}) + imgOp := paint.NewImageOp(img) + + // Ensure the default scales correctly. + dims := Image{Src: imgOp}.Layout(gtx) + expectedSize := imgSize + expectedSize.X = int(float32(expectedSize.X) * defaultScale) + expectedSize.Y = int(float32(expectedSize.Y) * defaultScale) + if dims.Size != expectedSize { + t.Fatalf("non-scaled image is wrong size, expected %v, got %v", expectedSize, dims.Size) + } + + // Ensure scaling the image via the Scale field works. + currentScale := float32(0.5) + dims = Image{Src: imgOp, Scale: float32(currentScale)}.Layout(gtx) + expectedSize = imgSize + expectedSize.X = int(float32(expectedSize.X) * currentScale) + expectedSize.Y = int(float32(expectedSize.Y) * currentScale) + if dims.Size != expectedSize { + t.Fatalf(".5 scale image is wrong size, expected %v, got %v", expectedSize, dims.Size) + } + + // Ensure the image responds to changes in DPI. + currentScale = float32(1) + gtx.Metric.PxPerDp = 2 + dims = Image{Src: imgOp, Scale: float32(currentScale)}.Layout(gtx) + expectedSize = imgSize + expectedSize.X = int(float32(expectedSize.X) * currentScale * gtx.Metric.PxPerDp) + expectedSize.Y = int(float32(expectedSize.Y) * currentScale * gtx.Metric.PxPerDp) + if dims.Size != expectedSize { + t.Fatalf("HiDPI non-scaled image is wrong size, expected %v, got %v", expectedSize, dims.Size) + } + + // Ensure scaling the image responds to changes in DPI. + currentScale = float32(.5) + gtx.Metric.PxPerDp = 2 + dims = Image{Src: imgOp, Scale: float32(currentScale)}.Layout(gtx) + expectedSize = imgSize + expectedSize.X = int(float32(expectedSize.X) * currentScale * gtx.Metric.PxPerDp) + expectedSize.Y = int(float32(expectedSize.Y) * currentScale * gtx.Metric.PxPerDp) + if dims.Size != expectedSize { + t.Fatalf("HiDPI .5 scale image is wrong size, expected %v, got %v", expectedSize, dims.Size) + } +}