diff --git a/context.go b/context.go index c3b44f2..e828af8 100644 --- a/context.go +++ b/context.go @@ -4,14 +4,15 @@ package gg import ( "image" "image/color" - "image/draw" "image/png" "io" "math" "github.com/golang/freetype/raster" + "golang.org/x/image/draw" "golang.org/x/image/font" "golang.org/x/image/font/basicfont" + "golang.org/x/image/math/f64" ) type LineCap int @@ -564,7 +565,6 @@ func (dc *Context) DrawRegularPolygon(n int, x, y, r, rotation float64) { } // DrawImage draws the specified image at the specified point. -// Currently, rotation and scaling transforms are not supported. func (dc *Context) DrawImage(im image.Image, x, y int) { dc.DrawImageAnchored(im, x, y, 0, 0) } @@ -576,12 +576,17 @@ func (dc *Context) DrawImageAnchored(im image.Image, x, y int, ax, ay float64) { s := im.Bounds().Size() x -= int(ax * float64(s.X)) y -= int(ay * float64(s.Y)) - p := image.Pt(x, y) - r := image.Rectangle{p, p.Add(s)} + transformer := draw.BiLinear + fx, fy := float64(x), float64(y) + m := dc.matrix.Translate(fx, fy) + s2d := f64.Aff3{m.XX, m.XY, m.X0, m.YX, m.YY, m.Y0} if dc.mask == nil { - draw.Draw(dc.im, r, im, image.ZP, draw.Over) + transformer.Transform(dc.im, s2d, im, im.Bounds(), draw.Over, nil) } else { - draw.DrawMask(dc.im, r, im, image.ZP, dc.mask, p, draw.Over) + transformer.Transform(dc.im, s2d, im, im.Bounds(), draw.Over, &draw.Options{ + DstMask: dc.mask, + DstMaskP: image.ZP, + }) } } @@ -608,11 +613,34 @@ func (dc *Context) drawString(im *image.RGBA, s string, x, y float64) { Face: dc.fontFace, Dot: fixp(x, y), } - d.DrawString(s) + // based on Drawer.DrawString() in golang.org/x/image/font/font.go + prevC := rune(-1) + for _, c := range s { + if prevC >= 0 { + d.Dot.X += d.Face.Kern(prevC, c) + } + dr, mask, maskp, advance, ok := d.Face.Glyph(d.Dot, c) + if !ok { + // TODO: is falling back on the U+FFFD glyph the responsibility of + // the Drawer or the Face? + // TODO: set prevC = '\ufffd'? + continue + } + sr := dr.Sub(dr.Min) + transformer := draw.BiLinear + fx, fy := float64(dr.Min.X), float64(dr.Min.Y) + m := dc.matrix.Translate(fx, fy) + s2d := f64.Aff3{m.XX, m.XY, m.X0, m.YX, m.YY, m.Y0} + transformer.Transform(d.Dst, s2d, d.Src, sr, draw.Over, &draw.Options{ + SrcMask: mask, + SrcMaskP: maskp, + }) + d.Dot.X += advance + prevC = c + } } // DrawString draws the specified text at the specified point. -// Currently, rotation and scaling transforms are not supported. func (dc *Context) DrawString(s string, x, y float64) { dc.DrawStringAnchored(s, x, y, 0, 0) } @@ -622,7 +650,6 @@ func (dc *Context) DrawString(s string, x, y float64) { // text. Use ax=0.5, ay=0.5 to center the text at the specified point. func (dc *Context) DrawStringAnchored(s string, x, y, ax, ay float64) { w, h := dc.MeasureString(s) - x, y = dc.TransformPoint(x, y) x -= ax * w y += ay * h if dc.mask == nil { diff --git a/examples/rotated-image.go b/examples/rotated-image.go new file mode 100644 index 0000000..0ce2544 --- /dev/null +++ b/examples/rotated-image.go @@ -0,0 +1,34 @@ +package main + +import "github.com/fogleman/gg" + +func main() { + const W = 400 + const H = 500 + im, err := gg.LoadPNG("examples/gopher.png") + if err != nil { + panic(err) + } + iw, ih := im.Bounds().Dx(), im.Bounds().Dy() + dc := gg.NewContext(W, H) + // draw outline + dc.SetHexColor("#ff0000") + dc.SetLineWidth(1) + dc.DrawRectangle(0, 0, float64(W), float64(H)) + dc.Stroke() + // draw full image + dc.SetHexColor("#0000ff") + dc.SetLineWidth(2) + dc.DrawRectangle(100, 210, float64(iw), float64(ih)) + dc.Stroke() + dc.DrawImage(im, 100, 210) + // draw image with current matrix applied + dc.SetHexColor("#0000ff") + dc.SetLineWidth(2) + dc.Rotate(gg.Radians(10)) + dc.DrawRectangle(100, 0, float64(iw), float64(ih)/2+20.0) + dc.StrokePreserve() + dc.Clip() + dc.DrawImageAnchored(im, 100, 0, 0.0, 0.0) + dc.SavePNG("out.png") +} diff --git a/examples/rotated-text.go b/examples/rotated-text.go new file mode 100644 index 0000000..2a3171d --- /dev/null +++ b/examples/rotated-text.go @@ -0,0 +1,30 @@ +package main + +import ( + "github.com/fogleman/gg" + "github.com/golang/freetype/truetype" + "golang.org/x/image/font/gofont/goregular" +) + +func main() { + const S = 400 + dc := gg.NewContext(S, S) + dc.SetRGB(1, 1, 1) + dc.Clear() + dc.SetRGB(0, 0, 0) + font, err := truetype.Parse(goregular.TTF) + if err != nil { + panic("") + } + face := truetype.NewFace(font, &truetype.Options{ + Size: 40, + }) + dc.SetFontFace(face) + text := "Hello, world!" + w, h := dc.MeasureString(text) + dc.Rotate(gg.Radians(10)) + dc.DrawRectangle(100, 180, w, h) + dc.Stroke() + dc.DrawStringAnchored(text, 100, 180, 0.0, 0.0) + dc.SavePNG("out.png") +}