Skip to content Search Apps Documentation Source Content File Folder Download Copy Actions State String Boolean Number Struct Map Slice Pointer Function Closure Reference Nil Package Type Interface Unknown

Community realm. Read the code first. Community realm, deployed by its author. Read the code before you interact or send coins.

canvas.gno

5.04 Kb · 208 lines
  1// Package canvas is a fixed-size, 16-color pixel grid.
  2//
  3// It holds no chain state and knows nothing about payments: the realm
  4// gno.land/r/g1sw5xklxjjuv0yvuxy5f5s3l3mnj0nqq626a9wr/million/v0 wraps it with pricing and access control.
  5//
  6// Storage layout: one []byte per row, allocated on first paint. A row that
  7// was never painted is nil and costs nothing. Each byte holds a palette
  8// index; Blank (0) means "never painted".
  9package canvas
 10
 11import (
 12	"errors"
 13	"strconv"
 14	"strings"
 15)
 16
 17const (
 18	// Blank is the color of a pixel nobody painted. It cannot be painted.
 19	Blank byte = 0
 20	// MaxColor is the highest valid palette index.
 21	MaxColor byte = 15
 22	// MaxSide bounds width and height so x*y fits comfortably in an int.
 23	MaxSide = 10000
 24)
 25
 26// Palette maps a color index to its CSS hex value. Index 0 is Blank and is
 27// rendered as white by clients.
 28var Palette = [MaxColor + 1]string{
 29	"#ffffff", // 0 blank
 30	"#ffffff", // 1 white
 31	"#e4e4e4", // 2 light gray
 32	"#888888", // 3 gray
 33	"#222222", // 4 black
 34	"#ffa7d1", // 5 pink
 35	"#e50000", // 6 red
 36	"#e59500", // 7 orange
 37	"#a06a42", // 8 brown
 38	"#e5d900", // 9 yellow
 39	"#94e044", // 10 lime
 40	"#02be01", // 11 green
 41	"#00d3dd", // 12 cyan
 42	"#0083c7", // 13 blue
 43	"#cf6ee4", // 14 magenta
 44	"#820080", // 15 purple
 45}
 46
 47var (
 48	ErrOutOfRange   = errors.New("canvas: pixel out of range")
 49	ErrInvalidColor = errors.New("canvas: color must be between 1 and 15")
 50	ErrBadPixel     = errors.New("canvas: pixel must be x,y,color")
 51)
 52
 53// Pixel is one paint request.
 54type Pixel struct {
 55	X, Y  int
 56	Color byte
 57}
 58
 59// Canvas is a width x height grid of palette indexes.
 60type Canvas struct {
 61	width, height int
 62	rows          [][]byte
 63	painted       int
 64}
 65
 66// New returns an empty canvas. It panics on a non-positive or oversized side.
 67func New(width, height int) *Canvas {
 68	if width <= 0 || height <= 0 || width > MaxSide || height > MaxSide {
 69		panic("canvas: width and height must be between 1 and " + strconv.Itoa(MaxSide))
 70	}
 71	return &Canvas{
 72		width:  width,
 73		height: height,
 74		rows:   make([][]byte, height),
 75	}
 76}
 77
 78func (c *Canvas) Width() int  { return c.width }
 79func (c *Canvas) Height() int { return c.height }
 80
 81// Painted is the number of pixels that are not Blank.
 82func (c *Canvas) Painted() int { return c.painted }
 83
 84// Check reports whether p can be painted on this canvas.
 85func (c *Canvas) Check(p Pixel) error {
 86	if p.X < 0 || p.Y < 0 || p.X >= c.width || p.Y >= c.height {
 87		return ErrOutOfRange
 88	}
 89	if p.Color == Blank || p.Color > MaxColor {
 90		return ErrInvalidColor
 91	}
 92	return nil
 93}
 94
 95// Get returns the color at (x, y). Out-of-range coordinates panic.
 96func (c *Canvas) Get(x, y int) byte {
 97	if x < 0 || y < 0 || x >= c.width || y >= c.height {
 98		panic(ErrOutOfRange)
 99	}
100	row := c.rows[y]
101	if row == nil {
102		return Blank
103	}
104	return row[x]
105}
106
107// Set paints p. It panics when Check(p) fails.
108func (c *Canvas) Set(p Pixel) {
109	if err := c.Check(p); err != nil {
110		panic(err)
111	}
112	row := c.rows[p.Y]
113	if row == nil {
114		row = make([]byte, c.width)
115		c.rows[p.Y] = row
116	}
117	if row[p.X] == Blank {
118		c.painted++
119	}
120	row[p.X] = p.Color
121}
122
123// Rows encodes rows [from, to) as one hex digit per pixel, row after row,
124// with no separator. Clients slice it with the known width.
125func (c *Canvas) Rows(from, to int) string {
126	if from < 0 || to > c.height || from > to {
127		panic(ErrOutOfRange)
128	}
129	var sb strings.Builder
130	sb.Grow((to - from) * c.width)
131	for y := from; y < to; y++ {
132		row := c.rows[y]
133		if row == nil {
134			for x := 0; x < c.width; x++ {
135				sb.WriteByte('0')
136			}
137			continue
138		}
139		for _, color := range row {
140			sb.WriteByte(hexDigit(color))
141		}
142	}
143	return sb.String()
144}
145
146// ParsePixels decodes "x,y,color;x,y,color;...". A trailing ';' is allowed.
147// It validates the syntax and the color range but not the coordinates,
148// which depend on the canvas: call Check for that.
149func ParsePixels(s string) ([]Pixel, error) {
150	s = strings.TrimSuffix(s, ";")
151	if s == "" {
152		return nil, ErrBadPixel
153	}
154	parts := strings.Split(s, ";")
155	pixels := make([]Pixel, 0, len(parts))
156	for _, part := range parts {
157		fields := strings.Split(part, ",")
158		if len(fields) != 3 {
159			return nil, ErrBadPixel
160		}
161		x, err := strconv.Atoi(fields[0])
162		if err != nil {
163			return nil, ErrBadPixel
164		}
165		y, err := strconv.Atoi(fields[1])
166		if err != nil {
167			return nil, ErrBadPixel
168		}
169		color, err := ParseColor(fields[2])
170		if err != nil {
171			return nil, err
172		}
173		pixels = append(pixels, Pixel{X: x, Y: y, Color: color})
174	}
175	return pixels, nil
176}
177
178// ParseColor converts a decimal color index, rejecting Blank.
179func ParseColor(s string) (byte, error) {
180	n, err := strconv.Atoi(s)
181	if err != nil || n <= int(Blank) || n > int(MaxColor) {
182		return 0, ErrInvalidColor
183	}
184	return byte(n), nil
185}
186
187// Encode is the inverse of ParsePixels.
188func Encode(pixels []Pixel) string {
189	var sb strings.Builder
190	for i, p := range pixels {
191		if i > 0 {
192			sb.WriteByte(';')
193		}
194		sb.WriteString(strconv.Itoa(p.X))
195		sb.WriteByte(',')
196		sb.WriteString(strconv.Itoa(p.Y))
197		sb.WriteByte(',')
198		sb.WriteString(strconv.Itoa(int(p.Color)))
199	}
200	return sb.String()
201}
202
203func hexDigit(v byte) byte {
204	if v < 10 {
205		return '0' + v
206	}
207	return 'a' + v - 10
208}