gioui.org@v0.6.1-0.20240506124620-7a9ce51988ce/gpu/internal/rendertest/clip_test.go (about)

     1  // SPDX-License-Identifier: Unlicense OR MIT
     2  
     3  package rendertest
     4  
     5  import (
     6  	"image"
     7  	"image/color"
     8  	"math"
     9  	"testing"
    10  
    11  	"golang.org/x/image/colornames"
    12  
    13  	"gioui.org/f32"
    14  	"gioui.org/op"
    15  	"gioui.org/op/clip"
    16  	"gioui.org/op/paint"
    17  )
    18  
    19  func TestPaintRect(t *testing.T) {
    20  	run(t, func(o *op.Ops) {
    21  		paint.FillShape(o, red, clip.Rect(image.Rect(0, 0, 50, 50)).Op())
    22  	}, func(r result) {
    23  		r.expect(0, 0, colornames.Red)
    24  		r.expect(49, 0, colornames.Red)
    25  		r.expect(50, 0, transparent)
    26  		r.expect(10, 50, transparent)
    27  	})
    28  }
    29  
    30  func TestPaintClippedRect(t *testing.T) {
    31  	run(t, func(o *op.Ops) {
    32  		defer clip.RRect{Rect: image.Rect(25, 25, 60, 60)}.Push(o).Pop()
    33  		paint.FillShape(o, red, clip.Rect(image.Rect(0, 0, 50, 50)).Op())
    34  	}, func(r result) {
    35  		r.expect(0, 0, transparent)
    36  		r.expect(24, 35, transparent)
    37  		r.expect(25, 35, colornames.Red)
    38  		r.expect(50, 0, transparent)
    39  		r.expect(10, 50, transparent)
    40  	})
    41  }
    42  
    43  func TestPaintClippedCircle(t *testing.T) {
    44  	run(t, func(o *op.Ops) {
    45  		const r = 10
    46  		defer clip.RRect{Rect: image.Rect(20, 20, 40, 40), SE: r, SW: r, NW: r, NE: r}.Push(o).Pop()
    47  		defer clip.Rect(image.Rect(0, 0, 30, 50)).Push(o).Pop()
    48  		paint.Fill(o, red)
    49  	}, func(r result) {
    50  		r.expect(21, 21, transparent)
    51  		r.expect(25, 30, colornames.Red)
    52  		r.expect(31, 30, transparent)
    53  	})
    54  }
    55  
    56  func TestPaintArc(t *testing.T) {
    57  	run(t, func(o *op.Ops) {
    58  		p := new(clip.Path)
    59  		p.Begin(o)
    60  		p.Move(f32.Pt(0, 20))
    61  		p.Line(f32.Pt(10, 0))
    62  		p.Arc(f32.Pt(10, 0), f32.Pt(40, 0), math.Pi)
    63  		p.Line(f32.Pt(30, 0))
    64  		p.Line(f32.Pt(0, 25))
    65  		p.Arc(f32.Pt(-10, 5), f32.Pt(10, 15), -math.Pi)
    66  		p.Line(f32.Pt(0, 25))
    67  		p.Arc(f32.Pt(10, 10), f32.Pt(10, 10), 2*math.Pi)
    68  		p.Line(f32.Pt(-10, 0))
    69  		p.Arc(f32.Pt(-10, 0), f32.Pt(-40, 0), -math.Pi)
    70  		p.Line(f32.Pt(-10, 0))
    71  		p.Line(f32.Pt(0, -10))
    72  		p.Arc(f32.Pt(-10, -20), f32.Pt(10, -5), math.Pi)
    73  		p.Line(f32.Pt(0, -10))
    74  		p.Line(f32.Pt(-50, 0))
    75  		p.Close()
    76  		defer clip.Outline{
    77  			Path: p.End(),
    78  		}.Op().Push(o).Pop()
    79  
    80  		paint.FillShape(o, red, clip.Rect(image.Rect(0, 0, 128, 128)).Op())
    81  	}, func(r result) {
    82  		r.expect(0, 0, transparent)
    83  		r.expect(0, 25, colornames.Red)
    84  		r.expect(0, 15, transparent)
    85  	})
    86  }
    87  
    88  func TestPaintAbsolute(t *testing.T) {
    89  	run(t, func(o *op.Ops) {
    90  		p := new(clip.Path)
    91  		p.Begin(o)
    92  		p.Move(f32.Pt(100, 100)) // offset the initial pen position to test "MoveTo"
    93  
    94  		p.MoveTo(f32.Pt(20, 20))
    95  		p.LineTo(f32.Pt(80, 20))
    96  		p.QuadTo(f32.Pt(80, 80), f32.Pt(20, 80))
    97  		p.Close()
    98  		defer clip.Outline{
    99  			Path: p.End(),
   100  		}.Op().Push(o).Pop()
   101  
   102  		paint.FillShape(o, red, clip.Rect(image.Rect(0, 0, 128, 128)).Op())
   103  	}, func(r result) {
   104  		r.expect(0, 0, transparent)
   105  		r.expect(30, 30, colornames.Red)
   106  		r.expect(79, 79, transparent)
   107  		r.expect(90, 90, transparent)
   108  	})
   109  }
   110  
   111  func TestPaintTexture(t *testing.T) {
   112  	run(t, func(o *op.Ops) {
   113  		squares.Add(o)
   114  		defer scale(80.0/512, 80.0/512).Push(o).Pop()
   115  		paint.PaintOp{}.Add(o)
   116  	}, func(r result) {
   117  		r.expect(0, 0, colornames.Blue)
   118  		r.expect(79, 10, colornames.Green)
   119  		r.expect(80, 0, transparent)
   120  		r.expect(10, 80, transparent)
   121  	})
   122  }
   123  
   124  func TestTexturedStrokeClipped(t *testing.T) {
   125  	run(t, func(o *op.Ops) {
   126  		smallSquares.Add(o)
   127  		defer op.Offset(image.Pt(50, 50)).Push(o).Pop()
   128  		defer clip.Stroke{
   129  			Path:  clip.RRect{Rect: image.Rect(0, 0, 30, 30)}.Path(o),
   130  			Width: 10,
   131  		}.Op().Push(o).Pop()
   132  		defer clip.RRect{Rect: image.Rect(-30, -30, 60, 60)}.Push(o).Pop()
   133  		defer op.Offset(image.Pt(-10, -10)).Push(o).Pop()
   134  		paint.PaintOp{}.Add(o)
   135  	}, func(r result) {
   136  	})
   137  }
   138  
   139  func TestTexturedStroke(t *testing.T) {
   140  	run(t, func(o *op.Ops) {
   141  		smallSquares.Add(o)
   142  		defer op.Offset(image.Pt(50, 50)).Push(o).Pop()
   143  		defer clip.Stroke{
   144  			Path:  clip.RRect{Rect: image.Rect(0, 0, 30, 30)}.Path(o),
   145  			Width: 10,
   146  		}.Op().Push(o).Pop()
   147  		defer op.Offset(image.Pt(-10, -10)).Push(o).Pop()
   148  		paint.PaintOp{}.Add(o)
   149  	}, func(r result) {
   150  	})
   151  }
   152  
   153  func TestPaintClippedTexture(t *testing.T) {
   154  	run(t, func(o *op.Ops) {
   155  		squares.Add(o)
   156  		defer clip.RRect{Rect: image.Rect(0, 0, 40, 40)}.Push(o).Pop()
   157  		defer scale(80.0/512, 80.0/512).Push(o).Pop()
   158  		paint.PaintOp{}.Add(o)
   159  	}, func(r result) {
   160  		r.expect(40, 40, transparent)
   161  		r.expect(25, 35, colornames.Blue)
   162  	})
   163  }
   164  
   165  func TestStrokedPathZeroWidth(t *testing.T) {
   166  	t.Skip("test broken, see issue 479")
   167  
   168  	run(t, func(o *op.Ops) {
   169  		{
   170  			p := new(clip.Path)
   171  			p.Begin(o)
   172  			p.Move(f32.Pt(10, 50))
   173  			p.Line(f32.Pt(30, 0))
   174  			cl := clip.Stroke{
   175  				Path: p.End(),
   176  			}.Op().Push(o) // width=0, disable stroke
   177  
   178  			paint.Fill(o, red)
   179  			cl.Pop()
   180  		}
   181  
   182  	}, func(r result) {
   183  		r.expect(0, 0, transparent)
   184  		r.expect(10, 50, colornames.Black)
   185  		r.expect(30, 50, colornames.Black)
   186  		r.expect(65, 50, transparent)
   187  	})
   188  }
   189  
   190  func TestStrokedPathCoincidentControlPoint(t *testing.T) {
   191  	run(t, func(o *op.Ops) {
   192  		p := new(clip.Path)
   193  		p.Begin(o)
   194  		p.MoveTo(f32.Pt(70, 20))
   195  		p.CubeTo(f32.Pt(70, 20), f32.Pt(70, 110), f32.Pt(120, 120))
   196  		p.LineTo(f32.Pt(20, 120))
   197  		p.LineTo(f32.Pt(70, 20))
   198  		cl := clip.Stroke{
   199  			Path:  p.End(),
   200  			Width: 20,
   201  		}.Op().Push(o)
   202  
   203  		paint.Fill(o, black)
   204  		cl.Pop()
   205  	}, func(r result) {
   206  		r.expect(0, 0, transparent)
   207  		r.expect(70, 20, colornames.Black)
   208  		r.expect(70, 90, transparent)
   209  	})
   210  }
   211  
   212  func TestStrokedPathBalloon(t *testing.T) {
   213  	run(t, func(o *op.Ops) {
   214  		// This shape is based on the one drawn by the Bubble function in
   215  		// github.com/llgcode/draw2d/samples/geometry/geometry.go.
   216  		p := new(clip.Path)
   217  		p.Begin(o)
   218  		p.MoveTo(f32.Pt(42.69375, 10.5))
   219  		p.CubeTo(f32.Pt(42.69375, 10.5), f32.Pt(14.85, 10.5), f32.Pt(14.85, 31.5))
   220  		p.CubeTo(f32.Pt(14.85, 31.5), f32.Pt(14.85, 52.5), f32.Pt(28.771875, 52.5))
   221  		p.CubeTo(f32.Pt(28.771875, 52.5), f32.Pt(28.771875, 63.7), f32.Pt(17.634375, 66.5))
   222  		p.CubeTo(f32.Pt(17.634375, 66.5), f32.Pt(34.340626, 63.7), f32.Pt(37.125, 52.5))
   223  		p.CubeTo(f32.Pt(37.125, 52.5), f32.Pt(70.5375, 52.5), f32.Pt(70.5375, 31.5))
   224  		p.CubeTo(f32.Pt(70.5375, 31.5), f32.Pt(70.5375, 10.5), f32.Pt(42.69375, 10.5))
   225  		cl := clip.Stroke{
   226  			Path:  p.End(),
   227  			Width: 2.83,
   228  		}.Op().Push(o)
   229  		paint.Fill(o, black)
   230  		cl.Pop()
   231  	}, func(r result) {
   232  		r.expect(0, 0, transparent)
   233  		r.expect(70, 52, colornames.Black)
   234  		r.expect(70, 90, transparent)
   235  	})
   236  }
   237  
   238  func TestPathReuse(t *testing.T) {
   239  	run(t, func(o *op.Ops) {
   240  		var path clip.Path
   241  		path.Begin(o)
   242  		path.MoveTo(f32.Pt(60, 10))
   243  		path.LineTo(f32.Pt(110, 75))
   244  		path.LineTo(f32.Pt(10, 75))
   245  		path.Close()
   246  		spec := path.End()
   247  
   248  		outline := clip.Outline{Path: spec}.Op().Push(o)
   249  		paint.Fill(o, color.NRGBA{R: 0xFF, A: 0xFF})
   250  		outline.Pop()
   251  
   252  		stroke := clip.Stroke{Path: spec, Width: 3}.Op().Push(o)
   253  		paint.Fill(o, color.NRGBA{B: 0xFF, A: 0xFF})
   254  		stroke.Pop()
   255  	}, func(r result) {
   256  	})
   257  }
   258  
   259  func TestPathInterleave(t *testing.T) {
   260  	t.Run("interleave op in clip.Path", func(t *testing.T) {
   261  		defer func() {
   262  			if err := recover(); err == nil {
   263  				t.Error("expected panic did not occur")
   264  			}
   265  		}()
   266  		ops := new(op.Ops)
   267  		var path clip.Path
   268  		path.Begin(ops)
   269  		path.LineTo(f32.Point{X: 123, Y: 456})
   270  		paint.ColorOp{}.Add(ops)
   271  		path.End()
   272  	})
   273  	t.Run("use ops after clip.Path", func(t *testing.T) {
   274  		ops := new(op.Ops)
   275  		var path clip.Path
   276  		path.Begin(ops)
   277  		path.LineTo(f32.Point{X: 123, Y: 456})
   278  		path.End()
   279  		paint.ColorOp{}.Add(ops)
   280  	})
   281  }
   282  
   283  func TestStrokedRect(t *testing.T) {
   284  	run(t, func(o *op.Ops) {
   285  		r := clip.Rect{Min: image.Pt(50, 50), Max: image.Pt(100, 100)}
   286  		paint.FillShape(o,
   287  			color.NRGBA{R: 0xff, A: 0xFF},
   288  			clip.Stroke{
   289  				Path:  r.Path(),
   290  				Width: 5,
   291  			}.Op(),
   292  		)
   293  	}, func(r result) {
   294  	})
   295  }