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.

260 lines
5.4 KiB

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