A rendering engine library for Go supporting both SDL2 and WebAssembly (HTML Canvas) targets.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

241 lines
4.9 KiB

  1. package render
  2. import (
  3. "encoding/json"
  4. "errors"
  5. "fmt"
  6. "image/color"
  7. "regexp"
  8. "strconv"
  9. )
  10. var (
  11. // Regexps to parse hex color codes. Three formats are supported:
  12. // * reHexColor3 uses only 3 hex characters, like #F90
  13. // * reHexColor6 uses standard 6 characters, like #FF9900
  14. // * reHexColor8 is the standard 6 plus alpha channel, like #FF9900FF
  15. reHexColor3 = regexp.MustCompile(`^([A-Fa-f0-9])([A-Fa-f0-9])([A-Fa-f0-9])$`)
  16. reHexColor6 = regexp.MustCompile(`^([A-Fa-f0-9]{2})([A-Fa-f0-9]{2})([A-Fa-f0-9]{2})$`)
  17. reHexColor8 = regexp.MustCompile(`^([A-Fa-f0-9]{2})([A-Fa-f0-9]{2})([A-Fa-f0-9]{2})([A-Fa-f0-9]{2})$`)
  18. )
  19. // Color holds an RGBA color value.
  20. type Color struct {
  21. Red uint8
  22. Green uint8
  23. Blue uint8
  24. Alpha uint8
  25. }
  26. // RGBA creates a new Color.
  27. func RGBA(r, g, b, a uint8) Color {
  28. return Color{
  29. Red: r,
  30. Green: g,
  31. Blue: b,
  32. Alpha: a,
  33. }
  34. }
  35. // FromColor creates a render.Color from a Go color.Color
  36. func FromColor(from color.Color) Color {
  37. // downscale a 16-bit color value to 8-bit. input range 0x0000..0xffff
  38. downscale := func(in uint32) uint8 {
  39. var scale = float64(in) / 0xffff
  40. return uint8(scale * 0xff)
  41. }
  42. r, g, b, a := from.RGBA()
  43. return RGBA(
  44. downscale(r),
  45. downscale(g),
  46. downscale(b),
  47. downscale(a),
  48. )
  49. }
  50. // ToRGBA converts to a standard Go color.Color
  51. func (c Color) ToRGBA() color.RGBA {
  52. return color.RGBA{
  53. R: c.Red,
  54. G: c.Green,
  55. B: c.Blue,
  56. A: c.Alpha,
  57. }
  58. }
  59. // MustHexColor parses a color from hex code or panics.
  60. func MustHexColor(hex string) Color {
  61. color, err := HexColor(hex)
  62. if err != nil {
  63. panic(err)
  64. }
  65. return color
  66. }
  67. // HexColor parses a color from hexadecimal code.
  68. func HexColor(hex string) (Color, error) {
  69. c := Black // default color
  70. if len(hex) > 0 && hex[0] == '#' {
  71. hex = hex[1:]
  72. }
  73. var m []string
  74. if len(hex) == 3 {
  75. m = reHexColor3.FindStringSubmatch(hex)
  76. // Double up the hex characters.
  77. m[1] += m[1]
  78. m[2] += m[2]
  79. m[3] += m[3]
  80. } else if len(hex) == 6 {
  81. m = reHexColor6.FindStringSubmatch(hex)
  82. } else if len(hex) == 8 {
  83. m = reHexColor8.FindStringSubmatch(hex)
  84. } else {
  85. return c, errors.New("not a valid length for color code; only 3, 6 and 8 supported")
  86. }
  87. // Any luck?
  88. if m == nil {
  89. return c, errors.New("not a valid hex color code")
  90. }
  91. // Parse the color values. 16=base, 8=bit size
  92. red, _ := strconv.ParseUint(m[1], 16, 8)
  93. green, _ := strconv.ParseUint(m[2], 16, 8)
  94. blue, _ := strconv.ParseUint(m[3], 16, 8)
  95. // Alpha channel available?
  96. var alpha uint64 = 255
  97. if len(m) == 5 {
  98. alpha, _ = strconv.ParseUint(m[4], 16, 8)
  99. }
  100. c.Red = uint8(red)
  101. c.Green = uint8(green)
  102. c.Blue = uint8(blue)
  103. c.Alpha = uint8(alpha)
  104. return c, nil
  105. }
  106. func (c Color) String() string {
  107. return fmt.Sprintf(
  108. "Color<#%02x%02x%02x+%02x>",
  109. c.Red, c.Green, c.Blue, c.Alpha,
  110. )
  111. }
  112. // ToHex converts a render.Color to standard #RRGGBB hexadecimal format.
  113. func (c Color) ToHex() string {
  114. return fmt.Sprintf(
  115. "#%02x%02x%02x",
  116. c.Red, c.Green, c.Blue,
  117. )
  118. }
  119. // ToColor converts a render.Color into a Go standard color.Color
  120. func (c Color) ToColor() color.RGBA {
  121. return color.RGBA{
  122. R: c.Red,
  123. G: c.Green,
  124. B: c.Blue,
  125. A: c.Alpha,
  126. }
  127. }
  128. // Transparent returns whether the alpha channel is zeroed out and the pixel
  129. // won't appear as anything when rendered.
  130. func (c Color) Transparent() bool {
  131. return c.Alpha == 0x00
  132. }
  133. // MarshalJSON serializes the Color for JSON.
  134. func (c Color) MarshalJSON() ([]byte, error) {
  135. return []byte(fmt.Sprintf(
  136. `"#%02x%02x%02x"`,
  137. c.Red, c.Green, c.Blue,
  138. )), nil
  139. }
  140. // UnmarshalJSON reloads the Color from JSON.
  141. func (c *Color) UnmarshalJSON(b []byte) error {
  142. var hex string
  143. err := json.Unmarshal(b, &hex)
  144. if err != nil {
  145. return err
  146. }
  147. parsed, err := HexColor(hex)
  148. if err != nil {
  149. return err
  150. }
  151. c.Red = parsed.Red
  152. c.Blue = parsed.Blue
  153. c.Green = parsed.Green
  154. c.Alpha = parsed.Alpha
  155. return nil
  156. }
  157. // IsZero returns if the color is all zeroes (invisible).
  158. func (c Color) IsZero() bool {
  159. return c.Red+c.Green+c.Blue+c.Alpha == 0
  160. }
  161. // Add a relative color value to the color.
  162. func (c Color) Add(r, g, b, a int) Color {
  163. var (
  164. R = int(c.Red) + r
  165. G = int(c.Green) + g
  166. B = int(c.Blue) + b
  167. A = int(c.Alpha) + a
  168. )
  169. cap8 := func(v int) uint8 {
  170. if v > 255 {
  171. v = 255
  172. } else if v < 0 {
  173. v = 0
  174. }
  175. return uint8(v)
  176. }
  177. return Color{
  178. Red: cap8(R),
  179. Green: cap8(G),
  180. Blue: cap8(B),
  181. Alpha: cap8(A),
  182. }
  183. }
  184. // AddColor adds another Color to your Color.
  185. func (c Color) AddColor(other Color) Color {
  186. return c.Add(
  187. int(other.Red),
  188. int(other.Green),
  189. int(other.Blue),
  190. int(other.Alpha),
  191. )
  192. }
  193. // Lighten a color value.
  194. func (c Color) Lighten(v int) Color {
  195. return c.Add(v, v, v, 0)
  196. }
  197. // Darken a color value.
  198. func (c Color) Darken(v int) Color {
  199. return c.Add(-v, -v, -v, 0)
  200. }
  201. // Transparentize adjusts the alpha value.
  202. func (c Color) Transparentize(v int) Color {
  203. return c.Add(0, 0, 0, v)
  204. }
  205. // SetAlpha sets the alpha value to a specific setting.
  206. func (c Color) SetAlpha(v uint8) Color {
  207. c.Alpha = v
  208. return c
  209. }