Noah Petherbridge
af67b20d9b
* Initial WebAssembly build target for Doodle in the wasm/ folder. * Add a new render.Engine implementation, lib/render/canvas that uses the HTML 5 Canvas API instead of SDL2 for the WebAssembly target. * Ported the basic DrawLine(), DrawBox() etc. functions from SDL2 to Canvas context2d API. * Fonts are handled with CSS embedded fonts named after the font filename and defined in wasm/index.html * `make wasm` builds the WASM program, and `make wasm-serve` runs a dev Go server that hosts the WASM file for development. The server also watches the dev tree for *.go files and rebuilds the WASM binary automatically on change. * This build "basically" runs the game. UI and fonts all work and mouse movements and clicks are detected. No wallpaper support yet or texture caching (which will crash the game as soon as you click and draw a pixel in your map!)
260 lines
5.4 KiB
Go
260 lines
5.4 KiB
Go
package render
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"image/color"
|
|
"regexp"
|
|
"strconv"
|
|
|
|
"github.com/vmihailenco/msgpack"
|
|
)
|
|
|
|
var (
|
|
// Regexps to parse hex color codes. Three formats are supported:
|
|
// * reHexColor3 uses only 3 hex characters, like #F90
|
|
// * reHexColor6 uses standard 6 characters, like #FF9900
|
|
// * reHexColor8 is the standard 6 plus alpha channel, like #FF9900FF
|
|
reHexColor3 = regexp.MustCompile(`^([A-Fa-f0-9])([A-Fa-f0-9])([A-Fa-f0-9])$`)
|
|
reHexColor6 = regexp.MustCompile(`^([A-Fa-f0-9]{2})([A-Fa-f0-9]{2})([A-Fa-f0-9]{2})$`)
|
|
reHexColor8 = regexp.MustCompile(`^([A-Fa-f0-9]{2})([A-Fa-f0-9]{2})([A-Fa-f0-9]{2})([A-Fa-f0-9]{2})$`)
|
|
)
|
|
|
|
// Color holds an RGBA color value.
|
|
type Color struct {
|
|
Red uint8
|
|
Green uint8
|
|
Blue uint8
|
|
Alpha uint8
|
|
}
|
|
|
|
// RGBA creates a new Color.
|
|
func RGBA(r, g, b, a uint8) Color {
|
|
return Color{
|
|
Red: r,
|
|
Green: g,
|
|
Blue: b,
|
|
Alpha: a,
|
|
}
|
|
}
|
|
|
|
// FromColor creates a render.Color from a Go color.Color
|
|
func FromColor(from color.Color) Color {
|
|
// downscale a 16-bit color value to 8-bit. input range 0x0000..0xffff
|
|
downscale := func(in uint32) uint8 {
|
|
var scale = float64(in) / 0xffff
|
|
return uint8(scale * 0xff)
|
|
}
|
|
r, g, b, a := from.RGBA()
|
|
return RGBA(
|
|
downscale(r),
|
|
downscale(g),
|
|
downscale(b),
|
|
downscale(a),
|
|
)
|
|
}
|
|
|
|
// MustHexColor parses a color from hex code or panics.
|
|
func MustHexColor(hex string) Color {
|
|
color, err := HexColor(hex)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
return color
|
|
}
|
|
|
|
// HexColor parses a color from hexadecimal code.
|
|
func HexColor(hex string) (Color, error) {
|
|
c := Black // default color
|
|
|
|
if len(hex) > 0 && hex[0] == '#' {
|
|
hex = hex[1:]
|
|
}
|
|
|
|
var m []string
|
|
if len(hex) == 3 {
|
|
m = reHexColor3.FindStringSubmatch(hex)
|
|
} else if len(hex) == 6 {
|
|
m = reHexColor6.FindStringSubmatch(hex)
|
|
} else if len(hex) == 8 {
|
|
m = reHexColor8.FindStringSubmatch(hex)
|
|
} else {
|
|
return c, errors.New("not a valid length for color code; only 3, 6 and 8 supported")
|
|
}
|
|
|
|
// Any luck?
|
|
if m == nil {
|
|
return c, errors.New("not a valid hex color code")
|
|
}
|
|
|
|
// Parse the color values. 16=base, 8=bit size
|
|
red, _ := strconv.ParseUint(m[1], 16, 8)
|
|
green, _ := strconv.ParseUint(m[2], 16, 8)
|
|
blue, _ := strconv.ParseUint(m[3], 16, 8)
|
|
|
|
// Alpha channel available?
|
|
var alpha uint64 = 255
|
|
if len(m) == 5 {
|
|
alpha, _ = strconv.ParseUint(m[4], 16, 8)
|
|
}
|
|
|
|
c.Red = uint8(red)
|
|
c.Green = uint8(green)
|
|
c.Blue = uint8(blue)
|
|
c.Alpha = uint8(alpha)
|
|
return c, nil
|
|
}
|
|
|
|
func (c Color) String() string {
|
|
return fmt.Sprintf(
|
|
"Color<#%02x%02x%02x+%02x>",
|
|
c.Red, c.Green, c.Blue, c.Alpha,
|
|
)
|
|
}
|
|
|
|
// ToHex converts a render.Color to standard #RRGGBB hexadecimal format.
|
|
func (c Color) ToHex() string {
|
|
return fmt.Sprintf(
|
|
"#%02x%02x%02x",
|
|
c.Red, c.Green, c.Blue,
|
|
)
|
|
}
|
|
|
|
// ToColor converts a render.Color into a Go standard color.Color
|
|
func (c Color) ToColor() color.RGBA {
|
|
return color.RGBA{
|
|
R: c.Red,
|
|
G: c.Green,
|
|
B: c.Blue,
|
|
A: c.Alpha,
|
|
}
|
|
}
|
|
|
|
// Transparent returns whether the alpha channel is zeroed out and the pixel
|
|
// won't appear as anything when rendered.
|
|
func (c Color) Transparent() bool {
|
|
return c.Alpha == 0x00
|
|
}
|
|
|
|
// MarshalJSON serializes the Color for JSON.
|
|
func (c Color) MarshalJSON() ([]byte, error) {
|
|
return []byte(fmt.Sprintf(
|
|
`"#%02x%02x%02x"`,
|
|
c.Red, c.Green, c.Blue,
|
|
)), nil
|
|
}
|
|
|
|
// UnmarshalJSON reloads the Color from JSON.
|
|
func (c *Color) UnmarshalJSON(b []byte) error {
|
|
var hex string
|
|
err := json.Unmarshal(b, &hex)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
parsed, err := HexColor(hex)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
c.Red = parsed.Red
|
|
c.Blue = parsed.Blue
|
|
c.Green = parsed.Green
|
|
c.Alpha = parsed.Alpha
|
|
return nil
|
|
}
|
|
|
|
func (c Color) EncodeMsgpack(enc *msgpack.Encoder) error {
|
|
return enc.EncodeString(fmt.Sprintf(
|
|
`"#%02x%02x%02x"`,
|
|
c.Red, c.Green, c.Blue,
|
|
))
|
|
}
|
|
|
|
func (c Color) DecodeMsgpack(dec *msgpack.Decoder) error {
|
|
hex, err := dec.DecodeString()
|
|
if err != nil {
|
|
return fmt.Errorf("Color.DecodeMsgpack: %s", err)
|
|
}
|
|
|
|
parsed, err := HexColor(hex)
|
|
if err != nil {
|
|
return fmt.Errorf("Color.DecodeMsgpack: HexColor: %s", err)
|
|
}
|
|
|
|
c.Red = parsed.Red
|
|
c.Blue = parsed.Blue
|
|
c.Green = parsed.Green
|
|
c.Alpha = parsed.Alpha
|
|
return nil
|
|
}
|
|
|
|
// // MarshalMsgpack serializes the Color for msgpack.
|
|
// func (c Color) MarshalMsgpack() ([]byte, error) {
|
|
// data := []uint8{
|
|
// c.Red, c.Green, c.Blue, c.Alpha,
|
|
// }
|
|
// return msgpack.Marshal(data)
|
|
// }
|
|
//
|
|
// // UnmarshalMsgpack decodes a Color from msgpack format.
|
|
// func (c *Color) UnmarshalMsgpack(b []byte) error {
|
|
// var data []uint8
|
|
// if err := msgpack.Unmarshal(data, b); err != nil {
|
|
// return err
|
|
// }
|
|
// c.Red = 255
|
|
// c.Green = data[1]
|
|
// c.Blue = data[2]
|
|
// c.Alpha = data[3]
|
|
// return nil
|
|
// }
|
|
|
|
// Add a relative color value to the color.
|
|
func (c Color) Add(r, g, b, a int) Color {
|
|
var (
|
|
R = int(c.Red) + r
|
|
G = int(c.Green) + g
|
|
B = int(c.Blue) + b
|
|
A = int(c.Alpha) + a
|
|
)
|
|
|
|
cap8 := func(v int) uint8 {
|
|
if v > 255 {
|
|
v = 255
|
|
} else if v < 0 {
|
|
v = 0
|
|
}
|
|
return uint8(v)
|
|
}
|
|
|
|
return Color{
|
|
Red: cap8(R),
|
|
Green: cap8(G),
|
|
Blue: cap8(B),
|
|
Alpha: cap8(A),
|
|
}
|
|
}
|
|
|
|
// Lighten a color value.
|
|
func (c Color) Lighten(v int) Color {
|
|
return c.Add(v, v, v, 0)
|
|
}
|
|
|
|
// Darken a color value.
|
|
func (c Color) Darken(v int) Color {
|
|
return c.Add(-v, -v, -v, 0)
|
|
}
|
|
|
|
// Transparentize adjusts the alpha value.
|
|
func (c Color) Transparentize(v int) Color {
|
|
return c.Add(0, 0, 0, v)
|
|
}
|
|
|
|
// SetAlpha sets the alpha value to a specific setting.
|
|
func (c Color) SetAlpha(v uint8) Color {
|
|
c.Alpha = v
|
|
return c
|
|
}
|