From 72643a28b20825dcbd4a51b305a94a59ce42980c Mon Sep 17 00:00:00 2001 From: Laurent Le Goff Date: Thu, 30 Apr 2015 14:11:23 +0200 Subject: [PATCH] Rename MatrixTransform to Matrix --- draw2dbase/flattener.go | 2 +- draw2dbase/stack_gc.go | 16 +-- draw2dimg/rgba_interpolation.go | 2 +- gc.go | 6 +- graphics.go | 13 +- matrix.go | 222 ++++++++++++++++++++++++++++++++ 6 files changed, 242 insertions(+), 19 deletions(-) create mode 100644 matrix.go diff --git a/draw2dbase/flattener.go b/draw2dbase/flattener.go index 63c3823..70ec305 100644 --- a/draw2dbase/flattener.go +++ b/draw2dbase/flattener.go @@ -67,7 +67,7 @@ func Flatten(path *draw2d.Path, flattener Flattener, scale float64) { // Transformer apply the Matrix transformation tr type Transformer struct { - Tr draw2d.MatrixTransform + Tr draw2d.Matrix Flattener Flattener } diff --git a/draw2dbase/stack_gc.go b/draw2dbase/stack_gc.go index 6170d42..27327ae 100644 --- a/draw2dbase/stack_gc.go +++ b/draw2dbase/stack_gc.go @@ -18,7 +18,7 @@ type StackGraphicContext struct { } type ContextStack struct { - Tr draw2d.MatrixTransform + Tr draw2d.Matrix Path *draw2d.Path LineWidth float64 Dash []float64 @@ -58,28 +58,28 @@ func NewStackGraphicContext() *StackGraphicContext { return gc } -func (gc *StackGraphicContext) GetMatrixTransform() draw2d.MatrixTransform { +func (gc *StackGraphicContext) GetMatrixTransform() draw2d.Matrix { return gc.Current.Tr } -func (gc *StackGraphicContext) SetMatrixTransform(Tr draw2d.MatrixTransform) { +func (gc *StackGraphicContext) SetMatrixTransform(Tr draw2d.Matrix) { gc.Current.Tr = Tr } -func (gc *StackGraphicContext) ComposeMatrixTransform(Tr draw2d.MatrixTransform) { - gc.Current.Tr = Tr.Multiply(gc.Current.Tr) +func (gc *StackGraphicContext) ComposeMatrixTransform(Tr draw2d.Matrix) { + gc.Current.Tr.Compose(Tr) } func (gc *StackGraphicContext) Rotate(angle float64) { - gc.Current.Tr = draw2d.NewRotationMatrix(angle).Multiply(gc.Current.Tr) + gc.Current.Tr.Rotate(angle) } func (gc *StackGraphicContext) Translate(tx, ty float64) { - gc.Current.Tr = draw2d.NewTranslationMatrix(tx, ty).Multiply(gc.Current.Tr) + gc.Current.Tr.Translate(tx, ty) } func (gc *StackGraphicContext) Scale(sx, sy float64) { - gc.Current.Tr = draw2d.NewScaleMatrix(sx, sy).Multiply(gc.Current.Tr) + gc.Current.Tr.Scale(sx, sy) } func (gc *StackGraphicContext) SetStrokeColor(c color.Color) { diff --git a/draw2dimg/rgba_interpolation.go b/draw2dimg/rgba_interpolation.go index 3b5fd66..8c2b399 100644 --- a/draw2dimg/rgba_interpolation.go +++ b/draw2dimg/rgba_interpolation.go @@ -105,7 +105,7 @@ func cubic(offset, v0, v1, v2, v3 float64) uint32 { (-9*v0+9*v2))*offset + (v0 + 16*v1 + v2)) / 18.0) } -func DrawImage(src image.Image, dest draw.Image, tr draw2d.MatrixTransform, op draw.Op, filter ImageFilter) { +func DrawImage(src image.Image, dest draw.Image, tr draw2d.Matrix, op draw.Op, filter ImageFilter) { bounds := src.Bounds() x0, y0, x1, y1 := tr.TransformRectangle(float64(bounds.Min.X), float64(bounds.Min.Y), float64(bounds.Max.X), float64(bounds.Max.Y)) var x, y, u, v float64 diff --git a/gc.go b/gc.go index ee3dcb4..22e89d2 100644 --- a/gc.go +++ b/gc.go @@ -13,11 +13,11 @@ type GraphicContext interface { // BeginPath creates a new path BeginPath() // GetMatrixTransform returns the current transformation matrix - GetMatrixTransform() MatrixTransform + GetMatrixTransform() Matrix // SetMatrixTransform sets the current transformation matrix - SetMatrixTransform(tr MatrixTransform) + SetMatrixTransform(tr Matrix) // ComposeMatrixTransform composes the current transformation matrix with tr - ComposeMatrixTransform(tr MatrixTransform) + ComposeMatrixTransform(tr Matrix) // Rotate applies a rotation to the current transformation matrix. angle is in radian. Rotate(angle float64) // Translate applies a translation to the current transformation matrix. diff --git a/graphics.go b/graphics.go index 47117f5..48ca305 100644 --- a/graphics.go +++ b/graphics.go @@ -1,6 +1,7 @@ package draw2d import ( + "image" "image/color" ) @@ -101,7 +102,7 @@ type ScalingPolicy int const ( // ScalingNone no scaling applied - ScalingNone = iota + ScalingNone ScalingPolicy = iota // ScalingStretch the image is stretched so that its width and height are exactly the given width and height ScalingStretch // ScalingWidth the image is scaled so that its width is exactly the given width @@ -144,11 +145,11 @@ type ImageStyle struct { // Style defines properties that type Style struct { - MatrixTransform MatrixTransform - StrokeStyle StrokeStyle - FillStyle FillStyle - TextStyle TextStyle - ImageStyle TextStyle + Matrix Matrix + StrokeStyle StrokeStyle + FillStyle FillStyle + TextStyle TextStyle + ImageStyle TextStyle } // Drawer can fill and stroke a path diff --git a/matrix.go b/matrix.go new file mode 100644 index 0000000..55f5e38 --- /dev/null +++ b/matrix.go @@ -0,0 +1,222 @@ +// Copyright 2010 The draw2d Authors. All rights reserved. +// created: 21/11/2010 by Laurent Le Goff + +package draw2d + +import ( + "math" +) + +// Matrix represents an affine transformation +type Matrix [6]float64 + +const ( + epsilon = 1e-6 +) + +// Determinant compute the determinant of the matrix +func (tr Matrix) Determinant() float64 { + return tr[0]*tr[3] - tr[1]*tr[2] +} + +// Transform applies the transformation matrix to points. It modify the points passed in parameter. +func (tr Matrix) Transform(points []float64) { + for i, j := 0, 1; j < len(points); i, j = i+2, j+2 { + x := points[i] + y := points[j] + points[i] = x*tr[0] + y*tr[2] + tr[4] + points[j] = x*tr[1] + y*tr[3] + tr[5] + } +} + +// TransformPoint applies the transformation matrix to point. It returns the point the transformed point. +func (tr Matrix) TransformPoint(x, y float64) (xres, yres float64) { + xres = x*tr[0] + y*tr[2] + tr[4] + yres = x*tr[1] + y*tr[3] + tr[5] + return xres, yres +} + +func minMax(x, y float64) (min, max float64) { + if x > y { + return y, x + } + return x, y +} + +// Transform applies the transformation matrix to the rectangle represented by the min and the max point of the rectangle +func (tr Matrix) TransformRectangle(x0, y0, x2, y2 float64) (nx0, ny0, nx2, ny2 float64) { + points := []float64{x0, y0, x2, y0, x2, y2, x0, y2} + tr.Transform(points) + points[0], points[2] = minMax(points[0], points[2]) + points[4], points[6] = minMax(points[4], points[6]) + points[1], points[3] = minMax(points[1], points[3]) + points[5], points[7] = minMax(points[5], points[7]) + + nx0 = math.Min(points[0], points[4]) + ny0 = math.Min(points[1], points[5]) + nx2 = math.Max(points[2], points[6]) + ny2 = math.Max(points[3], points[7]) + return nx0, ny0, nx2, ny2 +} + +// InverseTransform applies the transformation inverse matrix to the rectangle represented by the min and the max point of the rectangle +func (tr Matrix) InverseTransform(points []float64) { + d := tr.Determinant() // matrix determinant + for i, j := 0, 1; j < len(points); i, j = i+2, j+2 { + x := points[i] + y := points[j] + points[i] = ((x-tr[4])*tr[3] - (y-tr[5])*tr[2]) / d + points[j] = ((y-tr[5])*tr[0] - (x-tr[4])*tr[1]) / d + } +} + +// InverseTransformPoint applies the transformation inverse matrix to point. It returns the point the transformed point. +func (tr Matrix) InverseTransformPoint(x, y float64) (xres, yres float64) { + d := tr.Determinant() // matrix determinant + xres = ((x-tr[4])*tr[3] - (y-tr[5])*tr[2]) / d + yres = ((y-tr[5])*tr[0] - (x-tr[4])*tr[1]) / d + return xres, yres +} + +// VectorTransform applies the transformation matrix to points without using the translation parameter of the affine matrix. +// It modify the points passed in parameter. +func (tr Matrix) VectorTransform(points []float64) { + for i, j := 0, 1; j < len(points); i, j = i+2, j+2 { + x := points[i] + y := points[j] + points[i] = x*tr[0] + y*tr[2] + points[j] = x*tr[1] + y*tr[3] + } +} + +// NewIdentityMatrix creates an identity transformation matrix. +func NewIdentityMatrix() Matrix { + return Matrix{1, 0, 0, 1, 0, 0} +} + +// NewTranslationMatrix creates a transformation matrix with a translation tx and ty translation parameter +func NewTranslationMatrix(tx, ty float64) Matrix { + return Matrix{1, 0, 0, 1, tx, ty} +} + +// NewScaleMatrix creates a transformation matrix with a sx, sy scale factor +func NewScaleMatrix(sx, sy float64) Matrix { + return Matrix{sx, 0, 0, sy, 0, 0} +} + +// NewRotationMatrix creates a rotation transformation matrix. angle is in radian +func NewRotationMatrix(angle float64) Matrix { + c := math.Cos(angle) + s := math.Sin(angle) + return Matrix{c, s, -s, c, 0, 0} +} + +// NewMatrixFromRects creates a transformation matrix, combining a scale and a translation, that transform rectangle1 into rectangle2. +func NewMatrixFromRects(rectangle1, rectangle2 [4]float64) Matrix { + xScale := (rectangle2[2] - rectangle2[0]) / (rectangle1[2] - rectangle1[0]) + yScale := (rectangle2[3] - rectangle2[1]) / (rectangle1[3] - rectangle1[1]) + xOffset := rectangle2[0] - (rectangle1[0] * xScale) + yOffset := rectangle2[1] - (rectangle1[1] * yScale) + return Matrix{xScale, 0, 0, yScale, xOffset, yOffset} +} + +// Inverse computes the inverse matrix +func (tr *Matrix) Inverse() { + d := tr.Determinant() // matrix determinant + tr0, tr1, tr2, tr3, tr4, tr5 := tr[0], tr[1], tr[2], tr[3], tr[4], tr[5] + tr[0] = tr3 / d + tr[1] = -tr1 / d + tr[2] = -tr2 / d + tr[3] = tr0 / d + tr[4] = (tr2*tr5 - tr3*tr4) / d + tr[5] = (tr1*tr4 - tr0*tr5) / d +} + +func (tr Matrix) Copy() Matrix { + var result Matrix + copy(result[:], tr[:]) + return result +} + +// Compose multiplies trToConcat x tr +func (tr *Matrix) Compose(trToCompose Matrix) { + tr0, tr1, tr2, tr3, tr4, tr5 := tr[0], tr[1], tr[2], tr[3], tr[4], tr[5] + tr[0] = trToCompose[0]*tr0 + trToCompose[1]*tr2 + tr[1] = trToCompose[1]*tr3 + trToCompose[0]*tr1 + tr[2] = trToCompose[2]*tr0 + trToCompose[3]*tr2 + tr[3] = trToCompose[3]*tr3 + trToCompose[2]*tr1 + tr[4] = trToCompose[4]*tr0 + trToCompose[5]*tr2 + tr4 + tr[5] = trToCompose[5]*tr3 + trToCompose[4]*tr1 + tr5 +} + +// Scale adds a scale to the matrix +func (tr *Matrix) Scale(sx, sy float64) { + tr[0] = sx * tr[0] + tr[1] = sx * tr[1] + tr[2] = sy * tr[2] + tr[3] = sy * tr[3] +} + +// Translate adds a translation to the matrix +func (tr *Matrix) Translate(tx, ty float64) { + tr[4] = tx*tr[0] + ty*tr[2] + tr[4] + tr[5] = ty*tr[3] + tx*tr[1] + tr[5] +} + +// Rotate adds a rotation to the matrix. angle is in radian +func (tr *Matrix) Rotate(angle float64) { + c := math.Cos(angle) + s := math.Sin(angle) + t0 := c*tr[0] + s*tr[2] + t1 := s*tr[3] + c*tr[1] + t2 := c*tr[2] - s*tr[0] + t3 := c*tr[3] - s*tr[1] + tr[0] = t0 + tr[1] = t1 + tr[2] = t2 + tr[3] = t3 +} + +// GetTranslation +func (tr Matrix) GetTranslation() (x, y float64) { + return tr[4], tr[5] +} + +// GetScaling +func (tr Matrix) GetScaling() (x, y float64) { + return tr[0], tr[3] +} + +// GetScale computes a scale for the matrix +func (tr Matrix) GetScale() float64 { + x := 0.707106781*tr[0] + 0.707106781*tr[1] + y := 0.707106781*tr[2] + 0.707106781*tr[3] + return math.Sqrt(x*x + y*y) +} + +// ******************** Testing ******************** + +// Equals tests if a two transformation are equal. A tolerance is applied when comparing matrix elements. +func (tr1 Matrix) Equals(tr2 Matrix) bool { + for i := 0; i < 6; i = i + 1 { + if !fequals(tr1[i], tr2[i]) { + return false + } + } + return true +} + +// IsIdentity tests if a transformation is the identity transformation. A tolerance is applied when comparing matrix elements. +func (tr Matrix) IsIdentity() bool { + return fequals(tr[4], 0) && fequals(tr[5], 0) && tr.IsTranslation() +} + +// IsTranslation tests if a transformation is is a pure translation. A tolerance is applied when comparing matrix elements. +func (tr Matrix) IsTranslation() bool { + return fequals(tr[0], 1) && fequals(tr[1], 0) && fequals(tr[2], 0) && fequals(tr[3], 1) +} + +// fequals compares two floats. return true if the distance between the two floats is less than epsilon, false otherwise +func fequals(float1, float2 float64) bool { + return math.Abs(float1-float2) <= epsilon +}