diff --git a/.travis.yml b/.travis.yml index 7ced8fb..d3aafca 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,9 @@ language: go go: - - 1.7 + - '1.17' + - '1.18' + - '1.19' script: - go test -v ./... diff --git a/go.mod b/go.mod index a915813..9810f54 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,5 @@ module github.com/skip2/go-qrcode -go 1.13 +go 1.17 + +require github.com/ajstarks/svgo v0.0.0-20211024235047-1546f124cd8b diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..38027ba --- /dev/null +++ b/go.sum @@ -0,0 +1,29 @@ +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/ajstarks/deck v0.0.0-20200831202436-30c9fc6549a9/go.mod h1:JynElWSGnm/4RlzPXRlREEwqTHAN3T56Bv2ITsFT3gY= +github.com/ajstarks/deck/generate v0.0.0-20210309230005-c3f852c02e19/go.mod h1:T13YZdzov6OU0A1+RfKZiZN9ca6VeKdBdyDV+BY97Tk= +github.com/ajstarks/svgo v0.0.0-20211024235047-1546f124cd8b h1:slYM766cy2nI3BwyRiyQj/Ud48djTMtMebDqepE95rw= +github.com/ajstarks/svgo v0.0.0-20211024235047-1546f124cd8b/go.mod h1:1KcenG0jGWcpt8ov532z81sp/kMMUG485J2InIOyADM= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +honnef.co/go/tools v0.1.3/go.mod h1:NgwopIslSNH47DimFoV78dnkksY2EFtX0ajyb3K/las= diff --git a/qrcode.go b/qrcode.go index d0541bc..fb9e48a 100644 --- a/qrcode.go +++ b/qrcode.go @@ -58,8 +58,10 @@ import ( "io" "io/ioutil" "log" + "math" "os" + svg "github.com/ajstarks/svgo" bitset "github.com/skip2/go-qrcode/bitset" reedsolomon "github.com/skip2/go-qrcode/reedsolomon" ) @@ -351,6 +353,52 @@ func (q *QRCode) PNG(size int) ([]byte, error) { return b.Bytes(), nil } +func svgColor(c color.Color) string { + r, g, b, _ := c.RGBA() + return fmt.Sprintf("fill:rgb(%d,%d,%d)", r, g, b) +} + +// SVG returns the QR Code as a SVG image. +// +// size is both the image width and height in pixels. If size is too small then +// a larger image is silently returned. Negative values for size cause a +// variable sized image to be returned, as with PNG(). +func (q *QRCode) SVG(size int) ([]byte, error) { + // Build QR code. + q.encode() + + // Minimum pixels (both width and height) required. + realSize := q.symbol.size + + // Variable size support. + if size < 0 { + size = size * -1 * realSize + } + + // Actual pixels available to draw the symbol. Automatically increase the + // image size if it's not large enough. + if size < realSize { + size = realSize + } + bitmap := q.symbol.bitmap() + var buf bytes.Buffer + canvas := svg.New(&buf) + canvas.Start(size, size) + // Create background. + canvas.Rect(0, 0, size, size, svgColor(q.BackgroundColor)) + px := int(math.RoundToEven(float64(size) / float64(realSize))) + for yb, xbs := range bitmap { + for xb, v := range xbs { + if v { + x, y := (xb*px)-px, (yb*px)-px + canvas.Rect(x, y, px, px, svgColor(q.ForegroundColor)) + } + } + } + canvas.End() + return buf.Bytes(), nil +} + // Write writes the QR Code as a PNG image to io.Writer. // // size is both the image width and height in pixels. If size is too small then diff --git a/qrcode/main.go b/qrcode/main.go index a38731c..94ba739 100644 --- a/qrcode/main.go +++ b/qrcode/main.go @@ -13,8 +13,9 @@ import ( ) func main() { - outFile := flag.String("o", "", "out PNG file prefix, empty for stdout") + outFile := flag.String("o", "", "out file prefix, empty for stdout") size := flag.Int("s", 256, "image size (pixel)") + vector := flag.Bool("v", false, "render QR code as SVG instead of PNG") textArt := flag.Bool("t", false, "print as text-art on stdout") negative := flag.Bool("i", false, "invert black and white") disableBorder := flag.Bool("d", false, "disable QR Code border") @@ -67,18 +68,24 @@ Usage: q.ForegroundColor, q.BackgroundColor = q.BackgroundColor, q.ForegroundColor } - var png []byte - png, err = q.PNG(*size) + render := q.PNG + suffix := "png" + if *vector { + render = q.SVG + suffix = "svg" + } + + data, err := render(*size) checkError(err) if *outFile == "" { - os.Stdout.Write(png) + os.Stdout.Write(data) } else { var fh *os.File - fh, err = os.Create(*outFile + ".png") + fh, err = os.Create(fmt.Sprintf("%v.%v", *outFile, suffix)) checkError(err) defer fh.Close() - fh.Write(png) + fh.Write(data) } } diff --git a/qrcode_decode_test.go b/qrcode_decode_test.go index 1f4b1d3..ae5c652 100644 --- a/qrcode_decode_test.go +++ b/qrcode_decode_test.go @@ -118,13 +118,13 @@ func TestDecodeAllCharacters(t *testing.T) { t.Skip("Decode tests not enabled") } - var content string - + var raw []byte // zbarimg has trouble with null bytes, hence start from ASCII 1. for i := 1; i < 256; i++ { - content += string(i) + raw = append(raw, byte(i)) } + content := string(raw) q, err := New(content, Low) if err != nil { t.Error(err.Error()) @@ -150,13 +150,14 @@ func TestDecodeFuzz(t *testing.T) { for i := 0; i < iterations; i++ { len := r.Intn(maxLength-1) + 1 - var content string + var raw []byte for j := 0; j < len; j++ { // zbarimg seems to have trouble with special characters, test printable // characters only for now. - content += string(32 + r.Intn(94)) + raw = append(raw, byte(32+r.Intn(94))) } + content := string(raw) for _, level := range []RecoveryLevel{Low, Medium, High, Highest} { q, err := New(content, level) if err != nil {