338 lines
7.9 KiB
Go
338 lines
7.9 KiB
Go
package draw2d
|
|
|
|
import (
|
|
"exp/draw"
|
|
"image"
|
|
"freetype-go.googlecode.com/hg/freetype/raster"
|
|
)
|
|
|
|
type FillRule int
|
|
|
|
const (
|
|
FillRuleEvenOdd FillRule = iota
|
|
FillRuleWinding
|
|
)
|
|
|
|
type Cap int
|
|
|
|
const (
|
|
RoundCap Cap = iota
|
|
ButtCap
|
|
SquareCap
|
|
)
|
|
|
|
type Join int
|
|
|
|
const (
|
|
BevelJoin Join = iota
|
|
RoundJoin
|
|
MiterJoin
|
|
)
|
|
|
|
type GraphicContext struct {
|
|
PaintedImage *image.RGBA
|
|
rasterizer *raster.Rasterizer
|
|
current *contextStack
|
|
}
|
|
|
|
type contextStack struct {
|
|
path *Path
|
|
lineWidth float
|
|
dash []float
|
|
dashOffset float
|
|
strokeColor image.Color
|
|
fillColor image.Color
|
|
fillRule FillRule
|
|
cap Cap
|
|
join Join
|
|
|
|
previous *contextStack
|
|
}
|
|
|
|
/**
|
|
* Create a new Graphic context from an image
|
|
*/
|
|
func NewGraphicContext(pi *image.RGBA) *GraphicContext {
|
|
gc := new(GraphicContext)
|
|
gc.PaintedImage = pi
|
|
width, height := gc.PaintedImage.Bounds().Dx(), gc.PaintedImage.Bounds().Dy()
|
|
gc.rasterizer = raster.NewRasterizer(width, height)
|
|
|
|
gc.current = new(contextStack)
|
|
gc.current.lineWidth = 1.0
|
|
gc.current.strokeColor = image.Black
|
|
gc.current.fillColor = image.White
|
|
gc.current.cap = RoundCap
|
|
gc.current.fillRule = FillRuleEvenOdd
|
|
gc.current.join = RoundJoin
|
|
gc.current.path = new(Path)
|
|
return gc
|
|
}
|
|
|
|
func (gc *GraphicContext) Clear() {
|
|
width, height := gc.PaintedImage.Bounds().Dx(), gc.PaintedImage.Bounds().Dy()
|
|
gc.ClearRect(0, 0, width, height)
|
|
}
|
|
|
|
func (gc *GraphicContext) ClearRect(x1, y1, x2, y2 int) {
|
|
imageColor := image.NewColorImage(gc.current.fillColor)
|
|
draw.Draw(gc.PaintedImage, gc.PaintedImage.Bounds(), imageColor, image.ZP)
|
|
}
|
|
|
|
func (gc *GraphicContext) SetStrokeColor(c image.Color) {
|
|
gc.current.strokeColor = c
|
|
}
|
|
|
|
func (gc *GraphicContext) SetFillColor(c image.Color) {
|
|
gc.current.fillColor = c
|
|
}
|
|
|
|
func (gc *GraphicContext) SetFillRule(f FillRule) {
|
|
gc.current.fillRule = f
|
|
}
|
|
|
|
func (gc *GraphicContext) SetLineWidth(lineWidth float) {
|
|
gc.current.lineWidth = lineWidth
|
|
}
|
|
|
|
func (gc *GraphicContext) SetLineCap(cap Cap) {
|
|
gc.current.cap = cap
|
|
}
|
|
|
|
func (gc *GraphicContext) SetLineJoin(join Join) {
|
|
gc.current.join = join
|
|
}
|
|
|
|
func (gc *GraphicContext) SetLineDash(dash []float, dashOffset float) {
|
|
gc.current.dash = dash
|
|
gc.current.dashOffset = dashOffset
|
|
}
|
|
|
|
func (gc *GraphicContext) Save() {
|
|
context := new(contextStack)
|
|
context.lineWidth = gc.current.lineWidth
|
|
context.strokeColor = gc.current.strokeColor
|
|
context.fillColor = gc.current.fillColor
|
|
context.fillRule = gc.current.fillRule
|
|
context.dash = gc.current.dash
|
|
context.dashOffset = gc.current.dashOffset
|
|
context.cap = gc.current.cap
|
|
context.join = gc.current.join
|
|
context.path = gc.current.path.Copy()
|
|
context.previous = gc.current
|
|
gc.current = context
|
|
}
|
|
|
|
func (gc *GraphicContext) Restore() {
|
|
if gc.current.previous != nil {
|
|
oldContext := gc.current
|
|
gc.current = gc.current.previous
|
|
oldContext.previous = nil
|
|
}
|
|
}
|
|
|
|
func (gc *GraphicContext) BeginPath() {
|
|
gc.current.path = new(Path)
|
|
}
|
|
|
|
func (gc *GraphicContext) MoveTo(x, y float) {
|
|
gc.current.path.MoveTo(x, y)
|
|
}
|
|
|
|
func (gc *GraphicContext) RMoveTo(dx, dy float) {
|
|
gc.current.path.RMoveTo(dx, dy)
|
|
}
|
|
|
|
func (gc *GraphicContext) LineTo(x, y float) {
|
|
gc.current.path.LineTo(x, y)
|
|
}
|
|
|
|
func (gc *GraphicContext) RLineTo(dx, dy float) {
|
|
gc.current.path.RLineTo(dx, dy)
|
|
}
|
|
|
|
func (gc *GraphicContext) Rect(x1, y1, x2, y2 float) {
|
|
gc.current.path.Rect(x1, y1, x2, y2)
|
|
}
|
|
|
|
func (gc *GraphicContext) RRect(dx1, dy1, dx2, dy2 float) {
|
|
gc.current.path.RRect(dx1, dy1, dx2, dy2)
|
|
}
|
|
|
|
func (gc *GraphicContext) QuadCurveTo(cx, cy, x, y float) {
|
|
gc.current.path.QuadCurveTo(cx, cy, x, y)
|
|
}
|
|
|
|
func (gc *GraphicContext) RQuadCurveTo(dcx, dcy, dx, dy float) {
|
|
gc.current.path.RQuadCurveTo(dcx, dcy, dx, dy)
|
|
}
|
|
|
|
func (gc *GraphicContext) CubicCurveTo(cx1, cy1, cx2, cy2, x, y float) {
|
|
gc.current.path.CubicCurveTo(cx1, cy1, cx2, cy2, x, y)
|
|
}
|
|
|
|
func (gc *GraphicContext) RCubicCurveTo(dcx1, dcy1, dcx2, dcy2, dx, dy float) {
|
|
gc.current.path.RCubicCurveTo(dcx1, dcy1, dcx2, dcy2, dx, dy)
|
|
}
|
|
|
|
func (gc *GraphicContext) ArcTo(cx, cy, rx, ry, startAngle, angle float) {
|
|
gc.current.path.ArcTo(cx, cy, rx, ry, startAngle, angle)
|
|
}
|
|
|
|
func (gc *GraphicContext) RArcTo(dcx, dcy, rx, ry, startAngle, angle float) {
|
|
gc.current.path.RArcTo(dcx, dcy, rx, ry, startAngle, angle)
|
|
}
|
|
|
|
func (gc *GraphicContext) Close() {
|
|
gc.current.path.Close()
|
|
}
|
|
|
|
func (gc *GraphicContext) paint(color image.Color) {
|
|
painter := raster.NewRGBAPainter(gc.PaintedImage)
|
|
painter.SetColor(color)
|
|
gc.rasterizer.Rasterize(painter)
|
|
gc.rasterizer.Clear()
|
|
gc.current.path = new(Path)
|
|
}
|
|
|
|
func (gc *GraphicContext) Stroke(paths ...*Path) {
|
|
paths = append(paths, gc.current.path)
|
|
rasterPath := tracePath(gc.current.dash, gc.current.dashOffset, paths...)
|
|
gc.rasterizer.UseNonZeroWinding = true
|
|
gc.rasterizer.AddStroke(*rasterPath, raster.Fix32(gc.current.lineWidth*256), gc.current.cap.capper(), gc.current.join.joiner())
|
|
gc.paint(gc.current.strokeColor)
|
|
}
|
|
|
|
func (gc *GraphicContext) Fill(paths ...*Path) {
|
|
paths = append(paths, gc.current.path)
|
|
rasterPath := tracePath(nil, 0, paths...)
|
|
|
|
gc.rasterizer.UseNonZeroWinding = gc.current.fillRule.fillRule()
|
|
gc.rasterizer.AddPath(*rasterPath)
|
|
gc.paint(gc.current.fillColor)
|
|
}
|
|
|
|
func (gc *GraphicContext) FillStroke(paths ...*Path) {
|
|
paths = append(paths, gc.current.path)
|
|
rasterPath := tracePath(nil, 0, paths...)
|
|
|
|
gc.rasterizer.UseNonZeroWinding = gc.current.fillRule.fillRule()
|
|
gc.rasterizer.AddPath(*rasterPath)
|
|
gc.paint(gc.current.fillColor)
|
|
if gc.current.dash != nil {
|
|
rasterPath = tracePath(gc.current.dash, gc.current.dashOffset, paths...)
|
|
}
|
|
gc.rasterizer.UseNonZeroWinding = true
|
|
gc.rasterizer.AddStroke(*rasterPath, raster.Fix32(gc.current.lineWidth*256), gc.current.cap.capper(), gc.current.join.joiner())
|
|
gc.paint(gc.current.strokeColor)
|
|
}
|
|
|
|
func (f FillRule) fillRule() bool {
|
|
switch f {
|
|
case FillRuleEvenOdd:
|
|
return false
|
|
case FillRuleWinding:
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
func (c Cap) capper() raster.Capper {
|
|
switch c {
|
|
case RoundCap:
|
|
return raster.RoundCapper
|
|
case ButtCap:
|
|
return raster.ButtCapper
|
|
case SquareCap:
|
|
return raster.SquareCapper
|
|
}
|
|
return raster.RoundCapper
|
|
}
|
|
|
|
func (j Join) joiner() raster.Joiner {
|
|
switch j {
|
|
case RoundJoin:
|
|
return raster.RoundJoiner
|
|
case BevelJoin:
|
|
return raster.BevelJoiner
|
|
}
|
|
return raster.RoundJoiner
|
|
}
|
|
|
|
|
|
type PathAdapter struct {
|
|
path *raster.Path
|
|
x, y, distance float
|
|
dash []float
|
|
currentDash int
|
|
dashOffset float
|
|
}
|
|
|
|
func tracePath(dash []float, dashOffset float, paths ...*Path) *raster.Path {
|
|
var adapter PathAdapter
|
|
if dash != nil && len(dash) > 0 {
|
|
adapter.dash = dash
|
|
} else {
|
|
adapter.dash = nil
|
|
}
|
|
adapter.currentDash = 0
|
|
adapter.dashOffset = dashOffset
|
|
adapter.path = new(raster.Path)
|
|
for _, path := range paths {
|
|
path.TraceLine(&adapter)
|
|
}
|
|
return adapter.path
|
|
}
|
|
|
|
func floatToPoint(x, y float) raster.Point {
|
|
return raster.Point{raster.Fix32(x * 256), raster.Fix32(y * 256)}
|
|
}
|
|
|
|
func (p *PathAdapter) MoveTo(x, y float) {
|
|
p.path.Start(floatToPoint(x, y))
|
|
p.x, p.y = x, y
|
|
p.distance = p.dashOffset
|
|
p.currentDash = 0
|
|
}
|
|
|
|
func (p *PathAdapter) LineTo(x, y float) {
|
|
if p.dash != nil {
|
|
rest := p.dash[p.currentDash] - p.distance
|
|
for rest < 0 {
|
|
p.distance = p.distance - p.dash[p.currentDash]
|
|
p.currentDash = (p.currentDash + 1) % len(p.dash)
|
|
rest = p.dash[p.currentDash] - p.distance
|
|
}
|
|
d := distance(p.x, p.y, x, y)
|
|
for d >= rest {
|
|
k := rest / d
|
|
lx := p.x + k*(x-p.x)
|
|
ly := p.y + k*(y-p.y)
|
|
if p.currentDash%2 == 0 {
|
|
// line
|
|
p.path.Add1(floatToPoint(lx, ly))
|
|
} else {
|
|
// gap
|
|
p.path.Start(floatToPoint(lx, ly))
|
|
}
|
|
d = d - rest
|
|
p.x, p.y = lx, ly
|
|
p.currentDash = (p.currentDash + 1) % len(p.dash)
|
|
rest = p.dash[p.currentDash]
|
|
}
|
|
p.distance = d
|
|
if p.currentDash%2 == 0 {
|
|
p.path.Add1(floatToPoint(x, y))
|
|
} else {
|
|
p.path.Start(floatToPoint(x, y))
|
|
}
|
|
if p.distance >= p.dash[p.currentDash] {
|
|
p.distance = p.distance - p.dash[p.currentDash]
|
|
p.currentDash = (p.currentDash + 1) % len(p.dash)
|
|
}
|
|
} else {
|
|
p.path.Add1(floatToPoint(x, y))
|
|
}
|
|
p.x, p.y = x, y
|
|
}
|