diff --git a/README.md b/README.md index bc4fe02..8fdcabd 100644 --- a/README.md +++ b/README.md @@ -128,7 +128,7 @@ SetFillRule(fillRule FillRule) ## Gradients & Patterns -`gg` supports linear and radial gradients and surface patterns. You can also implement your own patterns. +`gg` supports linear, radial and conic gradients and surface patterns. You can also implement your own patterns. ```go SetFillStyle(pattern Pattern) @@ -136,6 +136,7 @@ SetStrokeStyle(pattern Pattern) NewSolidPattern(color color.Color) NewLinearGradient(x0, y0, x1, y1 float64) NewRadialGradient(x0, y0, r0, x1, y1, r1 float64) +NewConicGradient(cx, cy, deg float64) NewSurfacePattern(im image.Image, op RepeatOp) ``` diff --git a/examples/gradient-conic.go b/examples/gradient-conic.go new file mode 100644 index 0000000..2156f9b --- /dev/null +++ b/examples/gradient-conic.go @@ -0,0 +1,38 @@ +// +build ignore + +package main + +import ( + "image/color" + + "github.com/fogleman/gg" +) + +func main() { + dc := gg.NewContext(400, 400) + + grad1 := gg.NewConicGradient(200, 200, 0) + grad1.AddColorStop(0.0, color.Black) + grad1.AddColorStop(0.5, color.RGBA{255, 215, 0, 255}) + grad1.AddColorStop(1.0, color.RGBA{255, 0, 0, 255}) + + grad2 := gg.NewConicGradient(200, 200, 90) + grad2.AddColorStop(0.00, color.RGBA{255, 0, 0, 255}) + grad2.AddColorStop(0.16, color.RGBA{255, 255, 0, 255}) + grad2.AddColorStop(0.33, color.RGBA{0, 255, 0, 255}) + grad2.AddColorStop(0.50, color.RGBA{0, 255, 255, 255}) + grad2.AddColorStop(0.66, color.RGBA{0, 0, 255, 255}) + grad2.AddColorStop(0.83, color.RGBA{255, 0, 255, 255}) + grad2.AddColorStop(1.00, color.RGBA{255, 0, 0, 255}) + + dc.SetStrokeStyle(grad1) + dc.SetLineWidth(20) + dc.DrawCircle(200, 200, 180) + dc.Stroke() + + dc.SetFillStyle(grad2) + dc.DrawCircle(200, 200, 150) + dc.Fill() + + dc.SavePNG("gradient-conic.png") +} diff --git a/gradient.go b/gradient.go index a9cad59..9bbdbf3 100644 --- a/gradient.go +++ b/gradient.go @@ -164,6 +164,52 @@ func NewRadialGradient(x0, y0, r0, x1, y1, r1 float64) Gradient { return g } +// Conic Gradient +type conicGradient struct { + cx, cy float64 + rotation float64 + stops stops +} + +func (g *conicGradient) ColorAt(x, y int) color.Color { + if len(g.stops) == 0 { + return color.Transparent + } + a := math.Atan2(float64(y)-g.cy, float64(x)-g.cx) + t := norm(a, -math.Pi, math.Pi) - g.rotation + if t < 0 { + t += 1 + } + return getColor(t, g.stops) +} + +func (g *conicGradient) AddColorStop(offset float64, color color.Color) { + g.stops = append(g.stops, stop{pos: offset, color: color}) + sort.Sort(g.stops) +} + +func NewConicGradient(cx, cy, deg float64) Gradient { + g := &conicGradient{ + cx: cx, + cy: cy, + rotation: normalizeAngle(deg) / 360, + } + return g +} + +func normalizeAngle(t float64) float64 { + t = math.Mod(t, 360) + if t < 0 { + t += 360 + } + return t +} + +// Map value which is in range [a..b] to range [0..1] +func norm(value, a, b float64) float64 { + return (value - a) * (1.0 / (b - a)) +} + func getColor(pos float64, stops stops) color.Color { if pos <= 0.0 || len(stops) == 1 { return stops[0].color