gradient.go 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310
  1. // Gradient implementation fo rasterx package
  2. // Copyright 2018 All rights reserved.
  3. // Created: 5/12/2018 by S.R.Wiley
  4. package rasterx
  5. import (
  6. "image/color"
  7. "math"
  8. "sort"
  9. )
  10. // SVG bounds paremater constants
  11. const (
  12. ObjectBoundingBox GradientUnits = iota
  13. UserSpaceOnUse
  14. )
  15. // SVG spread parameter constants
  16. const (
  17. PadSpread SpreadMethod = iota
  18. ReflectSpread
  19. RepeatSpread
  20. )
  21. const epsilonF = 1e-5
  22. type (
  23. // SpreadMethod is the type for spread parameters
  24. SpreadMethod byte
  25. // GradientUnits is the type for gradient units
  26. GradientUnits byte
  27. // GradStop represents a stop in the SVG 2.0 gradient specification
  28. GradStop struct {
  29. StopColor color.Color
  30. Offset float64
  31. Opacity float64
  32. }
  33. // Gradient holds a description of an SVG 2.0 gradient
  34. Gradient struct {
  35. Points [5]float64
  36. Stops []GradStop
  37. Bounds struct{ X, Y, W, H float64 }
  38. Matrix Matrix2D
  39. Spread SpreadMethod
  40. Units GradientUnits
  41. IsRadial bool
  42. }
  43. )
  44. // ApplyOpacity sets the color's alpha channel to the given value
  45. func ApplyOpacity(c color.Color, opacity float64) color.NRGBA {
  46. r, g, b, _ := c.RGBA()
  47. return color.NRGBA{uint8(r), uint8(g), uint8(b), uint8(opacity * 0xFF)}
  48. }
  49. // tColor takes the paramaterized value along the gradient's stops and
  50. // returns a color depending on the spreadMethod value of the gradient and
  51. // the gradient's slice of stop values.
  52. func (g *Gradient) tColor(t, opacity float64) color.Color {
  53. d := len(g.Stops)
  54. // These cases can be taken care of early on
  55. if t >= 1.0 && g.Spread == PadSpread {
  56. s := g.Stops[d-1]
  57. return ApplyOpacity(s.StopColor, s.Opacity*opacity)
  58. }
  59. if t <= 0.0 && g.Spread == PadSpread {
  60. return ApplyOpacity(g.Stops[0].StopColor, g.Stops[0].Opacity*opacity)
  61. }
  62. var modRange = 1.0
  63. if g.Spread == ReflectSpread {
  64. modRange = 2.0
  65. }
  66. mod := math.Mod(t, modRange)
  67. if mod < 0 {
  68. mod += modRange
  69. }
  70. place := 0 // Advance to place where mod is greater than the indicated stop
  71. for place != len(g.Stops) && mod > g.Stops[place].Offset {
  72. place++
  73. }
  74. switch g.Spread {
  75. case RepeatSpread:
  76. var s1, s2 GradStop
  77. switch place {
  78. case 0, d:
  79. s1, s2 = g.Stops[d-1], g.Stops[0]
  80. default:
  81. s1, s2 = g.Stops[place-1], g.Stops[place]
  82. }
  83. return g.blendStops(mod, opacity, s1, s2, false)
  84. case ReflectSpread:
  85. switch place {
  86. case 0:
  87. return ApplyOpacity(g.Stops[0].StopColor, g.Stops[0].Opacity*opacity)
  88. case d:
  89. // Advance to place where mod-1 is greater than the stop indicated by place in reverse of the stop slice.
  90. // Since this is the reflect spead mode, the mod interval is two, allowing the stop list to be
  91. // iterated in reverse before repeating the sequence.
  92. for place != d*2 && mod-1 > (1-g.Stops[d*2-place-1].Offset) {
  93. place++
  94. }
  95. switch place {
  96. case d:
  97. s := g.Stops[d-1]
  98. return ApplyOpacity(s.StopColor, s.Opacity*opacity)
  99. case d * 2:
  100. return ApplyOpacity(g.Stops[0].StopColor, g.Stops[0].Opacity*opacity)
  101. default:
  102. return g.blendStops(mod-1, opacity,
  103. g.Stops[d*2-place], g.Stops[d*2-place-1], true)
  104. }
  105. default:
  106. return g.blendStops(mod, opacity,
  107. g.Stops[place-1], g.Stops[place], false)
  108. }
  109. default: // PadSpread
  110. switch place {
  111. case 0:
  112. return ApplyOpacity(g.Stops[0].StopColor, g.Stops[0].Opacity*opacity)
  113. case len(g.Stops):
  114. s := g.Stops[len(g.Stops)-1]
  115. return ApplyOpacity(s.StopColor, s.Opacity*opacity)
  116. default:
  117. return g.blendStops(mod, opacity, g.Stops[place-1], g.Stops[place], false)
  118. }
  119. }
  120. }
  121. func (g *Gradient) blendStops(t, opacity float64, s1, s2 GradStop, flip bool) color.Color {
  122. s1off := s1.Offset
  123. if s1.Offset > s2.Offset && !flip { // happens in repeat spread mode
  124. s1off--
  125. if t > 1 {
  126. t--
  127. }
  128. }
  129. if s2.Offset == s1off {
  130. return ApplyOpacity(s2.StopColor, s2.Opacity)
  131. }
  132. if flip {
  133. t = 1 - t
  134. }
  135. tp := (t - s1off) / (s2.Offset - s1off)
  136. r1, g1, b1, _ := s1.StopColor.RGBA()
  137. r2, g2, b2, _ := s2.StopColor.RGBA()
  138. return ApplyOpacity(color.RGBA{
  139. uint8((float64(r1)*(1-tp) + float64(r2)*tp) / 256),
  140. uint8((float64(g1)*(1-tp) + float64(g2)*tp) / 256),
  141. uint8((float64(b1)*(1-tp) + float64(b2)*tp) / 256),
  142. 0xFF}, (s1.Opacity*(1-tp)+s2.Opacity*tp)*opacity)
  143. }
  144. //GetColorFunction returns the color function
  145. func (g *Gradient) GetColorFunction(opacity float64) interface{} {
  146. return g.GetColorFunctionUS(opacity, Identity)
  147. }
  148. //GetColorFunctionUS returns the color function using the User Space objMatrix
  149. func (g *Gradient) GetColorFunctionUS(opacity float64, objMatrix Matrix2D) interface{} {
  150. switch len(g.Stops) {
  151. case 0:
  152. return ApplyOpacity(color.RGBA{0, 0, 0, 255}, opacity) // default error color for gradient w/o stops.
  153. case 1:
  154. return ApplyOpacity(g.Stops[0].StopColor, opacity) // Illegal, I think, should really should not happen.
  155. }
  156. // sort by offset in ascending order
  157. sort.Slice(g.Stops, func(i, j int) bool {
  158. return g.Stops[i].Offset < g.Stops[j].Offset
  159. })
  160. w, h := float64(g.Bounds.W), float64(g.Bounds.H)
  161. oriX, oriY := float64(g.Bounds.X), float64(g.Bounds.Y)
  162. gradT := Identity.Translate(oriX, oriY).Scale(w, h).
  163. Mult(g.Matrix).Scale(1/w, 1/h).Translate(-oriX, -oriY).Invert()
  164. if g.IsRadial {
  165. cx, cy, fx, fy, rx, ry := g.Points[0], g.Points[1], g.Points[2], g.Points[3], g.Points[4], g.Points[4]
  166. if g.Units == ObjectBoundingBox {
  167. cx = g.Bounds.X + g.Bounds.W*cx
  168. cy = g.Bounds.Y + g.Bounds.H*cy
  169. fx = g.Bounds.X + g.Bounds.W*fx
  170. fy = g.Bounds.Y + g.Bounds.H*fy
  171. rx *= g.Bounds.W
  172. ry *= g.Bounds.H
  173. } else {
  174. cx, cy = g.Matrix.Transform(cx, cy)
  175. fx, fy = g.Matrix.Transform(fx, fy)
  176. rx, ry = g.Matrix.TransformVector(rx, ry)
  177. cx, cy = objMatrix.Transform(cx, cy)
  178. fx, fy = objMatrix.Transform(fx, fy)
  179. rx, ry = objMatrix.TransformVector(rx, ry)
  180. }
  181. if cx == fx && cy == fy {
  182. // When the focus and center are the same things are much simpler;
  183. // t is just distance from center
  184. // scaled by the bounds aspect ratio times r
  185. if g.Units == ObjectBoundingBox {
  186. return ColorFunc(func(xi, yi int) color.Color {
  187. x, y := gradT.Transform(float64(xi)+0.5, float64(yi)+0.5)
  188. dx := float64(x) - cx
  189. dy := float64(y) - cy
  190. return g.tColor(math.Sqrt(dx*dx/(rx*rx)+(dy*dy)/(ry*ry)), opacity)
  191. })
  192. }
  193. return ColorFunc(func(xi, yi int) color.Color {
  194. x := float64(xi) + 0.5
  195. y := float64(yi) + 0.5
  196. dx := x - cx
  197. dy := y - cy
  198. return g.tColor(math.Sqrt(dx*dx/(rx*rx)+(dy*dy)/(ry*ry)), opacity)
  199. })
  200. }
  201. fx /= rx
  202. fy /= ry
  203. cx /= rx
  204. cy /= ry
  205. dfx := fx - cx
  206. dfy := fy - cy
  207. if dfx*dfx+dfy*dfy > 1 { // Focus outside of circle; use intersection
  208. // point of line from center to focus and circle as per SVG specs.
  209. nfx, nfy, intersects := RayCircleIntersectionF(fx, fy, cx, cy, cx, cy, 1.0-epsilonF)
  210. fx, fy = nfx, nfy
  211. if intersects == false {
  212. return color.RGBA{255, 255, 0, 255} // should not happen
  213. }
  214. }
  215. if g.Units == ObjectBoundingBox {
  216. return ColorFunc(func(xi, yi int) color.Color {
  217. x, y := gradT.Transform(float64(xi)+0.5, float64(yi)+0.5)
  218. ex := x / rx
  219. ey := y / ry
  220. t1x, t1y, intersects := RayCircleIntersectionF(ex, ey, fx, fy, cx, cy, 1.0)
  221. if intersects == false { //In this case, use the last stop color
  222. s := g.Stops[len(g.Stops)-1]
  223. return ApplyOpacity(s.StopColor, s.Opacity*opacity)
  224. }
  225. tdx, tdy := t1x-fx, t1y-fy
  226. dx, dy := ex-fx, ey-fy
  227. if tdx*tdx+tdy*tdy < epsilonF {
  228. s := g.Stops[len(g.Stops)-1]
  229. return ApplyOpacity(s.StopColor, s.Opacity*opacity)
  230. }
  231. return g.tColor(math.Sqrt(dx*dx+dy*dy)/math.Sqrt(tdx*tdx+tdy*tdy), opacity)
  232. })
  233. }
  234. return ColorFunc(func(xi, yi int) color.Color {
  235. x := float64(xi) + 0.5
  236. y := float64(yi) + 0.5
  237. ex := x / rx
  238. ey := y / ry
  239. t1x, t1y, intersects := RayCircleIntersectionF(ex, ey, fx, fy, cx, cy, 1.0)
  240. if intersects == false { //In this case, use the last stop color
  241. s := g.Stops[len(g.Stops)-1]
  242. return ApplyOpacity(s.StopColor, s.Opacity*opacity)
  243. }
  244. tdx, tdy := t1x-fx, t1y-fy
  245. dx, dy := ex-fx, ey-fy
  246. if tdx*tdx+tdy*tdy < epsilonF {
  247. s := g.Stops[len(g.Stops)-1]
  248. return ApplyOpacity(s.StopColor, s.Opacity*opacity)
  249. }
  250. return g.tColor(math.Sqrt(dx*dx+dy*dy)/math.Sqrt(tdx*tdx+tdy*tdy), opacity)
  251. })
  252. }
  253. p1x, p1y, p2x, p2y := g.Points[0], g.Points[1], g.Points[2], g.Points[3]
  254. if g.Units == ObjectBoundingBox {
  255. p1x = g.Bounds.X + g.Bounds.W*p1x
  256. p1y = g.Bounds.Y + g.Bounds.H*p1y
  257. p2x = g.Bounds.X + g.Bounds.W*p2x
  258. p2y = g.Bounds.Y + g.Bounds.H*p2y
  259. dx := p2x - p1x
  260. dy := p2y - p1y
  261. d := (dx*dx + dy*dy) // self inner prod
  262. return ColorFunc(func(xi, yi int) color.Color {
  263. x, y := gradT.Transform(float64(xi)+0.5, float64(yi)+0.5)
  264. dfx := x - p1x
  265. dfy := y - p1y
  266. return g.tColor((dx*dfx+dy*dfy)/d, opacity)
  267. })
  268. }
  269. p1x, p1y = g.Matrix.Transform(p1x, p1y)
  270. p2x, p2y = g.Matrix.Transform(p2x, p2y)
  271. p1x, p1y = objMatrix.Transform(p1x, p1y)
  272. p2x, p2y = objMatrix.Transform(p2x, p2y)
  273. dx := p2x - p1x
  274. dy := p2y - p1y
  275. d := (dx*dx + dy*dy)
  276. // if d == 0.0 {
  277. // fmt.Println("zero delta")
  278. // }
  279. return ColorFunc(func(xi, yi int) color.Color {
  280. x := float64(xi) + 0.5
  281. y := float64(yi) + 0.5
  282. dfx := x - p1x
  283. dfy := y - p1y
  284. return g.tColor((dx*dfx+dy*dfy)/d, opacity)
  285. })
  286. }