From 1aa57065bb81ca9a953286a84b5cfcff4294c3bf Mon Sep 17 00:00:00 2001 From: wsw Date: Mon, 21 Nov 2016 11:01:12 +0800 Subject: [PATCH] linear gradient --- examples/gradient-linear.go | 39 ++++++ examples/{fill-pattern.go => pattern-fill.go} | 17 ++- gradient.go | 131 ++++++++++++++++++ 3 files changed, 186 insertions(+), 1 deletion(-) create mode 100644 examples/gradient-linear.go rename examples/{fill-pattern.go => pattern-fill.go} (51%) create mode 100644 gradient.go diff --git a/examples/gradient-linear.go b/examples/gradient-linear.go new file mode 100644 index 0000000..5f1ceec --- /dev/null +++ b/examples/gradient-linear.go @@ -0,0 +1,39 @@ +package main + +import ( + "image/color" + + "github.com/fogleman/gg" +) + +func main() { + dc := gg.NewContext(500, 400) + + grad := gg.NewLinearGradient(20, 320, 400, 20) + grad.AddColorStop(0, color.RGBA{0, 255, 0, 255}) + grad.AddColorStop(1, color.RGBA{0, 0, 255, 255}) + grad.AddColorStop(0.5, color.RGBA{255, 0, 0, 255}) + + dc.SetColor(color.White) + dc.DrawRectangle(20, 20, 400-20, 300) + dc.Stroke() + + dc.SetStrokeStyle(grad) + dc.SetLineWidth(4) + dc.MoveTo(10, 10) + dc.LineTo(410, 10) + dc.LineTo(410, 100) + dc.LineTo(10, 100) + dc.ClosePath() + dc.Stroke() + + dc.SetFillStyle(grad) + dc.MoveTo(10, 120) + dc.LineTo(410, 120) + dc.LineTo(410, 300) + dc.LineTo(10, 300) + dc.ClosePath() + dc.Fill() + + dc.SavePNG("out.png") +} diff --git a/examples/fill-pattern.go b/examples/pattern-fill.go similarity index 51% rename from examples/fill-pattern.go rename to examples/pattern-fill.go index 4500350..d46998b 100644 --- a/examples/fill-pattern.go +++ b/examples/pattern-fill.go @@ -1,12 +1,27 @@ package main -import "github.com/fogleman/gg" +import ( + "fmt" + "math" + + "github.com/fogleman/gg" +) func main() { im, err := gg.LoadPNG("examples/lenna.png") if err != nil { panic(err) } + var x0, y0, x1, y1 float64 = 5, 5, 15, 15 + a := math.Atan2(y1-y0, x1-x0) + d := a * 180 / math.Pi + fmt.Println(a, d) + a = (2*math.Pi - a) + d = a * 180 / math.Pi + fmt.Println(a, d) + m := gg.Translate(-x0, -y0).Rotate(a) + tx, ty := m.TransformPoint(5, 5) + fmt.Println(tx, ty) pattern := gg.NewSurfacePattern(im, gg.RepeatBoth) dc := gg.NewContext(600, 600) dc.MoveTo(20, 20) diff --git a/gradient.go b/gradient.go new file mode 100644 index 0000000..5c155ba --- /dev/null +++ b/gradient.go @@ -0,0 +1,131 @@ +package gg + +import ( + "image/color" + "math" + "sort" +) + +type stop struct { + pos float64 + color color.Color +} + +type stops []stop + +// Len satisfies the Sort interface. +func (s stops) Len() int { + return len(s) +} + +// Less satisfies the Sort interface. +func (s stops) Less(i, j int) bool { + return s[i].pos < s[j].pos +} + +// Swap satisfies the Sort interface. +func (s stops) Swap(i, j int) { + s[i], s[j] = s[j], s[i] +} + +type Gradient interface { + Pattern + AddColorStop(offset float64, color color.Color) +} + +// Linear Gradient +type linearGradient struct { + x0, y0, x1, y1 float64 + dx, dy float64 + mag float64 + px0, py0 float64 + stops stops +} + +func (g *linearGradient) ColorAt(x, y int) color.Color { + if len(g.stops) == 0 { + return color.Transparent + } + + fx, fy := float64(x), float64(y) + x0, y0 := g.x0, g.y0 + dx, dy := g.dx, g.dy + + // Horizontal + if dy == 0 && dx != 0 { + return getColor((fx-x0)/dx, g.stops) + } + + // Vertical + if dx == 0 && dy != 0 { + return getColor((fy-y0)/dy, g.stops) + } + + px0, py0 := g.px0, g.py0 + mag := g.mag + + s0 := (px0-x0)*(fy-y0) - (py0-y0)*(fx-x0) + if s0 > 0 { + return g.stops[0].color + } + u := ((fx-x0)*(px0-x0) + (fy-y0)*(py0-y0)) / (mag * mag) + x2, y2 := x0+u*(px0-x0), y0+u*(py0-y0) + d := math.Hypot(fx-x2, fy-y2) / mag + return getColor(d, g.stops) +} + +func (g *linearGradient) AddColorStop(offset float64, color color.Color) { + g.stops = append(g.stops, stop{pos: offset, color: color}) + sort.Sort(g.stops) +} + +func NewLinearGradient(x0, y0, x1, y1 float64) Gradient { + dx, dy := x1-x0, y1-y0 + mag := math.Hypot(dx, dy) + px0, py0 := x0-dy, y0+dx + g := &linearGradient{ + x0: x0, y0: y0, + x1: x1, y1: y1, + dx: dx, dy: dy, + px0: px0, py0: py0, + mag: mag, + } + return g +} + +func getColor(pos float64, stops stops) color.Color { + if pos <= 0.0 || len(stops) == 1 { + return stops[0].color + } + + last := stops[len(stops)-1] + + if pos >= last.pos { + return last.color + } + + for i, stop := range stops[1:] { + if pos < stop.pos { + pos = (pos - stops[i].pos) / (stop.pos - stops[i].pos) + return colorLerp(stops[i].color, stop.color, pos) + } + } + + return last.color +} + +func colorLerp(c0, c1 color.Color, t float64) color.Color { + r0, g0, b0, a0 := c0.RGBA() + r1, g1, b1, a1 := c1.RGBA() + + return color.NRGBA{ + lerp(r0, r1, t), + lerp(g0, g1, t), + lerp(b0, b1, t), + lerp(a0, a1, t), + } +} + +func lerp(a, b uint32, t float64) uint8 { + return uint8(int32(float64(a)*(1.0-t)+float64(b)*t) >> 8) +}