diff --git a/context.go b/context.go index 1a1b6e3..58ca153 100644 --- a/context.go +++ b/context.go @@ -46,6 +46,7 @@ type Context struct { lineJoin LineJoin fillRule FillRule fontFace font.Face + matrix Matrix } func NewContext(width, height int) *Context { @@ -65,6 +66,7 @@ func NewContextForRGBA(im *image.RGBA) *Context { lineWidth: 1, fillRule: FillRuleWinding, fontFace: basicfont.Face7x13, + matrix: Identity(), } } @@ -80,8 +82,8 @@ func (dc *Context) Height() int { return dc.height } -func (dc *Context) WriteToPNG(path string) error { - return writeToPNG(path, dc.im) +func (dc *Context) WritePNG(path string) error { + return writePNG(path, dc.im) } func (dc *Context) SetLineWidth(lineWidth float64) { @@ -163,11 +165,13 @@ func (dc *Context) SetRGB(r, g, b float64) { // Path Manipulation func (dc *Context) MoveTo(x, y float64) { + x, y = dc.TransformPoint(x, y) dc.start = fp(x, y) dc.path.Start(dc.start) } func (dc *Context) LineTo(x, y float64) { + x, y = dc.TransformPoint(x, y) if len(dc.path) == 0 { dc.MoveTo(x, y) } else { @@ -176,6 +180,8 @@ func (dc *Context) LineTo(x, y float64) { } func (dc *Context) QuadraticTo(x1, y1, x2, y2 float64) { + x1, y1 = dc.TransformPoint(x1, y1) + x2, y2 = dc.TransformPoint(x2, y2) if len(dc.path) == 0 { dc.MoveTo(x1, y1) } else { @@ -307,6 +313,7 @@ func (dc *Context) LoadFontFace(path string, size float64) { } func (dc *Context) DrawString(x, y float64, s string) { + x, y = dc.TransformPoint(x, y) d := &font.Drawer{ Dst: dc.im, Src: image.NewUniform(dc.color), @@ -325,3 +332,33 @@ func (dc *Context) MeasureString(s string) float64 { a := d.MeasureString(s) return float64(a >> 6) } + +// Transformation Matrix Operations + +func (dc *Context) Identity() { + dc.matrix = Identity() +} + +func (dc *Context) Translate(x, y float64) { + dc.matrix = dc.matrix.Translate(x, y) +} + +func (dc *Context) Scale(x, y float64) { + dc.matrix = dc.matrix.Scale(x, y) +} + +func (dc *Context) Rotate(angle float64) { + dc.matrix = dc.matrix.Rotate(angle) +} + +func (dc *Context) RotateAbout(angle, x, y float64) { + dc.matrix = dc.matrix.RotateAbout(angle, x, y) +} + +func (dc *Context) Shear(x, y float64) { + dc.matrix = dc.matrix.Shear(x, y) +} + +func (dc *Context) TransformPoint(x, y float64) (tx, ty float64) { + return dc.matrix.TransformPoint(x, y) +} diff --git a/examples/circle.go b/examples/circle.go index bd3dfa7..3e17a09 100644 --- a/examples/circle.go +++ b/examples/circle.go @@ -12,5 +12,5 @@ func main() { dc.SetRGB(0, 0, 0.5) dc.SetLineWidth(8) dc.Stroke() - dc.WriteToPNG("out.png") + dc.WritePNG("out.png") } diff --git a/examples/ellipse.go b/examples/ellipse.go new file mode 100644 index 0000000..3519bbb --- /dev/null +++ b/examples/ellipse.go @@ -0,0 +1,18 @@ +package main + +import "github.com/fogleman/gg" + +func main() { + const S = 1024 + dc := gg.NewContext(S, S) + dc.SetRGB(1, 1, 1) + dc.Clear() + for i := 0; i < 360; i += 15 { + dc.Identity() + dc.RotateAbout(gg.Radians(float64(i)), S/2, S/2) + dc.DrawEllipse(S/2, S/2, S*7/16, S/8) + dc.SetRGBA(0, 0, 0, 0.1) + dc.Fill() + } + dc.WritePNG("out.png") +} diff --git a/examples/example0.go b/examples/example0.go index 80c6e3a..646564e 100644 --- a/examples/example0.go +++ b/examples/example0.go @@ -16,5 +16,5 @@ func main() { dc.SetRGB(0, 0, 0) dc.SetLineWidth(8) dc.Stroke() - dc.WriteToPNG("out.png") + dc.WritePNG("out.png") } diff --git a/examples/lines.go b/examples/lines.go index c49fcc6..a69a8ec 100644 --- a/examples/lines.go +++ b/examples/lines.go @@ -27,5 +27,5 @@ func main() { dc.DrawLine(x1, y1, x2, y2) dc.Stroke() } - dc.WriteToPNG("out.png") + dc.WritePNG("out.png") } diff --git a/examples/star.go b/examples/star.go index 6c3f448..89b63e3 100644 --- a/examples/star.go +++ b/examples/star.go @@ -36,5 +36,5 @@ func main() { dc.SetRGBA(0, 1, 0, 0.5) dc.SetLineWidth(16) dc.Stroke() - dc.WriteToPNG("out.png") + dc.WritePNG("out.png") } diff --git a/examples/text.go b/examples/text.go index fe0fc92..0ab6604 100644 --- a/examples/text.go +++ b/examples/text.go @@ -11,5 +11,5 @@ func main() { s := "Hello, world!" w := dc.MeasureString(s) dc.DrawString(500-w/2, 500, s) - dc.WriteToPNG("out.png") + dc.WritePNG("out.png") } diff --git a/matrix.go b/matrix.go new file mode 100644 index 0000000..0066dd6 --- /dev/null +++ b/matrix.go @@ -0,0 +1,99 @@ +package gg + +import "math" + +type Matrix struct { + XX, YX, XY, YY, X0, Y0 float64 +} + +func Identity() Matrix { + return Matrix{ + 1, 0, + 0, 1, + 0, 0, + } +} + +func Translate(x, y float64) Matrix { + return Matrix{ + 1, 0, + 0, 1, + x, y, + } +} + +func Scale(x, y float64) Matrix { + return Matrix{ + x, 0, + 0, y, + 0, 0, + } +} + +func Rotate(angle float64) Matrix { + c := math.Cos(angle) + s := math.Sin(angle) + return Matrix{ + c, s, + -s, c, + 0, 0, + } +} + +func RotateAbout(angle, x, y float64) Matrix { + a := Translate(x, y) + b := Rotate(angle) + c := Translate(-x, -y) + return c.Multiply(b).Multiply(a) +} + +func Shear(x, y float64) Matrix { + return Matrix{ + 1, x, + y, 1, + 0, 0, + } +} + +func (a Matrix) Multiply(b Matrix) Matrix { + return Matrix{ + a.XX*b.XX + a.YX*b.XY, + a.XX*b.YX + a.YX*b.YY, + a.XY*b.XX + a.YY*b.XY, + a.XY*b.YX + a.YY*b.YY, + a.X0*b.XX + a.Y0*b.XY + b.X0, + a.X0*b.YX + a.Y0*b.YY + b.Y0, + } +} + +func (a Matrix) TransformVector(x, y float64) (tx, ty float64) { + tx = a.XX*x + a.XY*y + ty = a.YX*x + a.YY*y + return +} + +func (a Matrix) TransformPoint(x, y float64) (tx, ty float64) { + tx = a.XX*x + a.XY*y + a.X0 + ty = a.YX*x + a.YY*y + a.Y0 + return +} + +func (a Matrix) Translate(x, y float64) Matrix { + return Translate(x, y).Multiply(a) +} + +func (a Matrix) Scale(x, y float64) Matrix { + return Scale(x, y).Multiply(a) +} + +func (a Matrix) Rotate(angle float64) Matrix { + return Rotate(angle).Multiply(a) +} + +func (a Matrix) RotateAbout(angle, x, y float64) Matrix { + return RotateAbout(angle, x, y).Multiply(a) +} + +func (a Matrix) Shear(x, y float64) Matrix { + return Shear(x, y).Multiply(a) +} diff --git a/util.go b/util.go index 1d36648..20f0017 100644 --- a/util.go +++ b/util.go @@ -6,6 +6,7 @@ import ( "image/draw" "image/png" "io/ioutil" + "math" "os" "strings" @@ -15,7 +16,15 @@ import ( "golang.org/x/image/math/fixed" ) -func writeToPNG(path string, im image.Image) error { +func Radians(degrees float64) float64 { + return degrees * math.Pi / 180 +} + +func Degrees(radians float64) float64 { + return radians * 180 / math.Pi +} + +func writePNG(path string, im image.Image) error { file, err := os.Create(path) if err != nil { return err