Introduce QR code generation & serve it via the media server (#3154)

* introduce QR code generation
This commit is contained in:
Siddarth Kumar 2023-02-02 19:26:00 +05:30 committed by GitHub
parent 550de3bff2
commit efee11d28a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
39 changed files with 7929 additions and 0 deletions

3
go.mod
View File

@ -78,6 +78,8 @@ require (
github.com/meirf/gopart v0.0.0-20180520194036-37e9492a85a8 github.com/meirf/gopart v0.0.0-20180520194036-37e9492a85a8
github.com/rmg/iso4217 v1.0.0 github.com/rmg/iso4217 v1.0.0
github.com/waku-org/go-waku v0.4.1-0.20230131145040-6169a44c242f github.com/waku-org/go-waku v0.4.1-0.20230131145040-6169a44c242f
github.com/yeqown/go-qrcode/v2 v2.2.1
github.com/yeqown/go-qrcode/writer/standard v1.2.1
) )
require ( require (
@ -243,6 +245,7 @@ require (
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f // indirect github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f // indirect
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
github.com/yeqown/reedsolomon v1.0.0 // indirect
go.etcd.io/bbolt v1.3.6 // indirect go.etcd.io/bbolt v1.3.6 // indirect
go.opencensus.io v0.23.0 // indirect go.opencensus.io v0.23.0 // indirect
go.uber.org/atomic v1.10.0 // indirect go.uber.org/atomic v1.10.0 // indirect

6
go.sum
View File

@ -2104,6 +2104,12 @@ github.com/xlab/treeprint v0.0.0-20180616005107-d6fb6747feb6/go.mod h1:ce1O1j6Ut
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU= github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU=
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8= github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8=
github.com/yeqown/go-qrcode/v2 v2.2.1 h1:Jc1Q916fwC05R8C7mpWDbrT9tyLPaLLKDABoC5XBCe8=
github.com/yeqown/go-qrcode/v2 v2.2.1/go.mod h1:2Qsk2APUCPne0TsRo40DIkI5MYnbzYKCnKGEFWrxd24=
github.com/yeqown/go-qrcode/writer/standard v1.2.1 h1:FMRZiur5yApUIe4fqtqmcdl/XQTZAZWt2DhkPx4VIW0=
github.com/yeqown/go-qrcode/writer/standard v1.2.1/go.mod h1:ZelyDFiVymrauRjUn454iF7bjsabmB1vixkDA5kq2bw=
github.com/yeqown/reedsolomon v1.0.0 h1:x1h/Ej/uJnNu8jaX7GLHBWmZKCAWjEJTetkqaabr4B0=
github.com/yeqown/reedsolomon v1.0.0/go.mod h1:P76zpcn2TCuL0ul1Fso373qHRc69LKwAw/Iy6g1WiiM=
github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA= github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA=
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=

182
qrcode/qrcode.go Normal file
View File

@ -0,0 +1,182 @@
package qrcode
import (
"bytes"
"errors"
"fmt"
"image"
"image/color"
"image/draw"
"image/png"
"net/url"
"os"
xdraw "golang.org/x/image/draw"
"github.com/status-im/status-go/multiaccounts"
)
const (
defaultPadding = 20
)
func GetImageDimensions(imgBytes []byte) (int, int, error) {
// Decode image bytes
img, _, err := image.Decode(bytes.NewReader(imgBytes))
if err != nil {
return 0, 0, err
}
// Get the image dimensions
bounds := img.Bounds()
width := bounds.Max.X - bounds.Min.X
height := bounds.Max.Y - bounds.Min.Y
return width, height, nil
}
func ToLogoImageFromBytes(imageBytes []byte, padding int) []byte {
img, _, err := image.Decode(bytes.NewReader(imageBytes))
if err != nil {
panic(err)
}
bounds := img.Bounds()
newBounds := image.Rect(bounds.Min.X-padding, bounds.Min.Y-padding, bounds.Max.X+padding, bounds.Max.Y+padding)
white := image.NewRGBA(newBounds)
draw.Draw(white, newBounds, &image.Uniform{C: color.White}, image.ZP, draw.Src)
// Create a circular mask
circle := image.NewRGBA(bounds)
draw.DrawMask(circle, bounds, img, image.ZP, &Circle{
X: bounds.Dx() / 2,
Y: bounds.Dy() / 2,
R: bounds.Dx() / 2,
}, image.ZP, draw.Over)
// Calculate the center point of the new image
centerX := (newBounds.Min.X + newBounds.Max.X) / 2
centerY := (newBounds.Min.Y + newBounds.Max.Y) / 2
// Draw the circular image in the center of the new image
draw.Draw(white, bounds.Add(image.Pt(centerX-bounds.Dx()/2, centerY-bounds.Dy()/2)), circle, image.ZP, draw.Over)
// Encode image to png format and save in a bytes
var resultImg bytes.Buffer
err = png.Encode(&resultImg, white)
if err != nil {
return nil
}
resultBytes := resultImg.Bytes()
return resultBytes
}
func SuperimposeImage(imageBytes []byte, qrFilepath []byte) []byte {
// Read the two images from bytes
img1, _, _ := image.Decode(bytes.NewReader(imageBytes))
img2, _, _ := image.Decode(bytes.NewReader(qrFilepath))
// Create a new image with the dimensions of the first image
result := image.NewRGBA(img1.Bounds())
// Draw the first image on the new image
draw.Draw(result, img1.Bounds(), img1, image.ZP, draw.Src)
// Get the dimensions of the second image
img2Bounds := img2.Bounds()
// Calculate the x and y coordinates to center the second image
x := (img1.Bounds().Dx() - img2Bounds.Dx()) / 2
y := (img1.Bounds().Dy() - img2Bounds.Dy()) / 2
// Draw the second image on top of the first image at the calculated coordinates
draw.Draw(result, img2Bounds.Add(image.Pt(x, y)), img2, image.ZP, draw.Over)
// Encode the final image to a desired format
var b bytes.Buffer
if err := png.Encode(&b, result); err != nil {
fmt.Println(err)
}
return b.Bytes()
}
func ResizeImage(imgBytes []byte, width, height int) ([]byte, error) {
// Decode image bytes
img, _, err := image.Decode(bytes.NewReader(imgBytes))
if err != nil {
return nil, err
}
// Create a new image with the desired dimensions
newImg := image.NewNRGBA(image.Rect(0, 0, width, height))
xdraw.BiLinear.Scale(newImg, newImg.Bounds(), img, img.Bounds(), draw.Over, nil)
// Encode the new image to bytes
var newImgBytes bytes.Buffer
if err = png.Encode(&newImgBytes, newImg); err != nil {
return nil, err
}
return newImgBytes.Bytes(), nil
}
func ImageToBytes(imagePath string) ([]byte, error) {
// Open the image file
file, err := os.Open(imagePath)
if err != nil {
return nil, err
}
defer file.Close()
// Decode the image
img, _, err := image.Decode(file)
if err != nil {
return nil, err
}
// Create a new buffer to hold the image data
var imgBuffer bytes.Buffer
// Encode the image to the desired format and save it in the buffer
err = png.Encode(&imgBuffer, img)
if err != nil {
return nil, err
}
// Return the image data as a byte slice
return imgBuffer.Bytes(), nil
}
func GetLogoImage(multiaccountsDB *multiaccounts.Database, params url.Values) ([]byte, error) {
var imageFolderBasePath = "../_assets/tests/"
var logoFileStaticPath = imageFolderBasePath + "status.png"
keyUids, ok := params["keyUid"]
if !ok || len(keyUids) == 0 {
return nil, errors.New("no keyUid")
}
imageNames, ok := params["imageName"]
if !ok || len(imageNames) == 0 {
return nil, errors.New("no imageName")
}
identityImageObjectFromDB, err := multiaccountsDB.GetIdentityImage(keyUids[0], imageNames[0])
if err != nil {
return nil, err
}
staticLogoFileBytes, _ := ImageToBytes(logoFileStaticPath)
if identityImageObjectFromDB == nil {
return ToLogoImageFromBytes(staticLogoFileBytes, GetPadding(staticLogoFileBytes)), nil
}
return ToLogoImageFromBytes(identityImageObjectFromDB.Payload, GetPadding(identityImageObjectFromDB.Payload)), nil
}
func GetPadding(imgBytes []byte) int {
size, _, err := GetImageDimensions(imgBytes)
if err != nil {
return defaultPadding
}
return size / 5
}
type Circle struct {
X, Y, R int
}
func (c *Circle) ColorModel() color.Model {
return color.AlphaModel
}
func (c *Circle) Bounds() image.Rectangle {
return image.Rect(c.X-c.R, c.Y-c.R, c.X+c.R, c.Y+c.R)
}
func (c *Circle) At(x, y int) color.Color {
xx, yy, rr := float64(x-c.X)+0.5, float64(y-c.Y)+0.5, float64(c.R)
if xx*xx+yy*yy < rr*rr {
return color.Alpha{255}
}
return color.Alpha{0}
}

View File

@ -3,12 +3,15 @@ package server
import ( import (
"bytes" "bytes"
"database/sql" "database/sql"
"encoding/base64"
"image" "image"
"net/http" "net/http"
"net/url" "net/url"
"strconv" "strconv"
"time" "time"
qrcode "github.com/yeqown/go-qrcode/v2"
"github.com/yeqown/go-qrcode/writer/standard"
"go.uber.org/zap" "go.uber.org/zap"
"github.com/status-im/status-go/ipfs" "github.com/status-im/status-go/ipfs"
@ -17,6 +20,7 @@ import (
"github.com/status-im/status-go/protocol/identity/identicon" "github.com/status-im/status-go/protocol/identity/identicon"
"github.com/status-im/status-go/protocol/identity/ring" "github.com/status-im/status-go/protocol/identity/ring"
"github.com/status-im/status-go/protocol/images" "github.com/status-im/status-go/protocol/images"
qrcodeutils "github.com/status-im/status-go/qrcode"
) )
const ( const (
@ -31,10 +35,30 @@ const (
// Handler routes for pairing // Handler routes for pairing
accountImagesPath = "/accountImages" accountImagesPath = "/accountImages"
contactImagesPath = "/contactImages" contactImagesPath = "/contactImages"
generateQRCode = "/GenerateQRCode"
) )
type HandlerPatternMap map[string]http.HandlerFunc type HandlerPatternMap map[string]http.HandlerFunc
type QROptions struct {
URL string `json:"url"`
ErrorCorrectionLevel string `json:"errorCorrectionLevel"`
Capacity string `json:"capacity"`
AllowProfileImage bool `json:"withLogo"`
}
type WriterCloserByteBuffer struct {
*bytes.Buffer
}
func (wc WriterCloserByteBuffer) Close() error {
return nil
}
func NewWriterCloserByteBuffer() *WriterCloserByteBuffer {
return &WriterCloserByteBuffer{bytes.NewBuffer([]byte{})}
}
func handleAccountImages(multiaccountsDB *multiaccounts.Database, logger *zap.Logger) http.HandlerFunc { func handleAccountImages(multiaccountsDB *multiaccounts.Database, logger *zap.Logger) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
params := r.URL.Query() params := r.URL.Query()
@ -384,3 +408,68 @@ func handleIPFS(downloader *ipfs.Downloader, logger *zap.Logger) http.HandlerFun
} }
} }
} }
func handleQRCodeGeneration(multiaccountsDB *multiaccounts.Database, logger *zap.Logger) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
params := r.URL.Query()
qrURLBase64Encoded, ok := params["qrurl"]
if !ok || len(qrURLBase64Encoded) == 0 {
logger.Error("no qr url provided")
return
}
qrURLBase64Decoded, err := base64.StdEncoding.DecodeString(qrURLBase64Encoded[0])
if err != nil {
logger.Error("error decoding string from base64", zap.Error(err))
}
level, ok := params["level"]
// Default error correction level
correctionLevel := qrcode.ErrorCorrectionMedium
if ok && len(level) == 1 {
switch level[0] {
case "4":
correctionLevel = qrcode.ErrorCorrectionHighest
case "1":
correctionLevel = qrcode.ErrorCorrectionLow
case "3":
correctionLevel = qrcode.ErrorCorrectionQuart
}
}
buf := NewWriterCloserByteBuffer()
qrc, err := qrcode.NewWith(string(qrURLBase64Decoded),
qrcode.WithEncodingMode(qrcode.EncModeAuto),
qrcode.WithErrorCorrectionLevel(correctionLevel),
)
if err != nil {
logger.Error("could not generate QRCode", zap.Error(err))
}
nw := standard.NewWithWriter(buf)
if err = qrc.Save(nw); err != nil {
logger.Error("could not save image", zap.Error(err))
}
payload := buf.Bytes()
logo, err := qrcodeutils.GetLogoImage(multiaccountsDB, params)
if err == nil {
qrWidth, qrHeight, _ := qrcodeutils.GetImageDimensions(payload)
logo, _ = qrcodeutils.ResizeImage(logo, qrWidth/5, qrHeight/5)
payload = qrcodeutils.SuperimposeImage(payload, logo)
}
size, ok := params["size"]
if ok && len(size) == 1 {
size, err := strconv.Atoi(size[0])
if err == nil {
payload, _ = qrcodeutils.ResizeImage(payload, size, size)
}
}
mime, err := images.ImageMime(payload)
if err != nil {
logger.Error("could not generate image from payload", zap.Error(err))
}
w.Header().Set("Content-Type", mime)
w.Header().Set("Cache-Control", "no-store")
_, err = w.Write(payload)
if err != nil {
logger.Error("failed to write image", zap.Error(err))
}
}
}

View File

@ -45,6 +45,7 @@ func NewMediaServer(db *sql.DB, downloader *ipfs.Downloader, multiaccountsDB *mu
contactImagesPath: handleContactImages(s.db, s.logger), contactImagesPath: handleContactImages(s.db, s.logger),
discordAuthorsPath: handleDiscordAuthorAvatar(s.db, s.logger), discordAuthorsPath: handleDiscordAuthorAvatar(s.db, s.logger),
discordAttachmentsPath: handleDiscordAttachment(s.db, s.logger), discordAttachmentsPath: handleDiscordAttachment(s.db, s.logger),
generateQRCode: handleQRCodeGeneration(s.multiaccountsDB, s.logger),
}) })
return s, nil return s, nil
@ -103,3 +104,11 @@ func (s *MediaServer) MakeStickerURL(stickerHash string) string {
return u.String() return u.String()
} }
func (s *MediaServer) MakeQRURL(qurul string) string {
u := s.MakeBaseURL()
u.Path = generateQRCode
u.RawQuery = url.Values{"qurul": {qurul}}.Encode()
return u.String()
}

16
vendor/github.com/yeqown/go-qrcode/v2/.gitignore generated vendored Normal file
View File

@ -0,0 +1,16 @@
vendor/
draft/
testdata/
js/
.idea/
.vscode/
# example/
*.png
default.jpeg
# *.json
*.log
.DS_store
tmp.png
go.sum

21
vendor/github.com/yeqown/go-qrcode/v2/LICENSE generated vendored Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2018 yeqown
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

14
vendor/github.com/yeqown/go-qrcode/v2/Makefile generated vendored Normal file
View File

@ -0,0 +1,14 @@
release: release-osx release-linux
release-osx:
- mkdir -p draft/osx
GOOS=darwin GOARCH=amd64 go build -o draft/osx/go-qrcode ./cmd/go-qrcode.go
cd draft/osx && tar -zcvf ../go-qrcode.osx.tar.gz .
release-linux:
- mkdir -p draft/linux
GOOS=linux GOARCH=amd64 go build -o draft/linux/go-qrcode ./cmd/go-qrcode.go
cd draft/linux && tar -zcvf ../go-qrcode.linux.tar.gz .
test-all:
go test -v --count=1 ./...

139
vendor/github.com/yeqown/go-qrcode/v2/README.md generated vendored Normal file
View File

@ -0,0 +1,139 @@
# go-qrcode #
[![Go Report Card](https://goreportcard.com/badge/github.com/yeqown/go-qrcode)](https://goreportcard.com/report/github.com/yeqown/go-qrcode)
[![go.dev reference](https://img.shields.io/badge/go.dev-reference-007d9c?logo=go&logoColor=white&style=flat-square)](https://pkg.go.dev/github.com/yeqown/go-qrcode/v2)
[![Go](https://github.com/yeqown/go-qrcode/actions/workflows/go.yml/badge.svg?branch=main)](https://github.com/yeqown/go-qrcode/actions/workflows/go.yml) ![](https://changkun.de/urlstat?mode=github&repo=yeqown/go-qrcode)
![GitHub release (latest SemVer)](https://img.shields.io/github/v/release/yeqown/go-qrcode)
![GitHub go.mod Go version](https://img.shields.io/github/go-mod/go-version/yeqown/go-qrcode)
[![License](https://img.shields.io/badge/license-MIT-green)](./LICENSE)
<img src="./assets/repository_qrcode.png" width="100px" align="right"/>
QR code (abbreviated from Quick Response Code) is the trademark for a type of matrix barcode (or two-dimensional barcode) first designed in 1994 for the automotive industry in Japan. A barcode is a machine-readable optical label that contains information about the item to which it is attached. A QR code uses four standardized encoding modes (numeric, alphanumeric, byte/binary, and kanji) to store data efficiently; extensions may also be used
### Features
- [x] Normally generate QR code across `version 1` to `version 40`.
- [x] Automatically analyze QR version by source text.
- [x] Specifying cell shape allowably with `WithCustomShape`, `WithCircleShape` (default is `rectangle`)
- [x] Specifying output file's format with `WithBuiltinImageEncoder`, `WithCustomImageEncoder` (default is `JPEG`)
- [x] Not only shape of cell, but also color of QR Code background and foreground color.
- [x] `WithLogoImage`, `WithLogoImageFilePNG`, `WithLogoImageFileJPEG` help you add an icon at the central of QR Code.
- [x] `WithBorderWidth` allows to specify any width of 4 sides around the qrcode.
- [x] `WebAssembly` support, check out the [Example](./example/webassembly/README.md) and [README](cmd/wasm/README.md) for more detail.
- [x] support Halftone QR Codes, check out the [Example](./example/with-halftone).
### Install
```sh
go get -u github.com/yeqown/go-qrcode/v2
```
### Quick Start
link to [CODE](./example/main.go)
```go
package main
import (
"github.com/yeqown/go-qrcode/v2"
"github.com/yeqown/go-qrcode/writer/standard"
)
func main() {
qrc, err := qrcode.New("https://github.com/yeqown/go-qrcode")
if err != nil {
fmt.Printf("could not generate QRCode: %v", err)
return
}
w, err := standard.New("../assets/repo-qrcode.jpeg")
if err != nil {
fmt.Printf("standard.New failed: %v", err)
return
}
// save file
if err = qrc.Save(w); err != nil {
fmt.Printf("could not save image: %v", err)
}
}
```
### Options
```go
const (
// EncModeNone mode ...
EncModeNone encMode = 1 << iota
// EncModeNumeric mode ...
EncModeNumeric
// EncModeAlphanumeric mode ...
EncModeAlphanumeric
// EncModeByte mode ...
EncModeByte
// EncModeJP mode ...
EncModeJP
)
// WithEncodingMode sets the encoding mode.
func WithEncodingMode(mode encMode) EncodeOption {}
const (
// ErrorCorrectionLow :Level L: 7% error recovery.
ErrorCorrectionLow ecLevel = iota + 1
// ErrorCorrectionMedium :Level M: 15% error recovery. Good default choice.
ErrorCorrectionMedium
// ErrorCorrectionQuart :Level Q: 25% error recovery.
ErrorCorrectionQuart
// ErrorCorrectionHighest :Level H: 30% error recovery.
ErrorCorrectionHighest
)
// WithErrorCorrectionLevel sets the error correction level.
func WithErrorCorrectionLevel(ecLevel ecLevel) EncodeOption {}
```
following are some shots:
<div>
<img src="./assets/example_fg_bg.jpeg" width="160px" align="left" title="with bg-fg color">
<img src="./assets/example_logo.jpeg" width="160px" align="left" title="with logo image">
<img src="./assets/example_circle.jpeg" width="160px" align="left" title="customize block shape">
<img src="./assets/example_transparent.png" width="160px" title="with transparent bg">
</div>
<div>
<img src="./assets/example_halftone0.jpeg" width="160px" align="left" title="halftone0">
<img src="./assets/example_halftone1.jpeg" width="160px" align="left" title="halftone1">
<img src="./assets/example_halftone2.jpeg" width="160px" align="left" title="halftone2">
<img src="./assets/example_halftone3.jpeg" width="160px" title="halftone3">
</div>
<br>
### Built-in Writers
- [Standard Writer](./writer/standard/README.md), prints QRCode into file and stream
- [Terminal Writer](./writer/terminal/README.md), prints QRCode into terminal
Of course, you can also code your own writer, just implement [Writer](./writer/README.md) interface.
### Migrating from v1
`go-qrcode.v2` is a major upgrade from v1, and it is not backward compatible. `v2` redesigned
the API, and it is more flexible and powerful. Features are split into different modules (according to functionality).
- github.com/yeqown/go-qrcode/v2 **_core_**
- github.com/yeqown/go-qrcode/writer/standard **_writer/imageFile_**
- github.com/yeqown/go-qrcode/writer/terminal **_writer/terminal_**
Check [example/migrating-from-v1](./example/migrating-from-v1/main.go) for more details.
### Links
* [QRCode Tourist](https://www.thonky.com/qr-code-tutorial/)
* [QRCode Wiki](https://en.wikipedia.org/wiki/QR_code)
* [二维码详解 (QRCode analysis in CN-zh)](https://zhuanlan.zhihu.com/p/21463650)
* [数据编码 (How to encode data payload in QRCode in CN-zh)](https://zhuanlan.zhihu.com/p/25432676)

109
vendor/github.com/yeqown/go-qrcode/v2/debug.go generated vendored Normal file
View File

@ -0,0 +1,109 @@
package qrcode
import (
"fmt"
"image"
"image/color"
"image/jpeg"
"io"
"log"
"os"
"sync"
)
var (
// _debug mode switch, true means enable debug mode, false means disable.
_debug = false
_debugOnce sync.Once
)
func debugEnabled() bool {
// load debug switch from environment only once.
_debugOnce.Do(func() {
switch os.Getenv("QRCODE_DEBUG") {
case "1", "true", "TRUE", "enabled", "ENABLED":
_debug = true
}
})
return _debug
}
// SetDebugMode open debug switch, you can also enable debug by runtime
// environments variables: QRCODE_DEBUG=1 [1, true, TRUE, enabled, ENABLED] which is recommended.
func SetDebugMode() {
_debug = true
}
func debugLogf(format string, v ...interface{}) {
if !debugEnabled() {
return
}
log.Printf("[qrcode] DEBUG: "+format, v...)
}
func debugDraw(filename string, mat Matrix) error {
if !debugEnabled() {
return nil
}
fd, err := os.OpenFile(filename, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0644)
if err != nil {
return fmt.Errorf("debugDraw open file %s failed: %w", filename, err)
}
defer func(fd *os.File) {
_ = fd.Close()
}(fd)
return debugDrawTo(fd, mat)
}
func debugDrawTo(w io.Writer, mat Matrix) error {
if !debugEnabled() {
return nil
}
// width as image width, height as image height
padding := 10
blockWidth := 10
width := mat.Width()*blockWidth + 2*padding
height := width
img := image.NewGray16(image.Rect(0, 0, width, height))
rectangle := func(x1, y1 int, x2, y2 int, img *image.Gray16, c color.Gray16) {
for x := x1; x < x2; x++ {
for y := y1; y < y2; y++ {
img.SetGray16(x, y, c)
}
}
}
// background
rectangle(0, 0, width, height, img, color.Gray16{Y: 0xff12})
mat.iter(IterDirection_COLUMN, func(x int, y int, v qrvalue) {
sx := x*blockWidth + padding
sy := y*blockWidth + padding
es := (x+1)*blockWidth + padding
ey := (y+1)*blockWidth + padding
// choose color, false use black, others use black on white background
var gray color.Gray16
switch v.qrbool() {
case false:
gray = color.White
default:
gray = color.Black
}
rectangle(sx, sy, es, ey, img, gray)
})
// save to writer
err := jpeg.Encode(w, img, nil)
if err != nil {
return fmt.Errorf("debugDrawTo: encode image in JPEG failed: %v", err)
}
return nil
}

350
vendor/github.com/yeqown/go-qrcode/v2/encoder.go generated vendored Normal file
View File

@ -0,0 +1,350 @@
// Package qrcode ...
// encoder.go working for data encoding
package qrcode
import (
"fmt"
"log"
"github.com/yeqown/reedsolomon/binary"
)
// encMode ...
type encMode uint
const (
// a qrbool of EncModeAuto will trigger a detection of the letter set from the input data,
EncModeAuto = 0
// EncModeNone mode ...
EncModeNone encMode = 1 << iota
// EncModeNumeric mode ...
EncModeNumeric
// EncModeAlphanumeric mode ...
EncModeAlphanumeric
// EncModeByte mode ...
EncModeByte
// EncModeJP mode ...
EncModeJP
)
var (
paddingByte1, _ = binary.NewFromBinaryString("11101100")
paddingByte2, _ = binary.NewFromBinaryString("00010001")
)
// getEncModeName ...
func getEncModeName(mode encMode) string {
switch mode {
case EncModeNone:
return "none"
case EncModeNumeric:
return "numeric"
case EncModeAlphanumeric:
return "alphanumeric"
case EncModeByte:
return "byte"
case EncModeJP:
return "japan"
default:
return "unknown"
}
}
// getEncodeModeIndicator ...
func getEncodeModeIndicator(mode encMode) *binary.Binary {
switch mode {
case EncModeNumeric:
return binary.New(false, false, false, true)
case EncModeAlphanumeric:
return binary.New(false, false, true, false)
case EncModeByte:
return binary.New(false, true, false, false)
case EncModeJP:
return binary.New(true, false, false, false)
default:
panic("no indicator")
}
}
// encoder ... data to bit stream ...
type encoder struct {
// self init
dst *binary.Binary
data []byte // raw input data
// initial params
mode encMode // encode mode
ecLv ecLevel // error correction level
// self load
version version // QR version ref
}
func newEncoder(m encMode, ec ecLevel, v version) *encoder {
return &encoder{
dst: nil,
data: nil,
mode: m,
ecLv: ec,
version: v,
}
}
// Encode ...
// 1. encode raw data into bitset
// 2. append _defaultPadding data
//
func (e *encoder) Encode(byts []byte) (*binary.Binary, error) {
e.dst = binary.New()
e.data = byts
// append mode indicator symbol
indicator := getEncodeModeIndicator(e.mode)
e.dst.Append(indicator)
// append chars length counter bits symbol
e.dst.AppendUint32(uint32(len(byts)), e.charCountBits())
// encode data with specified mode
switch e.mode {
case EncModeNumeric:
e.encodeNumeric()
case EncModeAlphanumeric:
e.encodeAlphanumeric()
case EncModeByte:
e.encodeByte()
case EncModeJP:
panic("this has not been finished")
}
// fill and _defaultPadding bits
e.breakUpInto8bit()
return e.dst, nil
}
// 0001b mode indicator
func (e *encoder) encodeNumeric() {
if e.dst == nil {
log.Println("e.dst is nil")
return
}
for i := 0; i < len(e.data); i += 3 {
charsRemaining := len(e.data) - i
var value uint32
bitsUsed := 1
for j := 0; j < charsRemaining && j < 3; j++ {
value *= 10
value += uint32(e.data[i+j] - 0x30)
bitsUsed += 3
}
e.dst.AppendUint32(value, bitsUsed)
}
}
// 0010b mode indicator
func (e *encoder) encodeAlphanumeric() {
if e.dst == nil {
log.Println("e.dst is nil")
return
}
for i := 0; i < len(e.data); i += 2 {
charsRemaining := len(e.data) - i
var value uint32
for j := 0; j < charsRemaining && j < 2; j++ {
value *= 45
value += encodeAlphanumericCharacter(e.data[i+j])
}
bitsUsed := 6
if charsRemaining > 1 {
bitsUsed = 11
}
e.dst.AppendUint32(value, bitsUsed)
}
}
// 0100b mode indicator
func (e *encoder) encodeByte() {
if e.dst == nil {
log.Println("e.dst is nil")
return
}
for _, b := range e.data {
_ = e.dst.AppendByte(b, 8)
}
}
// Break Up into 8-bit Codewords and Add Pad Bytes if Necessary
func (e *encoder) breakUpInto8bit() {
// fill ending code (max 4bit)
// depends on max capacity of current version and EC level
maxCap := e.version.NumTotalCodewords() * 8
if less := maxCap - e.dst.Len(); less < 0 {
err := fmt.Errorf(
"wrong version(%d) cap(%d bits) and could not contain all bits: %d bits",
e.version.Ver, maxCap, e.dst.Len(),
)
panic(err)
} else if less < 4 {
e.dst.AppendNumBools(less, false)
} else {
e.dst.AppendNumBools(4, false)
}
// append `0` to be 8 times bits length
if mod := e.dst.Len() % 8; mod != 0 {
e.dst.AppendNumBools(8-mod, false)
}
// _defaultPadding bytes
// _defaultPadding byte 11101100 00010001
if n := maxCap - e.dst.Len(); n > 0 {
debugLogf("maxCap: %d, len: %d, less: %d", maxCap, e.dst.Len(), n)
for i := 1; i <= (n / 8); i++ {
if i%2 == 1 {
e.dst.Append(paddingByte1)
} else {
e.dst.Append(paddingByte2)
}
}
}
}
// 字符计数指示符位长字典
var charCountMap = map[string]int{
"9_numeric": 10,
"9_alphanumeric": 9,
"9_byte": 8,
"9_japan": 8,
"26_numeric": 12,
"26_alphanumeric": 11,
"26_byte": 16,
"26_japan": 10,
"40_numeric": 14,
"40_alphanumeric": 13,
"40_byte": 16,
"40_japan": 12,
}
// charCountBits
func (e *encoder) charCountBits() int {
var lv int
if v := e.version.Ver; v <= 9 {
lv = 9
} else if v <= 26 {
lv = 26
} else {
lv = 40
}
pos := fmt.Sprintf("%d_%s", lv, getEncModeName(e.mode))
return charCountMap[pos]
}
// v must be a QR Code defined alphanumeric character: 0-9, A-Z, SP, $%*+-./ or
// :. The characters are mapped to values in the range 0-44 respectively.
func encodeAlphanumericCharacter(v byte) uint32 {
c := uint32(v)
switch {
case c >= '0' && c <= '9':
// 0-9 encoded as 0-9.
return c - '0'
case c >= 'A' && c <= 'Z':
// A-Z encoded as 10-35.
return c - 'A' + 10
case c == ' ':
return 36
case c == '$':
return 37
case c == '%':
return 38
case c == '*':
return 39
case c == '+':
return 40
case c == '-':
return 41
case c == '.':
return 42
case c == '/':
return 43
case c == ':':
return 44
default:
log.Panicf("encodeAlphanumericCharacter() with non alphanumeric char %c", v)
}
return 0
}
// analyzeEncFunc returns true is current byte matched in current mode,
// otherwise means you should use a bigger character set to check.
type analyzeEncFunc func(byte) bool
// analyzeEncodeModeFromRaw try to detect letter set of input data,
// so that encoder can determine which mode should be use.
// reference: https://en.wikipedia.org/wiki/QR_code
//
// case1: only numbers, use EncModeNumeric.
// case2: could not use EncModeNumeric, but you can find all of them in character mapping, use EncModeAlphanumeric.
// case3: could not use EncModeAlphanumeric, but you can find all of them in ISO-8859-1 character set, use EncModeByte.
// case4: could not use EncModeByte, use EncModeJP, no more choice.
//
func analyzeEncodeModeFromRaw(raw []byte) encMode {
analyzeFnMapping := map[encMode]analyzeEncFunc{
EncModeNumeric: analyzeNum,
EncModeAlphanumeric: analyzeAlphaNum,
EncModeByte: nil,
EncModeJP: nil,
}
var (
f analyzeEncFunc
mode = EncModeNumeric
)
// loop to check each character in raw data,
// from low mode to higher while current mode could bearing the input data.
for _, byt := range raw {
reAnalyze:
if f = analyzeFnMapping[mode]; f == nil {
break
}
// issue#28 @borislavone reports this bug.
// FIXED(@yeqown): next encMode analyzeVersionAuto func did not check the previous byte,
// add goto statement to reanalyze previous byte which can't be analyzed in last encMode.
if !f(byt) {
mode <<= 1
goto reAnalyze
}
}
return mode
}
// analyzeNum is byt in num encMode
func analyzeNum(byt byte) bool {
return byt >= '0' && byt <= '9'
}
// analyzeAlphaNum is byt in alpha number
func analyzeAlphaNum(byt byte) bool {
if (byt >= '0' && byt <= '9') || (byt >= 'A' && byt <= 'Z') {
return true
}
switch byt {
case ' ', '$', '%', '*', '+', '-', '.', '/', ':':
return true
}
return false
}
//// analyzeByte is byt in bytes.
//func analyzeByte(byt byte) qrbool {
// return false
//}

View File

@ -0,0 +1,71 @@
package qrcode
type EncodeOption interface {
apply(option *encodingOption)
}
// DefaultEncodingOption with EncMode = EncModeAuto, EcLevel = ErrorCorrectionQuart
func DefaultEncodingOption() *encodingOption {
return &encodingOption{
EncMode: EncModeAuto,
EcLevel: ErrorCorrectionQuart,
}
}
type encodingOption struct {
// Version of target QR code.
Version int
// EncMode specifies which encMode to use
EncMode encMode
// EcLevel specifies which ecLevel to use
EcLevel ecLevel
// PS: The version (which implicitly defines the byte capacity of the qrcode) is dynamically selected at runtime
}
type fnEncodingOption struct {
fn func(*encodingOption)
}
func (f fnEncodingOption) apply(option *encodingOption) {
f.fn(option)
}
func newFnEncodingOption(fn func(*encodingOption)) fnEncodingOption {
return fnEncodingOption{fn: fn}
}
// WithEncodingMode sets the encoding mode.
func WithEncodingMode(mode encMode) EncodeOption {
return newFnEncodingOption(func(option *encodingOption) {
if name := getEncModeName(mode); name == "" {
return
}
option.EncMode = mode
})
}
// WithErrorCorrectionLevel sets the error correction level.
func WithErrorCorrectionLevel(ecLevel ecLevel) EncodeOption {
return newFnEncodingOption(func(option *encodingOption) {
if ecLevel < ErrorCorrectionLow || ecLevel > ErrorCorrectionHighest {
return
}
option.EcLevel = ecLevel
})
}
// WithVersion sets the version of target QR code.
func WithVersion(version int) EncodeOption {
return newFnEncodingOption(func(option *encodingOption) {
if version < 1 || version > 40 {
return
}
option.Version = version
})
}

59
vendor/github.com/yeqown/go-qrcode/v2/kmp_variant.go generated vendored Normal file
View File

@ -0,0 +1,59 @@
package qrcode
// kmp is variant of kmp algorithm to count the pattern been in
// src slice.
// DONE(@yeqown): implement this in generic way.
func kmp[v comparable](src, pattern []v, next []int) (count int) {
if next == nil {
next = kmpGetNext(pattern)
}
slen := len(src)
plen := len(pattern)
i := 0 // cursor of src
j := 0 // cursor of pattern
loop:
for i < slen && j < plen {
if j == -1 || src[i] == pattern[j] {
i++
j++
} else {
j = next[j]
}
}
if j == plen {
if i-j >= 0 {
count++
}
// reset cursor to count duplicate pattern.
// such as: "aaaa" and "aa", we want 3 rather than 2.
i -= plen - 1
j = 0
goto loop
}
return count
}
func kmpGetNext[v comparable](pattern []v) []int {
fail := make([]int, len(pattern))
fail[0] = -1
j := 0
k := -1
for j < len(pattern)-1 {
if k == -1 || pattern[j] == pattern[k] {
k++
j++
fail[j] = k
} else {
k = fail[k]
}
}
return fail
}

138
vendor/github.com/yeqown/go-qrcode/v2/mask.go generated vendored Normal file
View File

@ -0,0 +1,138 @@
package qrcode
// maskPatternModulo ...
// mask Pattern ref to: https://www.thonky.com/qr-code-tutorial/mask-patterns
type maskPatternModulo uint32
const (
// modulo0 (x+y) mod 2 == 0
modulo0 maskPatternModulo = iota
// modulo1 (x) mod 2 == 0
modulo1
// modulo2 (y) mod 3 == 0
modulo2
// modulo3 (x+y) mod 3 == 0
modulo3
// modulo4 (floor (x/ 2) + floor (y/ 3) mod 2 == 0
modulo4
// modulo5 (x * y) mod 2) + (x * y) mod 3) == 0
modulo5
// modulo6 (x * y) mod 2) + (x * y) mod 3) mod 2 == 0
modulo6
// modulo7 (x + y) mod 2) + (x * y) mod 3) mod 2 == 0
modulo7
)
type mask struct {
mat *Matrix // matrix
mode maskPatternModulo // mode
moduloFn moduloFunc // moduloFn masking function
}
// newMask ...
func newMask(mat *Matrix, mode maskPatternModulo) *mask {
m := &mask{
mat: mat.Copy(),
mode: mode,
moduloFn: getModuloFunc(mode),
}
m.masking()
return m
}
// moduloFunc to define what's modulo func
type moduloFunc func(int, int) bool
func getModuloFunc(mode maskPatternModulo) (f moduloFunc) {
f = nil
switch mode {
case modulo0:
f = modulo0Func
case modulo1:
f = modulo1Func
case modulo2:
f = modulo2Func
case modulo3:
f = modulo3Func
case modulo4:
f = modulo4Func
case modulo5:
f = modulo5Func
case modulo6:
f = modulo6Func
case modulo7:
f = modulo7Func
}
return
}
// init generate maks by mode
func (m *mask) masking() {
moduloFn := m.moduloFn
if moduloFn == nil {
panic("impossible panic, contact maintainer plz")
}
m.mat.iter(IterDirection_COLUMN, func(x, y int, s qrvalue) {
// skip the function modules
if v, _ := m.mat.at(x, y); v.qrtype() != QRType_INIT {
_ = m.mat.set(x, y, QRValue_INIT_V0)
return
}
if moduloFn(x, y) {
_ = m.mat.set(x, y, QRValue_DATA_V1)
} else {
_ = m.mat.set(x, y, QRValue_DATA_V0)
}
})
}
// modulo0Func for maskPattern function
// modulo0 (x+y) mod 2 == 0
func modulo0Func(x, y int) bool {
return (x+y)%2 == 0
}
// modulo1Func for maskPattern function
// modulo1 (y) mod 2 == 0
func modulo1Func(x, y int) bool {
return y%2 == 0
}
// modulo2Func for maskPattern function
// modulo2 (x) mod 3 == 0
func modulo2Func(x, y int) bool {
return x%3 == 0
}
// modulo3Func for maskPattern function
// modulo3 (x+y) mod 3 == 0
func modulo3Func(x, y int) bool {
return (x+y)%3 == 0
}
// modulo4Func for maskPattern function
// modulo4 (floor (x/ 2) + floor (y/ 3) mod 2 == 0
func modulo4Func(x, y int) bool {
return (x/3+y/2)%2 == 0
}
// modulo5Func for maskPattern function
// modulo5 (x * y) mod 2 + (x * y) mod 3 == 0
func modulo5Func(x, y int) bool {
return (x*y)%2+(x*y)%3 == 0
}
// modulo6Func for maskPattern function
// modulo6 (x * y) mod 2) + (x * y) mod 3) mod 2 == 0
func modulo6Func(x, y int) bool {
return ((x*y)%2+(x*y)%3)%2 == 0
}
// modulo7Func for maskPattern function
// modulo7 (x + y) mod 2) + (x * y) mod 3) mod 2 == 0
func modulo7Func(x, y int) bool {
return ((x+y)%2+(x*y)%3)%2 == 0
}

View File

@ -0,0 +1,172 @@
package qrcode
import (
"math"
)
// evaluation calculate a score after masking matrix.
//
// reference:
// - https://www.thonky.com/qr-code-tutorial/data-masking#Determining-the-Best-Mask
func evaluation(mat *Matrix) (score int) {
debugLogf("calculate maskScore starting")
score1 := rule1(mat)
score2 := rule2(mat)
score3 := rule3(mat)
score4 := rule4(mat)
score = score1 + score2 + score3 + score4
debugLogf("maskScore: rule1=%d, rule2=%d, rule3=%d, rule4=%d", score1, score2, score3, score4)
return score
}
// check each row one-by-one. If there are five consecutive modules of the same color,
// add 3 to the penalty. If there are more modules of the same color after the first five,
// add 1 for each additional module of the same color. Afterward, check each column one-by-one,
// checking for the same condition. Add the horizontal and vertical total to obtain penalty score
func rule1(mat *Matrix) (score int) {
// prerequisites:
// mat.Width() == mat.Height()
if mat.Width() != mat.Height() {
debugLogf("matrix width != height, skip rule1")
return math.MaxInt32
}
dimension := mat.Width()
scoreLine := func(arr []qrvalue) int {
lScore, cnt, cur := 0, 0, QRValue_INIT_V0
for _, v := range arr {
if !samestate(v, cur) {
cur = v
cnt = 1
continue
}
cnt++
if cnt == 5 {
lScore += 3
} else if cnt > 5 {
lScore++
}
}
return lScore
}
for cur := 0; cur < dimension; cur++ {
row := mat.Row(cur)
col := mat.Col(cur)
score += scoreLine(row)
score += scoreLine(col)
}
return score
}
// rule2
// look for areas of the same color that are at least 2x2 modules or larger.
// The QR code specification says that for a solid-color block of size m × n,
// the penalty score is 3 × (m - 1) × (n - 1).
func rule2(mat *Matrix) int {
var (
score int
s0, s1, s2, s3 qrvalue
)
for x := 0; x < mat.Width()-1; x++ {
for y := 0; y < mat.Height()-1; y++ {
s0, _ = mat.at(x, y)
s1, _ = mat.at(x+1, y)
s2, _ = mat.at(x, y+1)
s3, _ = mat.at(x+1, y+1)
if s0 == s1 && s2 == s3 && s1 == s2 {
score += 3
}
}
}
return score
}
// rule3 calculate punishment score in rule3, find pattern in QR Code matrix.
// Looks for patterns of dark-light-dark-dark-dark-light-dark that have four
// light modules on either side. In other words, it looks for any of the
// following two patterns: 1011101 0000 or 0000 1011101.
//
// Each time this pattern is found, add 40 to the penalty score.
func rule3(mat *Matrix) (score int) {
var (
pattern1 = binaryToQRValueSlice("1011101 0000")
pattern2 = binaryToQRValueSlice("0000 1011101")
pattern1Next = kmpGetNext(pattern1)
pattern2Next = kmpGetNext(pattern2)
)
// prerequisites:
//
// mat.Width() == mat.Height()
if mat.Width() != mat.Height() {
debugLogf("rule3 got matrix but not matched prerequisites")
return math.MaxInt32
}
dimension := mat.Width()
for i := 0; i < dimension; i++ {
col := mat.Col(i)
row := mat.Row(i)
// DONE(@yeqown): statePattern1 and statePattern2 are fixed, so maybe kmpGetNext
// could cache result to speed up.
score += 40 * kmp(col, pattern1, pattern1Next)
score += 40 * kmp(col, pattern2, pattern2Next)
score += 40 * kmp(row, pattern1, pattern1Next)
score += 40 * kmp(row, pattern2, pattern2Next)
}
return score
}
// rule4 is based on the ratio of light modules to dark modules:
//
// 1. Count the total number of modules in the matrix.
// 2. Count how many dark modules there are in the matrix.
// 3. Calculate the percent of modules in the matrix that are dark: (darkmodules / totalmodules) * 100
// 4. Determine the previous and next multiple of five of this percent.
// 5. Subtract 50 from each of these multiples of five and take the absolute qrbool of the result.
// 6. Divide each of these by five. For example, 10/5 = 2 and 5/5 = 1.
// 7. Finally, take the smallest of the two numbers and multiply it by 10.
//
func rule4(mat *Matrix) int {
// prerequisites:
//
// mat.Width() == mat.Height()
if mat.Width() != mat.Height() {
debugLogf("rule4 got matrix but not matched prerequisites")
return math.MaxInt32
}
dimension := mat.Width()
dark, total := 0, dimension*dimension
for i := 0; i < dimension; i++ {
col := mat.Col(i)
// count dark modules
for j := 0; j < dimension; j++ {
if samestate(col[j], QRValue_DATA_V1) {
dark++
}
}
}
ratio := (dark * 100) / total // in range [0, 100]
step := 0
if ratio%5 == 0 {
step = 1
}
previous := abs((ratio/5-step)*5 - 50)
next := abs((ratio/5+1-step)*5 - 50)
return min(previous, next) / 5 * 10
}

167
vendor/github.com/yeqown/go-qrcode/v2/matrix.go generated vendored Normal file
View File

@ -0,0 +1,167 @@
package qrcode
import (
"errors"
"fmt"
)
var (
// ErrorOutRangeOfW x out of range of Width
ErrorOutRangeOfW = errors.New("out of range of width")
// ErrorOutRangeOfH y out of range of Height
ErrorOutRangeOfH = errors.New("out of range of height")
)
// newMatrix generate a matrix with map[][]qrbool
func newMatrix(width, height int) *Matrix {
mat := make([][]qrvalue, width)
for w := 0; w < width; w++ {
mat[w] = make([]qrvalue, height)
}
m := &Matrix{
mat: mat,
width: width,
height: height,
}
m.init()
return m
}
// Matrix is a matrix data type
// width:3 height: 4 for [3][4]int
type Matrix struct {
mat [][]qrvalue
width int
height int
}
// do some init work
func (m *Matrix) init() {
for w := 0; w < m.width; w++ {
for h := 0; h < m.height; h++ {
m.mat[w][h] = QRValue_INIT_V0
}
}
}
// print to stdout
func (m *Matrix) print() {
m.iter(IterDirection_ROW, func(x, y int, s qrvalue) {
fmt.Printf("%s ", s)
if (x + 1) == m.width {
fmt.Println()
}
})
}
// Copy matrix into a new Matrix
func (m *Matrix) Copy() *Matrix {
mat2 := make([][]qrvalue, m.width)
for w := 0; w < m.width; w++ {
mat2[w] = make([]qrvalue, m.height)
copy(mat2[w], m.mat[w])
}
m2 := &Matrix{
width: m.width,
height: m.height,
mat: mat2,
}
return m2
}
// Width ... width
func (m *Matrix) Width() int {
return m.width
}
// Height ... height
func (m *Matrix) Height() int {
return m.height
}
// set [w][h] as true
func (m *Matrix) set(w, h int, c qrvalue) error {
if w >= m.width || w < 0 {
return ErrorOutRangeOfW
}
if h >= m.height || h < 0 {
return ErrorOutRangeOfH
}
m.mat[w][h] = c
return nil
}
// at state qrvalue from matrix with position {x, y}
func (m *Matrix) at(w, h int) (qrvalue, error) {
if w >= m.width || w < 0 {
return QRValue_INIT_V0, ErrorOutRangeOfW
}
if h >= m.height || h < 0 {
return QRValue_INIT_V0, ErrorOutRangeOfH
}
return m.mat[w][h], nil
}
// iterDirection scan matrix direction
type iterDirection uint8
const (
// IterDirection_ROW for row first
IterDirection_ROW iterDirection = iota + 1
// IterDirection_COLUMN for column first
IterDirection_COLUMN
)
// Iterate the Matrix with loop direction IterDirection_ROW major or IterDirection_COLUMN major.
// IterDirection_COLUMN is recommended.
func (m *Matrix) Iterate(direction iterDirection, fn func(x, y int, s QRValue)) {
m.iter(direction, fn)
}
func (m *Matrix) iter(dir iterDirection, visitFn func(x int, y int, v qrvalue)) {
// row direction first
if dir == IterDirection_ROW {
for h := 0; h < m.height; h++ {
for w := 0; w < m.width; w++ {
visitFn(w, h, m.mat[w][h])
}
}
return
}
// column direction first
for w := 0; w < m.width; w++ {
for h := 0; h < m.height; h++ {
visitFn(w, h, m.mat[w][h])
}
}
return
}
// Row return a row of matrix, cur should be y dimension.
func (m *Matrix) Row(cur int) []qrvalue {
if cur >= m.height || cur < 0 {
return nil
}
col := make([]qrvalue, m.height)
for w := 0; w < m.width; w++ {
col[w] = m.mat[w][cur]
}
return col
}
// Col return a slice of column, cur should be x dimension.
func (m *Matrix) Col(cur int) []qrvalue {
if cur >= m.width || cur < 0 {
return nil
}
return m.mat[cur]
}

125
vendor/github.com/yeqown/go-qrcode/v2/matrix_type.go generated vendored Normal file
View File

@ -0,0 +1,125 @@
package qrcode
type QRType = qrtype
// qrtype
type qrtype uint8
const (
// QRType_INIT represents the initial block state of the matrix
QRType_INIT qrtype = 1 << 1
// QRType_DATA represents the data block state of the matrix
QRType_DATA qrtype = 2 << 1
// QRType_VERSION indicates the version block of matrix
QRType_VERSION qrtype = 3 << 1
// QRType_FORMAT indicates the format block of matrix
QRType_FORMAT qrtype = 4 << 1
// QRType_FINDER indicates the finder block of matrix
QRType_FINDER qrtype = 5 << 1
// QRType_DARK ...
QRType_DARK qrtype = 6 << 1
QRType_SPLITTER qrtype = 7 << 1
QRType_TIMING qrtype = 8 << 1
)
func (s qrtype) String() string {
switch s {
case QRType_INIT:
return "I"
case QRType_DATA:
return "d"
case QRType_VERSION:
return "V"
case QRType_FORMAT:
return "f"
case QRType_FINDER:
return "F"
case QRType_DARK:
return "D"
case QRType_SPLITTER:
return "S"
case QRType_TIMING:
return "T"
}
return "?"
}
type QRValue = qrvalue
func (v QRValue) Type() qrtype {
return v.qrtype()
}
func (v QRValue) IsSet() bool {
return v.qrbool()
}
// qrvalue represents the value of the matrix, it is composed of the qrtype(7bits) and the value(1bits).
// such as: 0b0000,0011 (QRValue_DATA_V1) represents the qrtype is QRType_DATA and the value is 1.
type qrvalue uint8
var (
// QRValue_INIT_V0 represents the value 0
QRValue_INIT_V0 = qrvalue(QRType_INIT | 0)
// QRValue_DATA_V0 represents the block has been set to false
QRValue_DATA_V0 = qrvalue(QRType_DATA | 0)
// QRValue_DATA_V1 represents the block has been set to TRUE
QRValue_DATA_V1 = qrvalue(QRType_DATA | 1)
// QRValue_VERSION_V0 represents the block has been set to false
QRValue_VERSION_V0 = qrvalue(QRType_VERSION | 0)
// QRValue_VERSION_V1 represents the block has been set to TRUE
QRValue_VERSION_V1 = qrvalue(QRType_VERSION | 1)
// QRValue_FORMAT_V0 represents the block has been set to false
QRValue_FORMAT_V0 = qrvalue(QRType_FORMAT | 0)
// QRValue_FORMAT_V1 represents the block has been set to TRUE
QRValue_FORMAT_V1 = qrvalue(QRType_FORMAT | 1)
// QRValue_FINDER_V0 represents the block has been set to false
QRValue_FINDER_V0 = qrvalue(QRType_FINDER | 0)
// QRValue_FINDER_V1 represents the block has been set to TRUE
QRValue_FINDER_V1 = qrvalue(QRType_FINDER | 1)
// QRValue_DARK_V0 represents the block has been set to false
QRValue_DARK_V0 = qrvalue(QRType_DARK | 0)
// QRValue_DARK_V1 represents the block has been set to TRUE
QRValue_DARK_V1 = qrvalue(QRType_DARK | 1)
// QRValue_SPLITTER_V0 represents the block has been set to false
QRValue_SPLITTER_V0 = qrvalue(QRType_SPLITTER | 0)
// QRValue_SPLITTER_V1 represents the block has been set to TRUE
QRValue_SPLITTER_V1 = qrvalue(QRType_SPLITTER | 1)
// QRValue_TIMING_V0 represents the block has been set to false
QRValue_TIMING_V0 = qrvalue(QRType_TIMING | 0)
// QRValue_TIMING_V1 represents the block has been set to TRUE
QRValue_TIMING_V1 = qrvalue(QRType_TIMING | 1)
)
func (v qrvalue) qrtype() qrtype {
return qrtype(v & 0xfe)
}
func (v qrvalue) qrbool() bool {
return v&0x01 == 1
}
func (v qrvalue) String() string {
t := v.qrtype()
if v.qrbool() {
return t.String() + "1"
}
return t.String() + "0"
}
func (v qrvalue) xor(v2 qrvalue) qrvalue {
if v != v2 {
return QRValue_DATA_V1
}
return QRValue_DATA_V0
}

738
vendor/github.com/yeqown/go-qrcode/v2/qrcode.go generated vendored Normal file
View File

@ -0,0 +1,738 @@
package qrcode
import (
"fmt"
"log"
"math"
"sync"
"github.com/yeqown/reedsolomon"
"github.com/yeqown/reedsolomon/binary"
)
// New generate a QRCode struct to create
func New(text string) (*QRCode, error) {
dst := DefaultEncodingOption()
return build(text, dst)
}
// NewWith generate a QRCode struct with
// specified `ver`(QR version) and `ecLv`(Error Correction level)
func NewWith(text string, opts ...EncodeOption) (*QRCode, error) {
dst := DefaultEncodingOption()
for _, opt := range opts {
opt.apply(dst)
}
return build(text, dst)
}
func build(text string, option *encodingOption) (*QRCode, error) {
qrc := &QRCode{
sourceText: text,
sourceRawBytes: []byte(text),
dataBSet: nil,
mat: nil,
ecBSet: nil,
v: version{},
encodingOption: option,
encoder: nil,
}
// initialize QRCode instance
if err := qrc.init(); err != nil {
return nil, err
}
qrc.masking()
return qrc, nil
}
// QRCode contains fields to generate QRCode matrix, outputImageOptions to Draw image,
// etc.
type QRCode struct {
sourceText string // sourceText input text
sourceRawBytes []byte // raw Data to transfer
dataBSet *binary.Binary // final data bit stream of encode data
mat *Matrix // matrix grid to store final bitmap
ecBSet *binary.Binary // final error correction bitset
encodingOption *encodingOption
encoder *encoder // encoder ptr to call its methods ~
v version // indicate the QR version to encode.
}
func (q *QRCode) Save(w Writer) error {
if w == nil {
w = nonWriter{}
}
defer func() {
if err := w.Close(); err != nil {
log.Printf("[WARNNING] [go-qrcode] close writer failed: %v\n", err)
}
}()
return w.Write(*q.mat)
}
func (q *QRCode) Dimension() int {
if q.mat == nil {
return 0
}
return q.mat.Width()
}
// init fill QRCode instance from settings and sourceText.
func (q *QRCode) init() (err error) {
// choose encode mode (num, alpha num, byte, Japanese)
if q.encodingOption.EncMode == EncModeAuto {
q.encodingOption.EncMode = analyzeEncodeModeFromRaw(q.sourceRawBytes)
}
// choose version
if _, err = q.calcVersion(); err != nil {
return fmt.Errorf("init: calc version failed: %v", err)
}
q.mat = newMatrix(q.v.Dimension(), q.v.Dimension())
_ = q.applyEncoder()
var (
dataBlocks []dataBlock // data encoding blocks
ecBlocks []ecBlock // error correction blocks
)
// data encoding, and be split into blocks
if dataBlocks, err = q.dataEncoding(); err != nil {
return err
}
// generate er bitsets, and also be split into blocks
if ecBlocks, err = q.errorCorrectionEncoding(dataBlocks); err != nil {
return err
}
// arrange data blocks and EC blocks
q.arrangeBits(dataBlocks, ecBlocks)
// append ec bits after data bits
q.dataBSet.Append(q.ecBSet)
// append remainder bits
q.dataBSet.AppendNumBools(q.v.RemainderBits, false)
// initial the 2d matrix
q.prefillMatrix()
return nil
}
// calcVersion
func (q *QRCode) calcVersion() (ver *version, err error) {
var needAnalyze = true
opt := q.encodingOption
if opt.Version >= 1 && opt.Version <= 40 &&
opt.EcLevel >= ErrorCorrectionLow && opt.EcLevel <= ErrorCorrectionHighest {
// only version and EC level are specified, can skip analyzeVersionAuto
needAnalyze = false
}
// automatically parse version
if needAnalyze {
// analyzeVersion the input data to choose to adapt version
analyzed, err2 := analyzeVersion(q.sourceRawBytes, opt.EcLevel, opt.EncMode)
if err2 != nil {
err = fmt.Errorf("calcVersion: analyzeVersionAuto failed: %v", err2)
return nil, err
}
opt.Version = analyzed.Ver
}
q.v = loadVersion(opt.Version, opt.EcLevel)
return
}
// applyEncoder
func (q *QRCode) applyEncoder() error {
q.encoder = newEncoder(q.encodingOption.EncMode, q.encodingOption.EcLevel, q.v)
return nil
}
// dataEncoding ref to:
// https://www.thonky.com/qr-code-tutorial/data-encoding
func (q *QRCode) dataEncoding() (blocks []dataBlock, err error) {
var (
bset *binary.Binary
)
bset, err = q.encoder.Encode(q.sourceRawBytes)
if err != nil {
err = fmt.Errorf("could not encode data: %v", err)
return
}
blocks = make([]dataBlock, q.v.TotalNumBlocks())
// split bitset into data Block
start, end, blockID := 0, 0, 0
for _, g := range q.v.Groups {
for j := 0; j < g.NumBlocks; j++ {
start = end
end = start + g.NumDataCodewords*8
blocks[blockID].Data, err = bset.Subset(start, end)
if err != nil {
panic(err)
}
blocks[blockID].StartOffset = end - start
blocks[blockID].NumECBlock = g.ECBlockwordsPerBlock
blockID++
}
}
return
}
// dataBlock ...
type dataBlock struct {
Data *binary.Binary
StartOffset int // length
NumECBlock int // error correction codewords num per data block
}
// ecBlock ...
type ecBlock struct {
Data *binary.Binary
// StartOffset int // length
}
// errorCorrectionEncoding ref to:
// https://www.thonky.com/qr-code-tutorial /error-correction-coding
func (q *QRCode) errorCorrectionEncoding(dataBlocks []dataBlock) (blocks []ecBlock, err error) {
// start, end, blockID := 0, 0, 0
blocks = make([]ecBlock, q.v.TotalNumBlocks())
for idx, b := range dataBlocks {
debugLogf("numOfECBlock: %d", b.NumECBlock)
bset := reedsolomon.Encode(b.Data, b.NumECBlock)
blocks[idx].Data, err = bset.Subset(b.StartOffset, bset.Len())
if err != nil {
panic(err)
}
// blocks[idx].StartOffset = b.StartOffset
}
return
}
// arrangeBits ... and save into dataBSet
func (q *QRCode) arrangeBits(dataBlocks []dataBlock, ecBlocks []ecBlock) {
if debugEnabled() {
log.Println("arrangeBits called, before")
for i := 0; i < len(ecBlocks); i++ {
debugLogf("ec block_%d: %v", i, ecBlocks[i])
}
for i := 0; i < len(dataBlocks); i++ {
debugLogf("data block_%d: %v", i, dataBlocks[i])
}
}
// arrange data blocks
var (
overflowCnt = 0
endFlag = false
curIdx = 0
start, end int
)
// check if bitsets initialized, or initial them
if q.dataBSet == nil {
q.dataBSet = binary.New()
}
if q.ecBSet == nil {
q.ecBSet = binary.New()
}
for !endFlag {
for _, block := range dataBlocks {
start = curIdx * 8
end = start + 8
if start >= block.Data.Len() {
overflowCnt++
continue
}
subBin, err := block.Data.Subset(start, end)
if err != nil {
panic(err)
}
q.dataBSet.Append(subBin)
debugLogf("arrange data blocks info: start: %d, end: %d, len: %d, overflowCnt: %d, curIdx: %d",
start, end, block.Data.Len(), overflowCnt, curIdx,
)
}
curIdx++
// loop finish check
if overflowCnt >= len(dataBlocks) {
endFlag = true
}
}
// arrange ec blocks and reinitialize
endFlag = false
overflowCnt = 0
curIdx = 0
for !endFlag {
for _, block := range ecBlocks {
start = curIdx * 8
end = start + 8
if start >= block.Data.Len() {
overflowCnt++
continue
}
subBin, err := block.Data.Subset(start, end)
if err != nil {
panic(err)
}
q.ecBSet.Append(subBin)
}
curIdx++
// loop finish check
if overflowCnt >= len(ecBlocks) {
endFlag = true
}
}
debugLogf("arrangeBits called, after")
debugLogf("data bitsets: %s", q.dataBSet.String())
debugLogf("ec bitsets: %s", q.ecBSet.String())
}
// prefillMatrix with version info: ref to:
// http://www.thonky.com/qr-code-tutorial/module-placement-matrix
func (q *QRCode) prefillMatrix() {
dimension := q.v.Dimension()
if q.mat == nil {
q.mat = newMatrix(dimension, dimension)
}
// add finder left-top
addFinder(q.mat, 0, 0)
addSplitter(q.mat, 7, 7, dimension)
debugLogf("finish left-top finder")
// add finder right-top
addFinder(q.mat, dimension-7, 0)
addSplitter(q.mat, dimension-8, 7, dimension)
debugLogf("finish right-top finder")
// add finder left-bottom
addFinder(q.mat, 0, dimension-7)
addSplitter(q.mat, 7, dimension-8, dimension)
debugLogf("finish left-bottom finder")
// only version-1 QR code has no alignment module
if q.v.Ver > 1 {
// add align-mode related to version cfg
for _, loc := range loadAlignmentPatternLoc(q.v.Ver) {
addAlignment(q.mat, loc.X, loc.Y)
}
debugLogf("finish align")
}
// add timing line
addTimingLine(q.mat, dimension)
// add darkBlock always be position (4*ver+9, 8)
addDarkBlock(q.mat, 8, 4*q.v.Ver+9)
// reserveFormatBlock for version and format info
reserveFormatBlock(q.mat, dimension)
// reserveVersionBlock for version over 7
// only version 7 and larger version should add version info
if q.v.Ver >= 7 {
reserveVersionBlock(q.mat, dimension)
}
}
// add finder module
func addFinder(m *Matrix, top, left int) {
// black outer
x, y := top, left
for i := 0; i < 24; i++ {
_ = m.set(x, y, QRValue_FINDER_V1)
if i < 6 {
x = x + 1
} else if i < 12 {
y = y + 1
} else if i < 18 {
x = x - 1
} else {
y = y - 1
}
}
// white inner
x, y = top+1, left+1
for i := 0; i < 16; i++ {
_ = m.set(x, y, QRValue_FINDER_V0)
if i < 4 {
x = x + 1
} else if i < 8 {
y = y + 1
} else if i < 12 {
x = x - 1
} else {
y = y - 1
}
}
// black inner
for x = left + 2; x < left+5; x++ {
for y = top + 2; y < top+5; y++ {
_ = m.set(x, y, QRValue_FINDER_V1)
}
}
}
// add splitter module
func addSplitter(m *Matrix, x, y, dimension int) {
// top-left
if x == 7 && y == 7 {
for pos := 0; pos < 8; pos++ {
_ = m.set(x, pos, QRValue_SPLITTER_V0)
_ = m.set(pos, y, QRValue_SPLITTER_V0)
}
return
}
// top-right
if x == dimension-8 && y == 7 {
for pos := 0; pos < 8; pos++ {
_ = m.set(x, y-pos, QRValue_SPLITTER_V0)
_ = m.set(x+pos, y, QRValue_SPLITTER_V0)
}
return
}
// bottom-left
if x == 7 && y == dimension-8 {
for pos := 0; pos < 8; pos++ {
_ = m.set(x, y+pos, QRValue_SPLITTER_V0)
_ = m.set(x-pos, y, QRValue_SPLITTER_V0)
}
return
}
}
// add matrix align module
func addAlignment(m *Matrix, centerX, centerY int) {
_ = m.set(centerX, centerY, QRValue_DATA_V1)
// black
x, y := centerX-2, centerY-2
for i := 0; i < 16; i++ {
_ = m.set(x, y, QRValue_DATA_V1)
if i < 4 {
x = x + 1
} else if i < 8 {
y = y + 1
} else if i < 12 {
x = x - 1
} else {
y = y - 1
}
}
// white
x, y = centerX-1, centerY-1
for i := 0; i < 8; i++ {
_ = m.set(x, y, QRValue_DATA_V0)
if i < 2 {
x = x + 1
} else if i < 4 {
y = y + 1
} else if i < 6 {
x = x - 1
} else {
y = y - 1
}
}
}
// addTimingLine ...
func addTimingLine(m *Matrix, dimension int) {
for pos := 8; pos < dimension-8; pos++ {
if pos%2 == 0 {
_ = m.set(6, pos, QRValue_TIMING_V1)
_ = m.set(pos, 6, QRValue_TIMING_V1)
} else {
_ = m.set(6, pos, QRValue_TIMING_V0)
_ = m.set(pos, 6, QRValue_TIMING_V0)
}
}
}
// addDarkBlock ...
func addDarkBlock(m *Matrix, x, y int) {
_ = m.set(x, y, QRValue_DARK_V1)
}
// reserveFormatBlock maintain the position in matrix for format info
func reserveFormatBlock(m *Matrix, dimension int) {
for pos := 1; pos < 9; pos++ {
// skip timing line
if pos == 6 {
_ = m.set(8, dimension-pos, QRValue_FORMAT_V0)
_ = m.set(dimension-pos, 8, QRValue_FORMAT_V0)
continue
}
// skip dark module
if pos == 8 {
_ = m.set(8, pos, QRValue_FORMAT_V0) // top-left-column
_ = m.set(pos, 8, QRValue_FORMAT_V0) // top-left-row
_ = m.set(dimension-pos, 8, QRValue_FORMAT_V0) // top-right-row
continue
}
_ = m.set(8, pos, QRValue_FORMAT_V0) // top-left-column
_ = m.set(pos, 8, QRValue_FORMAT_V0) // top-left-row
_ = m.set(dimension-pos, 8, QRValue_FORMAT_V0) // top-right-row
_ = m.set(8, dimension-pos, QRValue_FORMAT_V0) // bottom-left-column
}
// fix(@yeqown): b4b5ae3 reduced two format reversed blocks on top-left-column and top-left-row.
_ = m.set(0, 8, QRValue_FORMAT_V0)
_ = m.set(8, 0, QRValue_FORMAT_V0)
}
// reserveVersionBlock maintain the position in matrix for version info
func reserveVersionBlock(m *Matrix, dimension int) {
// 3x6=18 cells
for i := 1; i <= 3; i++ {
for pos := 0; pos < 6; pos++ {
_ = m.set(dimension-8-i, pos, QRValue_VERSION_V0)
_ = m.set(pos, dimension-8-i, QRValue_VERSION_V0)
}
}
}
// fillDataBinary fill q.dataBSet binary stream into q.mat.
// References:
// * http://www.thonky.com/qr-code-tutorial/module-placement-matrix#Place-the-Data-Bits
//
func (q *QRCode) fillDataBinary(m *Matrix, dimension int) {
var (
// x always move from right, left right loop (2 rows), y move upward, downward, upward loop
x, y = dimension - 1, dimension - 1
l = q.dataBSet.Len()
upForward = true
pos int
)
for i := 0; pos < l; i++ {
// debugLogf("fillDataBinary: dimension: %d, len: %d: pos: %d", dimension, l, pos)
set := QRValue_DATA_V0
if q.dataBSet.At(pos) {
set = QRValue_DATA_V1
}
state, err := m.at(x, y)
if err != nil {
if err == ErrorOutRangeOfW {
break
}
if err == ErrorOutRangeOfH {
// turn around while y is out of range.
x = x - 2
switch upForward {
case true:
y = y + 1
default:
y = y - 1
}
if x == 7 || x == 6 {
x = x - 1
}
upForward = !upForward
state, err = m.at(x, y) // renew state qrbool after turn around writing direction.
}
}
// data bit should only be set into un-set block in matrix.
if state.qrtype() == QRType_INIT {
_ = m.set(x, y, set)
pos++
debugLogf("normal set turn forward: upForward: %v, x: %d, y: %d", upForward, x, y)
}
// DO NOT CHANGE FOLLOWING CODE FOR NOW !!!
// change x, y
mod2 := i % 2
// in one 8bit block
if upForward {
if mod2 == 0 {
x = x - 1
} else {
y = y - 1
x = x + 1
}
} else {
if mod2 == 0 {
x = x - 1
} else {
y = y + 1
x = x + 1
}
}
}
debugLogf("fillDone and x: %d, y: %d, pos: %d, total: %d", x, y, pos, l)
}
// draw from bitset to matrix.Matrix, calculate all mask modula score,
// then decide which mask to use according to the mask's score (the lowest one).
func (q *QRCode) masking() {
type maskScore struct {
Score int
Idx int
}
var (
masks = make([]*mask, 8)
mats = make([]*Matrix, 8)
lowScore = math.MaxInt32
markMatsIdx int
scoreChan = make(chan maskScore, 8)
wg sync.WaitGroup
)
dimension := q.v.Dimension()
// fill bitset into matrix
cpy := q.mat.Copy()
q.fillDataBinary(cpy, dimension)
// init mask and mats
for i := 0; i < 8; i++ {
masks[i] = newMask(q.mat, maskPatternModulo(i))
mats[i] = cpy.Copy()
}
// generate 8 matrix with mask
for i := 0; i < 8; i++ {
wg.Add(1)
go func(i int) {
_ = debugDraw(fmt.Sprintf("draft/mats_%d.jpeg", i), *mats[i])
_ = debugDraw(fmt.Sprintf("draft/mask_%d.jpeg", i), *masks[i].mat)
// xor with mask
q.xorMask(mats[i], masks[i])
_ = debugDraw(fmt.Sprintf("draft/mats_mask_%d.jpeg", i), *mats[i])
// fill format info
q.fillFormatInfo(mats[i], maskPatternModulo(i), dimension)
// version7 and larger version has version info
if q.v.Ver >= 7 {
q.fillVersionInfo(mats[i], dimension)
}
// calculate score and decide the lowest score and Draw
score := evaluation(mats[i])
debugLogf("cur idx: %d, score: %d, current lowest: mats[%d]:%d", i, score, markMatsIdx, lowScore)
scoreChan <- maskScore{
Score: score,
Idx: i,
}
_ = debugDraw(fmt.Sprintf("draft/qrcode_mask_%d.jpeg", i), *mats[i])
wg.Done()
}(i)
}
wg.Wait()
close(scoreChan)
for c := range scoreChan {
if c.Score < lowScore {
lowScore = c.Score
markMatsIdx = c.Idx
}
}
q.mat = mats[markMatsIdx]
}
// all mask patter and check the maskScore choose the lowest mask result
func (q *QRCode) xorMask(m *Matrix, mask *mask) {
mask.mat.iter(IterDirection_COLUMN, func(x, y int, v qrvalue) {
// skip the empty place
if v.qrtype() == QRType_INIT {
return
}
v2, _ := m.at(x, y)
_ = m.set(x, y, v2.xor(v))
})
}
// fillVersionInfo ref to:
// https://www.thonky.com/qr-code-tutorial/format-version-tables
func (q *QRCode) fillVersionInfo(m *Matrix, dimension int) {
bin := q.v.verInfo()
// from high bit to lowest
pos := 0
for j := 5; j >= 0; j-- {
for i := 1; i <= 3; i++ {
if bin.At(pos) {
_ = m.set(dimension-8-i, j, QRValue_VERSION_V1)
_ = m.set(j, dimension-8-i, QRValue_VERSION_V1)
} else {
_ = m.set(dimension-8-i, j, QRValue_VERSION_V0)
_ = m.set(j, dimension-8-i, QRValue_VERSION_V0)
}
pos++
}
}
}
// fill format info ref to:
// https://www.thonky.com/qr-code-tutorial/format-version-tables
func (q *QRCode) fillFormatInfo(m *Matrix, mode maskPatternModulo, dimension int) {
fmtBSet := q.v.formatInfo(int(mode))
debugLogf("fmtBitSet: %s", fmtBSet.String())
var (
x, y = 0, dimension - 1
)
for pos := 0; pos < 15; pos++ {
if fmtBSet.At(pos) {
// row
_ = m.set(x, 8, QRValue_FORMAT_V1)
// column
_ = m.set(8, y, QRValue_FORMAT_V1)
} else {
// row
_ = m.set(x, 8, QRValue_FORMAT_V0)
// column
_ = m.set(8, y, QRValue_FORMAT_V0)
}
x = x + 1
y = y - 1
// row skip
if x == 6 {
x = 7
} else if x == 8 {
x = dimension - 8
}
// column skip
if y == dimension-8 {
y = 8
} else if y == 6 {
y = 5
}
}
}

38
vendor/github.com/yeqown/go-qrcode/v2/utilities.go generated vendored Normal file
View File

@ -0,0 +1,38 @@
package qrcode
// samestate judge two matrix qrtype is same with binary semantic.
// QRValue_DATA_V0/QRType_INIT only equal to QRValue_DATA_V0, other state are equal to each other.
func samestate(s1, s2 qrvalue) bool {
return s1.qrbool() == s2.qrbool()
}
func abs(x int) int {
if x < 0 {
return -x
}
return x
}
func min(x, y int) int {
if x < y {
return x
}
return y
}
func binaryToQRValueSlice(s string) []qrvalue {
var states = make([]qrvalue, 0, len(s))
for _, c := range s {
switch c {
case '1':
states = append(states, QRValue_DATA_V1)
case '0':
states = append(states, QRValue_DATA_V0)
default:
continue
}
}
return states
}

409
vendor/github.com/yeqown/go-qrcode/v2/version.go generated vendored Normal file
View File

@ -0,0 +1,409 @@
package qrcode
import (
"errors"
"log"
"strconv"
// "github.com/skip2/go-qrcode/bitset"
"github.com/yeqown/reedsolomon/binary"
)
// ecLevel error correction level
type ecLevel int
const (
// ErrorCorrectionLow :Level L: 7% error recovery.
ErrorCorrectionLow ecLevel = iota + 1
// ErrorCorrectionMedium :Level M: 15% error recovery. Good default choice.
ErrorCorrectionMedium
// ErrorCorrectionQuart :Level Q: 25% error recovery.
ErrorCorrectionQuart
// ErrorCorrectionHighest :Level H: 30% error recovery.
ErrorCorrectionHighest
formatInfoBitsNum = 15 // format info bits num
verInfoBitsNum = 18 // version info length bits num
)
var (
errInvalidErrorCorrectionLevel = errors.New("invalid error correction level")
errAnalyzeVersionFailed = errors.New("could not match version! " +
"check your content length is in limitation of encode mode and error correction level")
errMissMatchedVersion = errors.New("could not match version")
errMissMatchedEncodeType = errors.New("could not match the encode type")
// versions []version
// Each QR Code contains a 15-bit Format Information qrbool. The 15 bits
// consist of 5 data bits concatenated with 10 error correction bits.
//
// The 5 data bits consist of:
// - 2 bits for the error correction level (L=01, M=00, G=11, H=10).
// - 3 bits for the data mask pattern identifier.
//
// formatBitSequence is a mapping from the 5 data bits to the completed 15-bit
// Format Information qrbool.
//
// For example, a QR Code using error correction level L, and data mask
// pattern identifier 001:
//
// 01 | 001 = 01001 = 0x9
// formatBitSequence[0x9].qrCode = 0x72f3 = 111001011110011
formatBitSequence = []struct {
regular uint32
micro uint32
}{
{0x5412, 0x4445}, {0x5125, 0x4172}, {0x5e7c, 0x4e2b}, {0x5b4b, 0x4b1c},
{0x45f9, 0x55ae}, {0x40ce, 0x5099}, {0x4f97, 0x5fc0}, {0x4aa0, 0x5af7},
{0x77c4, 0x6793}, {0x72f3, 0x62a4}, {0x7daa, 0x6dfd}, {0x789d, 0x68ca},
{0x662f, 0x7678}, {0x6318, 0x734f}, {0x6c41, 0x7c16}, {0x6976, 0x7921},
{0x1689, 0x06de}, {0x13be, 0x03e9}, {0x1ce7, 0x0cb0}, {0x19d0, 0x0987},
{0x0762, 0x1735}, {0x0255, 0x1202}, {0x0d0c, 0x1d5b}, {0x083b, 0x186c},
{0x355f, 0x2508}, {0x3068, 0x203f}, {0x3f31, 0x2f66}, {0x3a06, 0x2a51},
{0x24b4, 0x34e3}, {0x2183, 0x31d4}, {0x2eda, 0x3e8d}, {0x2bed, 0x3bba},
}
// QR Codes version 7 and higher contain an 18-bit version Information qrbool,
// consisting of a 6 data bits and 12 error correction bits.
//
// versionBitSequence is a mapping from QR Code version to the completed
// 18-bit version Information qrbool.
//
// For example, a QR code of version 7:
// versionBitSequence[0x7] = 0x07c94 = 000111110010010100
versionBitSequence = []uint32{
0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x07c94,
0x085bc, 0x09a99, 0x0a4d3, 0x0bbf6, 0x0c762, 0x0d847, 0x0e60d, 0x0f928,
0x10b78, 0x1145d, 0x12a17, 0x13532, 0x149a6, 0x15683, 0x168c9, 0x177ec,
0x18ec4, 0x191e1, 0x1afab, 0x1b08e, 0x1cc1a, 0x1d33f, 0x1ed75, 0x1f250,
0x209d5, 0x216f0, 0x228ba, 0x2379f, 0x24b0b, 0x2542e, 0x26a64, 0x27541, 0x28c69,
}
)
// capacity struct includes data type max capacity
type capacity struct {
Numeric int `json:"n"` // num capacity
AlphaNumeric int `json:"a"` // char capacity
Byte int `json:"b"` // byte capacity (utf-8 also)
JP int `json:"j"` // Japanese capacity
}
// group contains fields to generate ECBlocks
// and append _defaultPadding bit
type group struct {
// NumBlocks num of blocks
NumBlocks int `json:"nbs"`
// NumDataCodewords Number of data codewords.
NumDataCodewords int `json:"ndcs"`
// ECBlockwordsPerBlock ...
ECBlockwordsPerBlock int `json:"ecbs_pb"`
}
// version ...
type version struct {
// version code 1-40
Ver int `json:"ver"`
// ECLevel error correction 0, 1, 2, 3
ECLevel ecLevel `json:"eclv"`
// Cap includes each type's max capacity (specified by `Ver` and `ecLevel`)
// ref to: https://www.thonky.com/qr-code-tutorial/character-capacities
Cap capacity `json:"cap"`
// RemainderBits remainder bits need to append finally
RemainderBits int `json:"rembits"`
// groups info to generate
// ref to: https://www.thonky.com/qr-code-tutorial/error-correction-table
// numGroup = len(Groups)
Groups []group `json:"groups"`
}
// Dimension ...
func (v version) Dimension() int {
return v.Ver*4 + 17
}
// NumTotalCodewords total data codewords
func (v version) NumTotalCodewords() int {
var total int
for _, g := range v.Groups {
total = total + (g.NumBlocks * g.NumDataCodewords)
}
return total
}
// NumGroups ... need group num. ref to version config file
func (v version) NumGroups() int {
return len(v.Groups)
}
// TotalNumBlocks ... total data blocks num, ref to version config file
func (v version) TotalNumBlocks() int {
var total int
for _, g := range v.Groups {
total = total + g.NumBlocks
}
return total
}
// VerInfo version info bitset
func (v version) verInfo() *binary.Binary {
if v.Ver < 7 {
return nil
}
result := binary.New()
result.AppendUint32(versionBitSequence[v.Ver], verInfoBitsNum)
return result
}
// formatInfo returns the 15-bit Format Information qrbool for a QR
// code.
func (v version) formatInfo(maskPattern int) *binary.Binary {
formatID := 0
switch v.ECLevel {
case ErrorCorrectionLow:
formatID = 0x08 // 0b01000
case ErrorCorrectionMedium:
formatID = 0x00 // 0b00000
case ErrorCorrectionQuart:
formatID = 0x18 // 0b11000
case ErrorCorrectionHighest:
formatID = 0x10 // 0b10000
default:
log.Panicf("Invalid level %d", v.ECLevel)
}
if maskPattern < 0 || maskPattern > 7 {
log.Panicf("Invalid maskPattern %d", maskPattern)
}
formatID |= maskPattern & 0x7
result := binary.New()
result.AppendUint32(formatBitSequence[formatID].regular, formatInfoBitsNum)
return result
}
var emptyVersion = version{Ver: -1}
// binarySearchVersion speed up searching target version in versions.
// low, high to set the left and right bound of the search range (min:0 to max:159).
// compare represents the function to compare the target version with the cursor version.
// negative means lower direction, positive means higher direction, zero mean hit.
func binarySearchVersion(low, high int, compare func(*version) int) (hit version, found bool) {
// left low and high in a valid range
if low > high || low > _VERSIONS_ITEM_COUNT || high < 0 {
return emptyVersion, false
}
if low < 0 {
low = 0
}
if high >= _VERSIONS_ITEM_COUNT {
high = len(versions) - 1
}
for low <= high {
mid := (low + high) / 2
r := compare(&versions[mid])
if r == 0 {
hit = versions[mid]
found = true
break
}
if r > 0 {
// move toward higher direction
low = mid + 1
} else {
// move toward lower direction
high = mid
}
}
return hit, found
}
// defaultBinaryCompare built-in compare function for binary search.
func defaultBinaryCompare(ver int, ec ecLevel) func(cursor *version) int {
return func(cursor *version) int {
switch r := ver - cursor.Ver; r {
case 0:
default:
// v is bigger return positive; otherwise return negative.
return r
}
return int(ec - cursor.ECLevel)
}
}
// loadVersion get version config by specified version indicator and error correction level.
// we can speed up this process, by shrink the range to search.
func loadVersion(lv int, ec ecLevel) version {
// each version only has 4 items in versions array,
// and them are ordered[ASC] already.
high := lv*4 - 1
low := (lv - 1) * 4
for i := low; i <= high; i++ {
if versions[i].ECLevel == ec {
return versions[i]
}
}
panic(errMissMatchedVersion)
}
// analyzeVersion the raw text, and then decide which version should be chosen
// according to the text length , error correction level and encode mode to choose the
// closest capacity of version.
//
// check out http://muyuchengfeng.xyz/%E4%BA%8C%E7%BB%B4%E7%A0%81-%E5%AD%97%E7%AC%A6%E5%AE%B9%E9%87%8F%E8%A1%A8/
// for more details.
func analyzeVersion(raw []byte, ec ecLevel, mode encMode) (*version, error) {
step := 0
switch ec {
case ErrorCorrectionLow:
step = 0
case ErrorCorrectionMedium:
step = 1
case ErrorCorrectionQuart:
step = 2
case ErrorCorrectionHighest:
step = 3
default:
return nil, errInvalidErrorCorrectionLevel
}
want, mark := len(raw), 0
for ; step < 160; step += 4 {
switch mode {
case EncModeNumeric:
mark = versions[step].Cap.Numeric
case EncModeAlphanumeric:
mark = versions[step].Cap.AlphaNumeric
case EncModeByte:
mark = versions[step].Cap.Byte
case EncModeJP:
mark = versions[step].Cap.JP
default:
return nil, errMissMatchedEncodeType
}
if mark >= want {
return &versions[step], nil
}
}
debugLogf("mismatched version, version's length: %d, ec: %v", len(versions), ec)
return nil, errAnalyzeVersionFailed
}
var (
// https://www.thonky.com/qr-code-tutorial/alignment-pattern-locations
// DONE(@yeqown): add more version
alignPatternLocation = map[int][]int{
2: {6, 18},
3: {6, 22},
4: {6, 26},
5: {6, 30},
6: {6, 34},
7: {6, 22, 38},
8: {6, 24, 42},
9: {6, 26, 46},
10: {6, 28, 50},
11: {6, 30, 54},
12: {6, 32, 58},
13: {6, 34, 62},
14: {6, 26, 46, 66},
15: {6, 26, 48, 70},
16: {6, 26, 50, 74},
17: {6, 30, 54, 78},
18: {6, 30, 56, 82},
19: {6, 30, 58, 86},
20: {6, 34, 62, 90},
21: {6, 28, 50, 72, 94},
22: {6, 26, 50, 74, 98},
23: {6, 30, 54, 78, 102},
24: {6, 28, 54, 80, 106},
25: {6, 32, 58, 84, 110},
26: {6, 30, 58, 86, 114},
27: {6, 34, 62, 90, 118},
28: {6, 26, 50, 74, 98, 122},
29: {6, 30, 54, 78, 102, 126},
30: {6, 26, 52, 78, 104, 130},
31: {6, 30, 56, 82, 108, 134},
32: {6, 34, 60, 86, 112, 138},
33: {6, 30, 58, 86, 114, 142},
34: {6, 34, 62, 90, 118, 146},
35: {6, 30, 54, 78, 102, 126, 150},
36: {6, 24, 50, 76, 102, 128, 154},
37: {6, 28, 54, 80, 106, 132, 158},
38: {6, 32, 58, 84, 110, 136, 162},
39: {6, 26, 54, 82, 110, 138, 166},
40: {6, 30, 58, 86, 114, 142, 170},
}
alignPatternCache = map[int][]loc{}
)
// loc point position(x,y)
type loc struct {
X int // for width
Y int // for height
}
// loadAlignmentPatternLoc ...
func loadAlignmentPatternLoc(ver int) (locs []loc) {
if ver < 2 {
return
}
var ok bool
if locs, ok = alignPatternCache[ver]; ok {
return
}
dimension := ver*4 + 17
positions, ok := alignPatternLocation[ver]
if !ok {
panic("could not found align at version: " + strconv.Itoa(ver))
}
for _, pos1 := range positions {
for _, pos2 := range positions {
if !valid(pos1, pos2, dimension) {
continue
}
locs = append(locs, loc{X: pos1, Y: pos2})
}
}
alignPatternCache[ver] = locs
return
}
// x, y center position x,y so
func valid(x, y, dimension int) bool {
// valid left-top
if (x-2) < 7 && (y-2) < 7 {
return false
}
// valid right-top
if (x+2) > dimension-7 && (y-2) < 7 {
return false
}
// valid left-bottom
if (x-2) < 7 && (y+2) > dimension-7 {
return false
}
return true
}

3532
vendor/github.com/yeqown/go-qrcode/v2/version_cfg.go generated vendored Normal file

File diff suppressed because it is too large Load Diff

25
vendor/github.com/yeqown/go-qrcode/v2/writer.go generated vendored Normal file
View File

@ -0,0 +1,25 @@
package qrcode
// Writer is the interface of a QR code writer, it defines the rule of how to
// `print` the code image from matrix. There's built-in writer to output into
// file, terminal.
type Writer interface {
// Write writes the code image into itself stream, such as io.Writer,
// terminal output stream, and etc
Write(mat Matrix) error
// Close the writer stream if it exists after QRCode.Save() is called.
Close() error
}
var _ Writer = (*nonWriter)(nil)
type nonWriter struct{}
func (n nonWriter) Close() error {
return nil
}
func (n nonWriter) Write(mat Matrix) error {
return nil
}

View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2018 yeqown
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -0,0 +1,82 @@
## Standard Writer
[![go.dev reference](https://img.shields.io/badge/go.dev-reference-007d9c?logo=go&logoColor=white&style=flat-square)](https://pkg.go.dev/github.com/yeqown/go-qrcode/writer/standard)
Standard Writer is a writer that is used to draw QR Code image into `io.Writer`, normally a file.
### Usage
```go
options := []ImageOption{
WithBgColorRGBHex("#ffffff"),
WithFgColorRGBHex("#000000"),
// more ...
}
// New will create file automatically.
writer, err := standard.New("filename", options...)
// or use io.WriteCloser
var w io.WriterCloser
writer2, err := standard.NewWith(w, options...)
```
### Options
```go
// WithBgTransparent makes the background transparent.
func WithBgTransparent() ImageOption {}
// WithBgColor background color
func WithBgColor(c color.Color) ImageOption {}
// WithBgColorRGBHex background color
func WithBgColorRGBHex(hex string) ImageOption {}
// WithFgColor QR color
func WithFgColor(c color.Color) ImageOption {}
// WithFgColorRGBHex Hex string to set QR Color
func WithFgColorRGBHex(hex string) ImageOption {}
// WithLogoImage .
func WithLogoImage(img image.Image) ImageOption {}
// WithLogoImageFilePNG load image from file, PNG is required
func WithLogoImageFilePNG(f string) ImageOption {}
// WithLogoImageFileJPEG load image from file, JPEG is required
func WithLogoImageFileJPEG(f string) ImageOption {}
// WithQRWidth specify width of each qr block
func WithQRWidth(width uint8) ImageOption {}
// WithCircleShape use circle shape as rectangle(default)
func WithCircleShape() ImageOption {}
// WithCustomShape use custom shape as rectangle(default)
func WithCustomShape(shape IShape) ImageOption {}
// WithBuiltinImageEncoder option includes: JPEG_FORMAT as default, PNG_FORMAT.
// This works like WithBuiltinImageEncoder, the different between them is
// formatTyp is enumerated in (JPEG_FORMAT, PNG_FORMAT)
func WithBuiltinImageEncoder(format formatTyp) ImageOption
// WithCustomImageEncoder to use custom image encoder to encode image.Image into
// io.Writer
func WithCustomImageEncoder(encoder ImageEncoder) ImageOption
// WithBorderWidth specify the both 4 sides' border width. Notice that
// WithBorderWidth(a) means all border width use this variable `a`,
// WithBorderWidth(a, b) mean top/bottom equal to `a`, left/right equal to `b`.
// WithBorderWidth(a, b, c, d) mean top, right, bottom, left.
func WithBorderWidth(widths ...int) ImageOption
// WithHalftone ...
func WithHalftone(path string) ImageOption
```
### extension
- [How to customize QR Code shape](./how-to-use-custom-shape.md)
- [How to customize ImageEncoder](./how-to-use-image-encoder.md)

View File

@ -0,0 +1,85 @@
## How to use custom shape
[Source Code](../../example/with-custom-shape/main.go)
first step, you must define your own shape to QRCode, which consists of two part:
* normal cell (of course, there are many types, separator, timing, alignment patter, data, format and version etc)
* finder cell (to help recognizer to locate the matrix's position)
<img src="../../assets/qrcode_structure.png" align="center" width="50%" />
```go
type IShape interface {
// Draw to fill the IShape of qrcode.
Draw(ctx *DrawContext)
// DrawFinder to fill the finder pattern of QRCode, what's finder? google it for more information.
DrawFinder(ctx *DrawContext)
}
```
> Notice:
>
> if you must be careful to design finder's shape, otherwise qrcode could not be recognized.
>
Now, if you're define your shape like this:
```go
func newShape(radiusPercent float64) qrcode.IShape {
return &smallerCircle{smallerPercent: radiusPercent}
}
// smallerCircle use smaller circle to qrcode.
type smallerCircle struct {
smallerPercent float64
}
func (sc *smallerCircle) DrawFinder(ctx *qrcode.DrawContext) {
// use normal radius to draw finder for that qrcode image can be recognized.
backup := sc.smallerPercent
sc.smallerPercent = 1.0
sc.Draw(ctx)
sc.smallerPercent = backup
}
func (sc *smallerCircle) Draw(ctx *qrcode.DrawContext) {
w, h := ctx.Edge()
upperLeft := ctx.UpperLeft()
color := ctx.Color()
// choose a proper radius values
radius := w / 2
r2 := h / 2
if r2 <= radius {
radius = r2
}
// 80 percent smaller
radius = int(float64(radius) * sc.smallerPercent)
cx, cy := upperLeft.X+w/2, upperLeft.Y+h/2 // get center point
ctx.DrawCircle(float64(cx), float64(cy), float64(radius))
ctx.SetColor(color)
ctx.Fill()
}
```
Finally, you can use your shape.
```go
func main() {
shape := newShape(0.7)
qrc, err := qrcode.New("with-custom-shape", qrcode.WithCustomShape(shape))
if err != nil {
panic(err)
}
err = qrc.Save("./smaller.png")
if err != nil {
panic(err)
}
}
```

View File

@ -0,0 +1 @@
## Customize image encoder

View File

@ -0,0 +1,35 @@
package standard
import (
"image"
"image/jpeg"
"image/png"
"io"
)
type formatTyp uint8
const (
// JPEG_FORMAT as default output file format.
JPEG_FORMAT formatTyp = iota
// PNG_FORMAT .
PNG_FORMAT
)
// ImageEncoder is an interface which describes the rule how to encode image.Image into io.Writer
type ImageEncoder interface {
// Encode specify which format to encode image into io.Writer.
Encode(w io.Writer, img image.Image) error
}
type jpegEncoder struct{}
func (j jpegEncoder) Encode(w io.Writer, img image.Image) error {
return jpeg.Encode(w, img, nil)
}
type pngEncoder struct{}
func (j pngEncoder) Encode(w io.Writer, img image.Image) error {
return png.Encode(w, img)
}

View File

@ -0,0 +1,190 @@
package standard
import (
"fmt"
"image"
"image/color"
"github.com/yeqown/go-qrcode/v2"
)
type ImageOption interface {
apply(o *outputImageOptions)
}
// defaultOutputImageOption default output image background color and etc options
func defaultOutputImageOption() *outputImageOptions {
return &outputImageOptions{
bgColor: color_WHITE, // white
bgTransparent: false, // not transparent
qrColor: color_BLACK, // black
logo: nil, //
qrWidth: 20, //
shape: _shapeRectangle, //
imageEncoder: jpegEncoder{},
borderWidths: [4]int{_defaultPadding, _defaultPadding, _defaultPadding, _defaultPadding},
}
}
// outputImageOptions to output QR code image
type outputImageOptions struct {
// bgColor is the background color of the QR code image.
bgColor color.RGBA
// bgTransparent only affects on PNG_FORMAT
bgTransparent bool
// qrColor is the foreground color of the QR code.
qrColor color.RGBA
// logo this icon image would be put the center of QR Code image
// NOTE: logo only should have 1/5 size of QRCode image
logo image.Image
// qrWidth width of each qr block
qrWidth int
// shape means how to draw the shape of each cell.
shape IShape
// imageEncoder specify which file format would be encoded the QR image.
imageEncoder ImageEncoder
// borderWidths indicates the border width of the output image. the order is
// top, right, bottom, left same as the WithBorder
borderWidths [4]int
// halftoneImg is the halftone image for the output image.
halftoneImg image.Image
}
func (oo *outputImageOptions) backgroundColor() color.RGBA {
if oo == nil {
return color_WHITE
}
if oo.bgTransparent {
(&oo.bgColor).A = 0x00
}
return oo.bgColor
}
func (oo *outputImageOptions) logoImage() image.Image {
if oo == nil || oo.logo == nil {
return nil
}
return oo.logo
}
func (oo *outputImageOptions) qrBlockWidth() int {
if oo == nil || (oo.qrWidth <= 0 || oo.qrWidth > 255) {
return 20
}
return oo.qrWidth
}
func (oo *outputImageOptions) getShape() IShape {
if oo == nil || oo.shape == nil {
return _shapeRectangle
}
return oo.shape
}
// preCalculateAttribute this function must reference to draw function.
func (oo *outputImageOptions) preCalculateAttribute(dimension int) *Attribute {
if oo == nil {
return nil
}
top, right, bottom, left := oo.borderWidths[0], oo.borderWidths[1], oo.borderWidths[2], oo.borderWidths[3]
return &Attribute{
W: dimension*oo.qrBlockWidth() + right + left,
H: dimension*oo.qrBlockWidth() + top + bottom,
Borders: oo.borderWidths,
BlockWidth: oo.qrBlockWidth(),
}
}
var (
color_WHITE = parseFromHex("#ffffff")
color_BLACK = parseFromHex("#000000")
)
var (
// _STATE_MAPPING mapping matrix.State to color.RGBA in debug mode.
_STATE_MAPPING = map[qrcode.QRType]color.RGBA{
qrcode.QRType_INIT: parseFromHex("#ffffff"), // [bg]
qrcode.QRType_DATA: parseFromHex("#cdc9c3"), // [bg]
qrcode.QRType_VERSION: parseFromHex("#000000"), // [fg]
qrcode.QRType_FORMAT: parseFromHex("#444444"), // [fg]
qrcode.QRType_FINDER: parseFromHex("#555555"), // [fg]
qrcode.QRType_DARK: parseFromHex("#2BA859"), // [fg]
qrcode.QRType_SPLITTER: parseFromHex("#2BA859"), // [fg]
qrcode.QRType_TIMING: parseFromHex("#000000"), // [fg]
}
)
// translateToRGBA get color.RGBA by value State, if not found, return outputImageOptions.qrColor.
// NOTE: this function decides the state should use qrColor or bgColor.
func (oo *outputImageOptions) translateToRGBA(v qrcode.QRValue) (rgba color.RGBA) {
// TODO(@yeqown): use _STATE_MAPPING to replace this function while in debug mode
// or some special flag.
if v.IsSet() {
rgba = oo.qrColor
return rgba
}
if oo.bgTransparent {
(&oo.bgColor).A = 0x00
}
rgba = oo.bgColor
return rgba
}
// parseFromHex convert hex string into color.RGBA
func parseFromHex(s string) color.RGBA {
c := color.RGBA{
R: 0,
G: 0,
B: 0,
A: 0xff,
}
var err error
switch len(s) {
case 7:
_, err = fmt.Sscanf(s, "#%02x%02x%02x", &c.R, &c.G, &c.B)
case 4:
_, 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")
}
if err != nil {
panic(err)
}
return c
}
func parseFromColor(c color.Color) color.RGBA {
rgba, ok := c.(color.RGBA)
if ok {
return rgba
}
r, g, b, a := c.RGBA()
return color.RGBA{
R: uint8(r),
G: uint8(g),
B: uint8(b),
A: uint8(a),
}
}

View File

@ -0,0 +1,219 @@
package standard
import (
"fmt"
"image"
"image/color"
"image/jpeg"
"image/png"
"os"
"github.com/yeqown/go-qrcode/writer/standard/imgkit"
)
// funcOption wraps a function that modifies outputImageOptions into an
// implementation of the ImageOption interface.
type funcOption struct {
f func(oo *outputImageOptions)
}
func (fo *funcOption) apply(oo *outputImageOptions) {
fo.f(oo)
}
func newFuncOption(f func(oo *outputImageOptions)) *funcOption {
return &funcOption{
f: f,
}
}
// WithBgTransparent makes the background transparent.
func WithBgTransparent() ImageOption {
return newFuncOption(func(oo *outputImageOptions) {
oo.bgTransparent = true
})
}
// WithBgColor background color
func WithBgColor(c color.Color) ImageOption {
return newFuncOption(func(oo *outputImageOptions) {
if c == nil {
return
}
oo.bgColor = parseFromColor(c)
})
}
// WithBgColorRGBHex background color
func WithBgColorRGBHex(hex string) ImageOption {
return newFuncOption(func(oo *outputImageOptions) {
if hex == "" {
return
}
oo.bgColor = parseFromHex(hex)
})
}
// WithFgColor QR color
func WithFgColor(c color.Color) ImageOption {
return newFuncOption(func(oo *outputImageOptions) {
if c == nil {
return
}
oo.qrColor = parseFromColor(c)
})
}
// WithFgColorRGBHex Hex string to set QR Color
func WithFgColorRGBHex(hex string) ImageOption {
return newFuncOption(func(oo *outputImageOptions) {
oo.qrColor = parseFromHex(hex)
})
}
// WithLogoImage image should only has 1/5 width of QRCode at most
func WithLogoImage(img image.Image) ImageOption {
return newFuncOption(func(oo *outputImageOptions) {
if img == nil {
return
}
oo.logo = img
})
}
// WithLogoImageFileJPEG load image from file, jpeg is required.
// image should only have 1/5 width of QRCode at most
func WithLogoImageFileJPEG(f string) ImageOption {
return newFuncOption(func(oo *outputImageOptions) {
fd, err := os.Open(f)
if err != nil {
fmt.Printf("could not open file(%s), error=%v\n", f, err)
return
}
img, err := jpeg.Decode(fd)
if err != nil {
fmt.Printf("could not open file(%s), error=%v\n", f, err)
return
}
oo.logo = img
})
}
// WithLogoImageFilePNG load image from file, PNG is required.
// image should only have 1/5 width of QRCode at most
func WithLogoImageFilePNG(f string) ImageOption {
return newFuncOption(func(oo *outputImageOptions) {
fd, err := os.Open(f)
if err != nil {
fmt.Printf("Open file(%s) failed: %v\n", f, err)
return
}
img, err := png.Decode(fd)
if err != nil {
fmt.Printf("Decode file(%s) as PNG failed: %v\n", f, err)
return
}
oo.logo = img
})
}
// WithQRWidth specify width of each qr block
func WithQRWidth(width uint8) ImageOption {
return newFuncOption(func(oo *outputImageOptions) {
oo.qrWidth = int(width)
})
}
// WithCircleShape use circle shape as rectangle(default)
func WithCircleShape() ImageOption {
return newFuncOption(func(oo *outputImageOptions) {
oo.shape = _shapeCircle
})
}
// WithCustomShape use custom shape as rectangle(default)
func WithCustomShape(shape IShape) ImageOption {
return newFuncOption(func(oo *outputImageOptions) {
oo.shape = shape
})
}
// WithBuiltinImageEncoder option includes: JPEG_FORMAT as default, PNG_FORMAT.
// This works like WithBuiltinImageEncoder, the different between them is
// formatTyp is enumerated in (JPEG_FORMAT, PNG_FORMAT)
func WithBuiltinImageEncoder(format formatTyp) ImageOption {
return newFuncOption(func(oo *outputImageOptions) {
var encoder ImageEncoder
switch format {
case JPEG_FORMAT:
encoder = jpegEncoder{}
case PNG_FORMAT:
encoder = pngEncoder{}
default:
panic("Not supported file format")
}
oo.imageEncoder = encoder
})
}
// WithCustomImageEncoder to use custom image encoder to encode image.Image into
// io.Writer
func WithCustomImageEncoder(encoder ImageEncoder) ImageOption {
return newFuncOption(func(oo *outputImageOptions) {
if encoder == nil {
return
}
oo.imageEncoder = encoder
})
}
// WithBorderWidth specify the both 4 sides' border width. Notice that
// WithBorderWidth(a) means all border width use this variable `a`,
// WithBorderWidth(a, b) mean top/bottom equal to `a`, left/right equal to `b`.
// WithBorderWidth(a, b, c, d) mean top, right, bottom, left.
func WithBorderWidth(widths ...int) ImageOption {
apply := func(arr *[4]int, top, right, bottom, left int) {
arr[0] = top
arr[1] = right
arr[2] = bottom
arr[3] = left
}
return newFuncOption(func(oo *outputImageOptions) {
n := len(widths)
switch n {
case 0:
apply(&oo.borderWidths, _defaultPadding, _defaultPadding, _defaultPadding, _defaultPadding)
case 1:
apply(&oo.borderWidths, widths[0], widths[0], widths[0], widths[0])
case 2, 3:
apply(&oo.borderWidths, widths[0], widths[1], widths[0], widths[1])
default:
// 4+
apply(&oo.borderWidths, widths[0], widths[1], widths[2], widths[3])
}
})
}
// WithHalftone ...
func WithHalftone(path string) ImageOption {
return newFuncOption(func(oo *outputImageOptions) {
srcImg, err := imgkit.Read(path)
if err != nil {
fmt.Println("Read halftone image failed: ", err)
return
}
oo.halftoneImg = srcImg
})
}

View File

@ -0,0 +1,84 @@
package standard
import (
"image/color"
"github.com/fogleman/gg"
)
var (
_shapeRectangle IShape = rectangle{}
_shapeCircle IShape = circle{}
)
type IShape interface {
// Draw the shape of QRCode block in IShape implemented way.
Draw(ctx *DrawContext)
// DrawFinder to fill the finder pattern of QRCode, what's finder? google it for more information.
DrawFinder(ctx *DrawContext)
}
// DrawContext is a rectangle area
type DrawContext struct {
*gg.Context
x, y float64
w, h int
color color.Color
}
// UpperLeft returns the point which indicates the upper left position.
func (dc *DrawContext) UpperLeft() (dx, dy float64) {
return dc.x, dc.y
}
// Edge returns width and height of each shape could take at most.
func (dc *DrawContext) Edge() (width, height int) {
return dc.w, dc.h
}
// Color returns the color which should be fill into the shape. Note that if you're not
// using this color but your coded color.Color, some ImageOption functions those set foreground color
// would take no effect.
func (dc *DrawContext) Color() color.Color {
return dc.color
}
// rectangle IShape
type rectangle struct{}
func (r rectangle) Draw(c *DrawContext) {
// FIXED(@yeqown): miss parameter of DrawRectangle
c.DrawRectangle(c.x, c.y, float64(c.w), float64(c.h))
c.SetColor(c.color)
c.Fill()
}
func (r rectangle) DrawFinder(ctx *DrawContext) {
r.Draw(ctx)
}
// circle IShape
type circle struct{}
// Draw
// FIXED: Draw could not draw circle
func (r circle) Draw(c *DrawContext) {
// choose a proper radius values
radius := c.w / 2
r2 := c.h / 2
if r2 <= radius {
radius = r2
}
cx, cy := c.x+float64(c.w)/2.0, c.y+float64(c.h)/2.0 // get center point
c.DrawCircle(cx, cy, float64(radius))
c.SetColor(c.color)
c.Fill()
}
func (r circle) DrawFinder(ctx *DrawContext) {
r.Draw(ctx)
}

View File

@ -0,0 +1,47 @@
package imgkit
import (
"image"
"image/jpeg"
"image/png"
"os"
"path/filepath"
"github.com/pkg/errors"
)
// Read reads an image from a file. only support PNG and JPEG yet.
func Read(path string) (img image.Image, err error) {
fd, err := os.Open(path)
if err != nil {
return nil, errors.Wrap(err, "failed to open file")
}
defer fd.Close()
img, _, err = image.Decode(fd)
if err != nil {
return nil, errors.Wrap(err, "failed to decode image")
}
return img, nil
}
// Save saves the image to the given path.
func Save(img image.Image, filename string) error {
fd, err := os.Create(filename)
if err != nil {
return err
}
defer fd.Close()
switch filepath.Ext(filename) {
case ".jpg", ".jpeg":
err = jpeg.Encode(fd, img, nil)
case ".png":
err = png.Encode(fd, img)
default:
err = errors.New("unsupported image format, jpg or png only")
}
return err
}

View File

@ -0,0 +1,57 @@
package imgkit
import (
"image"
"image/color"
"golang.org/x/image/draw"
)
// Binaryzation process image with threshold value (0-255) and return new image.
func Binaryzation(src image.Image, threshold uint8) image.Image {
if threshold < 0 || threshold > 255 {
threshold = 128
}
gray := Gray(src)
bounds := src.Bounds()
height, width := bounds.Max.Y-bounds.Min.Y, bounds.Max.X-bounds.Min.X
for i := 0; i < height; i++ {
for j := 0; j < width; j++ {
// var rgb int = int(gray[i][j][0]) + int(gray[i][j][1]) + int(gray[i][j][2])
if gray.At(j, i).(color.Gray).Y > threshold {
gray.Set(j, i, color.White)
} else {
gray.Set(j, i, color.Black)
}
}
}
return gray
}
func Gray(src image.Image) *image.Gray {
bounds := src.Bounds()
height, width := bounds.Max.Y-bounds.Min.Y, bounds.Max.X-bounds.Min.X
gray := image.NewGray(bounds)
for i := 0; i < height; i++ {
for j := 0; j < width; j++ {
c := color.GrayModel.Convert(src.At(j, i))
gray.SetGray(j, i, c.(color.Gray))
}
}
return gray
}
func Scale(src image.Image, rect image.Rectangle, scale draw.Scaler) image.Image {
if scale == nil {
scale = draw.ApproxBiLinear
}
dst := image.NewRGBA(rect)
scale.Scale(dst, rect, src, src.Bounds(), draw.Over, nil)
return dst
}

View File

@ -0,0 +1,219 @@
package standard
import (
"fmt"
"image"
"image/color"
"io"
"log"
"os"
"github.com/yeqown/go-qrcode/v2"
"github.com/yeqown/go-qrcode/writer/standard/imgkit"
"github.com/fogleman/gg"
"github.com/pkg/errors"
)
var _ qrcode.Writer = (*Writer)(nil)
var (
ErrNilWriter = errors.New("nil writer")
)
// Writer is a writer that writes QR Code to io.Writer.
type Writer struct {
option *outputImageOptions
closer io.WriteCloser
}
// New creates a standard writer.
func New(filename string, opts ...ImageOption) (*Writer, error) {
if _, err := os.Stat(filename); err != nil && os.IsExist(err) {
// custom path got: "file exists"
log.Printf("could not find path: %s, then save to %s", filename, _defaultFilename)
filename = _defaultFilename
}
fd, err := os.OpenFile(filename, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0644)
if err != nil {
return nil, errors.Wrap(err, "create file failed")
}
return NewWithWriter(fd, opts...), nil
}
func NewWithWriter(writeCloser io.WriteCloser, opts ...ImageOption) *Writer {
dst := defaultOutputImageOption()
for _, opt := range opts {
opt.apply(dst)
}
if writeCloser == nil {
panic("writeCloser could not be nil")
}
return &Writer{
option: dst,
closer: writeCloser,
}
}
const (
_defaultFilename = "default.jpeg"
_defaultPadding = 40
)
func (w Writer) Write(mat qrcode.Matrix) error {
return drawTo(w.closer, mat, w.option)
}
func (w Writer) Close() error {
if w.closer == nil {
return nil
}
if err := w.closer.Close(); !errors.Is(err, os.ErrClosed) {
return err
}
return nil
}
func (w Writer) Attribute(dimension int) *Attribute {
return w.option.preCalculateAttribute(dimension)
}
func drawTo(w io.Writer, mat qrcode.Matrix, option *outputImageOptions) (err error) {
if option == nil {
option = defaultOutputImageOption()
}
if w == nil {
return ErrNilWriter
}
img := draw(mat, option)
// DONE(@yeqown): support file format specified config option
if err = option.imageEncoder.Encode(w, img); err != nil {
err = fmt.Errorf("imageEncoder.Encode failed: %v", err)
}
return
}
// draw deal QRCode's matrix to be an image.Image. Notice that if anyone changed this function,
// please also check the function outputImageOptions.preCalculateAttribute().
func draw(mat qrcode.Matrix, opt *outputImageOptions) image.Image {
top, right, bottom, left := opt.borderWidths[0], opt.borderWidths[1], opt.borderWidths[2], opt.borderWidths[3]
// closer as image width, h as image height
w := mat.Width()*opt.qrBlockWidth() + left + right
h := mat.Height()*opt.qrBlockWidth() + top + bottom
dc := gg.NewContext(w, h)
// draw background
dc.SetColor(opt.backgroundColor())
dc.DrawRectangle(0, 0, float64(w), float64(h))
dc.Fill()
// qrcode block draw context
ctx := &DrawContext{
Context: dc,
x: 0.0,
y: 0.0,
w: opt.qrBlockWidth(),
h: opt.qrBlockWidth(),
color: color.Black,
}
shape := opt.getShape()
var (
halftoneImg image.Image
halftoneW = float64(opt.qrBlockWidth()) / 3.0
)
if opt.halftoneImg != nil {
halftoneImg = imgkit.Binaryzation(
imgkit.Scale(opt.halftoneImg, image.Rect(0, 0, mat.Width()*3, mat.Width()*3), nil),
60,
)
//_ = imgkit.Save(halftoneImg, "mask.jpeg")
}
// iterate the matrix to Draw each pixel
mat.Iterate(qrcode.IterDirection_ROW, func(x int, y int, v qrcode.QRValue) {
// Draw the block
ctx.x, ctx.y = float64(x*opt.qrBlockWidth()+left), float64(y*opt.qrBlockWidth()+top)
ctx.w, ctx.h = opt.qrBlockWidth(), opt.qrBlockWidth()
ctx.color = opt.translateToRGBA(v)
// DONE(@yeqown): make this abstract to Shapes
switch typ := v.Type(); typ {
case qrcode.QRType_FINDER:
shape.DrawFinder(ctx)
case qrcode.QRType_DATA:
if halftoneImg == nil {
shape.Draw(ctx)
return
}
ctx2 := &DrawContext{
Context: ctx.Context,
w: int(halftoneW),
h: int(halftoneW),
}
// only halftone image enabled and current block is Data.
for i := 0; i < 3; i++ {
for j := 0; j < 3; j++ {
ctx2.x, ctx2.y = ctx.x+float64(i)*halftoneW, ctx.y+float64(j)*halftoneW
ctx2.color = halftoneImg.At(x*3+i, y*3+j)
if i == 1 && j == 1 {
ctx2.color = ctx.color
// only center block keep the origin color.
}
shape.Draw(ctx2)
}
}
default:
shape.Draw(ctx)
}
// EOFn
})
// DONE(@yeqown): add logo image
if opt.logoImage() != nil {
// Draw logo image into rgba
bound := opt.logo.Bounds()
upperLeft, lowerRight := bound.Min, bound.Max
logoWidth, logoHeight := lowerRight.X-upperLeft.X, lowerRight.Y-upperLeft.Y
if !validLogoImage(w, h, logoWidth, logoHeight) {
log.Printf("w=%d, h=%d, logoW=%d, logoH=%d, logo is over than 1/5 of QRCode \n",
w, h, logoWidth, logoHeight)
goto done
}
// DONE(@yeqown): calculate the xOffset and yOffset which point(xOffset, yOffset)
//should icon upper-left to start
dc.DrawImage(opt.logoImage(), (w-logoWidth)/2, (h-logoHeight)/2)
}
done:
return dc.Image()
}
func validLogoImage(qrWidth, qrHeight, logoWidth, logoHeight int) bool {
return qrWidth >= 5*logoWidth && qrHeight >= 5*logoHeight
}
// Attribute contains basic information of generated image.
type Attribute struct {
// width and height of image
W, H int
// in the order of "top, right, bottom, left"
Borders [4]int
// the length of block edges
BlockWidth int
}

1
vendor/github.com/yeqown/reedsolomon/README.md generated vendored Normal file
View File

@ -0,0 +1 @@
# reed solomon encoding lib

272
vendor/github.com/yeqown/reedsolomon/binary/binary.go generated vendored Normal file
View File

@ -0,0 +1,272 @@
// Package binary ...
// thanks to https://github.com/skip2/go-qrcode/blob/master/bitset/bitset.go
// I cannot do any better for now, so I just learn and write it again~
package binary
import (
"bytes"
"fmt"
"log"
)
const (
byteTrue byte = '1'
byteFalse byte = '0'
)
var (
// format string
format = "Binary length: %d, bits: %s"
)
// New ...
func New(booleans ...bool) *Binary {
b := &Binary{
bits: make([]byte, 0),
lenBits: 0,
}
b.AppendBools(booleans...)
return b
}
// NewFromBinaryString ... generate Bitset from binary string
// auto get length
func NewFromBinaryString(s string) (*Binary, error) {
var n = len(s) / 8
if len(s)%8 != 0 {
n++
}
b := &Binary{
bits: make([]byte, n), // prealloc memory, reducing useless space
lenBits: 0,
}
for _, c := range s {
switch c {
case '1':
b.AppendBools(true)
case '0':
b.AppendBools(false)
case ' ':
// skip space blank
continue
default:
err := fmt.Errorf("invalid char %c in NewFromBinaryString", c)
return nil, err
}
}
return b, nil
}
// Binary struct contains bits stream and methods to be called from outside
// exsample:
// b.Len()
// b.Subset(start, end)
// b.At(pos)
type Binary struct {
bits []byte // 1byte = 8bit
lenBits int // len(bits) * 8
}
// ensureCapacity ensures the Bitset can store an additional |numBits|.
//
// The underlying array is expanded if necessary. To prevent frequent
// reallocation, expanding the underlying array at least doubles its capacity.
//
// then no need to use append ~ will no panic (out of range)
func (b *Binary) ensureCapacity(numBits int) {
numBits += b.lenBits
newNumBytes := numBits / 8
if numBits%8 != 0 {
newNumBytes++
}
// if larger enough
if len(b.bits) >= newNumBytes {
return
}
// larger capcity, about 3 times of current capcity
b.bits = append(b.bits, make([]byte, newNumBytes+2*len(b.bits))...)
}
// At .get boolean value from
func (b *Binary) At(pos int) bool {
if pos < 0 || pos >= b.lenBits {
panic("out range of bits")
}
return (b.bits[pos/8]&(0x80>>uint(pos%8)) != 0)
}
// Subset do the same work like slice[start:end]
func (b *Binary) Subset(start, end int) (*Binary, error) {
if start > end || end > b.lenBits {
err := fmt.Errorf("Out of range start=%d end=%d lenBits=%d", start, end, b.lenBits)
return nil, err
}
result := New()
result.ensureCapacity(end - start)
for i := start; i < end; i++ {
if b.At(i) {
result.bits[result.lenBits/8] |= 0x80 >> uint(result.lenBits%8)
}
result.lenBits++
}
return result, nil
}
// Append other bitset link another Bitset to after the b
func (b *Binary) Append(other *Binary) {
b.ensureCapacity(other.Len())
for i := 0; i < other.lenBits; i++ {
if other.At(i) {
b.bits[b.lenBits/8] |= 0x80 >> uint(b.lenBits%8)
}
b.lenBits++
}
}
// AppendUint32 other bitset link another Bitset to after the b
func (b *Binary) AppendUint32(value uint32, numBits int) {
b.ensureCapacity(numBits)
if numBits > 32 {
log.Panicf("numBits %d out of range 0-32", numBits)
}
for i := numBits - 1; i >= 0; i-- {
if value&(1<<uint(i)) != 0 {
b.bits[b.lenBits/8] |= 0x80 >> uint(b.lenBits%8)
}
b.lenBits++
}
}
// AppendBytes ...
func (b *Binary) AppendBytes(byts ...byte) {
for _, byt := range byts {
b.AppendByte(byt, 8)
}
}
// AppendByte ... specified num bits to append
func (b *Binary) AppendByte(byt byte, numBits int) error {
if numBits > 8 || numBits < 0 {
return fmt.Errorf("numBits out of range 0-8")
}
b.ensureCapacity(numBits)
// append bit in byte
for i := numBits - 1; i >= 0; i-- {
// 0x01 << left shift count
// 0x80 >> right shift count
if byt&(0x01<<uint(i)) != 0 {
b.bits[b.lenBits/8] |= 0x80 >> uint(b.lenBits%8)
}
b.lenBits++
}
return nil
}
// AppendBools append multi bool after the bit stream of b
func (b *Binary) AppendBools(booleans ...bool) {
b.ensureCapacity(len(booleans))
for _, bv := range booleans {
if bv {
b.bits[b.lenBits/8] |= 0x80 >> uint(b.lenBits%8)
}
b.lenBits++
}
}
// AppendNumBools appends num bits of value value.
func (b *Binary) AppendNumBools(num int, boolean bool) {
booleans := make([]bool, num)
// if not false just append
if boolean {
for i := 0; i < num; i++ {
booleans[i] = boolean
}
}
b.AppendBools(booleans...)
}
// IterFunc used by func b.VisitAll ...
type IterFunc func(pos int, v bool)
// VisitAll loop the b.bits stream and send value into IterFunc
func (b *Binary) VisitAll(f IterFunc) {
for pos := 0; pos < b.Len(); pos++ {
f(pos, b.At(pos))
}
}
// String for printing
func (b *Binary) String() string {
var (
bitstr []byte
vb byte
)
b.VisitAll(func(pos int, v bool) {
vb = byteFalse
if v {
vb = byteTrue
}
bitstr = append(bitstr, vb)
})
return fmt.Sprintf(format, b.Len(), string(bitstr))
}
// Len ...
func (b *Binary) Len() int {
return b.lenBits
}
// Bytes ...
func (b *Binary) Bytes() []byte {
numBytes := b.lenBits / 8
if b.lenBits%8 != 0 {
numBytes++
}
return b.bits[:numBytes]
}
// EqualTo ...
func (b *Binary) EqualTo(other *Binary) bool {
if b.lenBits != other.lenBits {
return false
}
numByte := b.lenBits / 8
if !bytes.Equal(b.bits[:numByte], other.bits[:numByte]) {
return false
}
for pos := numByte * 8; pos < b.lenBits; pos++ {
if b.At(pos) != other.At(pos) {
return false
}
}
return true
}
// Copy ...
func (b *Binary) Copy() *Binary {
return &Binary{
bits: b.bits,
lenBits: b.lenBits,
}
}

72
vendor/github.com/yeqown/reedsolomon/galois_field.go generated vendored Normal file
View File

@ -0,0 +1,72 @@
package reedsolomon
// ref to https://en.wikiversity.org/wiki/Reed%E2%80%93Solomon_codes_for_coders
// ref to https://www.jianshu.com/p/8208aad537bb
// gf.go Galois Fields
var (
gfLog = []byte{}
gfExp = []byte{}
)
const prim = 0x011d
// init calls all initial funcs
func init() {
initTables()
}
// init gfExp and gfLog array
func initTables() {
gfExp = make([]byte, 512)
gfLog = make([]byte, 256)
var (
x uint16 = 1
)
for i := 0; i < 255; i++ {
gfExp[i] = byte(x)
gfLog[x] = byte(i)
x <<= 1
// x overflow 256
if (x & 0x100) != 0 {
x ^= prim
}
}
for i := 255; i < 512; i++ {
gfExp[i] = gfExp[i-255]
}
}
// multpy
func gfMul(x, y byte) byte {
if x == 0 || y == 0 {
return 0
}
// byte max: 256 but exp cap is 512
return gfExp[uint(gfLog[x])+uint(gfLog[y])]
}
// divide
// func gfDiv(x, y byte) byte {
// if y == 0 {
// panic("zero division error")
// }
// if x == 0 {
// return 0
// }
// return gfExp[(uint(gfLog[x])+255-uint(gfLog[y]))%255]
// }
// // inverse
// func gfInverse(x byte) byte {
// return gfExp[255-uint(gfLog[x])]
// }
// // pow
// func gfPow(x, power byte) byte {
// return gfExp[(gfLog[x]*power)%255]
// }

88
vendor/github.com/yeqown/reedsolomon/polynomial.go generated vendored Normal file
View File

@ -0,0 +1,88 @@
package reedsolomon
// generator polynomial
// (x-a^1) * (x - a^2) * .... * (x -a^numECWords-1)
func rsGenPoly(numECWords int) []byte {
var generator = []byte{1}
for i := 0; i < numECWords; i++ {
generator = polyMul(generator, []byte{1, gfExp[i]})
}
return generator
}
// 将一个多项式和一个标量相乘
func polyScale(poly []byte, x byte) []byte {
result := make([]byte, len(poly))
for i := 0; i < len(poly); i++ {
result[i] = gfMul(poly[i], x)
}
return result
}
func polyAdd(poly1, poly2 []byte) []byte {
size1 := len(poly1)
size2 := len(poly2)
size := size1
if size2 > size1 {
size = size2
}
result := make([]byte, size)
for i := 0; i < size1; i++ {
result[i] = byte(poly1[i])
}
for i := 0; i < size2; i++ {
result[i] ^= byte(poly2[i])
}
return result
}
// mul polynomial
func polyMul(poly1, poly2 []byte) []byte {
result := make([]byte, len(poly1)+len(poly2)-1)
for i := 0; i < len(poly1); i++ {
for j := 0; j < len(poly2); j++ {
result[i+j] ^= gfMul(poly1[i], poly2[j])
}
}
return result
}
// func polyEval(poly []byte, x byte) byte {
// y := poly[0]
// for i := 1; i < len(poly); i++ {
// y = gfMul(y, x) ^ poly[i]
// }
// return y
// }
// ref to: https://www.thonky.com/qr-code-tutorial/show-division-steps?msg_coeff=12%2C34%2C56%2C23&num_ecc_blocks=3
func polyDiv(dividend, divisor []byte) []byte {
if len(dividend) == 0 {
panic("could not div with 0 length dividend")
}
var (
leadTerm = dividend[0]
reminder, a, b []byte
)
reminder = dividend
for i := 0; i < len(dividend); i++ {
// step a: generator * leadTerm
a = polyScale(divisor, leadTerm)
// step b, xor operation
b = polyAdd(reminder, a)
// discard lead term of b
reminder = b[1:]
leadTerm = reminder[0]
}
return reminder
}

33
vendor/github.com/yeqown/reedsolomon/reedsolomon.go generated vendored Normal file
View File

@ -0,0 +1,33 @@
// Package reedsolomon ...
// ref to doc: https://en.wikiversity.org/wiki/Reed%E2%80%93Solomon_codes_for_coders#Polynomial_division
// ref to project: github.com/skip2/go-qrcode/reedsolomon
package reedsolomon
import (
"github.com/yeqown/reedsolomon/binary"
)
type word byte // 8bit as a word
// Encode ...
func Encode(bin *binary.Binary, numECWords int) *binary.Binary {
if bin.Len()%8 != 0 {
panic("could not deal with binary times 8bits")
}
// generate polynomial
generator := rsGenPoly(numECWords)
// poly div
remainder := polyDiv(bin.Bytes(), generator)
// append error correction stream
bout := bin.Copy()
bout.AppendBytes(remainder...)
return bout
}
// Decode ...
// TODO: finish this ~
func Decode(bin *binary.Binary, numECWords int) *binary.Binary {
return nil
}

11
vendor/modules.txt vendored
View File

@ -1045,6 +1045,17 @@ github.com/xeipuuv/gojsonschema
# github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 # github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673
## explicit ## explicit
github.com/xrash/smetrics github.com/xrash/smetrics
# github.com/yeqown/go-qrcode/v2 v2.2.1
## explicit; go 1.18
github.com/yeqown/go-qrcode/v2
# github.com/yeqown/go-qrcode/writer/standard v1.2.1
## explicit; go 1.17
github.com/yeqown/go-qrcode/writer/standard
github.com/yeqown/go-qrcode/writer/standard/imgkit
# github.com/yeqown/reedsolomon v1.0.0
## explicit
github.com/yeqown/reedsolomon
github.com/yeqown/reedsolomon/binary
# github.com/zenthangplus/goccm v0.0.0-20211005163543-2f2e522aca15 # github.com/zenthangplus/goccm v0.0.0-20211005163543-2f2e522aca15
## explicit; go 1.14 ## explicit; go 1.14
github.com/zenthangplus/goccm github.com/zenthangplus/goccm