| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310 |
- // Gradient implementation fo rasterx package
- // Copyright 2018 All rights reserved.
- // Created: 5/12/2018 by S.R.Wiley
- package rasterx
- import (
- "image/color"
- "math"
- "sort"
- )
- // SVG bounds paremater constants
- const (
- ObjectBoundingBox GradientUnits = iota
- UserSpaceOnUse
- )
- // SVG spread parameter constants
- const (
- PadSpread SpreadMethod = iota
- ReflectSpread
- RepeatSpread
- )
- const epsilonF = 1e-5
- type (
- // SpreadMethod is the type for spread parameters
- SpreadMethod byte
- // GradientUnits is the type for gradient units
- GradientUnits byte
- // GradStop represents a stop in the SVG 2.0 gradient specification
- GradStop struct {
- StopColor color.Color
- Offset float64
- Opacity float64
- }
- // Gradient holds a description of an SVG 2.0 gradient
- Gradient struct {
- Points [5]float64
- Stops []GradStop
- Bounds struct{ X, Y, W, H float64 }
- Matrix Matrix2D
- Spread SpreadMethod
- Units GradientUnits
- IsRadial bool
- }
- )
- // ApplyOpacity sets the color's alpha channel to the given value
- func ApplyOpacity(c color.Color, opacity float64) color.NRGBA {
- r, g, b, _ := c.RGBA()
- return color.NRGBA{uint8(r), uint8(g), uint8(b), uint8(opacity * 0xFF)}
- }
- // tColor takes the paramaterized value along the gradient's stops and
- // returns a color depending on the spreadMethod value of the gradient and
- // the gradient's slice of stop values.
- func (g *Gradient) tColor(t, opacity float64) color.Color {
- d := len(g.Stops)
- // These cases can be taken care of early on
- if t >= 1.0 && g.Spread == PadSpread {
- s := g.Stops[d-1]
- return ApplyOpacity(s.StopColor, s.Opacity*opacity)
- }
- if t <= 0.0 && g.Spread == PadSpread {
- return ApplyOpacity(g.Stops[0].StopColor, g.Stops[0].Opacity*opacity)
- }
- var modRange = 1.0
- if g.Spread == ReflectSpread {
- modRange = 2.0
- }
- mod := math.Mod(t, modRange)
- if mod < 0 {
- mod += modRange
- }
- place := 0 // Advance to place where mod is greater than the indicated stop
- for place != len(g.Stops) && mod > g.Stops[place].Offset {
- place++
- }
- switch g.Spread {
- case RepeatSpread:
- var s1, s2 GradStop
- switch place {
- case 0, d:
- s1, s2 = g.Stops[d-1], g.Stops[0]
- default:
- s1, s2 = g.Stops[place-1], g.Stops[place]
- }
- return g.blendStops(mod, opacity, s1, s2, false)
- case ReflectSpread:
- switch place {
- case 0:
- return ApplyOpacity(g.Stops[0].StopColor, g.Stops[0].Opacity*opacity)
- case d:
- // Advance to place where mod-1 is greater than the stop indicated by place in reverse of the stop slice.
- // Since this is the reflect spead mode, the mod interval is two, allowing the stop list to be
- // iterated in reverse before repeating the sequence.
- for place != d*2 && mod-1 > (1-g.Stops[d*2-place-1].Offset) {
- place++
- }
- switch place {
- case d:
- s := g.Stops[d-1]
- return ApplyOpacity(s.StopColor, s.Opacity*opacity)
- case d * 2:
- return ApplyOpacity(g.Stops[0].StopColor, g.Stops[0].Opacity*opacity)
- default:
- return g.blendStops(mod-1, opacity,
- g.Stops[d*2-place], g.Stops[d*2-place-1], true)
- }
- default:
- return g.blendStops(mod, opacity,
- g.Stops[place-1], g.Stops[place], false)
- }
- default: // PadSpread
- switch place {
- case 0:
- return ApplyOpacity(g.Stops[0].StopColor, g.Stops[0].Opacity*opacity)
- case len(g.Stops):
- s := g.Stops[len(g.Stops)-1]
- return ApplyOpacity(s.StopColor, s.Opacity*opacity)
- default:
- return g.blendStops(mod, opacity, g.Stops[place-1], g.Stops[place], false)
- }
- }
- }
- func (g *Gradient) blendStops(t, opacity float64, s1, s2 GradStop, flip bool) color.Color {
- s1off := s1.Offset
- if s1.Offset > s2.Offset && !flip { // happens in repeat spread mode
- s1off--
- if t > 1 {
- t--
- }
- }
- if s2.Offset == s1off {
- return ApplyOpacity(s2.StopColor, s2.Opacity)
- }
- if flip {
- t = 1 - t
- }
- tp := (t - s1off) / (s2.Offset - s1off)
- r1, g1, b1, _ := s1.StopColor.RGBA()
- r2, g2, b2, _ := s2.StopColor.RGBA()
- return ApplyOpacity(color.RGBA{
- uint8((float64(r1)*(1-tp) + float64(r2)*tp) / 256),
- uint8((float64(g1)*(1-tp) + float64(g2)*tp) / 256),
- uint8((float64(b1)*(1-tp) + float64(b2)*tp) / 256),
- 0xFF}, (s1.Opacity*(1-tp)+s2.Opacity*tp)*opacity)
- }
- //GetColorFunction returns the color function
- func (g *Gradient) GetColorFunction(opacity float64) interface{} {
- return g.GetColorFunctionUS(opacity, Identity)
- }
- //GetColorFunctionUS returns the color function using the User Space objMatrix
- func (g *Gradient) GetColorFunctionUS(opacity float64, objMatrix Matrix2D) interface{} {
- switch len(g.Stops) {
- case 0:
- return ApplyOpacity(color.RGBA{0, 0, 0, 255}, opacity) // default error color for gradient w/o stops.
- case 1:
- return ApplyOpacity(g.Stops[0].StopColor, opacity) // Illegal, I think, should really should not happen.
- }
- // sort by offset in ascending order
- sort.Slice(g.Stops, func(i, j int) bool {
- return g.Stops[i].Offset < g.Stops[j].Offset
- })
- w, h := float64(g.Bounds.W), float64(g.Bounds.H)
- oriX, oriY := float64(g.Bounds.X), float64(g.Bounds.Y)
- gradT := Identity.Translate(oriX, oriY).Scale(w, h).
- Mult(g.Matrix).Scale(1/w, 1/h).Translate(-oriX, -oriY).Invert()
- if g.IsRadial {
- cx, cy, fx, fy, rx, ry := g.Points[0], g.Points[1], g.Points[2], g.Points[3], g.Points[4], g.Points[4]
- if g.Units == ObjectBoundingBox {
- cx = g.Bounds.X + g.Bounds.W*cx
- cy = g.Bounds.Y + g.Bounds.H*cy
- fx = g.Bounds.X + g.Bounds.W*fx
- fy = g.Bounds.Y + g.Bounds.H*fy
- rx *= g.Bounds.W
- ry *= g.Bounds.H
- } else {
- cx, cy = g.Matrix.Transform(cx, cy)
- fx, fy = g.Matrix.Transform(fx, fy)
- rx, ry = g.Matrix.TransformVector(rx, ry)
- cx, cy = objMatrix.Transform(cx, cy)
- fx, fy = objMatrix.Transform(fx, fy)
- rx, ry = objMatrix.TransformVector(rx, ry)
- }
- if cx == fx && cy == fy {
- // When the focus and center are the same things are much simpler;
- // t is just distance from center
- // scaled by the bounds aspect ratio times r
- if g.Units == ObjectBoundingBox {
- return ColorFunc(func(xi, yi int) color.Color {
- x, y := gradT.Transform(float64(xi)+0.5, float64(yi)+0.5)
- dx := float64(x) - cx
- dy := float64(y) - cy
- return g.tColor(math.Sqrt(dx*dx/(rx*rx)+(dy*dy)/(ry*ry)), opacity)
- })
- }
- return ColorFunc(func(xi, yi int) color.Color {
- x := float64(xi) + 0.5
- y := float64(yi) + 0.5
- dx := x - cx
- dy := y - cy
- return g.tColor(math.Sqrt(dx*dx/(rx*rx)+(dy*dy)/(ry*ry)), opacity)
- })
- }
- fx /= rx
- fy /= ry
- cx /= rx
- cy /= ry
- dfx := fx - cx
- dfy := fy - cy
- if dfx*dfx+dfy*dfy > 1 { // Focus outside of circle; use intersection
- // point of line from center to focus and circle as per SVG specs.
- nfx, nfy, intersects := RayCircleIntersectionF(fx, fy, cx, cy, cx, cy, 1.0-epsilonF)
- fx, fy = nfx, nfy
- if intersects == false {
- return color.RGBA{255, 255, 0, 255} // should not happen
- }
- }
- if g.Units == ObjectBoundingBox {
- return ColorFunc(func(xi, yi int) color.Color {
- x, y := gradT.Transform(float64(xi)+0.5, float64(yi)+0.5)
- ex := x / rx
- ey := y / ry
- t1x, t1y, intersects := RayCircleIntersectionF(ex, ey, fx, fy, cx, cy, 1.0)
- if intersects == false { //In this case, use the last stop color
- s := g.Stops[len(g.Stops)-1]
- return ApplyOpacity(s.StopColor, s.Opacity*opacity)
- }
- tdx, tdy := t1x-fx, t1y-fy
- dx, dy := ex-fx, ey-fy
- if tdx*tdx+tdy*tdy < epsilonF {
- s := g.Stops[len(g.Stops)-1]
- return ApplyOpacity(s.StopColor, s.Opacity*opacity)
- }
- return g.tColor(math.Sqrt(dx*dx+dy*dy)/math.Sqrt(tdx*tdx+tdy*tdy), opacity)
- })
- }
- return ColorFunc(func(xi, yi int) color.Color {
- x := float64(xi) + 0.5
- y := float64(yi) + 0.5
- ex := x / rx
- ey := y / ry
- t1x, t1y, intersects := RayCircleIntersectionF(ex, ey, fx, fy, cx, cy, 1.0)
- if intersects == false { //In this case, use the last stop color
- s := g.Stops[len(g.Stops)-1]
- return ApplyOpacity(s.StopColor, s.Opacity*opacity)
- }
- tdx, tdy := t1x-fx, t1y-fy
- dx, dy := ex-fx, ey-fy
- if tdx*tdx+tdy*tdy < epsilonF {
- s := g.Stops[len(g.Stops)-1]
- return ApplyOpacity(s.StopColor, s.Opacity*opacity)
- }
- return g.tColor(math.Sqrt(dx*dx+dy*dy)/math.Sqrt(tdx*tdx+tdy*tdy), opacity)
- })
- }
- p1x, p1y, p2x, p2y := g.Points[0], g.Points[1], g.Points[2], g.Points[3]
- if g.Units == ObjectBoundingBox {
- p1x = g.Bounds.X + g.Bounds.W*p1x
- p1y = g.Bounds.Y + g.Bounds.H*p1y
- p2x = g.Bounds.X + g.Bounds.W*p2x
- p2y = g.Bounds.Y + g.Bounds.H*p2y
- dx := p2x - p1x
- dy := p2y - p1y
- d := (dx*dx + dy*dy) // self inner prod
- return ColorFunc(func(xi, yi int) color.Color {
- x, y := gradT.Transform(float64(xi)+0.5, float64(yi)+0.5)
- dfx := x - p1x
- dfy := y - p1y
- return g.tColor((dx*dfx+dy*dfy)/d, opacity)
- })
- }
- p1x, p1y = g.Matrix.Transform(p1x, p1y)
- p2x, p2y = g.Matrix.Transform(p2x, p2y)
- p1x, p1y = objMatrix.Transform(p1x, p1y)
- p2x, p2y = objMatrix.Transform(p2x, p2y)
- dx := p2x - p1x
- dy := p2y - p1y
- d := (dx*dx + dy*dy)
- // if d == 0.0 {
- // fmt.Println("zero delta")
- // }
- return ColorFunc(func(xi, yi int) color.Color {
- x := float64(xi) + 0.5
- y := float64(yi) + 0.5
- dfx := x - p1x
- dfy := y - p1y
- return g.tColor((dx*dfx+dy*dfy)/d, opacity)
- })
- }
|