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) + } +}