gg/context.go

404 lines
7.9 KiB
Go
Raw Normal View History

2016-02-19 15:43:37 +01:00
package gg
2016-02-18 22:02:57 +01:00
import (
"image"
"image/color"
"image/draw"
2016-02-19 04:11:53 +01:00
"math"
2016-02-18 22:02:57 +01:00
"github.com/golang/freetype/raster"
2016-02-19 04:53:47 +01:00
"golang.org/x/image/font"
"golang.org/x/image/font/basicfont"
2016-02-18 22:02:57 +01:00
"golang.org/x/image/math/fixed"
)
type LineCap int
const (
LineCapRound LineCap = iota
LineCapButt
LineCapSquare
)
type LineJoin int
const (
LineJoinRound LineJoin = iota
LineJoinBevel
)
2016-02-19 03:30:17 +01:00
type FillRule int
const (
FillRuleWinding FillRule = iota
FillRuleEvenOdd
)
2016-02-18 22:02:57 +01:00
type Context struct {
width int
height int
im *image.RGBA
color color.Color
path raster.Path
start fixed.Point26_6
lineWidth float64
2016-02-19 17:07:25 +01:00
lineCap LineCap
lineJoin LineJoin
2016-02-19 03:30:17 +01:00
fillRule FillRule
2016-02-19 04:53:47 +01:00
fontFace font.Face
2016-02-19 19:16:40 +01:00
matrix Matrix
2016-02-19 19:34:04 +01:00
stack []*Context
2016-02-18 22:02:57 +01:00
}
func NewContext(width, height int) *Context {
2016-02-19 17:07:25 +01:00
return NewContextForRGBA(image.NewRGBA(image.Rect(0, 0, width, height)))
}
func NewContextForImage(im image.Image) *Context {
return NewContextForRGBA(imageToRGBA(im))
}
func NewContextForRGBA(im *image.RGBA) *Context {
2016-02-18 22:02:57 +01:00
return &Context{
2016-02-19 17:07:25 +01:00
width: im.Bounds().Size().X,
height: im.Bounds().Size().Y,
2016-02-18 22:02:57 +01:00
im: im,
color: color.Transparent,
lineWidth: 1,
2016-02-19 03:30:17 +01:00
fillRule: FillRuleWinding,
2016-02-19 04:53:47 +01:00
fontFace: basicfont.Face7x13,
2016-02-19 19:16:40 +01:00
matrix: Identity(),
2016-02-18 22:02:57 +01:00
}
}
2016-02-19 04:11:53 +01:00
func (dc *Context) Image() image.Image {
return dc.im
2016-02-18 22:02:57 +01:00
}
2016-02-19 04:11:53 +01:00
func (dc *Context) Width() int {
return dc.width
2016-02-18 22:02:57 +01:00
}
2016-02-19 04:11:53 +01:00
func (dc *Context) Height() int {
return dc.height
2016-02-18 22:02:57 +01:00
}
2016-02-19 19:16:40 +01:00
func (dc *Context) WritePNG(path string) error {
2016-02-19 19:49:03 +01:00
return SavePNG(path, dc.im)
2016-02-18 22:02:57 +01:00
}
2016-02-19 17:07:25 +01:00
func (dc *Context) SetLineWidth(lineWidth float64) {
dc.lineWidth = lineWidth
2016-02-18 22:02:57 +01:00
}
2016-02-19 17:07:25 +01:00
func (dc *Context) SetLineCap(lineCap LineCap) {
dc.lineCap = lineCap
2016-02-18 22:02:57 +01:00
}
2016-02-19 17:20:50 +01:00
func (dc *Context) SetLineCapRound() {
dc.lineCap = LineCapRound
}
func (dc *Context) SetLineCapButt() {
dc.lineCap = LineCapButt
}
func (dc *Context) SetLineCapSquare() {
dc.lineCap = LineCapSquare
}
2016-02-19 17:07:25 +01:00
func (dc *Context) SetLineJoin(lineJoin LineJoin) {
dc.lineJoin = lineJoin
2016-02-18 22:02:57 +01:00
}
2016-02-19 17:20:50 +01:00
func (dc *Context) SetLineJoinRound() {
dc.lineJoin = LineJoinRound
}
func (dc *Context) SetLineJoinBevel() {
dc.lineJoin = LineJoinBevel
}
2016-02-19 17:07:25 +01:00
func (dc *Context) SetFillRule(fillRule FillRule) {
dc.fillRule = fillRule
2016-02-18 22:02:57 +01:00
}
2016-02-19 17:20:50 +01:00
func (dc *Context) SetFillRuleWinding() {
dc.fillRule = FillRuleWinding
}
func (dc *Context) SetFillRuleEvenOdd() {
dc.fillRule = FillRuleEvenOdd
}
2016-02-19 17:07:25 +01:00
// Color Setters
func (dc *Context) SetColor(c color.Color) {
dc.color = c
2016-02-18 22:02:57 +01:00
}
2016-02-19 17:07:25 +01:00
func (dc *Context) SetHexColor(x string) {
r, g, b := parseHexColor(x)
dc.SetRGB255(r, g, b)
}
func (dc *Context) SetRGBA255(r, g, b, a int) {
dc.color = color.NRGBA{uint8(r), uint8(g), uint8(b), uint8(a)}
}
func (dc *Context) SetRGB255(r, g, b int) {
dc.SetRGBA255(r, g, b, 255)
}
func (dc *Context) SetRGBA(r, g, b, a float64) {
dc.color = color.NRGBA{
uint8(r * 255),
uint8(g * 255),
uint8(b * 255),
uint8(a * 255),
2016-02-18 22:02:57 +01:00
}
}
2016-02-19 17:07:25 +01:00
func (dc *Context) SetRGB(r, g, b float64) {
dc.SetRGBA(r, g, b, 1)
2016-02-19 03:30:17 +01:00
}
2016-02-19 17:07:25 +01:00
// Path Manipulation
2016-02-19 04:11:53 +01:00
func (dc *Context) MoveTo(x, y float64) {
2016-02-19 19:16:40 +01:00
x, y = dc.TransformPoint(x, y)
2016-02-19 04:11:53 +01:00
dc.start = fp(x, y)
dc.path.Start(dc.start)
2016-02-18 22:02:57 +01:00
}
2016-02-19 04:11:53 +01:00
func (dc *Context) LineTo(x, y float64) {
2016-02-19 19:16:40 +01:00
x, y = dc.TransformPoint(x, y)
2016-02-19 04:11:53 +01:00
if len(dc.path) == 0 {
dc.MoveTo(x, y)
2016-02-19 03:30:17 +01:00
} else {
2016-02-19 04:11:53 +01:00
dc.path.Add1(fp(x, y))
2016-02-19 03:30:17 +01:00
}
2016-02-18 22:02:57 +01:00
}
2016-02-19 04:11:53 +01:00
func (dc *Context) QuadraticTo(x1, y1, x2, y2 float64) {
2016-02-19 19:16:40 +01:00
x1, y1 = dc.TransformPoint(x1, y1)
x2, y2 = dc.TransformPoint(x2, y2)
2016-02-19 04:11:53 +01:00
if len(dc.path) == 0 {
dc.MoveTo(x1, y1)
2016-02-19 03:30:17 +01:00
} else {
2016-02-19 04:11:53 +01:00
dc.path.Add2(fp(x1, y1), fp(x2, y2))
2016-02-19 03:30:17 +01:00
}
2016-02-18 22:02:57 +01:00
}
2016-02-19 04:11:53 +01:00
func (dc *Context) ClosePath() {
if len(dc.path) > 0 {
dc.path.Add1(dc.start)
2016-02-19 03:30:17 +01:00
}
2016-02-18 22:02:57 +01:00
}
2016-02-19 17:07:25 +01:00
func (dc *Context) ClearPath() {
2016-02-19 04:11:53 +01:00
dc.path.Clear()
2016-02-18 22:02:57 +01:00
}
2016-02-19 17:07:25 +01:00
// Path Drawing
2016-02-19 04:11:53 +01:00
func (dc *Context) StrokePreserve() {
2016-02-19 17:07:25 +01:00
var capper raster.Capper
switch dc.lineCap {
case LineCapButt:
capper = raster.ButtCapper
case LineCapRound:
capper = raster.RoundCapper
case LineCapSquare:
capper = raster.SquareCapper
}
var joiner raster.Joiner
switch dc.lineJoin {
case LineJoinBevel:
joiner = raster.BevelJoiner
case LineJoinRound:
joiner = raster.RoundJoiner
}
2016-02-19 04:11:53 +01:00
painter := raster.NewRGBAPainter(dc.im)
painter.SetColor(dc.color)
r := raster.NewRasterizer(dc.width, dc.height)
2016-02-18 22:02:57 +01:00
r.UseNonZeroWinding = true
2016-02-19 17:07:25 +01:00
r.AddStroke(dc.path, fi(dc.lineWidth), capper, joiner)
2016-02-18 22:02:57 +01:00
r.Rasterize(painter)
}
2016-02-19 04:11:53 +01:00
func (dc *Context) Stroke() {
dc.StrokePreserve()
2016-02-19 17:07:25 +01:00
dc.ClearPath()
2016-02-18 22:02:57 +01:00
}
2016-02-19 04:11:53 +01:00
func (dc *Context) FillPreserve() {
2016-02-18 22:02:57 +01:00
// make sure the path is closed
2016-02-19 04:11:53 +01:00
path := make(raster.Path, len(dc.path))
copy(path, dc.path)
path.Add1(dc.start)
painter := raster.NewRGBAPainter(dc.im)
painter.SetColor(dc.color)
r := raster.NewRasterizer(dc.width, dc.height)
r.UseNonZeroWinding = dc.fillRule == FillRuleWinding
2016-02-18 22:02:57 +01:00
r.AddPath(path)
r.Rasterize(painter)
}
2016-02-19 04:11:53 +01:00
func (dc *Context) Fill() {
dc.FillPreserve()
2016-02-19 17:07:25 +01:00
dc.ClearPath()
2016-02-19 04:11:53 +01:00
}
2016-02-19 04:53:47 +01:00
// Convenient Drawing Functions
2016-02-19 17:07:25 +01:00
func (dc *Context) Clear() {
draw.Draw(dc.im, dc.im.Bounds(), image.NewUniform(dc.color), image.ZP, draw.Src)
}
2016-02-19 04:11:53 +01:00
func (dc *Context) DrawLine(x1, y1, x2, y2 float64) {
dc.MoveTo(x1, y1)
dc.LineTo(x2, y2)
}
2016-02-19 17:07:25 +01:00
func (dc *Context) DrawRectangle(x, y, w, h float64) {
dc.MoveTo(x, y)
dc.LineTo(x+w, y)
dc.LineTo(x+w, y+h)
dc.LineTo(x, y+h)
dc.LineTo(x, y)
}
func (dc *Context) DrawEllipticalArc(x, y, rx, ry, angle1, angle2 float64) {
2016-02-19 04:11:53 +01:00
const n = 16
for i := 0; i <= n; i++ {
p1 := float64(i+0) / n
p2 := float64(i+1) / n
a1 := angle1 + (angle2-angle1)*p1
a2 := angle1 + (angle2-angle1)*p2
x0 := x + rx*math.Cos(a1)
y0 := y + ry*math.Sin(a1)
x1 := x + rx*math.Cos(a1+(a2-a1)/2)
y1 := y + ry*math.Sin(a1+(a2-a1)/2)
x2 := x + rx*math.Cos(a2)
y2 := y + ry*math.Sin(a2)
cx := 2*x1 - x0/2 - x2/2
cy := 2*y1 - y0/2 - y2/2
if i == 0 {
dc.MoveTo(x0, y0)
}
dc.QuadraticTo(cx, cy, x2, y2)
}
}
func (dc *Context) DrawEllipse(x, y, rx, ry float64) {
2016-02-19 17:07:25 +01:00
dc.DrawEllipticalArc(x, y, rx, ry, 0, 2*math.Pi)
2016-02-19 04:11:53 +01:00
}
func (dc *Context) DrawArc(x, y, r, angle1, angle2 float64) {
2016-02-19 17:07:25 +01:00
dc.DrawEllipticalArc(x, y, r, r, angle1, angle2)
2016-02-19 04:11:53 +01:00
}
func (dc *Context) DrawCircle(x, y, r float64) {
2016-02-19 17:07:25 +01:00
dc.DrawEllipticalArc(x, y, r, r, 0, 2*math.Pi)
2016-02-18 22:02:57 +01:00
}
2016-02-19 04:53:47 +01:00
2016-02-19 19:49:03 +01:00
func (dc *Context) DrawImage(im image.Image, x, y int) {
p := image.Pt(x, y)
r := image.Rectangle{p, p.Add(im.Bounds().Size())}
draw.Draw(dc.im, r, im, image.ZP, draw.Src)
}
2016-02-19 04:53:47 +01:00
// Text Functions
func (dc *Context) SetFontFace(fontFace font.Face) {
dc.fontFace = fontFace
}
func (dc *Context) LoadFontFace(path string, size float64) {
dc.fontFace = loadFontFace(path, size)
}
func (dc *Context) DrawString(x, y float64, s string) {
2016-02-19 19:16:40 +01:00
x, y = dc.TransformPoint(x, y)
2016-02-19 04:53:47 +01:00
d := &font.Drawer{
Dst: dc.im,
Src: image.NewUniform(dc.color),
Face: dc.fontFace,
Dot: fp(x, y),
}
d.DrawString(s)
}
func (dc *Context) MeasureString(s string) float64 {
d := &font.Drawer{
Dst: nil,
Src: nil,
Face: dc.fontFace,
}
a := d.MeasureString(s)
return float64(a >> 6)
}
2016-02-19 19:16:40 +01:00
// 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)
}
2016-02-19 19:34:04 +01:00
// Stack
func (dc *Context) Push() {
path := make(raster.Path, len(dc.path))
copy(path, dc.path)
dc.stack = append(dc.stack, &Context{
color: dc.color,
path: path,
start: dc.start,
lineWidth: dc.lineWidth,
lineCap: dc.lineCap,
lineJoin: dc.lineJoin,
fillRule: dc.fillRule,
fontFace: dc.fontFace,
matrix: dc.matrix,
})
}
func (dc *Context) Pop() {
s := dc.stack
x, s := s[len(s)-1], s[:len(s)-1]
dc.color = x.color
dc.path = x.path
dc.start = x.start
dc.lineWidth = x.lineWidth
dc.lineCap = x.lineCap
dc.lineJoin = x.lineJoin
dc.fillRule = x.fillRule
dc.fontFace = x.fontFace
dc.matrix = x.matrix
}