github.com/Kintar/etxt@v0.0.0-20221224033739-2fc69f000137/emask/helper_funcs.go (about)

     1  package emask
     2  
     3  import "math"
     4  import "image"
     5  
     6  import "golang.org/x/image/math/fixed"
     7  import "github.com/Kintar/etxt/efixed"
     8  
     9  // Given some glyph bounds and a fractional pixel position, it figures out
    10  // what integer size must be used to fit the bounds, what normalization
    11  // offset must be applied to keep the coordinates in the positive plane,
    12  // and what final offset must be applied to the final mask to align its
    13  // bounds to the glyph origin. This is used in NewContour functions.
    14  func figureOutBounds(bounds fixed.Rectangle26_6, fract fixed.Point26_6) (image.Point, fixed.Point26_6, image.Point) {
    15  	floorMinX := efixed.Floor(bounds.Min.X)
    16  	floorMinY := efixed.Floor(bounds.Min.Y)
    17  	var maskCorrection image.Point
    18  	maskCorrection.X = int(floorMinX >> 6)
    19  	maskCorrection.Y = int(floorMinY >> 6)
    20  
    21  	var normOffset fixed.Point26_6
    22  	normOffset.X = -floorMinX + fract.X
    23  	normOffset.Y = -floorMinY + fract.Y
    24  	width := (bounds.Max.X + normOffset.X).Ceil()
    25  	height := (bounds.Max.Y + normOffset.Y).Ceil()
    26  	return image.Pt(width, height), normOffset, maskCorrection
    27  }
    28  
    29  // Around 9 times as fast as using a regular for loop.
    30  // This can trivially be made generic, and can also be adapted
    31  // to fill buffers with patterns (for example to fill
    32  // images with a specific color).
    33  func fastFillFloat64(buffer []float64, value float64) {
    34  	if len(buffer) <= 24 { // no-copy case
    35  		for i, _ := range buffer {
    36  			buffer[i] = value
    37  		}
    38  	} else { // copy case
    39  		for i, _ := range buffer[:16] {
    40  			buffer[i] = value
    41  		}
    42  		for i := 16; i < len(buffer); i *= 2 {
    43  			copy(buffer[i:], buffer[:i])
    44  		}
    45  	}
    46  }
    47  
    48  func fastFillUint8(buffer []uint8, value uint8) {
    49  	if len(buffer) <= 24 { // no-copy case
    50  		for i, _ := range buffer {
    51  			buffer[i] = value
    52  		}
    53  	} else { // copy case
    54  		for i, _ := range buffer[:16] {
    55  			buffer[i] = value
    56  		}
    57  		for i := 16; i < len(buffer); i *= 2 {
    58  			copy(buffer[i:], buffer[:i])
    59  		}
    60  	}
    61  }
    62  
    63  // linearly interpolate (ax, ay) and (bx, by) at the given t, which
    64  // must be in [0, 1]
    65  func lerp(ax, ay, bx, by float64, t float64) (float64, float64) {
    66  	return interpolateAt(ax, bx, t), interpolateAt(ay, by, t)
    67  }
    68  
    69  // interpolate a and b at the given t, which must be in [0, 1]
    70  func interpolateAt(a, b float64, t float64) float64 { return a + t*(b-a) }
    71  
    72  // Given two points of a line, it returns its A, B and C
    73  // coefficients from the form "Ax + By + C = 0".
    74  func toLinearFormABC(ox, oy, fx, fy float64) (float64, float64, float64) {
    75  	a, b, c := fy-oy, -(fx - ox), (fx-ox)*oy-(fy-oy)*ox
    76  	return a, b, c
    77  }
    78  
    79  // If we had two line equations like this:
    80  // >> a1*x + b1*y = c1
    81  // >> a2*x + b2*y = c2
    82  // We would apply cramer's rule to solve the system:
    83  // >> x = (b2*c1 - b1*c2)/(b2*a1 - b1*a2)
    84  // This function solves this system, but assuming c1 and c2 have
    85  // a negative sign (ax + by + c = 0), and taking a precomputed
    86  // xdiv = (b2*a1 - b1*a2) value
    87  func shortCramer(xdiv, a1, b1, c1, a2, b2, c2 float64) (float64, float64) {
    88  	if xdiv == 0 {
    89  		panic("parallel lines")
    90  	}
    91  
    92  	// actual application of cramer's rule
    93  	x := (b2*-c1 - b1*-c2) / xdiv
    94  	if b1 != 0 {
    95  		return x, (-c1 - a1*x) / b1
    96  	}
    97  	return x, (-c2 - a2*x) / b2
    98  }
    99  
   100  // given a line equation in Ax + By + C = 0 form and a point, finds
   101  // the perpendicular ABC line equation that passes through the given
   102  // point. the C is not in the parameters because it's not necessary
   103  func perpendicularABC(a, b, x, y float64) (float64, float64, float64) {
   104  	// we have ax + by + c = 0, and we want to find dx + ey + f = 0...
   105  	// we can use d = b, e = -a and f = -d*x - e*y
   106  	d := b
   107  	e := -a
   108  	f := -d*x - e*y
   109  	return d, e, f
   110  }
   111  
   112  // given a line equation in the form Ax + By + C = 0, it returns
   113  // C1 and C2 such that two new line equations can be created that
   114  // are parallel to the original line, but at distance 'dist' from it
   115  func parallelsAtDist(a, b, c float64, dist float64) (float64, float64) {
   116  	var c1, c2 float64
   117  	if a == 0 { // horizontal line
   118  		y := -c / b
   119  		c1 = -(y + dist) * b
   120  		c2 = -(y - dist) * b
   121  	} else if b == 0 { // vertical line
   122  		x := -c / a
   123  		c1 = -(x + dist) * a
   124  		c2 = -(x - dist) * a
   125  	} else {
   126  		// We use the formula for the distance between a point and a line:
   127  		// >> dist = |ax + by + c|/sqrt(a*a + b*b)
   128  		// We assume x = 0 and find the two y possible values.
   129  		// We use the points (0, y1) and (0, y2) to find the new c1 and c2.
   130  		f := dist * math.Sqrt(a*a+b*b)
   131  		y1 := (-c + f) / b
   132  		y2 := (-c - f) / b
   133  		c1 = -b * y1
   134  		c2 = -b * y2
   135  	}
   136  	return c1, c2
   137  }
   138  
   139  // Returns the distance between two points, squared. The squared value
   140  // can be used to compare distances, only applying the square root if
   141  // necessary later with math.Sqrt().
   142  func dist2(x1, y1, x2, y2 float64) float64 {
   143  	dx := x1 - x2
   144  	dy := y1 - y2
   145  	return dx*dx + dy*dy
   146  }
   147  
   148  func abs64(value float64) float64 {
   149  	if value >= 0 {
   150  		return value
   151  	}
   152  	return -value
   153  }
   154  
   155  func clampUnit64(value float64) float64 {
   156  	if value <= 1.0 {
   157  		return value
   158  	}
   159  	return 1.0
   160  }