Skip to content

Commit 59a0913

Browse files
committed
Add CLI
Adds a cli together with an blueprint encoder.
1 parent 106c7d1 commit 59a0913

7 files changed

Lines changed: 347 additions & 105 deletions

File tree

File renamed without changes.
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,8 @@ func Decompress(buf []byte) ([]byte, error) {
88
d, err := zstd.Decompress(nil, buf)
99
return d, err
1010
}
11+
12+
func Compress(buf []byte) ([]byte, error) {
13+
d, err := zstd.Compress(nil, buf)
14+
return d, err
15+
}

decode.go

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
package main
2+
3+
import (
4+
"bytes"
5+
"encoding/base64"
6+
"encoding/binary"
7+
"fmt"
8+
"image"
9+
"image/png"
10+
"io"
11+
"log"
12+
"os"
13+
"path/filepath"
14+
"strings"
15+
16+
"github.com/atotto/clipboard"
17+
"github.com/mkideal/cli"
18+
"github.com/nfnt/resize"
19+
)
20+
21+
type decodeCmdT struct {
22+
cli.Helper
23+
Scale uint `cli:"s,scale" usage:"scale of the output image" dft:"1"`
24+
Clipboard bool `cli:"c,clipboard" usage:"read directly from system's clipboard instead of stdin or file"`
25+
OutputName string `cli:"o,output" usage:"name of the output file" dft:"blueprint.png"`
26+
OutputStdout bool `cli:"stdout" usage:"write to stdout instead of to file"`
27+
}
28+
29+
var decodeCmd = &cli.Command{
30+
Name: "decode",
31+
Aliases: []string{"d"},
32+
Desc: "Takes in a blueprint and outputs a PNG image with the content of the blueprint.",
33+
Text: fmt.Sprintf(`example: %s decode [options] [file]`, filepath.Base(os.Args[0])),
34+
Argv: func() interface{} { return new(decodeCmdT) },
35+
36+
Fn: func(ctx *cli.Context) error {
37+
argv := ctx.Argv().(*decodeCmdT)
38+
args := ctx.Args()
39+
40+
var blueprint Blueprint
41+
var inputFile io.Reader = os.Stdin
42+
43+
if len(args) > 0 && args[0] != "-" {
44+
var err error
45+
inputFile, err = os.Open(args[0])
46+
if err != nil {
47+
log.Fatalf("Failed to read file (%s): %v\n", args[0], err)
48+
}
49+
}
50+
51+
if argv.Clipboard {
52+
s, err := clipboard.ReadAll()
53+
if err != nil {
54+
log.Fatalf("Failed to read clipboard: %v\n", err)
55+
}
56+
inputFile = strings.NewReader(s)
57+
}
58+
59+
if ctx.IsSet("--clipboard") && len(args) > 0 {
60+
argv.OutputName = strings.TrimSuffix(filepath.Base(args[0]), filepath.Ext(args[0])) + ".png"
61+
} else if argv.OutputName == "" {
62+
argv.OutputName = defaultOutputName
63+
}
64+
65+
// Reads base64 encoded string from stdin.
66+
buf, err := io.ReadAll(base64.NewDecoder(base64.StdEncoding, inputFile))
67+
if err != nil {
68+
log.Fatalf("Failed to read input: %v\n", err)
69+
}
70+
71+
// Read the footer.
72+
FooterSize := binary.Size(blueprint.Footer)
73+
binary.Read(bytes.NewReader(buf[len(buf)-FooterSize:]), binary.LittleEndian, &blueprint.Footer)
74+
75+
// Decompress zstd-compressed pixel data.
76+
d, err := Decompress(buf[:len(buf)-FooterSize])
77+
if err != nil {
78+
log.Fatalf("Failed zstd decompression: %v", err)
79+
}
80+
blueprint.Pixels = d
81+
82+
// Create new image with pixel data and write to file.
83+
img := image.NewRGBA(image.Rectangle{
84+
image.Point{0, 0},
85+
image.Point{int(blueprint.Width), int(blueprint.Height)},
86+
})
87+
img.Pix = blueprint.Pixels
88+
89+
img = resize.Resize(
90+
uint(blueprint.Width)*argv.Scale,
91+
uint(blueprint.Height)*argv.Scale,
92+
img,
93+
resize.NearestNeighbor,
94+
).(*image.RGBA)
95+
96+
f, err := os.Create(argv.OutputName)
97+
if err != nil {
98+
log.Fatalf("Failed to create image file: %v", err)
99+
}
100+
101+
if argv.OutputStdout {
102+
err = png.Encode(os.Stdout, img)
103+
if err != nil {
104+
log.Fatalf("Failed to encode image file: %v", err)
105+
}
106+
} else {
107+
err = png.Encode(f, img)
108+
if err != nil {
109+
log.Fatalf("Failed to encode image file: %v", err)
110+
}
111+
fmt.Println("Wrote to file", argv.OutputName)
112+
}
113+
114+
return nil
115+
},
116+
}

encode.go

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
package main
2+
3+
import (
4+
"encoding/base64"
5+
"encoding/binary"
6+
"fmt"
7+
"image"
8+
"image/draw"
9+
"image/png"
10+
"io"
11+
"log"
12+
"os"
13+
"path/filepath"
14+
"sync"
15+
16+
"github.com/atotto/clipboard"
17+
"github.com/mkideal/cli"
18+
)
19+
20+
type encodeCmdT struct {
21+
cli.Helper
22+
Clipboard bool `cli:"c,clipboard" usage:"write directly to system's clipboard instead of stdout"`
23+
OutputName string `cli:"o,output" usage:"write to this file instead of stdout"`
24+
Layer uint `cli:"l,layer" usage:"set blueprint layer: 0=logic, 1=on, 2=off" dft:"0"`
25+
}
26+
27+
var encodeDesc = fmt.Sprintf(
28+
`example: %s encode [options] <file>
29+
Takes in a PNG image containing the circuit and outputs a corresponding blueprint.`,
30+
filepath.Base(os.Args[0]),
31+
)
32+
33+
var encodeCmd = &cli.Command{
34+
Name: "encode",
35+
Aliases: []string{"e"},
36+
Desc: "Takes in a PNG image containing the circuit and outputs a corresponding blueprint.",
37+
Text: fmt.Sprintf("example: %s encode [options] <file>", filepath.Base(os.Args[0])),
38+
Argv: func() interface{} { return new(encodeCmdT) },
39+
40+
Fn: func(ctx *cli.Context) error {
41+
argv := ctx.Argv().(*encodeCmdT)
42+
args := ctx.Args()
43+
44+
var outputFile io.WriteCloser = os.Stdout
45+
var outputReader io.Reader
46+
var outputString string
47+
48+
// Open and decode image file.
49+
if len(ctx.Args()) < 1 {
50+
ctx.WriteUsage()
51+
log.Fatalf("Missing file argument\n")
52+
}
53+
54+
file, err := os.Open(args[0])
55+
if err != nil {
56+
log.Fatalf("Failed to open file (%s): %v\n", args[0], err)
57+
}
58+
59+
if ctx.IsSet("-o") {
60+
outputFile, err = os.Create(argv.OutputName)
61+
if err != nil {
62+
log.Fatalf("Failed to create output file (%s): %v\n", argv.OutputName, err)
63+
}
64+
}
65+
66+
var wg sync.WaitGroup
67+
if argv.Clipboard {
68+
outputReader, outputFile = io.Pipe()
69+
wg.Add(1)
70+
go func() {
71+
defer wg.Done()
72+
buf, _ := io.ReadAll(outputReader)
73+
outputString = string(buf)
74+
}()
75+
}
76+
77+
srcimg, err := png.Decode(file)
78+
if err != nil {
79+
log.Fatalf("Failed to decode file (%s): %v\n", args[0], err)
80+
}
81+
img := image.NewRGBA(srcimg.Bounds())
82+
draw.Draw(img, img.Bounds(), srcimg, img.Bounds().Min, draw.Src)
83+
84+
// Create blueprint data.
85+
var blueprint Blueprint
86+
87+
blueprint.Pixels, err = Compress(img.Pix)
88+
if err != nil {
89+
log.Fatalf("Failed to compress pixel data: %v\n", err)
90+
}
91+
92+
blueprint.Footer.HeightType = 2
93+
blueprint.Footer.WidthType = 2
94+
blueprint.Footer.BytesType = 2
95+
blueprint.Footer.LayerType = 2
96+
blueprint.Height = int32(img.Rect.Dy())
97+
blueprint.Width = int32(img.Rect.Dx())
98+
blueprint.Bytes = int32(len(img.Pix))
99+
blueprint.Layer = ToLayer(argv.Layer)
100+
101+
// fmt.Println("Blueprint layer: ", blueprint.Layer)
102+
103+
if blueprint.Layer == -1 {
104+
log.Fatalf("Got unknown layer type: %d\n", argv.Layer)
105+
}
106+
107+
w := base64.NewEncoder(base64.StdEncoding, outputFile)
108+
_, err = w.Write(blueprint.Pixels)
109+
if err != nil {
110+
log.Fatalf("Failed to write blueprint data: %v\n", err)
111+
}
112+
err = binary.Write(w, binary.LittleEndian, blueprint.Footer)
113+
if err != nil {
114+
log.Fatalf("Failed to write blueprint data: %v\n", err)
115+
}
116+
w.Close()
117+
118+
fmt.Fprintln(outputFile)
119+
outputFile.Close()
120+
121+
if argv.Clipboard {
122+
wg.Wait()
123+
err = clipboard.WriteAll(outputString)
124+
if err != nil {
125+
log.Fatalf("Failed to write to clipboard: %v\n", err)
126+
}
127+
}
128+
129+
return nil
130+
},
131+
}

go.mod

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,15 @@ require github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646
99
require (
1010
github.com/atotto/clipboard v0.1.4
1111
github.com/klauspost/compress v1.15.4
12+
github.com/mkideal/cli v0.2.7
13+
)
14+
15+
require (
16+
github.com/labstack/gommon v0.3.0 // indirect
17+
github.com/mattn/go-colorable v0.1.7 // indirect
18+
github.com/mattn/go-isatty v0.0.12 // indirect
19+
github.com/mkideal/expr v0.1.0 // indirect
20+
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad // indirect
21+
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd // indirect
22+
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221 // indirect
1223
)

go.sum

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,51 @@ github.com/DataDog/zstd v1.5.2 h1:vUG4lAyuPCXO0TLbXvPv7EB7cNK1QV/luu55UHLrrn8=
22
github.com/DataDog/zstd v1.5.2/go.mod h1:g4AWEaM3yOg3HYfnJ3YIawPnVdXJh9QME85blwSAmyw=
33
github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4=
44
github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI=
5+
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
6+
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
57
github.com/klauspost/compress v1.15.4 h1:1kn4/7MepF/CHmYub99/nNX8az0IJjfSOU/jbnTVfqQ=
68
github.com/klauspost/compress v1.15.4/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU=
9+
github.com/labstack/gommon v0.3.0 h1:JEeO0bvc78PKdyHxloTKiF8BD5iGrH8T6MSeGvSgob0=
10+
github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k=
11+
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
12+
github.com/mattn/go-colorable v0.1.7 h1:bQGKb3vps/j0E9GfJQ03JyhRuxsvdAanXlT9BTw3mdw=
13+
github.com/mattn/go-colorable v0.1.7/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
14+
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
15+
github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ=
16+
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
17+
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
18+
github.com/mkideal/cli v0.2.7 h1:mB/XrMzuddmTJ8f7KY1c+KzfYoM149tYGAnzmqRdvOU=
19+
github.com/mkideal/cli v0.2.7/go.mod h1:efaTeFI4jdPqzAe0bv3myLB2NW5yzMBLvWB70a6feco=
20+
github.com/mkideal/expr v0.1.0 h1:fzborV9TeSUmLm0aEQWTWcexDURFFo4v5gHSc818Kl8=
21+
github.com/mkideal/expr v0.1.0/go.mod h1:vL1DsSb87ZtU6IEjOtUfxw98z0FQbzS8xlGtnPkKdzg=
22+
github.com/mkideal/pkg v0.1.3/go.mod h1:u/enAxPeRcYSsxtu1NUifWSeOTU/31VsCaOPg54SMJ4=
723
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ=
824
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8=
25+
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
26+
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
27+
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
28+
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
29+
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
30+
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
31+
github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8=
32+
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
33+
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
34+
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad h1:DN0cp81fZ3njFcrLCytUHRSUkqBjfTo4Tx9RJTWs0EY=
35+
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
36+
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
37+
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
38+
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
39+
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
40+
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
41+
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
42+
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
43+
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
44+
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
45+
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd h1:xhmwyvizuTgC2qz7ZlMluP20uW+C3Rm0FD/WLDX8884=
46+
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
47+
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221 h1:/ZHdbVpdR/jk3g30/d4yUL0JU9kksj8+F/bnQUVLGDM=
48+
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
49+
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
50+
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
51+
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
52+
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=

0 commit comments

Comments
 (0)