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