github.com/kintar/etxt@v0.0.9/emask/helper_buffer_quad.go (about)

     1  package emask
     2  
     3  import "math"
     4  
     5  // This file contains methods that implement a "fill quadrilateral"
     6  // operation used with the outliner rasterizer. This is a CPU-heavy
     7  // process, fiddly, slow and annoying. It could be done with an
     8  // uint8 buffer, it could be much more optimized and many other
     9  // things. Whatever.
    10  
    11  // Fill a convex quadrilateral polygon whose bounds are defined by
    12  // the points given as parameters. The points don't need to follow
    13  // any particular order, but must define a convex quadrilateral
    14  // (triangles and lines also work as they are like quadrilaterals
    15  // with one or two sides collapsed, which the algorithm handles ok).
    16  //
    17  // Values outside the buffer's bounds will be clipped. (TODO: UNIMPLEMENTED)
    18  //
    19  // TODO: this could also be done on the GPU and the algorithms would be easier.
    20  func (self *buffer) FillConvexQuad(ax, ay, bx, by, cx, cy, dx, dy float64) {
    21  	// the first part is all about clipping
    22  	// TODO: clip directly at the outliner stage..? clip on both places..?
    23  
    24  	// clip polygon
    25  	if ay < 0 {
    26  		panic("clipping unimplemented")
    27  		// TODO: recursive calls after splitting...
    28  		//self.uncheckedFillConvexQuad(ax, ay, bx, by, cx, cy, dx, dy)
    29  		//self.uncheckedFillConvexQuad(ax, ay, bx, by, cx, cy, dx, dy)
    30  	} else if by > float64(self.Height) {
    31  		// two calls
    32  		panic("clipping unimplemented")
    33  	} else if ax < 0 {
    34  		panic("clipping unimplemented")
    35  	} else if bx < 0 {
    36  		panic("clipping unimplemented")
    37  	} else if cx < 0 {
    38  		panic("clipping unimplemented")
    39  	} else if dx < 0 {
    40  		panic("clipping unimplemented")
    41  	} else if ax > float64(self.Width) {
    42  		panic("clipping unimplemented")
    43  	} else if bx > float64(self.Width) {
    44  		panic("clipping unimplemented")
    45  	} else if cx > float64(self.Width) {
    46  		panic("clipping unimplemented")
    47  	} else if dx > float64(self.Width) {
    48  		panic("clipping unimplemented")
    49  	} else { // no clipping required, nice
    50  		self.uncheckedFillConvexQuad(ax, ay, bx, by, cx, cy, dx, dy)
    51  	}
    52  }
    53  
    54  // Precondition: vertices must be all inside the working area. The call
    55  //
    56  //	will panic otherwise.
    57  func (self *buffer) uncheckedFillConvexQuad(ax, ay, bx, by, cx, cy, dx, dy float64) {
    58  	if math.IsNaN(ax) || math.IsNaN(ay) || math.IsNaN(bx) || math.IsNaN(by) || math.IsNaN(cx) || math.IsNaN(cy) || math.IsNaN(dx) || math.IsNaN(dy) {
    59  		panic("nan")
    60  	}
    61  
    62  	// sort points so they go from smallest y to biggest y
    63  	type point struct{ x, y float64 }
    64  	pts := [4]point{point{ax, ay}, point{bx, by}, point{cx, cy}, point{dx, dy}}
    65  	if pts[0].y > pts[3].y {
    66  		pts[0], pts[3] = pts[3], pts[0]
    67  	}
    68  	if pts[0].y > pts[1].y {
    69  		pts[0], pts[1] = pts[1], pts[0]
    70  	}
    71  	if pts[2].y > pts[3].y {
    72  		pts[2], pts[3] = pts[3], pts[2]
    73  	}
    74  	if pts[1].y > pts[2].y {
    75  		pts[1], pts[2] = pts[2], pts[1]
    76  		if pts[0].y > pts[1].y {
    77  			pts[0], pts[1] = pts[1], pts[0]
    78  		}
    79  		if pts[2].y > pts[3].y {
    80  			pts[2], pts[3] = pts[3], pts[2]
    81  		}
    82  	}
    83  
    84  	// define some local helper functions
    85  	sort2f64 := func(a, b float64) (float64, float64) {
    86  		if a <= b {
    87  			return a, b
    88  		} else {
    89  			return b, a
    90  		}
    91  	}
    92  	leftVertId := func(a, b int) int {
    93  		if pts[a].x <= pts[b].x {
    94  			return a
    95  		} else {
    96  			return b
    97  		}
    98  	}
    99  
   100  	// since the quadrilateral is convex, we know that points 0 and 1 are
   101  	// connected and that points 2 and 3 are also connected. What we don't
   102  	// know is whether point 0 also connects to 2 or 3, and same for 1.
   103  	// find it out as this is necessary later in most cases.
   104  	pt0Conn := leftVertId(2, 3) // set pt0Pair to the bottom left vert id
   105  	if pts[0].x < pts[1].x {    // if 0 on the left, 0 connects to bottom left
   106  		// bottom left was correct
   107  	} else if pts[0].x > pts[1].x { // if 0 on the right, 0 connects to bottom right
   108  		pt0Conn = 5 - pt0Conn // if pair was 2, set to 3, if it was 3, set to 2
   109  	} else if pts[0].x < pts[2].x { // 0.x == 1.x, both bottom verts are on one side
   110  		pt0Conn = 5 - pt0Conn // if pair was 2, set to 3, if it was 3, set to 2
   111  	} // else { /* bottom left was correct */ }
   112  
   113  	// subdivide the polygon in one, two or three parts
   114  	// (the code may seem both confusing and repetitive. don't get
   115  	// too hung up, try to understand each case one by one and follow
   116  	// what's happening geometrically... vertex indices are tricky)
   117  	flatTop := (pts[0].y == pts[1].y)
   118  	flatBottom := (pts[2].y == pts[3].y)
   119  	if flatTop && flatBottom { // quad can be drawn as a single trapeze
   120  		tlx, trx := sort2f64(pts[0].x, pts[1].x)
   121  		blx, brx := sort2f64(pts[2].x, pts[3].x)
   122  		self.FillAlignedQuad(pts[0].y, pts[3].y, tlx, trx, blx, brx)
   123  	} else if flatTop { // quad can be drawn with a trapeze and a triangle
   124  		tlx, trx := sort2f64(pts[0].x, pts[1].x)
   125  
   126  		// to get the first trapeze we need to intersect lines 0-3 or
   127  		// 1-3 (whichever would form a triangle with vert 2) with an
   128  		// horizontal line going through pts[2].y
   129  		vertIdOpp2 := 3 - pt0Conn // vertex id opposite to vert 2
   130  		ia, ib, ic := toLinearFormABC(pts[vertIdOpp2].x, pts[vertIdOpp2].y, pts[3].x, pts[3].y)
   131  		ix := -(ic + ib*pts[2].y) / ia // ax + by + c = 0, then x = (-c - by)/a
   132  		blx, brx := sort2f64(pts[2].x, ix)
   133  		self.FillAlignedQuad(pts[0].y, pts[2].y, tlx, trx, blx, brx)           // fill trapeze
   134  		self.FillAlignedQuad(pts[2].y, pts[3].y, blx, brx, pts[3].x, pts[3].x) // fill bottom triangle
   135  		// ...remaining code is barely documented as it doesn't introduce any new ideas
   136  	} else if flatBottom { // quad can be drawn with a triangle and a trapeze
   137  		ia, ib, ic := toLinearFormABC(pts[0].x, pts[0].y, pts[pt0Conn].x, pts[pt0Conn].y)
   138  		ix := -(ic + ib*pts[1].y) / ia
   139  		blx, brx := sort2f64(pts[1].x, ix)
   140  		self.FillAlignedQuad(pts[0].y, pts[1].y, pts[0].x, pts[0].x, blx, brx) // fill top triangle
   141  		tlx, trx := blx, brx
   142  		blx, brx = sort2f64(pts[2].x, pts[3].x)
   143  		self.FillAlignedQuad(pts[1].y, pts[3].y, tlx, trx, blx, brx) // fill bottom trapeze
   144  	} else { // quad is drawn with a triangle, a trapeze and then yet another triangle
   145  		// notice: this could be the only case, as it works for the general case,
   146  		//         but having separate cases improves performance in X% (TODO: benchmark)
   147  		ia, ib, ic := toLinearFormABC(pts[0].x, pts[0].y, pts[pt0Conn].x, pts[pt0Conn].y)
   148  		ix := -(ic + ib*pts[1].y) / ia // ax + by + c = 0, then x = (-c - by)/a
   149  		blx, brx := sort2f64(pts[1].x, ix)
   150  		self.FillAlignedQuad(pts[0].y, pts[1].y, pts[0].x, pts[0].x, blx, brx) // fill top triangle
   151  		vertIdOpp2 := 3 - pt0Conn                                              // vertex id opposite to vert 2
   152  		ia, ib, ic = toLinearFormABC(pts[vertIdOpp2].x, pts[vertIdOpp2].y, pts[3].x, pts[3].y)
   153  		ix = -(ic + ib*pts[2].y) / ia
   154  		tlx, trx := blx, brx
   155  		blx, brx = sort2f64(pts[2].x, ix)
   156  		self.FillAlignedQuad(pts[1].y, pts[2].y, tlx, trx, blx, brx)           // fill trapeze
   157  		self.FillAlignedQuad(pts[2].y, pts[3].y, blx, brx, pts[3].x, pts[3].x) // fill bottom triangle
   158  	}
   159  }
   160  
   161  // Fills a quadrilateral defined by the given coordinates, where the
   162  // top and bottom sides are perpendicular to the Y axis (which makes
   163  // the quadrilateral a trapezoid, with flat top and bottom sides).
   164  func (self *buffer) FillAlignedQuad(ty, by, tlx, trx, blx, brx float64) {
   165  	// assert validity of arguments order
   166  	if ty > by {
   167  		panic("ty > by")
   168  	}
   169  	if tlx > trx {
   170  		panic("tlx > trx")
   171  	}
   172  	if blx > brx {
   173  		panic("blx > brx")
   174  	}
   175  
   176  	// early return cases
   177  	if ty == by {
   178  		return
   179  	}
   180  	if tlx == trx && blx == brx {
   181  		return
   182  	} // line, no area
   183  
   184  	// prepare x advance deltas
   185  	dy := by - ty
   186  	dlx := (blx - tlx) / dy // left delta per y
   187  	drx := (brx - trx) / dy // right delta per y
   188  	dly := dy / math.Abs(tlx-blx)
   189  	dry := dy / math.Abs(trx-brx)
   190  
   191  	// lousily iterate each row
   192  	for {
   193  		// get next top y position
   194  		nextTy := math.Floor(ty + 1)
   195  		if nextTy > by {
   196  			nextTy = by
   197  		}
   198  
   199  		// prepare next bottom x coords for iterating the row
   200  		blxRow := tlx + (nextTy-ty)*dlx
   201  		brxRow := trx + (nextTy-ty)*drx
   202  
   203  		// corrections to blxRow and brxRow that may happen at
   204  		// most once due to floating point precision errors
   205  		if dlx > 0 {
   206  			if blxRow > blx {
   207  				blxRow = blx
   208  			}
   209  		} else if blxRow < blx {
   210  			blxRow = blx
   211  		}
   212  		if drx > 0 {
   213  			if brxRow > brx {
   214  				brxRow = brx
   215  			}
   216  		} else if brxRow < brx {
   217  			brxRow = brx
   218  		}
   219  
   220  		// fill the row
   221  		self.fillRow(ty, nextTy, tlx, trx, blxRow, brxRow, dly, dry)
   222  		tlx, trx = blxRow, brxRow
   223  
   224  		// update variables for next iteration
   225  		if nextTy == by {
   226  			break
   227  		}
   228  		ty = nextTy
   229  	}
   230  }
   231  
   232  func (self *buffer) fillRow(ty, by, tlx, trx, blx, brx float64, dly, dry float64) {
   233  	baseRowIndex := int(math.Floor(ty)) * self.Width
   234  	olx, orx := math.Max(tlx, blx), math.Min(trx, brx)
   235  	if olx <= orx {
   236  		// overlap case, center is a rect
   237  		left, right := tlx, trx
   238  		if tlx > blx { // left triangle with flat bottom
   239  			self.fillRowRectTriangle(by, -dly, blx, tlx, baseRowIndex)
   240  		} else if blx > tlx { // left triangle with flat top
   241  			left = blx
   242  			self.fillRowRectTriangle(ty, dly, tlx, blx, baseRowIndex)
   243  		}
   244  
   245  		if trx > brx { // right triangle with flat top
   246  			right = brx
   247  			self.fillRowRectTriangleRTL(ty, dry, trx, brx, baseRowIndex)
   248  		} else if brx > trx { // right triangle with flat bottom
   249  			self.fillRowRectTriangleRTL(by, -dry, brx, trx, baseRowIndex)
   250  		}
   251  
   252  		if left != right {
   253  			self.fillRowRect(ty, by, left, right, baseRowIndex)
   254  		}
   255  	} else { // tilted quad or triangle, but at least one part is flat
   256  		// non-overlap case, center can be a tilted quad
   257  		var qleft, qright float64                // quad left, quad right
   258  		qlty, qlby, qrty, qrby := ty, by, ty, by // quad left top y, quad left bottom y, etc.
   259  		if tlx > blx {                           // left triangle with flat bottom and right triangle with flat top case
   260  			qleft, qright = brx, tlx
   261  			qlty = self.fillRowRectTriangle(by, -dly, blx, brx, baseRowIndex)
   262  			qrby = self.fillRowRectTriangleRTL(ty, dry, trx, tlx, baseRowIndex)
   263  			if qrby > by {
   264  				qrby = by
   265  			}
   266  			if qlty < ty {
   267  				qlty = ty
   268  			}
   269  		} else if blx > tlx { // left triangle with flat top and right triangle with flat bottom case
   270  			qleft, qright = trx, blx
   271  			qlby = self.fillRowRectTriangle(ty, dly, tlx, trx, baseRowIndex)
   272  			qrty = self.fillRowRectTriangleRTL(by, -dry, brx, blx, baseRowIndex)
   273  			if qlby > by {
   274  				qlby = by
   275  			}
   276  			if qrty < ty {
   277  				qrty = ty
   278  			}
   279  		} else {
   280  			panic("unexpected tlx == blx")
   281  		}
   282  
   283  		if qleft != qright {
   284  			self.fillRowAlignedQuad(qleft, qright, qlty, qlby, qrty, qrby, dly, dry, baseRowIndex)
   285  		}
   286  	}
   287  }
   288  
   289  func (self *buffer) fillRowRectTriangle(startY, yChange, left, right float64, baseRowIndex int) float64 {
   290  	alignedLeft := math.Ceil(left)
   291  	alignedRight := math.Floor(right)
   292  
   293  	if alignedLeft > alignedRight { // single pixel special case
   294  		xdiff := right - left
   295  		ydiff := xdiff * yChange
   296  		self.Values[baseRowIndex+int(math.Floor(left))] += math.Abs(ydiff) * xdiff / 2.0
   297  		return startY + ydiff
   298  	}
   299  
   300  	y := startY
   301  	if left != alignedLeft { // fractional left part
   302  		xdiff := alignedLeft - left
   303  		ydiff := xdiff * yChange
   304  		self.Values[baseRowIndex+int(math.Floor(left))] += math.Abs(ydiff) * xdiff / 2.0
   305  		y += ydiff
   306  	}
   307  
   308  	partialChange := math.Abs(yChange) / 2.0
   309  	for x := int(alignedLeft); x < int(alignedRight); x++ {
   310  		self.Values[baseRowIndex+x] += partialChange + math.Abs(startY-y)
   311  		y += yChange
   312  	}
   313  
   314  	if right != alignedRight { // fractional right part
   315  		xdiff := right - alignedRight
   316  		ydiff := xdiff * yChange
   317  		self.Values[baseRowIndex+int(alignedRight)] += math.Abs(ydiff)*xdiff/2.0 + xdiff*math.Abs(startY-y)
   318  		y += ydiff
   319  	}
   320  
   321  	return y
   322  }
   323  
   324  func (self *buffer) fillRowRectTriangleRTL(startY, yChange, right, left float64, baseRowIndex int) float64 {
   325  	alignedLeft := math.Ceil(left)
   326  	alignedRight := math.Floor(right)
   327  
   328  	if alignedLeft > alignedRight { // single pixel special case
   329  		xdiff := right - left
   330  		ydiff := xdiff * yChange
   331  		self.Values[baseRowIndex+int(math.Floor(left))] += math.Abs(ydiff) * xdiff / 2.0
   332  		return startY + ydiff
   333  	}
   334  
   335  	y := startY
   336  	if right != alignedRight {
   337  		xdiff := right - alignedRight
   338  		ydiff := xdiff * yChange
   339  		self.Values[baseRowIndex+int(alignedRight)] += math.Abs(ydiff) * xdiff / 2.0
   340  		y += ydiff
   341  	}
   342  
   343  	partialChange := math.Abs(yChange) / 2.0
   344  	for x := int(alignedRight) - 1; x >= int(alignedLeft); x-- {
   345  		self.Values[baseRowIndex+x] += partialChange + math.Abs(startY-y)
   346  		y += yChange
   347  	}
   348  
   349  	if left != alignedLeft { // fractional left part
   350  		xdiff := alignedLeft - left
   351  		ydiff := xdiff * yChange
   352  		self.Values[baseRowIndex+int(math.Floor(left))] += math.Abs(ydiff)*xdiff/2.0 + xdiff*math.Abs(startY-y)
   353  		y += ydiff
   354  	}
   355  
   356  	return y
   357  }
   358  
   359  func (self *buffer) fillRowRect(ty, by, left, right float64, baseRowIndex int) {
   360  	alignedLeft := math.Ceil(left)
   361  	alignedRight := math.Floor(right)
   362  	dy := by - ty
   363  
   364  	if alignedLeft > alignedRight { // single pixel special case
   365  		self.Values[baseRowIndex+int(math.Floor(left))] += dy * (right - left)
   366  		return
   367  	}
   368  
   369  	if left != alignedLeft { // fractional left part
   370  		xdiff := alignedLeft - left
   371  		self.Values[baseRowIndex+int(math.Floor(left))] += dy * xdiff
   372  	}
   373  
   374  	if alignedLeft != alignedRight {
   375  		if dy == 1 {
   376  			si, fi := baseRowIndex+int(alignedLeft), baseRowIndex+int(alignedRight)
   377  			fastFillFloat64(self.Values[si:fi], 1.0)
   378  		} else {
   379  			for x := int(alignedLeft); x < int(alignedRight); x++ {
   380  				self.Values[baseRowIndex+x] += dy
   381  			}
   382  		}
   383  	}
   384  
   385  	if right != alignedRight { // fractional left part
   386  		xdiff := right - alignedRight
   387  		self.Values[baseRowIndex+int(alignedRight)] += dy * xdiff
   388  	}
   389  }
   390  
   391  func (self *buffer) fillRowAlignedQuad(left, right, tly, bly, try, bry, dly, dry float64, baseRowIndex int) {
   392  	// figure out orientation
   393  	var dty, dby float64        // delta top y, delta bottom y
   394  	if try < tly || bry < bly { // moving upwards, negative
   395  		dty = -dly
   396  		dby = -dry
   397  	} else { // moving downwards
   398  		dty = dry
   399  		dby = dly
   400  	}
   401  
   402  	alignedLeft := math.Ceil(left)
   403  	alignedRight := math.Floor(right)
   404  
   405  	if alignedLeft > alignedRight { // single pixel special case
   406  		ab := (bly - tly) + (bry - try)
   407  		self.Values[baseRowIndex+int(math.Floor(left))] += ab * (right - left) / 2
   408  		return
   409  	}
   410  
   411  	if left != alignedLeft { // fractional left part
   412  		xdiff := alignedLeft - left
   413  		newTly, newBly := tly+dty*xdiff, bly+dby*xdiff
   414  		ab := (bly - tly) + (newBly - newTly)
   415  		self.Values[baseRowIndex+int(math.Floor(left))] += ab * xdiff / 2
   416  		tly, bly = newTly, newBly
   417  
   418  	}
   419  
   420  	// main loop
   421  	if dly == dry { // optimized version when dly == dry
   422  		partialChange := bly - tly // simplified from ((bly - tly) + (bly+dly - bry+dry))*1/2
   423  		for x := int(alignedLeft); x < int(alignedRight); x++ {
   424  			self.Values[baseRowIndex+x] += partialChange
   425  		}
   426  		iters := (alignedRight - alignedLeft)
   427  		tly, bly = tly+dty*iters, bly+dby*iters
   428  	} else {
   429  		for x := int(alignedLeft); x < int(alignedRight); x++ {
   430  			// TODO: optimize expression once tests are working, I
   431  			//       doubt the compiler will catch this otherwise
   432  			newTly, newBly := tly+dty, bly+dby
   433  			ab := (bly - tly) + (newBly - newTly)
   434  			self.Values[baseRowIndex+x] += ab / 2
   435  			tly, bly = newTly, newBly
   436  		}
   437  	}
   438  
   439  	if right != alignedRight { // fractional right part
   440  		xdiff := right - alignedRight
   441  		ab := (bly - tly) + (bry - try)
   442  		self.Values[baseRowIndex+int(alignedRight)] += ab * xdiff / 2
   443  	}
   444  }