clip_test.go raw

   1  package rendertest
   2  
   3  import (
   4  	"image"
   5  	"math"
   6  	"testing"
   7  
   8  	"golang.org/x/image/colornames"
   9  
  10  	"github.com/p9c/p9/pkg/gel/gio/f32"
  11  	"github.com/p9c/p9/pkg/gel/gio/op"
  12  	"github.com/p9c/p9/pkg/gel/gio/op/clip"
  13  	"github.com/p9c/p9/pkg/gel/gio/op/paint"
  14  )
  15  
  16  func TestPaintRect(t *testing.T) {
  17  	run(t, func(o *op.Ops) {
  18  		paint.FillShape(o, red, clip.Rect(image.Rect(0, 0, 50, 50)).Op())
  19  	}, func(r result) {
  20  		r.expect(0, 0, colornames.Red)
  21  		r.expect(49, 0, colornames.Red)
  22  		r.expect(50, 0, transparent)
  23  		r.expect(10, 50, transparent)
  24  	})
  25  }
  26  
  27  func TestPaintClippedRect(t *testing.T) {
  28  	run(t, func(o *op.Ops) {
  29  		clip.RRect{Rect: f32.Rect(25, 25, 60, 60)}.Add(o)
  30  		paint.FillShape(o, red, clip.Rect(image.Rect(0, 0, 50, 50)).Op())
  31  	}, func(r result) {
  32  		r.expect(0, 0, transparent)
  33  		r.expect(24, 35, transparent)
  34  		r.expect(25, 35, colornames.Red)
  35  		r.expect(50, 0, transparent)
  36  		r.expect(10, 50, transparent)
  37  	})
  38  }
  39  
  40  func TestPaintClippedCircle(t *testing.T) {
  41  	run(t, func(o *op.Ops) {
  42  		r := float32(10)
  43  		clip.RRect{Rect: f32.Rect(20, 20, 40, 40), SE: r, SW: r, NW: r, NE: r}.Add(o)
  44  		clip.Rect(image.Rect(0, 0, 30, 50)).Add(o)
  45  		paint.Fill(o, red)
  46  	}, func(r result) {
  47  		r.expect(21, 21, transparent)
  48  		r.expect(25, 30, colornames.Red)
  49  		r.expect(31, 30, transparent)
  50  	})
  51  }
  52  
  53  func TestPaintArc(t *testing.T) {
  54  	run(t, func(o *op.Ops) {
  55  		p := new(clip.Path)
  56  		p.Begin(o)
  57  		p.Move(f32.Pt(0, 20))
  58  		p.Line(f32.Pt(10, 0))
  59  		p.Arc(f32.Pt(10, 0), f32.Pt(40, 0), math.Pi)
  60  		p.Line(f32.Pt(30, 0))
  61  		p.Line(f32.Pt(0, 25))
  62  		p.Arc(f32.Pt(-10, 5), f32.Pt(10, 15), -math.Pi)
  63  		p.Line(f32.Pt(0, 25))
  64  		p.Arc(f32.Pt(10, 10), f32.Pt(10, 10), 2*math.Pi)
  65  		p.Line(f32.Pt(-10, 0))
  66  		p.Arc(f32.Pt(-10, 0), f32.Pt(-40, 0), -math.Pi)
  67  		p.Line(f32.Pt(-10, 0))
  68  		p.Line(f32.Pt(0, -10))
  69  		p.Arc(f32.Pt(-10, -20), f32.Pt(10, -5), math.Pi)
  70  		p.Line(f32.Pt(0, -10))
  71  		p.Line(f32.Pt(-50, 0))
  72  		p.Close()
  73  		clip.Outline{
  74  			Path: p.End(),
  75  		}.Op().Add(o)
  76  
  77  		paint.FillShape(o, red, clip.Rect(image.Rect(0, 0, 128, 128)).Op())
  78  	}, func(r result) {
  79  		r.expect(0, 0, transparent)
  80  		r.expect(0, 25, colornames.Red)
  81  		r.expect(0, 15, transparent)
  82  	})
  83  }
  84  
  85  func TestPaintAbsolute(t *testing.T) {
  86  	run(t, func(o *op.Ops) {
  87  		p := new(clip.Path)
  88  		p.Begin(o)
  89  		p.Move(f32.Pt(100, 100)) // offset the initial pen position to test "MoveTo"
  90  
  91  		p.MoveTo(f32.Pt(20, 20))
  92  		p.LineTo(f32.Pt(80, 20))
  93  		p.QuadTo(f32.Pt(80, 80), f32.Pt(20, 80))
  94  		p.Close()
  95  		clip.Outline{
  96  			Path: p.End(),
  97  		}.Op().Add(o)
  98  
  99  		paint.FillShape(o, red, clip.Rect(image.Rect(0, 0, 128, 128)).Op())
 100  	}, func(r result) {
 101  		r.expect(0, 0, transparent)
 102  		r.expect(30, 30, colornames.Red)
 103  		r.expect(79, 79, transparent)
 104  		r.expect(90, 90, transparent)
 105  	})
 106  }
 107  
 108  func TestPaintTexture(t *testing.T) {
 109  	run(t, func(o *op.Ops) {
 110  		squares.Add(o)
 111  		scale(80.0/512, 80.0/512).Add(o)
 112  		paint.PaintOp{}.Add(o)
 113  	}, func(r result) {
 114  		r.expect(0, 0, colornames.Blue)
 115  		r.expect(79, 10, colornames.Green)
 116  		r.expect(80, 0, transparent)
 117  		r.expect(10, 80, transparent)
 118  	})
 119  }
 120  
 121  func TestTexturedStrokeClipped(t *testing.T) {
 122  	run(t, func(o *op.Ops) {
 123  		smallSquares.Add(o)
 124  		op.Offset(f32.Pt(50, 50)).Add(o)
 125  		clip.Stroke{
 126  			Path: clip.RRect{Rect: f32.Rect(0, 0, 30, 30)}.Path(o),
 127  			Style: clip.StrokeStyle{
 128  				Width: 10,
 129  			},
 130  		}.Op().Add(o)
 131  		clip.RRect{Rect: f32.Rect(-30, -30, 60, 60)}.Add(o)
 132  		op.Offset(f32.Pt(-10, -10)).Add(o)
 133  		paint.PaintOp{}.Add(o)
 134  	}, func(r result) {
 135  	})
 136  }
 137  
 138  func TestTexturedStroke(t *testing.T) {
 139  	run(t, func(o *op.Ops) {
 140  		smallSquares.Add(o)
 141  		op.Offset(f32.Pt(50, 50)).Add(o)
 142  		clip.Stroke{
 143  			Path: clip.RRect{Rect: f32.Rect(0, 0, 30, 30)}.Path(o),
 144  			Style: clip.StrokeStyle{
 145  				Width: 10,
 146  			},
 147  		}.Op().Add(o)
 148  		op.Offset(f32.Pt(-10, -10)).Add(o)
 149  		paint.PaintOp{}.Add(o)
 150  	}, func(r result) {
 151  	})
 152  }
 153  
 154  func TestPaintClippedTexture(t *testing.T) {
 155  	run(t, func(o *op.Ops) {
 156  		squares.Add(o)
 157  		clip.RRect{Rect: f32.Rect(0, 0, 40, 40)}.Add(o)
 158  		scale(80.0/512, 80.0/512).Add(o)
 159  		paint.PaintOp{}.Add(o)
 160  	}, func(r result) {
 161  		r.expect(40, 40, transparent)
 162  		r.expect(25, 35, colornames.Blue)
 163  	})
 164  }
 165  
 166  func TestStrokedPathBevelFlat(t *testing.T) {
 167  	run(t, func(o *op.Ops) {
 168  		p := newStrokedPath(o)
 169  		clip.Stroke{
 170  			Path: p,
 171  			Style: clip.StrokeStyle{
 172  				Width: 2.5,
 173  				Cap:   clip.FlatCap,
 174  				Join:  clip.BevelJoin,
 175  			},
 176  		}.Op().Add(o)
 177  
 178  		paint.Fill(o, red)
 179  	}, func(r result) {
 180  		r.expect(0, 0, transparent)
 181  		r.expect(10, 50, colornames.Red)
 182  	})
 183  }
 184  
 185  func TestStrokedPathBevelRound(t *testing.T) {
 186  	run(t, func(o *op.Ops) {
 187  		p := newStrokedPath(o)
 188  		clip.Stroke{
 189  			Path: p,
 190  			Style: clip.StrokeStyle{
 191  				Width: 2.5,
 192  				Cap:   clip.RoundCap,
 193  				Join:  clip.BevelJoin,
 194  			},
 195  		}.Op().Add(o)
 196  
 197  		paint.Fill(o, red)
 198  	}, func(r result) {
 199  		r.expect(0, 0, transparent)
 200  		r.expect(10, 50, colornames.Red)
 201  	})
 202  }
 203  
 204  func TestStrokedPathBevelSquare(t *testing.T) {
 205  	run(t, func(o *op.Ops) {
 206  		p := newStrokedPath(o)
 207  		clip.Stroke{
 208  			Path: p,
 209  			Style: clip.StrokeStyle{
 210  				Width: 2.5,
 211  				Cap:   clip.SquareCap,
 212  				Join:  clip.BevelJoin,
 213  			},
 214  		}.Op().Add(o)
 215  
 216  		paint.Fill(o, red)
 217  	}, func(r result) {
 218  		r.expect(0, 0, transparent)
 219  		r.expect(10, 50, colornames.Red)
 220  	})
 221  }
 222  
 223  func TestStrokedPathRoundRound(t *testing.T) {
 224  	run(t, func(o *op.Ops) {
 225  		p := newStrokedPath(o)
 226  		clip.Stroke{
 227  			Path: p,
 228  			Style: clip.StrokeStyle{
 229  				Width: 2.5,
 230  				Cap:   clip.RoundCap,
 231  				Join:  clip.RoundJoin,
 232  			},
 233  		}.Op().Add(o)
 234  
 235  		paint.Fill(o, red)
 236  	}, func(r result) {
 237  		r.expect(0, 0, transparent)
 238  		r.expect(10, 50, colornames.Red)
 239  	})
 240  }
 241  
 242  func TestStrokedPathFlatMiter(t *testing.T) {
 243  	run(t, func(o *op.Ops) {
 244  		{
 245  			stk := op.Save(o)
 246  			p := newZigZagPath(o)
 247  			clip.Stroke{
 248  				Path: p,
 249  				Style: clip.StrokeStyle{
 250  					Width: 10,
 251  					Cap:   clip.FlatCap,
 252  					Join:  clip.BevelJoin,
 253  					Miter: 5,
 254  				},
 255  			}.Op().Add(o)
 256  			paint.Fill(o, red)
 257  			stk.Load()
 258  		}
 259  		{
 260  			stk := op.Save(o)
 261  			p := newZigZagPath(o)
 262  			clip.Stroke{
 263  				Path: p,
 264  				Style: clip.StrokeStyle{
 265  					Width: 2,
 266  					Cap:   clip.FlatCap,
 267  					Join:  clip.BevelJoin,
 268  				},
 269  			}.Op().Add(o)
 270  			paint.Fill(o, black)
 271  			stk.Load()
 272  		}
 273  
 274  	}, func(r result) {
 275  		r.expect(0, 0, transparent)
 276  		r.expect(40, 10, colornames.Black)
 277  		r.expect(40, 12, colornames.Red)
 278  	})
 279  }
 280  
 281  func TestStrokedPathFlatMiterInf(t *testing.T) {
 282  	run(t, func(o *op.Ops) {
 283  		{
 284  			stk := op.Save(o)
 285  			p := newZigZagPath(o)
 286  			clip.Stroke{
 287  				Path: p,
 288  				Style: clip.StrokeStyle{
 289  					Width: 10,
 290  					Cap:   clip.FlatCap,
 291  					Join:  clip.BevelJoin,
 292  					Miter: float32(math.Inf(+1)),
 293  				},
 294  			}.Op().Add(o)
 295  			paint.Fill(o, red)
 296  			stk.Load()
 297  		}
 298  		{
 299  			stk := op.Save(o)
 300  			p := newZigZagPath(o)
 301  			clip.Stroke{
 302  				Path: p,
 303  				Style: clip.StrokeStyle{
 304  					Width: 2,
 305  					Cap:   clip.FlatCap,
 306  					Join:  clip.BevelJoin,
 307  				},
 308  			}.Op().Add(o)
 309  			paint.Fill(o, black)
 310  			stk.Load()
 311  		}
 312  
 313  	}, func(r result) {
 314  		r.expect(0, 0, transparent)
 315  		r.expect(40, 10, colornames.Black)
 316  		r.expect(40, 12, colornames.Red)
 317  	})
 318  }
 319  
 320  func TestStrokedPathZeroWidth(t *testing.T) {
 321  	run(t, func(o *op.Ops) {
 322  		{
 323  			stk := op.Save(o)
 324  			p := new(clip.Path)
 325  			p.Begin(o)
 326  			p.Move(f32.Pt(10, 50))
 327  			p.Line(f32.Pt(50, 0))
 328  			clip.Stroke{
 329  				Path: p.End(),
 330  				Style: clip.StrokeStyle{
 331  					Width: 2,
 332  					Cap:   clip.FlatCap,
 333  					Join:  clip.BevelJoin,
 334  				},
 335  			}.Op().Add(o)
 336  
 337  			paint.Fill(o, black)
 338  			stk.Load()
 339  		}
 340  
 341  		{
 342  			stk := op.Save(o)
 343  			p := new(clip.Path)
 344  			p.Begin(o)
 345  			p.Move(f32.Pt(10, 50))
 346  			p.Line(f32.Pt(30, 0))
 347  			clip.Stroke{
 348  				Path: p.End(),
 349  			}.Op().Add(o) // width=0, disable stroke
 350  
 351  			paint.Fill(o, red)
 352  			stk.Load()
 353  		}
 354  
 355  	}, func(r result) {
 356  		r.expect(0, 0, transparent)
 357  		r.expect(10, 50, colornames.Black)
 358  		r.expect(30, 50, colornames.Black)
 359  		r.expect(65, 50, transparent)
 360  	})
 361  }
 362  
 363  func TestDashedPathFlatCapEllipse(t *testing.T) {
 364  	run(t, func(o *op.Ops) {
 365  		{
 366  			stk := op.Save(o)
 367  			p := newEllipsePath(o)
 368  
 369  			var dash clip.Dash
 370  			dash.Begin(o)
 371  			dash.Dash(5)
 372  			dash.Dash(3)
 373  
 374  			clip.Stroke{
 375  				Path: p,
 376  				Style: clip.StrokeStyle{
 377  					Width: 10,
 378  					Cap:   clip.FlatCap,
 379  					Join:  clip.BevelJoin,
 380  					Miter: float32(math.Inf(+1)),
 381  				},
 382  				Dashes: dash.End(),
 383  			}.Op().Add(o)
 384  
 385  			paint.Fill(
 386  				o,
 387  				red,
 388  			)
 389  			stk.Load()
 390  		}
 391  		{
 392  			stk := op.Save(o)
 393  			p := newEllipsePath(o)
 394  			clip.Stroke{
 395  				Path: p,
 396  				Style: clip.StrokeStyle{
 397  					Width: 2,
 398  				},
 399  			}.Op().Add(o)
 400  
 401  			paint.Fill(
 402  				o,
 403  				black,
 404  			)
 405  			stk.Load()
 406  		}
 407  
 408  	}, func(r result) {
 409  		r.expect(0, 0, transparent)
 410  		r.expect(0, 62, colornames.Red)
 411  		r.expect(0, 65, colornames.Black)
 412  	})
 413  }
 414  
 415  func TestDashedPathFlatCapZ(t *testing.T) {
 416  	run(t, func(o *op.Ops) {
 417  		{
 418  			stk := op.Save(o)
 419  			p := newZigZagPath(o)
 420  			var dash clip.Dash
 421  			dash.Begin(o)
 422  			dash.Dash(5)
 423  			dash.Dash(3)
 424  
 425  			clip.Stroke{
 426  				Path: p,
 427  				Style: clip.StrokeStyle{
 428  					Width: 10,
 429  					Cap:   clip.FlatCap,
 430  					Join:  clip.BevelJoin,
 431  					Miter: float32(math.Inf(+1)),
 432  				},
 433  				Dashes: dash.End(),
 434  			}.Op().Add(o)
 435  			paint.Fill(o, red)
 436  			stk.Load()
 437  		}
 438  
 439  		{
 440  			stk := op.Save(o)
 441  			p := newZigZagPath(o)
 442  			clip.Stroke{
 443  				Path: p,
 444  				Style: clip.StrokeStyle{
 445  					Width: 2,
 446  					Cap:   clip.FlatCap,
 447  					Join:  clip.BevelJoin,
 448  				},
 449  			}.Op().Add(o)
 450  			paint.Fill(o, black)
 451  			stk.Load()
 452  		}
 453  	}, func(r result) {
 454  		r.expect(0, 0, transparent)
 455  		r.expect(40, 10, colornames.Black)
 456  		r.expect(40, 12, colornames.Red)
 457  		r.expect(46, 12, transparent)
 458  	})
 459  }
 460  
 461  func TestDashedPathFlatCapZNoDash(t *testing.T) {
 462  	run(t, func(o *op.Ops) {
 463  		{
 464  			stk := op.Save(o)
 465  			p := newZigZagPath(o)
 466  			var dash clip.Dash
 467  			dash.Begin(o)
 468  			dash.Phase(1)
 469  
 470  			clip.Stroke{
 471  				Path: p,
 472  				Style: clip.StrokeStyle{
 473  					Width: 10,
 474  					Cap:   clip.FlatCap,
 475  					Join:  clip.BevelJoin,
 476  					Miter: float32(math.Inf(+1)),
 477  				},
 478  				Dashes: dash.End(),
 479  			}.Op().Add(o)
 480  			paint.Fill(o, red)
 481  			stk.Load()
 482  		}
 483  		{
 484  			stk := op.Save(o)
 485  			clip.Stroke{
 486  				Path: newZigZagPath(o),
 487  				Style: clip.StrokeStyle{
 488  					Width: 2,
 489  					Cap:   clip.FlatCap,
 490  					Join:  clip.BevelJoin,
 491  				},
 492  			}.Op().Add(o)
 493  			paint.Fill(o, black)
 494  			stk.Load()
 495  		}
 496  	}, func(r result) {
 497  		r.expect(0, 0, transparent)
 498  		r.expect(40, 10, colornames.Black)
 499  		r.expect(40, 12, colornames.Red)
 500  		r.expect(46, 12, colornames.Red)
 501  	})
 502  }
 503  
 504  func TestDashedPathFlatCapZNoPath(t *testing.T) {
 505  	run(t, func(o *op.Ops) {
 506  		{
 507  			stk := op.Save(o)
 508  			var dash clip.Dash
 509  			dash.Begin(o)
 510  			dash.Dash(0)
 511  			clip.Stroke{
 512  				Path: newZigZagPath(o),
 513  				Style: clip.StrokeStyle{
 514  					Width: 10,
 515  					Cap:   clip.FlatCap,
 516  					Join:  clip.BevelJoin,
 517  					Miter: float32(math.Inf(+1)),
 518  				},
 519  				Dashes: dash.End(),
 520  			}.Op().Add(o)
 521  			paint.Fill(o, red)
 522  			stk.Load()
 523  		}
 524  		{
 525  			stk := op.Save(o)
 526  			p := newZigZagPath(o)
 527  			clip.Stroke{
 528  				Path: p,
 529  				Style: clip.StrokeStyle{
 530  					Width: 2,
 531  					Cap:   clip.FlatCap,
 532  					Join:  clip.BevelJoin,
 533  				},
 534  			}.Op().Add(o)
 535  			paint.Fill(o, black)
 536  			stk.Load()
 537  		}
 538  	}, func(r result) {
 539  		r.expect(0, 0, transparent)
 540  		r.expect(40, 10, colornames.Black)
 541  		r.expect(40, 12, transparent)
 542  		r.expect(46, 12, transparent)
 543  	})
 544  }
 545  
 546  func newStrokedPath(o *op.Ops) clip.PathSpec {
 547  	p := new(clip.Path)
 548  	p.Begin(o)
 549  	p.Move(f32.Pt(10, 50))
 550  	p.Line(f32.Pt(10, 0))
 551  	p.Arc(f32.Pt(10, 0), f32.Pt(20, 0), math.Pi)
 552  	p.Line(f32.Pt(10, 0))
 553  	p.Line(f32.Pt(10, 10))
 554  	p.Arc(f32.Pt(0, 30), f32.Pt(0, 30), 2*math.Pi)
 555  	p.Line(f32.Pt(-20, 0))
 556  	p.Quad(f32.Pt(-10, -10), f32.Pt(-30, 30))
 557  	return p.End()
 558  }
 559  
 560  func newZigZagPath(o *op.Ops) clip.PathSpec {
 561  	p := new(clip.Path)
 562  	p.Begin(o)
 563  	p.Move(f32.Pt(40, 10))
 564  	p.Line(f32.Pt(50, 0))
 565  	p.Line(f32.Pt(-50, 50))
 566  	p.Line(f32.Pt(50, 0))
 567  	p.Quad(f32.Pt(-50, 20), f32.Pt(-50, 50))
 568  	p.Line(f32.Pt(50, 0))
 569  	return p.End()
 570  }
 571  
 572  func newEllipsePath(o *op.Ops) clip.PathSpec {
 573  	p := new(clip.Path)
 574  	p.Begin(o)
 575  	p.Move(f32.Pt(0, 65))
 576  	p.Line(f32.Pt(20, 0))
 577  	p.Arc(f32.Pt(20, 0), f32.Pt(70, 0), 2*math.Pi)
 578  	return p.End()
 579  }
 580