adam-gui/vendor/github.com/srwiley/rasterx/gradient.go

311 lines
9.0 KiB
Go
Raw Permalink Normal View History

2024-04-29 19:13:50 +02:00
// 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)
})
}