From f2563306e4182daaa78b60f297f31cbe5b91e119 Mon Sep 17 00:00:00 2001 From: Laurent Le Goff Date: Thu, 30 Apr 2015 16:27:23 +0200 Subject: [PATCH] Start implementing drawer --- draw2d.go | 150 ++++++++++++++++++++++++++++++ draw2dimg/drawer.go | 79 ++++++++++++++++ draw2dkit/droid.go | 74 +++++++++------ graphics.go | 162 -------------------------------- transform.go | 218 -------------------------------------------- 5 files changed, 277 insertions(+), 406 deletions(-) create mode 100644 draw2dimg/drawer.go delete mode 100644 graphics.go delete mode 100644 transform.go diff --git a/draw2d.go b/draw2d.go index 424abf4..94c8983 100644 --- a/draw2d.go +++ b/draw2d.go @@ -3,3 +3,153 @@ // Package draw2d provides a Graphic Context that can draw vector form on canvas. package draw2d + +import ( + "image" + "image/color" +) + +// FillRule defines the fill rule used when fill +type FillRule int + +const ( + // FillRuleEvenOdd determines the "insideness" of a point in the shape + // by drawing a ray from that point to infinity in any direction + // and counting the number of path segments from the given shape that the ray crosses. + // If this number is odd, the point is inside; if even, the point is outside. + FillRuleEvenOdd FillRule = iota + // FillRuleWinding determines the "insideness" of a point in the shape + // by drawing a ray from that point to infinity in any direction + // and then examining the places where a segment of the shape crosses the ray. + // Starting with a count of zero, add one each time a path segment crosses + // the ray from left to right and subtract one each time + // a path segment crosses the ray from right to left. After counting the crossings, + // if the result is zero then the point is outside the path. Otherwise, it is inside. + FillRuleWinding +) + +// LineCap is the style of line extremities +type LineCap int + +const ( + // RoundCap defines a rounded shape at the end of the line + RoundCap LineCap = iota + // ButtCap defines a squared shape exactly at the end of the line + ButtCap + // SquareCap defines a squared shape at the end of the line + SquareCap +) + +// LineJoin is the style of segments joint +type LineJoin int + +const ( + // BevelJoin represents cut segments joint + BevelJoin LineJoin = iota + // RoundJoin represents rounded segments joint + RoundJoin + // MiterJoin represents peaker segments joint + MiterJoin +) + +// StrokeStyle keeps stroke style attributes +// that is used by the Stroke method of a Drawer +type StrokeStyle struct { + // Color defines the color of stroke + Color color.Color + // Line width + Width float64 + // Line cap style rounded, butt or square + LineCap LineCap + // Line join style bevel, round or miter + LineJoin LineJoin + // offset of the first dash + DashOffset float64 + // array represented dash length pair values are plain dash and impair are space between dash + // if empty display plain line + Dash []float64 +} + +type FillStyle interface { +} + +// SolidFillStyle define style attributes for a solid fill style +type SolidFillStyle struct { + // Color defines the line color + Color color.Color + // FillRule defines the file rule to used + FillRule FillRule +} + +// Vertical Alignment of the text +type Valign int + +const ( + ValignTop Valign = iota + ValignTopCenter + ValignTopBottom + ValignTopBaseline +) + +// Horizontal Alignment of the text +type Halign int + +const ( + HalignLeft = iota + HalignCenter + HalignRight +) + +// TextStyle +type TextStyle struct { + // Color defines the color of text + Color color.Color + // Size font size + Size float64 + // The font to use + Font FontData + // Horizontal Alignment of the text + Halign Halign + // Vertical Alignment of the text + Valign Valign +} + +type ScalingPolicy int + +const ( + // ScalingNone no scaling applied + 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 + ScalingWidth + // ScalingHeight the image is scaled so that its height is exactly the given height + ScalingHeight + // ScalingFit the image is scaled to the largest scale that allow the image to fit within a rectangle width x height + ScalingFit + // ScalingSameArea the image is scaled so that its area is exactly the area of the given rectangle width x height + ScalingSameArea + // ScalingFill the image is scaled to the smallest scale that allow the image to fully cover a rectangle width x height + ScalingFill +) + +// ImageScaling style attributes used to display the image +type ImageScaling struct { + // Horizontal Alignment of the image + Halign Halign + // Vertical Alignment of the image + Valign Valign + // Width Height used by scaling policy + Width, Height float64 + // ScalingPolicy defines the scaling policy to applied to the image + ScalingPolicy ScalingPolicy +} + +// Drawer can fill and stroke a path +type Drawer interface { + Matrix() *Matrix + Fill(path *Path, style FillStyle) + Stroke(path *Path, style StrokeStyle) + Text(text string, x, y float64, style TextStyle) + Image(image image.Image, x, y float64, scaling ImageScaling) +} diff --git a/draw2dimg/drawer.go b/draw2dimg/drawer.go new file mode 100644 index 0000000..a2a8b25 --- /dev/null +++ b/draw2dimg/drawer.go @@ -0,0 +1,79 @@ +package draw2dimg + +import ( + "code.google.com/p/freetype-go/freetype/raster" + "code.google.com/p/freetype-go/freetype/truetype" + "github.com/llgcode/draw2d" + "github.com/llgcode/draw2d/draw2dbase" + "image" + "image/draw" +) + +type Drawer struct { + matrix draw2d.Matrix + img draw.Image + painter Painter + fillRasterizer *raster.Rasterizer + strokeRasterizer *raster.Rasterizer + glyphBuf *truetype.GlyphBuf +} + +func NewDrawer(img *image.RGBA) *Drawer { + width, height := img.Bounds().Dx(), img.Bounds().Dy() + return &Drawer{ + draw2d.NewIdentityMatrix(), + img, + raster.NewRGBAPainter(img), + raster.NewRasterizer(width, height), + raster.NewRasterizer(width, height), + truetype.NewGlyphBuf(), + } +} + +func (d *Drawer) Matrix() *draw2d.Matrix { + return d.Matrix() +} + +func (d *Drawer) Fill(path *draw2d.Path, style draw2d.FillStyle) { + switch fillStyle := style.(type) { + case draw2d.SolidFillStyle: + d.fillRasterizer.UseNonZeroWinding = useNonZeroWinding(fillStyle.FillRule) + d.painter.SetColor(fillStyle.Color) + default: + panic("FillStyle not supported") + } + + flattener := draw2dbase.Transformer{d.matrix, draw2dbase.FtLineBuilder{d.fillRasterizer}} + + draw2dbase.Flatten(path, flattener, d.matrix.GetScale()) + + d.fillRasterizer.Rasterize(d.painter) + d.fillRasterizer.Clear() +} + +func (d *Drawer) Stroke(path *draw2d.Path, style draw2d.StrokeStyle) { + d.strokeRasterizer.UseNonZeroWinding = true + + stroker := draw2dbase.NewLineStroker(style.LineCap, style.LineJoin, draw2dbase.Transformer{d.matrix, draw2dbase.FtLineBuilder{d.strokeRasterizer}}) + stroker.HalfLineWidth = style.Width / 2 + + var liner draw2dbase.Flattener + if style.Dash != nil && len(style.Dash) > 0 { + liner = draw2dbase.NewDashConverter(style.Dash, style.DashOffset, stroker) + } else { + liner = stroker + } + + draw2dbase.Flatten(path, liner, d.matrix.GetScale()) + + d.painter.SetColor(style.Color) + d.strokeRasterizer.Rasterize(d.painter) + d.strokeRasterizer.Clear() +} + +func (d *Drawer) Text(text string, x, y float64, style draw2d.TextStyle) { + +} + +func (d *Drawer) Image(image image.Image, x, y float64, scaling draw2d.ImageScaling) { +} diff --git a/draw2dkit/droid.go b/draw2dkit/droid.go index 0adfe42..79edaf5 100644 --- a/draw2dkit/droid.go +++ b/draw2dkit/droid.go @@ -6,46 +6,68 @@ import ( ) // Droid draws a droid at specified position -func Droid(gc draw2d.GraphicContext, x, y float64) { - gc.SetLineCap(draw2d.RoundCap) - gc.SetLineWidth(5) +func Droid(drawer draw2d.Drawer, x, y float64, fillStyle draw2d.FillStyle, strokeStyle draw2d.StrokeStyle) { + strokeStyle.LineCap = draw2d.RoundCap + strokeStyle.Width = 5 + + path := &draw2d.Path{} // head - gc.ArcTo(x+80, y+70, 50, 50, 180*(math.Pi/180), 360*(math.Pi/180)) - gc.FillStroke() - gc.MoveTo(x+60, y+25) - gc.LineTo(x+50, y+10) - gc.MoveTo(x+100, y+25) - gc.LineTo(x+110, y+10) - gc.Stroke() + path.ArcTo(x+80, y+70, 50, 50, 180*(math.Pi/180), 360*(math.Pi/180)) + drawer.Fill(path, fillStyle) + drawer.Stroke(path, strokeStyle) + + path.Clear() + path.MoveTo(x+60, y+25) + path.LineTo(x+50, y+10) + path.MoveTo(x+100, y+25) + path.LineTo(x+110, y+10) + drawer.Stroke(path, strokeStyle) // left eye - Circle(gc, x+60, y+45, 5) - gc.FillStroke() + path.Clear() + Circle(path, x+60, y+45, 5) + drawer.Fill(path, fillStyle) + drawer.Stroke(path, strokeStyle) // right eye - Circle(gc, x+100, y+45, 5) - gc.FillStroke() + path.Clear() + Circle(path, x+100, y+45, 5) + drawer.Fill(path, fillStyle) + drawer.Stroke(path, strokeStyle) // body - RoundedRectangle(gc, x+30, y+75, x+30+100, y+75+90, 10, 10) - gc.FillStroke() - Rectangle(gc, x+30, y+75, x+30+100, y+75+80) - gc.FillStroke() + path.Clear() + RoundedRectangle(path, x+30, y+75, x+30+100, y+75+90, 10, 10) + drawer.Fill(path, fillStyle) + drawer.Stroke(path, strokeStyle) + + path.Clear() + Rectangle(path, x+30, y+75, x+30+100, y+75+80) + drawer.Fill(path, fillStyle) + drawer.Stroke(path, strokeStyle) // left arm - RoundedRectangle(gc, x+5, y+80, x+5+20, y+80+70, 10, 10) - gc.FillStroke() + path.Clear() + RoundedRectangle(path, x+5, y+80, x+5+20, y+80+70, 10, 10) + drawer.Fill(path, fillStyle) + drawer.Stroke(path, strokeStyle) // right arm - RoundedRectangle(gc, x+135, y+80, x+135+20, y+80+70, 10, 10) - gc.FillStroke() + path.Clear() + RoundedRectangle(path, x+135, y+80, x+135+20, y+80+70, 10, 10) + drawer.Fill(path, fillStyle) + drawer.Stroke(path, strokeStyle) // left leg - RoundedRectangle(gc, x+50, y+150, x+50+20, y+150+50, 10, 10) - gc.FillStroke() + path.Clear() + RoundedRectangle(path, x+50, y+150, x+50+20, y+150+50, 10, 10) + drawer.Fill(path, fillStyle) + drawer.Stroke(path, strokeStyle) // right leg - RoundedRectangle(gc, x+90, y+150, x+90+20, y+150+50, 10, 10) - gc.FillStroke() + path.Clear() + RoundedRectangle(path, x+90, y+150, x+90+20, y+150+50, 10, 10) + drawer.Fill(path, fillStyle) + drawer.Stroke(path, strokeStyle) } diff --git a/graphics.go b/graphics.go deleted file mode 100644 index 48ca305..0000000 --- a/graphics.go +++ /dev/null @@ -1,162 +0,0 @@ -package draw2d - -import ( - "image" - "image/color" -) - -// FillRule defines the fill rule used when fill -type FillRule int - -const ( - // FillRuleEvenOdd determines the "insideness" of a point in the shape - // by drawing a ray from that point to infinity in any direction - // and counting the number of path segments from the given shape that the ray crosses. - // If this number is odd, the point is inside; if even, the point is outside. - FillRuleEvenOdd FillRule = iota - // FillRuleWinding determines the "insideness" of a point in the shape - // by drawing a ray from that point to infinity in any direction - // and then examining the places where a segment of the shape crosses the ray. - // Starting with a count of zero, add one each time a path segment crosses - // the ray from left to right and subtract one each time - // a path segment crosses the ray from right to left. After counting the crossings, - // if the result is zero then the point is outside the path. Otherwise, it is inside. - FillRuleWinding -) - -// LineCap is the style of line extremities -type LineCap int - -const ( - // RoundCap defines a rounded shape at the end of the line - RoundCap LineCap = iota - // ButtCap defines a squared shape exactly at the end of the line - ButtCap - // SquareCap defines a squared shape at the end of the line - SquareCap -) - -// LineJoin is the style of segments joint -type LineJoin int - -const ( - // BevelJoin represents cut segments joint - BevelJoin LineJoin = iota - // RoundJoin represents rounded segments joint - RoundJoin - // MiterJoin represents peaker segments joint - MiterJoin -) - -// StrokeStyle keeps stroke style attributes -// that is used by the Stroke method of a Drawer -type StrokeStyle struct { - // Color defines the color of stroke - Color color.Color - // Line width - Width float64 - // Line cap style rounded, butt or square - LineCap LineCap - // Line join style bevel, round or miter - LineJoin LineJoin - // offset of the first dash - dashOffset float64 - // array represented dash length pair values are plain dash and impair are space between dash - // if empty display plain line - dash []float64 -} - -// FillStyle -type FillStyle struct { -} - -// SolidFillStyle define style attributes for a solid fill style -type SolidFillStyle struct { - FillStyle - // Color defines the line color - Color color.Color - // FillRule defines the file rule to used - FillRule FillRule -} - -// Vertical Alignment of the text -type Valign int - -const ( - ValignTop Valign = iota - ValignTopCenter - ValignTopBottom - ValignTopBaseline -) - -// Horizontal Alignment of the text -type Halign int - -const ( - HalignLeft = iota - HalignCenter - HalignRight -) - -type ScalingPolicy int - -const ( - // ScalingNone no scaling applied - 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 - ScalingWidth - // ScalingHeight the image is scaled so that its height is exactly the given height - ScalingHeight - // ScalingFit the image is scaled to the largest scale that allow the image to fit within a rectangle width x height - ScalingFit - // ScalingSameArea the image is scaled so that its area is exactly the area of the given rectangle width x height - ScalingSameArea - // ScalingFill the image is scaled to the smallest scale that allow the image to fully cover a rectangle width x height - ScalingFill -) - -// TextStyle -type TextStyle struct { - // Color defines the color of text - Color color.Color - // Size font size - Size float64 - // The font to use - Font FontData - // Horizontal Alignment of the text - Halign Halign - // Vertical Alignment of the text - Valign Valign -} - -// ImageStyle style attributes used to display the image -type ImageStyle struct { - // Horizontal Alignment of the image - Halign Halign - // Vertical Alignment of the image - Valign Valign - // Width Height used by scaling policy - Width, Height float64 - // ScalingPolicy defines the scaling policy to applied to the image - ScalingPolicy ScalingPolicy -} - -// Style defines properties that -type Style struct { - Matrix Matrix - StrokeStyle StrokeStyle - FillStyle FillStyle - TextStyle TextStyle - ImageStyle TextStyle -} - -// Drawer can fill and stroke a path -type Drawer interface { - Style() *Style - Fill(Path) - Stroke(Path) - Text(text string, x, y float64) - Image(image image.Image, x, y float64) -} diff --git a/transform.go b/transform.go deleted file mode 100644 index a648d9e..0000000 --- a/transform.go +++ /dev/null @@ -1,218 +0,0 @@ -// Copyright 2010 The draw2d Authors. All rights reserved. -// created: 21/11/2010 by Laurent Le Goff - -package draw2d - -import ( - "math" -) - -type MatrixTransform [6]float64 - -const ( - epsilon = 1e-6 -) - -// Determinant compute the determinant of the matrix -func (tr MatrixTransform) 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 MatrixTransform) 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 MatrixTransform) 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 MatrixTransform) 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 MatrixTransform) 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 MatrixTransform) 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 MatrixTransform) 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() MatrixTransform { - return [6]float64{1, 0, 0, 1, 0, 0} -} - -// NewTranslationMatrix creates a transformation matrix with a translation tx and ty translation parameter -func NewTranslationMatrix(tx, ty float64) MatrixTransform { - return [6]float64{1, 0, 0, 1, tx, ty} -} - -// NewScaleMatrix creates a transformation matrix with a sx, sy scale factor -func NewScaleMatrix(sx, sy float64) MatrixTransform { - return [6]float64{sx, 0, 0, sy, 0, 0} -} - -// NewRotationMatrix creates a rotation transformation matrix. angle is in radian -func NewRotationMatrix(angle float64) MatrixTransform { - c := math.Cos(angle) - s := math.Sin(angle) - return [6]float64{c, s, -s, c, 0, 0} -} - -// NewMatrixTransform creates a transformation matrix, combining a scale and a translation, that transform rectangle1 into rectangle2. -func NewMatrixFromRects(rectangle1, rectangle2 [4]float64) MatrixTransform { - 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 [6]float64{xScale, 0, 0, yScale, xOffset, yOffset} -} - -// Inverse returns a matrix that is the inverse of the given matrix. -func (tr MatrixTransform) Inverse() MatrixTransform { - d := tr.Determinant() // matrix determinant - return [6]float64{ - tr[3] / d, - -tr[1] / d, - -tr[2] / d, - tr[0] / d, - (tr[2]*tr[5] - tr[3]*tr[4]) / d, - (tr[1]*tr[4] - tr[0]*tr[5]) / d} -} - -// Multiply composes Matrix tr1 with tr2 returns the resulting matrix -func (tr1 MatrixTransform) Multiply(tr2 MatrixTransform) MatrixTransform { - return [6]float64{ - tr1[0]*tr2[0] + tr1[1]*tr2[2], - tr1[1]*tr2[3] + tr1[0]*tr2[1], - tr1[2]*tr2[0] + tr1[3]*tr2[2], - tr1[3]*tr2[3] + tr1[2]*tr2[1], - tr1[4]*tr2[0] + tr1[5]*tr2[2] + tr2[4], - tr1[5]*tr2[3] + tr1[4]*tr2[1] + tr2[5]} -} - -// Scale adds a scale to the matrix -func (tr *MatrixTransform) Scale(sx, sy float64) *MatrixTransform { - tr[0] = sx * tr[0] - tr[1] = sx * tr[1] - tr[2] = sy * tr[2] - tr[3] = sy * tr[3] - return tr -} - -// Translate adds a translation to the matrix -func (tr *MatrixTransform) Translate(tx, ty float64) *MatrixTransform { - tr[4] = tx*tr[0] + ty*tr[2] + tr[4] - tr[5] = ty*tr[3] + tx*tr[1] + tr[5] - return tr -} - -// Rotate adds a rotation to the matrix. angle is in radian -func (tr *MatrixTransform) Rotate(angle float64) *MatrixTransform { - 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 - return tr -} - -// GetTranslation -func (tr MatrixTransform) GetTranslation() (x, y float64) { - return tr[4], tr[5] -} - -// GetScaling -func (tr MatrixTransform) GetScaling() (x, y float64) { - return tr[0], tr[3] -} - -// GetScale computes the scale of the matrix -func (tr MatrixTransform) 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 MatrixTransform) Equals(tr2 MatrixTransform) 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 MatrixTransform) 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 MatrixTransform) 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 -}