How to create vector paths?
Closed this issue · 3 comments
I would like to create vector paths, more complex than just circles and rectangles I used to create with IMDraw, how to create a custom shape with a contour composed of arcs and lines?
To be more precise, I'm looking for something similar to Qt's qpainterpath, on which we can add arcs and lines.
I did it! \o/
I used svgo to create a svg from basic instructions, and oksvg to rasterize it in an image.
I'm sharing a simple working example, since I'm new to Go please let me know if any improvements can be added. ;)
Also, do you think that this code deserve a place in the Pixel wiki? I suppose that I'm not the only one that wanted to do this kind of stuff.
package main
import (
"fmt"
"io"
"image"
"github.com/ajstarks/svgo"
"golang.org/x/image/colornames"
"github.com/faiface/pixel"
"github.com/faiface/pixel/pixelgl"
. "github.com/srwiley/rasterx"
. "github.com/srwiley/oksvg"
)
func drawSvg(writer io.Writer) {
fmt.Println("drawSvg: creating svg image in writer...")
width := 400
height := 400
canvas := svg.New(writer)
canvas.Start(width, height)
canvas.Circle(width/2, height/2, 200)
canvas.End()
fmt.Println("drawSvg: svg image created in writer.")
}
func loadSvg(svgStream io.Reader) (pixel.Picture, error) {
fmt.Println("loadSvg: loading svg image from reader...")
svg, err := ReadIconStream(svgStream, WarnErrorMode)
if err != nil {
return nil, err
}
w, h := int(svg.ViewBox.W), int(svg.ViewBox.H)
fmt.Printf("loadSvg: image size = %vx%v\n", w, h)
img := image.NewRGBA(image.Rect(0, 0, w, h))
scannerGV := NewScannerGV(w, h, img, img.Bounds())
raster := NewDasher(w, h, scannerGV)
svg.Draw(raster, 1.0)
image := pixel.PictureDataFromImage(img)
fmt.Println("loadSvg: image loaded.")
return image, nil
}
func startApp() {
config := pixelgl.WindowConfig {
Title: "Shapes drawing example",
Bounds: pixel.R(0, 0, 600, 600),
}
win, err := pixelgl.NewWindow(config)
if err != nil {
panic(err)
}
fmt.Println("startApp: creating pipe...")
var reader, writer = io.Pipe()
fmt.Println("startApp: pipe created.")
go func() {
drawSvg(writer)
writer.Close()
}()
pic, err := loadSvg(reader)
if err != nil {
panic(err)
}
sprite := pixel.NewSprite(pic, pic.Bounds())
win.Clear(colornames.Greenyellow)
sprite.Draw(win, pixel.IM.Moved(win.Bounds().Center()))
for !win.Closed() {
win.Update()
}
}
func main() {
pixelgl.Run(startApp)
}
Even better: instead of drawing in svg then reading this svg, I draw directly in a graphic context with the draw2d package.
Less code and I didn't benchmarked it but it's also probably faster.
package main
import (
"image"
"math"
"golang.org/x/image/colornames"
"github.com/faiface/pixel"
"github.com/faiface/pixel/pixelgl"
"github.com/llgcode/draw2d/draw2dimg"
)
const (
window_width, window_height int = 400, 400
)
func drawSvg() image.Image {
radius := 150
cx, cy := float64(window_width) / 2, float64(window_height) / 2
img := image.NewRGBA(image.Rect(0, 0, window_width, window_height))
gc := draw2dimg.NewGraphicContext(img)
gc.BeginPath()
gc.SetFillColor(image.Black)
gc.MoveTo(cx, cy)
gc.ArcTo(cx, cy, float64(radius), float64(radius), 0, math.Pi*2)
gc.Fill()
return img
}
func startApp() {
config := pixelgl.WindowConfig {
Title: "Shapes drawing example",
Bounds: pixel.R(0, 0, float64(window_width), float64(window_height)),
}
win, err := pixelgl.NewWindow(config)
if err != nil {
panic(err)
}
image := pixel.PictureDataFromImage(drawSvg())
sprite := pixel.NewSprite(image, image.Bounds())
win.Clear(colornames.Greenyellow)
sprite.Draw(win, pixel.IM.Moved(win.Bounds().Center()))
for !win.Closed() {
win.Update()
}
}
func main() {
pixelgl.Run(startApp)
}
Gonna go ahead and close this as you figured it out :) Cool solution!