Skip to content

Commit c4f6a90

Browse files
committed
draw: tool to draw via lua
1 parent d0245f5 commit c4f6a90

File tree

5 files changed

+314
-0
lines changed

5 files changed

+314
-0
lines changed

README.mdwn

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@ echo "---\nfoo: 1" | leatherman yaml2json
9797
### misc
9898

9999
* `backlight`: Modifies screen brightness. ([internal/tool/misc/backlight/backlight.md](https://github.com/frioux/leatherman/blob/main/internal/tool/misc/backlight/backlight.md))
100+
* `draw`: Draws images with lua. ([internal/tool/misc/img/draw.md](https://github.com/frioux/leatherman/blob/main/internal/tool/misc/img/draw.md))
100101
* `export-bamboohr`: Exports company directory as JSON. ([internal/tool/misc/bamboo/export-bamboohr.md](https://github.com/frioux/leatherman/blob/main/internal/tool/misc/bamboo/export-bamboohr.md))
101102
* `export-bamboohr-tree`: Exports company org chart as JSON. ([internal/tool/misc/bamboo/export-bamboohr-tree.md](https://github.com/frioux/leatherman/blob/main/internal/tool/misc/bamboo/export-bamboohr-tree.md))
102103
* `prepend-hist`: Combines a history file and stdin. ([internal/tool/misc/prependhist/prepend-hist.md](https://github.com/frioux/leatherman/blob/main/internal/tool/misc/prependhist/prepend-hist.md))

dispatch.go

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

help_generated.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import "embed"
3030
//go:embed internal/tool/mail/email/email2json.md
3131
//go:embed internal/tool/mail/email/render-mail.md
3232
//go:embed internal/tool/misc/backlight/backlight.md
33+
//go:embed internal/tool/misc/img/draw.md
3334
//go:embed internal/tool/misc/bamboo/export-bamboohr.md
3435
//go:embed internal/tool/misc/bamboo/export-bamboohr-tree.md
3536
//go:embed internal/tool/misc/prependhist/prepend-hist.md
@@ -101,6 +102,8 @@ var helpPaths = map[string]string{
101102

102103
"backlight": "internal/tool/misc/backlight/backlight.md",
103104

105+
"draw": "internal/tool/misc/img/draw.md",
106+
104107
"export-bamboohr": "internal/tool/misc/bamboo/export-bamboohr.md",
105108

106109
"export-bamboohr-tree": "internal/tool/misc/bamboo/export-bamboohr-tree.md",

internal/tool/misc/img/draw.go

Lines changed: 237 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,237 @@
1+
package img
2+
3+
import (
4+
"image"
5+
"image/color"
6+
"image/png"
7+
"io"
8+
"math"
9+
"os"
10+
11+
lua "github.com/yuin/gopher-lua"
12+
)
13+
14+
func Draw(args []string, _ io.Reader) error {
15+
if len(args) == 1 {
16+
args = append(args, "")
17+
}
18+
19+
img := image.NewNRGBA(image.Rect(0, 0, 128, 128))
20+
for x := 0; x < 128; x++ {
21+
for y := 0; y < 128; y++ {
22+
img.Set(x, y, color.Black)
23+
}
24+
}
25+
26+
if err := luaEval(img, args[1:]); err != nil {
27+
return err
28+
}
29+
30+
return png.Encode(os.Stdout, img)
31+
}
32+
33+
func luaEval(img *image.NRGBA, code []string) error {
34+
L := lua.NewState()
35+
defer L.Close()
36+
37+
registerImageFunctions(L, img)
38+
39+
for _, c := range code {
40+
if err := L.DoString(c); err != nil {
41+
return err
42+
}
43+
}
44+
45+
return nil
46+
}
47+
48+
func checkColor(L *lua.LState, w int) color.Color {
49+
ud := L.CheckUserData(w)
50+
if v, ok := ud.Value.(color.Color); ok {
51+
return v
52+
}
53+
L.ArgError(w, "image.Color expected")
54+
return nil
55+
}
56+
57+
func registerImageFunctions(L *lua.LState, img *image.NRGBA) {
58+
L.SetGlobal("set", L.NewFunction(func(L *lua.LState) int {
59+
x := L.CheckNumber(1)
60+
y := L.CheckNumber(2)
61+
c := checkColor(L, 3)
62+
63+
img.Set(int(x), int(y), c)
64+
65+
return 0
66+
}))
67+
68+
L.SetGlobal("rgb", L.NewFunction(func(L *lua.LState) int {
69+
r := L.CheckNumber(1)
70+
g := L.CheckNumber(2)
71+
b := L.CheckNumber(3)
72+
73+
ud := L.NewUserData()
74+
ud.Value = color.RGBA{uint8(r), uint8(g), uint8(b), 255}
75+
76+
L.Push(ud)
77+
return 1
78+
}))
79+
80+
{
81+
black := L.NewUserData()
82+
black.Value = color.Black
83+
L.SetGlobal("black", black)
84+
85+
white := L.NewUserData()
86+
white.Value = color.RGBA{0, 0, 0, 255}
87+
L.SetGlobal("white", white)
88+
89+
red := L.NewUserData()
90+
red.Value = color.RGBA{255, 0, 0, 255}
91+
L.SetGlobal("red", red)
92+
93+
blue := L.NewUserData()
94+
blue.Value = color.RGBA{0, 0, 255, 255}
95+
L.SetGlobal("blue", blue)
96+
97+
yellow := L.NewUserData()
98+
yellow.Value = color.RGBA{255, 255, 0, 255}
99+
L.SetGlobal("yellow", yellow)
100+
101+
green := L.NewUserData()
102+
green.Value = color.RGBA{0, 255, 0, 255}
103+
L.SetGlobal("green", green)
104+
105+
orange := L.NewUserData()
106+
orange.Value = color.RGBA{255, 165, 0, 255}
107+
L.SetGlobal("orange", orange)
108+
109+
purple := L.NewUserData()
110+
purple.Value = color.RGBA{128, 0, 128, 255}
111+
L.SetGlobal("purple", purple)
112+
113+
cyan := L.NewUserData()
114+
cyan.Value = color.RGBA{0, 255, 255, 255}
115+
L.SetGlobal("cyan", cyan)
116+
117+
magenta := L.NewUserData()
118+
magenta.Value = color.RGBA{255, 0, 255, 255}
119+
L.SetGlobal("magenta", magenta)
120+
}
121+
122+
L.SetGlobal("sin", L.NewFunction(func(L *lua.LState) int {
123+
t := L.CheckNumber(1)
124+
125+
L.Push(lua.LNumber(math.Sin(float64(t))))
126+
return 1
127+
}))
128+
129+
L.SetGlobal("cos", L.NewFunction(func(L *lua.LState) int {
130+
t := L.CheckNumber(1)
131+
132+
L.Push(lua.LNumber(math.Cos(float64(t))))
133+
return 1
134+
}))
135+
136+
L.SetGlobal("tan", L.NewFunction(func(L *lua.LState) int {
137+
t := L.CheckNumber(1)
138+
139+
L.Push(lua.LNumber(math.Tan(float64(t))))
140+
return 1
141+
}))
142+
143+
L.SetGlobal("PI", lua.LNumber(math.Pi))
144+
145+
L.SetGlobal("rect", L.NewFunction(func(L *lua.LState) int {
146+
x1 := int(L.CheckNumber(1))
147+
y1 := int(L.CheckNumber(2))
148+
x2 := int(L.CheckNumber(3))
149+
y2 := int(L.CheckNumber(4))
150+
border := checkColor(L, 5)
151+
fill := checkColor(L, 6)
152+
153+
// draw borders
154+
for x := x1; x <= x2; x++ {
155+
img.Set(x, y1, border)
156+
img.Set(x, y2, border)
157+
}
158+
for y := y1 + 1; y < y2; y++ {
159+
img.Set(x1, y, border)
160+
img.Set(x2, y, border)
161+
}
162+
163+
// draw fill
164+
for x := x1 + 1; x < x2; x++ {
165+
for y := y1 + 1; y < y2; y++ {
166+
img.Set(x, y, fill)
167+
}
168+
}
169+
170+
return 0
171+
}))
172+
173+
line := func(x1, y1, x2, y2 float64, c color.Color) {
174+
m := (y2 - y1) / (x2 - x1)
175+
// y = m*x + b
176+
// y - m*x = b
177+
// b = y - m*x
178+
b := y1 - m*x1
179+
l := math.Sqrt(math.Pow(x2-x1, 2) + math.Pow(y2-y1, 2))
180+
181+
if m == math.Inf(1) || m == math.Inf(-1) {
182+
start, end := y1, y2
183+
if start > end {
184+
start, end = end, start
185+
}
186+
for y := start; y <= end; y += l / 1000 {
187+
img.Set(int(math.Round(x1)), int(math.Round(y)), c)
188+
}
189+
} else {
190+
start, end := x1, x2
191+
if start > end {
192+
start, end = end, start
193+
}
194+
195+
for x := start; x <= end; x += l / 1000 {
196+
y := m*x + b
197+
img.Set(int(math.Round(x)), int(math.Round(y)), c)
198+
}
199+
}
200+
}
201+
202+
L.SetGlobal("circ", L.NewFunction(func(L *lua.LState) int {
203+
x := int(L.CheckNumber(1))
204+
y := int(L.CheckNumber(2))
205+
r := float64(L.CheckNumber(3))
206+
border := checkColor(L, 4)
207+
fill := checkColor(L, 5)
208+
209+
// draw borders
210+
for t := 0.0; t < 2*math.Pi; t += 0.001 /* uhh */ {
211+
xt := r*math.Cos(t) + float64(x)
212+
yt := r*math.Sin(t) + float64(y)
213+
214+
line(float64(x), float64(y), xt, yt, fill)
215+
}
216+
for t := 0.0; t < 2*math.Pi; t += 0.001 /* uhh */ {
217+
xt := r*math.Cos(t) + float64(x)
218+
yt := r*math.Sin(t) + float64(y)
219+
220+
img.Set(int(math.Round(xt)), int(math.Round(yt)), border)
221+
}
222+
223+
return 0
224+
}))
225+
226+
L.SetGlobal("line", L.NewFunction(func(L *lua.LState) int {
227+
x1 := float64(L.CheckNumber(1))
228+
y1 := float64(L.CheckNumber(2))
229+
x2 := float64(L.CheckNumber(3))
230+
y2 := float64(L.CheckNumber(4))
231+
c := checkColor(L, 5)
232+
233+
line(x1, y1, x2, y2, c)
234+
235+
return 0
236+
}))
237+
}

internal/tool/misc/img/draw.md

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
Draws images with lua.
2+
3+
```bash
4+
$ draw 'rect(10, 10, 118, 118, red, yellow)' > x.png
5+
```
6+
7+
Inspired by pico-8. This tool takes lua scripts as strings and writes a png to
8+
standard out.
9+
10+
Consider this tool unstable, I'll likely make it read scripts from either
11+
standard in, or files, or both, and make arguments no longer the default.
12+
13+
## Lua API
14+
15+
### `set(x, y, c)`
16+
17+
Takes an x, y coordinate and sets it to a color.
18+
19+
### `rgb(r, g, b)`
20+
21+
Takes a red, green, and blue value (from 0 to 255), returns a color value.
22+
23+
The following colors are defined as globals for you:
24+
25+
* black
26+
* white
27+
* red
28+
* blue
29+
* yellow
30+
* green
31+
* orange
32+
* purple
33+
* cyan
34+
* magenta
35+
36+
### `sin(t)`
37+
38+
Returns sine of t, in terms of pi, not degrees.
39+
40+
### `cos(t)`
41+
42+
Returns cosine of t, in terms of pi, not degrees.
43+
44+
### `tan(t)`
45+
46+
Returns tangent of t, in terms of pi, not degrees.
47+
48+
### `PI`
49+
50+
Constant for pi.
51+
52+
### `rect(x1, y1, x2, y2, bordercolor, fillcolor)`
53+
54+
Draws a rectangle from (x1, y1) to (x2, y2) with a border of bordercolor and
55+
filled with fillcolor.
56+
57+
### `circ(x, y, r, bordercolor, fillcolor)`
58+
59+
Draws a circle around (x, y) with radius r with a border of bordercolor and
60+
filled with fillcolor.
61+
62+
### `line(x1, y1, x2, y2, color)`
63+
64+
Draws a line from (x1, y1) to (x2, y2) in color.
65+
66+
## BUGS
67+
68+
Something is wrong with `line` in certain situations (I'm assuming infinity or
69+
NaN or something is causing the issue.) `line` is used when drawing `circle`s,
70+
so you can see the bug by drawing a circle and there will be weird gaps in the
71+
top and bottom of them.

0 commit comments

Comments
 (0)