Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Using go-qrcode as REST-API #66

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 23 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ All examples use the qrcode.Medium error Recovery Level and create a fixed 256x2

## Demoapp

[http://go-qrcode.appspot.com](http://go-qrcode.appspot.com)
[http://cuong.net](http://cuong.net:6868/api/qrcode?data=abc&size=800&color=00FF00&bgcolor=FF00FF&ecc=L&format=png)

## CLI

Expand Down Expand Up @@ -70,6 +70,28 @@ Usage:
qrcode "homepage: https://github.com/skip2/go-qrcode" > out.png

```

## REST-API

### Build and Start

```
go build ./qrcode-restapi/main.go
./main
```

### Usage
```
wget "http://localhost:6868/api/qrcode?data=abc&size=800&color=00FF00&bgcolor=FF00FF&ecc=L&format=png"
```

### Parameters
- data as string
- size in pixel
- color and bgcolor as hex
- ecc aka recovery level can be set as L, M, Q or H for Low, Medium, Quality or Highest
- format supported are png or svg

## Maximum capacity
The maximum capacity of a QR Code varies according to the content encoded and the error recovery level. The maximum capacity is 2,953 bytes, 4,296 alphanumeric characters, 7,089 numeric digits, or a combination of these.

Expand Down
10 changes: 5 additions & 5 deletions encoder.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import (
"errors"
"log"

bitset "github.com/skip2/go-qrcode/bitset"
bitset "github.com/uncopied/go-qrcode/bitset"
)

// Data encoding.
Expand Down Expand Up @@ -406,10 +406,10 @@ func (d *dataEncoder) charCountBits(dataMode dataMode) int {
// dataMode.
//
// The number of bits required is affected by:
// - QR code type - Mode Indicator length.
// - Data mode - number of bits used to represent data length.
// - Data mode - how the data is encoded.
// - Number of symbols encoded.
// - QR code type - Mode Indicator length.
// - Data mode - number of bits used to represent data length.
// - Data mode - how the data is encoded.
// - Number of symbols encoded.
//
// An error is returned if the mode is not supported, or the length requested is
// too long to be represented.
Expand Down
2 changes: 1 addition & 1 deletion encoder_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import (
"reflect"
"testing"

bitset "github.com/skip2/go-qrcode/bitset"
bitset "github.com/uncopied/go-qrcode/bitset"
)

func TestClassifyDataMode(t *testing.T) {
Expand Down
10 changes: 9 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
module github.com/skip2/go-qrcode
module github.com/uncopied/go-qrcode

go 1.13

require (
github.com/gin-gonic/gin v1.9.1
github.com/sirupsen/logrus v1.9.3
github.com/tdewolff/canvas v0.0.0-20210423005539-2093b39b633b
gonum.org/v1/netlib v0.0.0-20200824093956-f0ca4b3a5ef5 // indirect

)
249 changes: 249 additions & 0 deletions go.sum

Large diffs are not rendered by default.

176 changes: 176 additions & 0 deletions qrcode-restapi/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
package main

import (
"bytes"
"fmt"
"image/color"
"io"
"net/http"
"os"
"strconv"
"strings"
"time"

log "github.com/sirupsen/logrus"

"github.com/gin-gonic/gin"
qrcode "github.com/uncopied/go-qrcode"
)

func init() {
initLog()
}

func initLog() {
// setup logrus
logLevel, err := log.ParseLevel(os.Getenv("LOG_LEVEL"))
if err != nil {
logLevel = log.InfoLevel
}

log.SetLevel(logLevel)
}

func LoggingMiddleware() gin.HandlerFunc {
return func(ctx *gin.Context) {
// Starting time
startTime := time.Now()

// Processing request
// ctx.Next() // bug?

// End Time
endTime := time.Now()

// execution time
latencyTime := endTime.Sub(startTime)

// Request method
reqMethod := ctx.Request.Method

// Request route
reqUri := ctx.Request.RequestURI

// status code
statusCode := ctx.Writer.Status()

// Request IP
clientIP := ctx.ClientIP()

reqBody, _ := io.ReadAll(ctx.Request.Body)
ctx.Request.Body = io.NopCloser(bytes.NewReader(reqBody))

log.WithFields(log.Fields{
"6_BODY": string(reqBody),
"5_METHOD": reqMethod,
"2_URI": reqUri,
"3_STATUS": statusCode,
"4_LATENCY": latencyTime,
"1_CLIENT_IP": clientIP,
}).Info("HTTP REQUEST")

ctx.Next()
}
}

// https://stackoverflow.com/questions/54197913/parse-hex-string-to-image-color
func ParseHexColor(s string) (c color.RGBA, err error) {
c.A = 0xff
switch len(s) {
case 6:
_, err = fmt.Sscanf(s, "%02x%02x%02x", &c.R, &c.G, &c.B)
case 3:
_, err = fmt.Sscanf(s, "%1x%1x%1x", &c.R, &c.G, &c.B)
// Double the hex digits:
c.R *= 17
c.G *= 17
c.B *= 17
default:
err = fmt.Errorf("invalid length, must be 7 or 4")
}
return
}

func getQRCode(c *gin.Context) {

var data string = c.Query("data")
size, _ := strconv.Atoi(c.Query("size"))
var ecc string = strings.ToUpper(c.Query("ecc"))
var recovery_level qrcode.RecoveryLevel = qrcode.Low
// margin, _ := strconv.Atoi(c.Query("margin")), no margin is supported yet in go-qrcode
var color string = c.Query("color")
var bgcolor string = c.Query("bgcolor")
// qzone, no qzone is supported yet in go-qrcode
var format string = strings.ToLower(c.Query("format"))

if (strings.Compare(ecc, "L") == 0) ||
(strings.Compare(ecc, "M") == 0) ||
(strings.Compare(ecc, "Q") == 0) ||
(strings.Compare(ecc, "H") == 0) {

if strings.Compare(ecc, "L") == 0 {
recovery_level = qrcode.Low
} else if strings.Compare(ecc, "M") == 0 {
recovery_level = qrcode.Medium
} else if strings.Compare(ecc, "Q") == 0 {
recovery_level = qrcode.High
} else if strings.Compare(ecc, "H") == 0 {
recovery_level = qrcode.Highest
} else {
log.Error("ECC aka RecoveryLevel is not supported")
}
}

mycolor, _ := ParseHexColor(color)
mybgcolor, _ := ParseHexColor(bgcolor)

var q *qrcode.QRCode
q, err := qrcode.New(data, recovery_level)
checkError(err)
q.ForegroundColor = mycolor
q.BackgroundColor = mybgcolor

if strings.Compare(format, "svg") == 0 {
qrSVG, err := q.SVG()
checkError(err)

c.Header("Content-Disposition", "inline; filename=qrcode.svg")
c.Data(http.StatusOK, "application/octet-stream", []byte(qrSVG))
} else /* if strings.Compare(format, "eps") == 0 {
qrEPS, err := q.EPS()
checkError(err)

c.Header("Content-Disposition", "inline; filename=qrcode.eps")
c.Data(http.StatusOK, "application/octet-stream", []byte(qrEPS))
} else if strings.Compare(format, "pdf") == 0 {
qrPDF, err := q.PDF()
checkError(err)

c.Header("Content-Disposition", "inline; filename=qrcode.pdf")
c.Data(http.StatusOK, "application/octet-stream", []byte(qrPDF))
} else */{
var png []byte
png, err = q.PNG(size)
checkError(err)

c.Header("Content-Disposition", "inline; filename=qrcode.png")
c.Data(http.StatusOK, "application/octet-stream", png)
}
}

func main() {
router := gin.New()

router.Use(gin.Recovery())
router.Use(LoggingMiddleware())

router.GET("/api/qrcode", getQRCode)

router.Run(":6868")
}

func checkError(err error) {
if err != nil {
log.Error(err)
}
}
110 changes: 108 additions & 2 deletions qrcode.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,10 @@ import (
"log"
"os"

bitset "github.com/skip2/go-qrcode/bitset"
reedsolomon "github.com/skip2/go-qrcode/reedsolomon"
canvas "github.com/tdewolff/canvas"
"github.com/tdewolff/canvas/renderers/svg"
bitset "github.com/uncopied/go-qrcode/bitset"
reedsolomon "github.com/uncopied/go-qrcode/reedsolomon"
)

// Encode a QR Code and return a raw PNG image.
Expand Down Expand Up @@ -331,6 +333,70 @@ func (q *QRCode) Image(size int) image.Image {
return img
}

func (q *QRCode) DrawQRCode(ctx *canvas.Context, xQRCode float64, yQRCode float64, widthQRCode float64) {
// Build QR code.
q.encode()

// Minimum pixels (both width and height) required.
size := q.symbol.size
if size == 0 {
log.Fatal("q.symbol.size is ZERO, will /0 fail")
}
pixelWidth := widthQRCode / float64(size)

ctx.SetFillColor(canvas.White)
ctx.DrawPath(xQRCode, yQRCode, canvas.Rectangle(widthQRCode, widthQRCode))

// Saves a few bytes to have them in this order
ctx.SetFillColor(canvas.Black)

// QR code bitmap.
bitmap := q.symbol.bitmap()

// Map each image pixel to the nearest QR code module.
for y := 0; y < size; y++ {
for x := 0; x < size; x++ {
v := bitmap[y][x]
if v {
ctx.DrawPath(xQRCode+float64(x)*pixelWidth, yQRCode+float64(size-y)*pixelWidth, canvas.Rectangle(pixelWidth, pixelWidth))
}
}
}
}

// Canvas returns the QR Code as an image.Image.
//
// Based on Image
func (q *QRCode) Canvas() *canvas.Canvas {
// Build QR code.
q.encode()

// Minimum pixels (both width and height) required.
size := q.symbol.size

c := canvas.New(float64(size), float64(size))
ctx := canvas.NewContext(c)
ctx.SetFillColor(canvas.White)
ctx.DrawPath(float64(0), float64(0), canvas.Rectangle(float64(size), float64(size)))

// Saves a few bytes to have them in this order
ctx.SetFillColor(canvas.Black)

// QR code bitmap.
bitmap := q.symbol.bitmap()

// Map each image pixel to the nearest QR code module.
for y := 0; y < size; y++ {
for x := 0; x < size; x++ {
v := bitmap[y][x]
if v {
ctx.DrawPath(float64(x), float64(size-y), canvas.Rectangle(float64(1), float64(1)))
}
}
}
return c
}

// PNG returns the QR Code as a PNG image.
//
// size is both the image width and height in pixels. If size is too small then
Expand All @@ -351,6 +417,46 @@ func (q *QRCode) PNG(size int) ([]byte, error) {
return b.Bytes(), nil
}

// 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: See the documentation for Image().
func (q *QRCode) SVG() (string, error) {
c := q.Canvas()
// import "bytes"
buf := new(bytes.Buffer)
w := svg.Writer

w(buf, c)
// for debug
//c.WriteFile("qrcode_out.svg", svg.Writer)
//c.WriteFile("qrcode_out.pdf", pdf.Writer)
//c.WriteFile("qrcode_out.eps", eps.Writer)
//c.WriteFile("qrcode_out.png", rasterizer.PNGWriter(3.2))
return buf.String(), nil
}

/*
func (q *QRCode) EPS() (string, error) {
c := q.Canvas()
buf := new(bytes.Buffer)
w := eps.Writer

w(buf, c)
return buf.String(), nil
}

func (q *QRCode) PDF() (string, error) {
c := q.Canvas()
buf := new(bytes.Buffer)
w := pdf.Writer

w(buf, c)
return buf.String(), 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
Expand Down
Loading