Start implementing drawer

This commit is contained in:
Laurent Le Goff 2015-04-30 16:27:23 +02:00
parent 72643a28b2
commit f2563306e4
5 changed files with 277 additions and 406 deletions

150
draw2d.go
View file

@ -3,3 +3,153 @@
// Package draw2d provides a Graphic Context that can draw vector form on canvas. // Package draw2d provides a Graphic Context that can draw vector form on canvas.
package draw2d 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)
}

79
draw2dimg/drawer.go Normal file
View file

@ -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) {
}

View file

@ -6,46 +6,68 @@ import (
) )
// Droid draws a droid at specified position // Droid draws a droid at specified position
func Droid(gc draw2d.GraphicContext, x, y float64) { func Droid(drawer draw2d.Drawer, x, y float64, fillStyle draw2d.FillStyle, strokeStyle draw2d.StrokeStyle) {
gc.SetLineCap(draw2d.RoundCap) strokeStyle.LineCap = draw2d.RoundCap
gc.SetLineWidth(5) strokeStyle.Width = 5
path := &draw2d.Path{}
// head // head
gc.ArcTo(x+80, y+70, 50, 50, 180*(math.Pi/180), 360*(math.Pi/180)) path.ArcTo(x+80, y+70, 50, 50, 180*(math.Pi/180), 360*(math.Pi/180))
gc.FillStroke() drawer.Fill(path, fillStyle)
gc.MoveTo(x+60, y+25) drawer.Stroke(path, strokeStyle)
gc.LineTo(x+50, y+10)
gc.MoveTo(x+100, y+25) path.Clear()
gc.LineTo(x+110, y+10) path.MoveTo(x+60, y+25)
gc.Stroke() path.LineTo(x+50, y+10)
path.MoveTo(x+100, y+25)
path.LineTo(x+110, y+10)
drawer.Stroke(path, strokeStyle)
// left eye // left eye
Circle(gc, x+60, y+45, 5) path.Clear()
gc.FillStroke() Circle(path, x+60, y+45, 5)
drawer.Fill(path, fillStyle)
drawer.Stroke(path, strokeStyle)
// right eye // right eye
Circle(gc, x+100, y+45, 5) path.Clear()
gc.FillStroke() Circle(path, x+100, y+45, 5)
drawer.Fill(path, fillStyle)
drawer.Stroke(path, strokeStyle)
// body // body
RoundedRectangle(gc, x+30, y+75, x+30+100, y+75+90, 10, 10) path.Clear()
gc.FillStroke() RoundedRectangle(path, x+30, y+75, x+30+100, y+75+90, 10, 10)
Rectangle(gc, x+30, y+75, x+30+100, y+75+80) drawer.Fill(path, fillStyle)
gc.FillStroke() 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 // left arm
RoundedRectangle(gc, x+5, y+80, x+5+20, y+80+70, 10, 10) path.Clear()
gc.FillStroke() RoundedRectangle(path, x+5, y+80, x+5+20, y+80+70, 10, 10)
drawer.Fill(path, fillStyle)
drawer.Stroke(path, strokeStyle)
// right arm // right arm
RoundedRectangle(gc, x+135, y+80, x+135+20, y+80+70, 10, 10) path.Clear()
gc.FillStroke() RoundedRectangle(path, x+135, y+80, x+135+20, y+80+70, 10, 10)
drawer.Fill(path, fillStyle)
drawer.Stroke(path, strokeStyle)
// left leg // left leg
RoundedRectangle(gc, x+50, y+150, x+50+20, y+150+50, 10, 10) path.Clear()
gc.FillStroke() RoundedRectangle(path, x+50, y+150, x+50+20, y+150+50, 10, 10)
drawer.Fill(path, fillStyle)
drawer.Stroke(path, strokeStyle)
// right leg // right leg
RoundedRectangle(gc, x+90, y+150, x+90+20, y+150+50, 10, 10) path.Clear()
gc.FillStroke() RoundedRectangle(path, x+90, y+150, x+90+20, y+150+50, 10, 10)
drawer.Fill(path, fillStyle)
drawer.Stroke(path, strokeStyle)
} }

View file

@ -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)
}

View file

@ -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
}