f32.mx raw

   1  // SPDX-License-Identifier: Unlicense OR MIT
   2  
   3  // Package gio/f32 is a float32 implementation of package image's
   4  // Point, Rectangle, and affine transformations.
   5  //
   6  // The coordinate space has the origin in the top left
   7  // corner with the axes extending right and down.
   8  //
   9  // Ported from gioui.org/f32 and gioui.org/internal/f32.
  10  package gio
  11  
  12  import (
  13  	"image"
  14  	"math"
  15  	"strconv"
  16  )
  17  
  18  // A Point is a two dimensional point.
  19  type Point struct {
  20  	X, Y float32
  21  }
  22  
  23  // String returns a string representation of p.
  24  func (p Point) String() string {
  25  	return "(" | strconv.FormatFloat(float64(p.X), 'f', -1, 32) |
  26  		"," | strconv.FormatFloat(float64(p.Y), 'f', -1, 32) | ")"
  27  }
  28  
  29  // Pt is shorthand for Point{X: x, Y: y}.
  30  func Pt(x, y float32) Point {
  31  	return Point{X: x, Y: y}
  32  }
  33  
  34  // Add returns the point p+p2.
  35  func (p Point) Add(p2 Point) Point {
  36  	return Point{X: p.X + p2.X, Y: p.Y + p2.Y}
  37  }
  38  
  39  // Sub returns the vector p-p2.
  40  func (p Point) Sub(p2 Point) Point {
  41  	return Point{X: p.X - p2.X, Y: p.Y - p2.Y}
  42  }
  43  
  44  // Mul returns p scaled by s.
  45  func (p Point) Mul(s float32) Point {
  46  	return Point{X: p.X * s, Y: p.Y * s}
  47  }
  48  
  49  // Div returns the vector p/s.
  50  func (p Point) Div(s float32) Point {
  51  	return Point{X: p.X / s, Y: p.Y / s}
  52  }
  53  
  54  // Round returns the integer point closest to p.
  55  func (p Point) Round() image.Point {
  56  	return image.Point{
  57  		X: int(math.Round(float64(p.X))),
  58  		Y: int(math.Round(float64(p.Y))),
  59  	}
  60  }
  61  
  62  // A Rectangle contains the points (X, Y) where Min.X <= X < Max.X,
  63  // Min.Y <= Y < Max.Y.
  64  type Rectangle struct {
  65  	Min, Max Point
  66  }
  67  
  68  // String returns a string representation of r.
  69  func (r Rectangle) String() string {
  70  	return r.Min.String() | "-" | r.Max.String()
  71  }
  72  
  73  // Rect is a shorthand for Rectangle{Point{x0, y0}, Point{x1, y1}}.
  74  // The returned Rectangle has x0 and y0 swapped if necessary so that
  75  // it's correctly formed.
  76  func Rect(x0, y0, x1, y1 float32) Rectangle {
  77  	if x0 > x1 {
  78  		x0, x1 = x1, x0
  79  	}
  80  	if y0 > y1 {
  81  		y0, y1 = y1, y0
  82  	}
  83  	return Rectangle{Point{x0, y0}, Point{x1, y1}}
  84  }
  85  
  86  // Size returns r's width and height.
  87  func (r Rectangle) Size() Point {
  88  	return Point{X: r.Dx(), Y: r.Dy()}
  89  }
  90  
  91  // Dx returns r's width.
  92  func (r Rectangle) Dx() float32 {
  93  	return r.Max.X - r.Min.X
  94  }
  95  
  96  // Dy returns r's height.
  97  func (r Rectangle) Dy() float32 {
  98  	return r.Max.Y - r.Min.Y
  99  }
 100  
 101  // Intersect returns the intersection of r and s.
 102  func (r Rectangle) Intersect(s Rectangle) Rectangle {
 103  	if r.Min.X < s.Min.X {
 104  		r.Min.X = s.Min.X
 105  	}
 106  	if r.Min.Y < s.Min.Y {
 107  		r.Min.Y = s.Min.Y
 108  	}
 109  	if r.Max.X > s.Max.X {
 110  		r.Max.X = s.Max.X
 111  	}
 112  	if r.Max.Y > s.Max.Y {
 113  		r.Max.Y = s.Max.Y
 114  	}
 115  	if r.Empty() {
 116  		return Rectangle{}
 117  	}
 118  	return r
 119  }
 120  
 121  // Union returns the union of r and s.
 122  func (r Rectangle) Union(s Rectangle) Rectangle {
 123  	if r.Empty() {
 124  		return s
 125  	}
 126  	if s.Empty() {
 127  		return r
 128  	}
 129  	if r.Min.X > s.Min.X {
 130  		r.Min.X = s.Min.X
 131  	}
 132  	if r.Min.Y > s.Min.Y {
 133  		r.Min.Y = s.Min.Y
 134  	}
 135  	if r.Max.X < s.Max.X {
 136  		r.Max.X = s.Max.X
 137  	}
 138  	if r.Max.Y < s.Max.Y {
 139  		r.Max.Y = s.Max.Y
 140  	}
 141  	return r
 142  }
 143  
 144  // Canon returns the canonical version of r, where Min is to
 145  // the upper left of Max.
 146  func (r Rectangle) Canon() Rectangle {
 147  	if r.Max.X < r.Min.X {
 148  		r.Min.X, r.Max.X = r.Max.X, r.Min.X
 149  	}
 150  	if r.Max.Y < r.Min.Y {
 151  		r.Min.Y, r.Max.Y = r.Max.Y, r.Min.Y
 152  	}
 153  	return r
 154  }
 155  
 156  // Empty reports whether r represents the empty area.
 157  func (r Rectangle) Empty() bool {
 158  	return r.Min.X >= r.Max.X || r.Min.Y >= r.Max.Y
 159  }
 160  
 161  // Add offsets r with the vector p.
 162  func (r Rectangle) Add(p Point) Rectangle {
 163  	return Rectangle{
 164  		Point{r.Min.X + p.X, r.Min.Y + p.Y},
 165  		Point{r.Max.X + p.X, r.Max.Y + p.Y},
 166  	}
 167  }
 168  
 169  // Sub offsets r with the vector -p.
 170  func (r Rectangle) Sub(p Point) Rectangle {
 171  	return Rectangle{
 172  		Point{r.Min.X - p.X, r.Min.Y - p.Y},
 173  		Point{r.Max.X - p.X, r.Max.Y - p.Y},
 174  	}
 175  }
 176  
 177  // Round returns the smallest integer rectangle that contains r.
 178  func (r Rectangle) Round() image.Rectangle {
 179  	return image.Rectangle{
 180  		Min: image.Point{
 181  			X: int(floor(r.Min.X)),
 182  			Y: int(floor(r.Min.Y)),
 183  		},
 184  		Max: image.Point{
 185  			X: int(ceil(r.Max.X)),
 186  			Y: int(ceil(r.Max.Y)),
 187  		},
 188  	}
 189  }
 190  
 191  // FRect converts an image.Rectangle to a Rectangle.
 192  func FRect(r image.Rectangle) Rectangle {
 193  	return Rectangle{
 194  		Min: FPt(r.Min), Max: FPt(r.Max),
 195  	}
 196  }
 197  
 198  // FPt converts an image.Point to a Point.
 199  func FPt(p image.Point) Point {
 200  	return Point{
 201  		X: float32(p.X), Y: float32(p.Y),
 202  	}
 203  }
 204  
 205  func ceil(v float32) int {
 206  	return int(math.Ceil(float64(v)))
 207  }
 208  
 209  func floor(v float32) int {
 210  	return int(math.Floor(float64(v)))
 211  }
 212  
 213  // Affine2D represents an affine 2D transformation. The zero value of Affine2D
 214  // represents the identity transform.
 215  type Affine2D struct {
 216  	// in order to make the zero value of Affine2D represent the identity
 217  	// transform we store it with the identity matrix subtracted, that is
 218  	// if the actual transformation matrix is:
 219  	// [sx, hx, ox]
 220  	// [hy, sy, oy]
 221  	// [ 0,  0,  1]
 222  	// we store a = sx-1 and e = sy-1
 223  	a, b, c float32
 224  	d, e, f float32
 225  }
 226  
 227  // NewAffine2D creates a new Affine2D transform from the matrix elements
 228  // in row major order. The rows are: [sx, hx, ox], [hy, sy, oy], [0, 0, 1].
 229  func NewAffine2D(sx, hx, ox, hy, sy, oy float32) Affine2D {
 230  	return Affine2D{
 231  		a: sx - 1, b: hx, c: ox,
 232  		d: hy, e: sy - 1, f: oy,
 233  	}
 234  }
 235  
 236  // AffineId returns an identity transformation matrix.
 237  func AffineId() Affine2D {
 238  	return NewAffine2D(
 239  		1, 0, 0,
 240  		0, 1, 0,
 241  	)
 242  }
 243  
 244  // Offset the transformation.
 245  func (a Affine2D) Offset(offset Point) Affine2D {
 246  	return Affine2D{
 247  		a.a, a.b, a.c + offset.X,
 248  		a.d, a.e, a.f + offset.Y,
 249  	}
 250  }
 251  
 252  // Scale the transformation around the given origin.
 253  func (a Affine2D) Scale(origin, factor Point) Affine2D {
 254  	if origin == (Point{}) {
 255  		return a.scale(factor)
 256  	}
 257  	a = a.Offset(origin.Mul(-1))
 258  	a = a.scale(factor)
 259  	return a.Offset(origin)
 260  }
 261  
 262  // Rotate the transformation by the given angle (in radians) counter clockwise
 263  // around the given origin.
 264  func (a Affine2D) Rotate(origin Point, radians float32) Affine2D {
 265  	if origin == (Point{}) {
 266  		return a.rotate(radians)
 267  	}
 268  	a = a.Offset(origin.Mul(-1))
 269  	a = a.rotate(radians)
 270  	return a.Offset(origin)
 271  }
 272  
 273  // Shear the transformation by the given angle (in radians) around the given
 274  // origin.
 275  func (a Affine2D) Shear(origin Point, radiansX, radiansY float32) Affine2D {
 276  	if origin == (Point{}) {
 277  		return a.shear(radiansX, radiansY)
 278  	}
 279  	a = a.Offset(origin.Mul(-1))
 280  	a = a.shear(radiansX, radiansY)
 281  	return a.Offset(origin)
 282  }
 283  
 284  // Mul returns A*B.
 285  func (A Affine2D) Mul(B Affine2D) (r Affine2D) {
 286  	r.a = (A.a+1)*(B.a+1) + A.b*B.d - 1
 287  	r.b = (A.a+1)*B.b + A.b*(B.e+1)
 288  	r.c = (A.a+1)*B.c + A.b*B.f + A.c
 289  	r.d = A.d*(B.a+1) + (A.e+1)*B.d
 290  	r.e = A.d*B.b + (A.e+1)*(B.e+1) - 1
 291  	r.f = A.d*B.c + (A.e+1)*B.f + A.f
 292  	return r
 293  }
 294  
 295  // Invert the transformation. Note that if the matrix is close to singular
 296  // numerical errors may become large or infinity.
 297  func (a Affine2D) Invert() Affine2D {
 298  	if a.a == 0 && a.b == 0 && a.d == 0 && a.e == 0 {
 299  		return Affine2D{a: 0, b: 0, c: -a.c, d: 0, e: 0, f: -a.f}
 300  	}
 301  	a.a += 1
 302  	a.e += 1
 303  	det := a.a*a.e - a.b*a.d
 304  	a.a, a.e = a.e/det, a.a/det
 305  	a.b, a.d = -a.b/det, -a.d/det
 306  	temp := a.c
 307  	a.c = -a.a*a.c - a.b*a.f
 308  	a.f = -a.d*temp - a.e*a.f
 309  	a.a -= 1
 310  	a.e -= 1
 311  	return a
 312  }
 313  
 314  // Transform p by returning a*p.
 315  func (a Affine2D) Transform(p Point) Point {
 316  	return Point{
 317  		X: p.X*(a.a+1) + p.Y*a.b + a.c,
 318  		Y: p.X*a.d + p.Y*(a.e+1) + a.f,
 319  	}
 320  }
 321  
 322  // Elems returns the matrix elements of the transform in row-major order. The
 323  // rows are: [sx, hx, ox], [hy, sy, oy], [0, 0, 1].
 324  func (a Affine2D) Elems() (sx, hx, ox, hy, sy, oy float32) {
 325  	return a.a + 1, a.b, a.c, a.d, a.e + 1, a.f
 326  }
 327  
 328  // Split a transform into two parts, one which is pure offset and the
 329  // other representing the scaling, shearing and rotation part.
 330  func (a Affine2D) Split() (srs Affine2D, offset Point) {
 331  	return Affine2D{
 332  		a: a.a, b: a.b, c: 0,
 333  		d: a.d, e: a.e, f: 0,
 334  	}, Point{X: a.c, Y: a.f}
 335  }
 336  
 337  func (a Affine2D) scale(factor Point) Affine2D {
 338  	return Affine2D{
 339  		(a.a+1)*factor.X - 1, a.b * factor.X, a.c * factor.X,
 340  		a.d * factor.Y, (a.e+1)*factor.Y - 1, a.f * factor.Y,
 341  	}
 342  }
 343  
 344  func (a Affine2D) rotate(radians float32) Affine2D {
 345  	sin, cos := math.Sincos(float64(radians))
 346  	s, c := float32(sin), float32(cos)
 347  	return Affine2D{
 348  		(a.a+1)*c - a.d*s - 1, a.b*c - (a.e+1)*s, a.c*c - a.f*s,
 349  		(a.a+1)*s + a.d*c, a.b*s + (a.e+1)*c - 1, a.c*s + a.f*c,
 350  	}
 351  }
 352  
 353  func (a Affine2D) shear(radiansX, radiansY float32) Affine2D {
 354  	tx := float32(math.Tan(float64(radiansX)))
 355  	ty := float32(math.Tan(float64(radiansY)))
 356  	return Affine2D{
 357  		(a.a + 1) + a.d*tx - 1, a.b + (a.e+1)*tx, a.c + a.f*tx,
 358  		(a.a+1)*ty + a.d, a.b*ty + (a.e + 1) - 1, a.c*ty + a.f,
 359  	}
 360  }
 361  
 362  func (a Affine2D) String() string {
 363  	sx, hx, ox, hy, sy, oy := a.Elems()
 364  
 365  	const prec = 6
 366  	const charsPerFloat = prec + 2 + 1
 367  	s := []byte{:0:6*charsPerFloat + 6}
 368  
 369  	s = append(s, '[', '[')
 370  	s = strconv.AppendFloat(s, float64(sx), 'g', prec, 32)
 371  	s = append(s, ' ')
 372  	s = strconv.AppendFloat(s, float64(hx), 'g', prec, 32)
 373  	s = append(s, ' ')
 374  	s = strconv.AppendFloat(s, float64(ox), 'g', prec, 32)
 375  	s = append(s, ']', ' ', '[')
 376  	s = strconv.AppendFloat(s, float64(hy), 'g', prec, 32)
 377  	s = append(s, ' ')
 378  	s = strconv.AppendFloat(s, float64(sy), 'g', prec, 32)
 379  	s = append(s, ' ')
 380  	s = strconv.AppendFloat(s, float64(oy), 'g', prec, 32)
 381  	s = append(s, ']', ']')
 382  
 383  	return s
 384  }
 385