192 lines
4.1 KiB
Go
192 lines
4.1 KiB
Go
// Implements SVG style matrix transformations.
|
|
// https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/transform
|
|
// Copyright 2018 All rights reserved.
|
|
|
|
package rasterx
|
|
|
|
import (
|
|
"math"
|
|
|
|
"golang.org/x/image/math/fixed"
|
|
)
|
|
|
|
// Matrix2D represents an SVG style matrix
|
|
type Matrix2D struct {
|
|
A, B, C, D, E, F float64
|
|
}
|
|
|
|
// matrix3 is a full 3x3 float64 matrix
|
|
// used for inverting
|
|
type matrix3 [9]float64
|
|
|
|
func otherPair(i int) (a, b int) {
|
|
switch i {
|
|
case 0:
|
|
a, b = 1, 2
|
|
case 1:
|
|
a, b = 0, 2
|
|
case 2:
|
|
a, b = 0, 1
|
|
}
|
|
return
|
|
}
|
|
|
|
func (m *matrix3) coFact(i, j int) float64 {
|
|
ai, bi := otherPair(i)
|
|
aj, bj := otherPair(j)
|
|
a, b, c, d := m[ai+aj*3], m[bi+bj*3], m[ai+bj*3], m[bi+aj*3]
|
|
return a*b - c*d
|
|
}
|
|
|
|
func (m *matrix3) Invert() *matrix3 {
|
|
var cofact matrix3
|
|
for i := 0; i < 3; i++ {
|
|
for j := 0; j < 3; j++ {
|
|
sign := float64(1 - (i+j%2)%2*2) // "checkerboard of minuses" grid
|
|
cofact[i+j*3] = m.coFact(i, j) * sign
|
|
}
|
|
}
|
|
deteriminate := m[0]*cofact[0] + m[1]*cofact[1] + m[2]*cofact[2]
|
|
|
|
// transpose cofact
|
|
for i := 0; i < 2; i++ {
|
|
for j := i + 1; j < 3; j++ {
|
|
cofact[i+j*3], cofact[j+i*3] = cofact[j+i*3], cofact[i+j*3]
|
|
}
|
|
}
|
|
for i := 0; i < 9; i++ {
|
|
cofact[i] /= deteriminate
|
|
}
|
|
return &cofact
|
|
}
|
|
|
|
// Invert returns the inverse matrix
|
|
func (a Matrix2D) Invert() Matrix2D {
|
|
n := &matrix3{a.A, a.C, a.E, a.B, a.D, a.F, 0, 0, 1}
|
|
n = n.Invert()
|
|
return Matrix2D{A: n[0], C: n[1], E: n[2], B: n[3], D: n[4], F: n[5]}
|
|
}
|
|
|
|
// Mult returns a*b
|
|
func (a Matrix2D) Mult(b Matrix2D) Matrix2D {
|
|
return Matrix2D{
|
|
A: a.A*b.A + a.C*b.B,
|
|
B: a.B*b.A + a.D*b.B,
|
|
C: a.A*b.C + a.C*b.D,
|
|
D: a.B*b.C + a.D*b.D,
|
|
E: a.A*b.E + a.C*b.F + a.E,
|
|
F: a.B*b.E + a.D*b.F + a.F}
|
|
}
|
|
|
|
// Identity is the identity matrix
|
|
var Identity = Matrix2D{1, 0, 0, 1, 0, 0}
|
|
|
|
// TFixed transforms a fixed.Point26_6 by the matrix
|
|
func (a Matrix2D) TFixed(x fixed.Point26_6) (y fixed.Point26_6) {
|
|
y.X = fixed.Int26_6((float64(x.X)*a.A + float64(x.Y)*a.C) + a.E*64)
|
|
y.Y = fixed.Int26_6((float64(x.X)*a.B + float64(x.Y)*a.D) + a.F*64)
|
|
return
|
|
}
|
|
|
|
// Transform multiples the input vector by matrix m and outputs the results vector
|
|
// components.
|
|
func (a Matrix2D) Transform(x1, y1 float64) (x2, y2 float64) {
|
|
x2 = x1*a.A + y1*a.C + a.E
|
|
y2 = x1*a.B + y1*a.D + a.F
|
|
return
|
|
}
|
|
|
|
// TransformVector is a modidifed version of Transform that ignores the
|
|
// translation components.
|
|
func (a Matrix2D) TransformVector(x1, y1 float64) (x2, y2 float64) {
|
|
x2 = x1*a.A + y1*a.C
|
|
y2 = x1*a.B + y1*a.D
|
|
return
|
|
}
|
|
|
|
//Scale matrix in x and y dimensions
|
|
func (a Matrix2D) Scale(x, y float64) Matrix2D {
|
|
return a.Mult(Matrix2D{
|
|
A: x,
|
|
B: 0,
|
|
C: 0,
|
|
D: y,
|
|
E: 0,
|
|
F: 0})
|
|
}
|
|
|
|
//SkewY skews the matrix in the Y dimension
|
|
func (a Matrix2D) SkewY(theta float64) Matrix2D {
|
|
return a.Mult(Matrix2D{
|
|
A: 1,
|
|
B: math.Tan(theta),
|
|
C: 0,
|
|
D: 1,
|
|
E: 0,
|
|
F: 0})
|
|
}
|
|
|
|
//SkewX skews the matrix in the X dimension
|
|
func (a Matrix2D) SkewX(theta float64) Matrix2D {
|
|
return a.Mult(Matrix2D{
|
|
A: 1,
|
|
B: 0,
|
|
C: math.Tan(theta),
|
|
D: 1,
|
|
E: 0,
|
|
F: 0})
|
|
}
|
|
|
|
//Translate translates the matrix to the x , y point
|
|
func (a Matrix2D) Translate(x, y float64) Matrix2D {
|
|
return a.Mult(Matrix2D{
|
|
A: 1,
|
|
B: 0,
|
|
C: 0,
|
|
D: 1,
|
|
E: x,
|
|
F: y})
|
|
}
|
|
|
|
//Rotate rotate the matrix by theta
|
|
func (a Matrix2D) Rotate(theta float64) Matrix2D {
|
|
return a.Mult(Matrix2D{
|
|
A: math.Cos(theta),
|
|
B: math.Sin(theta),
|
|
C: -math.Sin(theta),
|
|
D: math.Cos(theta),
|
|
E: 0,
|
|
F: 0})
|
|
}
|
|
|
|
// MatrixAdder is an adder that applies matrix M to all points
|
|
type MatrixAdder struct {
|
|
Adder
|
|
M Matrix2D
|
|
}
|
|
|
|
// Reset sets the matrix M to identity
|
|
func (t *MatrixAdder) Reset() {
|
|
t.M = Identity
|
|
}
|
|
|
|
// Start starts a new path
|
|
func (t *MatrixAdder) Start(a fixed.Point26_6) {
|
|
t.Adder.Start(t.M.TFixed(a))
|
|
}
|
|
|
|
// Line adds a linear segment to the current curve.
|
|
func (t *MatrixAdder) Line(b fixed.Point26_6) {
|
|
t.Adder.Line(t.M.TFixed(b))
|
|
}
|
|
|
|
// QuadBezier adds a quadratic segment to the current curve.
|
|
func (t *MatrixAdder) QuadBezier(b, c fixed.Point26_6) {
|
|
t.Adder.QuadBezier(t.M.TFixed(b), t.M.TFixed(c))
|
|
}
|
|
|
|
// CubeBezier adds a cubic segment to the current curve.
|
|
func (t *MatrixAdder) CubeBezier(b, c, d fixed.Point26_6) {
|
|
t.Adder.CubeBezier(t.M.TFixed(b), t.M.TFixed(c), t.M.TFixed(d))
|
|
}
|