gradient.go 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202
  1. package gg
  2. import (
  3. "image/color"
  4. "math"
  5. "sort"
  6. )
  7. type stop struct {
  8. pos float64
  9. color color.Color
  10. }
  11. type stops []stop
  12. // Len satisfies the Sort interface.
  13. func (s stops) Len() int {
  14. return len(s)
  15. }
  16. // Less satisfies the Sort interface.
  17. func (s stops) Less(i, j int) bool {
  18. return s[i].pos < s[j].pos
  19. }
  20. // Swap satisfies the Sort interface.
  21. func (s stops) Swap(i, j int) {
  22. s[i], s[j] = s[j], s[i]
  23. }
  24. type Gradient interface {
  25. Pattern
  26. AddColorStop(offset float64, color color.Color)
  27. }
  28. // Linear Gradient
  29. type linearGradient struct {
  30. x0, y0, x1, y1 float64
  31. stops stops
  32. }
  33. func (g *linearGradient) ColorAt(x, y int) color.Color {
  34. if len(g.stops) == 0 {
  35. return color.Transparent
  36. }
  37. fx, fy := float64(x), float64(y)
  38. x0, y0, x1, y1 := g.x0, g.y0, g.x1, g.y1
  39. dx, dy := x1-x0, y1-y0
  40. // Horizontal
  41. if dy == 0 && dx != 0 {
  42. return getColor((fx-x0)/dx, g.stops)
  43. }
  44. // Vertical
  45. if dx == 0 && dy != 0 {
  46. return getColor((fy-y0)/dy, g.stops)
  47. }
  48. // Dot product
  49. s0 := dx*(fx-x0) + dy*(fy-y0)
  50. if s0 < 0 {
  51. return g.stops[0].color
  52. }
  53. // Calculate distance to (x0,y0) alone (x0,y0)->(x1,y1)
  54. mag := math.Hypot(dx, dy)
  55. u := ((fx-x0)*-dy + (fy-y0)*dx) / (mag * mag)
  56. x2, y2 := x0+u*-dy, y0+u*dx
  57. d := math.Hypot(fx-x2, fy-y2) / mag
  58. return getColor(d, g.stops)
  59. }
  60. func (g *linearGradient) AddColorStop(offset float64, color color.Color) {
  61. g.stops = append(g.stops, stop{pos: offset, color: color})
  62. sort.Sort(g.stops)
  63. }
  64. func NewLinearGradient(x0, y0, x1, y1 float64) Gradient {
  65. g := &linearGradient{
  66. x0: x0, y0: y0,
  67. x1: x1, y1: y1,
  68. }
  69. return g
  70. }
  71. // Radial Gradient
  72. type circle struct {
  73. x, y, r float64
  74. }
  75. type radialGradient struct {
  76. c0, c1, cd circle
  77. a, inva float64
  78. mindr float64
  79. stops stops
  80. }
  81. func dot3(x0, y0, z0, x1, y1, z1 float64) float64 {
  82. return x0*x1 + y0*y1 + z0*z1
  83. }
  84. func (g *radialGradient) ColorAt(x, y int) color.Color {
  85. if len(g.stops) == 0 {
  86. return color.Transparent
  87. }
  88. // copy from pixman's pixman-radial-gradient.c
  89. dx, dy := float64(x)+0.5-g.c0.x, float64(y)+0.5-g.c0.y
  90. b := dot3(dx, dy, g.c0.r, g.cd.x, g.cd.y, g.cd.r)
  91. c := dot3(dx, dy, -g.c0.r, dx, dy, g.c0.r)
  92. if g.a == 0 {
  93. if b == 0 {
  94. return color.Transparent
  95. }
  96. t := 0.5 * c / b
  97. if t*g.cd.r >= g.mindr {
  98. return getColor(t, g.stops)
  99. }
  100. return color.Transparent
  101. }
  102. discr := dot3(b, g.a, 0, b, -c, 0)
  103. if discr >= 0 {
  104. sqrtdiscr := math.Sqrt(discr)
  105. t0 := (b + sqrtdiscr) * g.inva
  106. t1 := (b - sqrtdiscr) * g.inva
  107. if t0*g.cd.r >= g.mindr {
  108. return getColor(t0, g.stops)
  109. } else if t1*g.cd.r >= g.mindr {
  110. return getColor(t1, g.stops)
  111. }
  112. }
  113. return color.Transparent
  114. }
  115. func (g *radialGradient) AddColorStop(offset float64, color color.Color) {
  116. g.stops = append(g.stops, stop{pos: offset, color: color})
  117. sort.Sort(g.stops)
  118. }
  119. func NewRadialGradient(x0, y0, r0, x1, y1, r1 float64) Gradient {
  120. c0 := circle{x0, y0, r0}
  121. c1 := circle{x1, y1, r1}
  122. cd := circle{x1 - x0, y1 - y0, r1 - r0}
  123. a := dot3(cd.x, cd.y, -cd.r, cd.x, cd.y, cd.r)
  124. var inva float64
  125. if a != 0 {
  126. inva = 1.0 / a
  127. }
  128. mindr := -c0.r
  129. g := &radialGradient{
  130. c0: c0,
  131. c1: c1,
  132. cd: cd,
  133. a: a,
  134. inva: inva,
  135. mindr: mindr,
  136. }
  137. return g
  138. }
  139. func getColor(pos float64, stops stops) color.Color {
  140. if pos <= 0.0 || len(stops) == 1 {
  141. return stops[0].color
  142. }
  143. last := stops[len(stops)-1]
  144. if pos >= last.pos {
  145. return last.color
  146. }
  147. for i, stop := range stops[1:] {
  148. if pos < stop.pos {
  149. pos = (pos - stops[i].pos) / (stop.pos - stops[i].pos)
  150. return colorLerp(stops[i].color, stop.color, pos)
  151. }
  152. }
  153. return last.color
  154. }
  155. func colorLerp(c0, c1 color.Color, t float64) color.Color {
  156. r0, g0, b0, a0 := c0.RGBA()
  157. r1, g1, b1, a1 := c1.RGBA()
  158. return color.RGBA{
  159. lerp(r0, r1, t),
  160. lerp(g0, g1, t),
  161. lerp(b0, b1, t),
  162. lerp(a0, a1, t),
  163. }
  164. }
  165. func lerp(a, b uint32, t float64) uint8 {
  166. return uint8(int32(float64(a)*(1.0-t)+float64(b)*t) >> 8)
  167. }