Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
draw: tool to draw via lua
  • Loading branch information
frioux committed Feb 21, 2021
1 parent d0245f5 commit c4f6a90
Show file tree
Hide file tree
Showing 5 changed files with 314 additions and 0 deletions.
1 change: 1 addition & 0 deletions README.mdwn
Expand Up @@ -97,6 +97,7 @@ echo "---\nfoo: 1" | leatherman yaml2json
### misc

* `backlight`: Modifies screen brightness. ([internal/tool/misc/backlight/backlight.md](https://github.com/frioux/leatherman/blob/main/internal/tool/misc/backlight/backlight.md))
* `draw`: Draws images with lua. ([internal/tool/misc/img/draw.md](https://github.com/frioux/leatherman/blob/main/internal/tool/misc/img/draw.md))
* `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))
* `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))
* `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))
Expand Down
2 changes: 2 additions & 0 deletions dispatch.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions help_generated.go
Expand Up @@ -30,6 +30,7 @@ import "embed"
//go:embed internal/tool/mail/email/email2json.md
//go:embed internal/tool/mail/email/render-mail.md
//go:embed internal/tool/misc/backlight/backlight.md
//go:embed internal/tool/misc/img/draw.md
//go:embed internal/tool/misc/bamboo/export-bamboohr.md
//go:embed internal/tool/misc/bamboo/export-bamboohr-tree.md
//go:embed internal/tool/misc/prependhist/prepend-hist.md
Expand Down Expand Up @@ -101,6 +102,8 @@ var helpPaths = map[string]string{

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

"draw": "internal/tool/misc/img/draw.md",

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

"export-bamboohr-tree": "internal/tool/misc/bamboo/export-bamboohr-tree.md",
Expand Down
237 changes: 237 additions & 0 deletions internal/tool/misc/img/draw.go
@@ -0,0 +1,237 @@
package img

import (
"image"
"image/color"
"image/png"
"io"
"math"
"os"

lua "github.com/yuin/gopher-lua"
)

func Draw(args []string, _ io.Reader) error {
if len(args) == 1 {
args = append(args, "")
}

img := image.NewNRGBA(image.Rect(0, 0, 128, 128))
for x := 0; x < 128; x++ {
for y := 0; y < 128; y++ {
img.Set(x, y, color.Black)
}
}

if err := luaEval(img, args[1:]); err != nil {
return err
}

return png.Encode(os.Stdout, img)
}

func luaEval(img *image.NRGBA, code []string) error {
L := lua.NewState()
defer L.Close()

registerImageFunctions(L, img)

for _, c := range code {
if err := L.DoString(c); err != nil {
return err
}
}

return nil
}

func checkColor(L *lua.LState, w int) color.Color {
ud := L.CheckUserData(w)
if v, ok := ud.Value.(color.Color); ok {
return v
}
L.ArgError(w, "image.Color expected")
return nil
}

func registerImageFunctions(L *lua.LState, img *image.NRGBA) {
L.SetGlobal("set", L.NewFunction(func(L *lua.LState) int {
x := L.CheckNumber(1)
y := L.CheckNumber(2)
c := checkColor(L, 3)

img.Set(int(x), int(y), c)

return 0
}))

L.SetGlobal("rgb", L.NewFunction(func(L *lua.LState) int {
r := L.CheckNumber(1)
g := L.CheckNumber(2)
b := L.CheckNumber(3)

ud := L.NewUserData()
ud.Value = color.RGBA{uint8(r), uint8(g), uint8(b), 255}

L.Push(ud)
return 1
}))

{
black := L.NewUserData()
black.Value = color.Black
L.SetGlobal("black", black)

white := L.NewUserData()
white.Value = color.RGBA{0, 0, 0, 255}
L.SetGlobal("white", white)

red := L.NewUserData()
red.Value = color.RGBA{255, 0, 0, 255}
L.SetGlobal("red", red)

blue := L.NewUserData()
blue.Value = color.RGBA{0, 0, 255, 255}
L.SetGlobal("blue", blue)

yellow := L.NewUserData()
yellow.Value = color.RGBA{255, 255, 0, 255}
L.SetGlobal("yellow", yellow)

green := L.NewUserData()
green.Value = color.RGBA{0, 255, 0, 255}
L.SetGlobal("green", green)

orange := L.NewUserData()
orange.Value = color.RGBA{255, 165, 0, 255}
L.SetGlobal("orange", orange)

purple := L.NewUserData()
purple.Value = color.RGBA{128, 0, 128, 255}
L.SetGlobal("purple", purple)

cyan := L.NewUserData()
cyan.Value = color.RGBA{0, 255, 255, 255}
L.SetGlobal("cyan", cyan)

magenta := L.NewUserData()
magenta.Value = color.RGBA{255, 0, 255, 255}
L.SetGlobal("magenta", magenta)
}

L.SetGlobal("sin", L.NewFunction(func(L *lua.LState) int {
t := L.CheckNumber(1)

L.Push(lua.LNumber(math.Sin(float64(t))))
return 1
}))

L.SetGlobal("cos", L.NewFunction(func(L *lua.LState) int {
t := L.CheckNumber(1)

L.Push(lua.LNumber(math.Cos(float64(t))))
return 1
}))

L.SetGlobal("tan", L.NewFunction(func(L *lua.LState) int {
t := L.CheckNumber(1)

L.Push(lua.LNumber(math.Tan(float64(t))))
return 1
}))

L.SetGlobal("PI", lua.LNumber(math.Pi))

L.SetGlobal("rect", L.NewFunction(func(L *lua.LState) int {
x1 := int(L.CheckNumber(1))
y1 := int(L.CheckNumber(2))
x2 := int(L.CheckNumber(3))
y2 := int(L.CheckNumber(4))
border := checkColor(L, 5)
fill := checkColor(L, 6)

// draw borders
for x := x1; x <= x2; x++ {
img.Set(x, y1, border)
img.Set(x, y2, border)
}
for y := y1 + 1; y < y2; y++ {
img.Set(x1, y, border)
img.Set(x2, y, border)
}

// draw fill
for x := x1 + 1; x < x2; x++ {
for y := y1 + 1; y < y2; y++ {
img.Set(x, y, fill)
}
}

return 0
}))

line := func(x1, y1, x2, y2 float64, c color.Color) {
m := (y2 - y1) / (x2 - x1)
// y = m*x + b
// y - m*x = b
// b = y - m*x
b := y1 - m*x1
l := math.Sqrt(math.Pow(x2-x1, 2) + math.Pow(y2-y1, 2))

if m == math.Inf(1) || m == math.Inf(-1) {
start, end := y1, y2
if start > end {
start, end = end, start
}
for y := start; y <= end; y += l / 1000 {
img.Set(int(math.Round(x1)), int(math.Round(y)), c)
}
} else {
start, end := x1, x2
if start > end {
start, end = end, start
}

for x := start; x <= end; x += l / 1000 {
y := m*x + b
img.Set(int(math.Round(x)), int(math.Round(y)), c)
}
}
}

L.SetGlobal("circ", L.NewFunction(func(L *lua.LState) int {
x := int(L.CheckNumber(1))
y := int(L.CheckNumber(2))
r := float64(L.CheckNumber(3))
border := checkColor(L, 4)
fill := checkColor(L, 5)

// draw borders
for t := 0.0; t < 2*math.Pi; t += 0.001 /* uhh */ {
xt := r*math.Cos(t) + float64(x)
yt := r*math.Sin(t) + float64(y)

line(float64(x), float64(y), xt, yt, fill)
}
for t := 0.0; t < 2*math.Pi; t += 0.001 /* uhh */ {
xt := r*math.Cos(t) + float64(x)
yt := r*math.Sin(t) + float64(y)

img.Set(int(math.Round(xt)), int(math.Round(yt)), border)
}

return 0
}))

L.SetGlobal("line", L.NewFunction(func(L *lua.LState) int {
x1 := float64(L.CheckNumber(1))
y1 := float64(L.CheckNumber(2))
x2 := float64(L.CheckNumber(3))
y2 := float64(L.CheckNumber(4))
c := checkColor(L, 5)

line(x1, y1, x2, y2, c)

return 0
}))
}
71 changes: 71 additions & 0 deletions internal/tool/misc/img/draw.md
@@ -0,0 +1,71 @@
Draws images with lua.

```bash
$ draw 'rect(10, 10, 118, 118, red, yellow)' > x.png
```

Inspired by pico-8. This tool takes lua scripts as strings and writes a png to
standard out.

Consider this tool unstable, I'll likely make it read scripts from either
standard in, or files, or both, and make arguments no longer the default.

## Lua API

### `set(x, y, c)`

Takes an x, y coordinate and sets it to a color.

### `rgb(r, g, b)`

Takes a red, green, and blue value (from 0 to 255), returns a color value.

The following colors are defined as globals for you:

* black
* white
* red
* blue
* yellow
* green
* orange
* purple
* cyan
* magenta

### `sin(t)`

Returns sine of t, in terms of pi, not degrees.

### `cos(t)`

Returns cosine of t, in terms of pi, not degrees.

### `tan(t)`

Returns tangent of t, in terms of pi, not degrees.

### `PI`

Constant for pi.

### `rect(x1, y1, x2, y2, bordercolor, fillcolor)`

Draws a rectangle from (x1, y1) to (x2, y2) with a border of bordercolor and
filled with fillcolor.

### `circ(x, y, r, bordercolor, fillcolor)`

Draws a circle around (x, y) with radius r with a border of bordercolor and
filled with fillcolor.

### `line(x1, y1, x2, y2, color)`

Draws a line from (x1, y1) to (x2, y2) in color.

## BUGS

Something is wrong with `line` in certain situations (I'm assuming infinity or
NaN or something is causing the issue.) `line` is used when drawing `circle`s,
so you can see the bug by drawing a circle and there will be weird gaps in the
top and bottom of them.

0 comments on commit c4f6a90

Please sign in to comment.