mirror of
https://github.com/42wim/matterbridge.git
synced 2024-12-29 14:42:39 +01:00
590 lines
15 KiB
Go
590 lines
15 KiB
Go
// go-qrcode
|
|
// Copyright 2014 Tom Harwood
|
|
|
|
/*
|
|
Package qrcode implements a QR Code encoder.
|
|
|
|
A QR Code is a matrix (two-dimensional) barcode. Arbitrary content may be
|
|
encoded.
|
|
|
|
A QR Code contains error recovery information to aid reading damaged or
|
|
obscured codes. There are four levels of error recovery: qrcode.{Low, Medium,
|
|
High, Highest}. QR Codes with a higher recovery level are more robust to damage,
|
|
at the cost of being physically larger.
|
|
|
|
Three functions cover most use cases:
|
|
|
|
- Create a PNG image:
|
|
|
|
var png []byte
|
|
png, err := qrcode.Encode("https://example.org", qrcode.Medium, 256)
|
|
|
|
- Create a PNG image and write to a file:
|
|
|
|
err := qrcode.WriteFile("https://example.org", qrcode.Medium, 256, "qr.png")
|
|
|
|
- Create a PNG image with custom colors and write to file:
|
|
|
|
err := qrcode.WriteColorFile("https://example.org", qrcode.Medium, 256, color.Black, color.White, "qr.png")
|
|
|
|
All examples use the qrcode.Medium error Recovery Level and create a fixed
|
|
256x256px size QR Code. The last function creates a white on black instead of black
|
|
on white QR Code.
|
|
|
|
To generate a variable sized image instead, specify a negative size (in place of
|
|
the 256 above), such as -4 or -5. Larger negative numbers create larger images:
|
|
A size of -5 sets each module (QR Code "pixel") to be 5px wide/high.
|
|
|
|
- Create a PNG image (variable size, with minimum white padding) and write to a file:
|
|
|
|
err := qrcode.WriteFile("https://example.org", qrcode.Medium, -5, "qr.png")
|
|
|
|
The maximum capacity of a QR Code varies according to the content encoded and
|
|
the error recovery level. The maximum capacity is 2,953 bytes, 4,296
|
|
alphanumeric characters, 7,089 numeric digits, or a combination of these.
|
|
|
|
This package implements a subset of QR Code 2005, as defined in ISO/IEC
|
|
18004:2006.
|
|
*/
|
|
package qrcode
|
|
|
|
import (
|
|
"bytes"
|
|
"errors"
|
|
"image"
|
|
"image/color"
|
|
"image/png"
|
|
"io"
|
|
"io/ioutil"
|
|
"log"
|
|
"os"
|
|
|
|
bitset "github.com/skip2/go-qrcode/bitset"
|
|
reedsolomon "github.com/skip2/go-qrcode/reedsolomon"
|
|
)
|
|
|
|
// Encode a QR Code and return a raw PNG image.
|
|
//
|
|
// size is both the image width and height in pixels. If size is too small then
|
|
// a larger image is silently returned. Negative values for size cause a
|
|
// variable sized image to be returned: See the documentation for Image().
|
|
//
|
|
// To serve over HTTP, remember to send a Content-Type: image/png header.
|
|
func Encode(content string, level RecoveryLevel, size int) ([]byte, error) {
|
|
var q *QRCode
|
|
|
|
q, err := New(content, level)
|
|
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return q.PNG(size)
|
|
}
|
|
|
|
// WriteFile encodes, then writes a QR Code to the given filename in PNG format.
|
|
//
|
|
// size is both the image width and height in pixels. If size is too small then
|
|
// a larger image is silently written. Negative values for size cause a variable
|
|
// sized image to be written: See the documentation for Image().
|
|
func WriteFile(content string, level RecoveryLevel, size int, filename string) error {
|
|
var q *QRCode
|
|
|
|
q, err := New(content, level)
|
|
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return q.WriteFile(size, filename)
|
|
}
|
|
|
|
// WriteColorFile encodes, then writes a QR Code to the given filename in PNG format.
|
|
// With WriteColorFile you can also specify the colors you want to use.
|
|
//
|
|
// size is both the image width and height in pixels. If size is too small then
|
|
// a larger image is silently written. Negative values for size cause a variable
|
|
// sized image to be written: See the documentation for Image().
|
|
func WriteColorFile(content string, level RecoveryLevel, size int, background,
|
|
foreground color.Color, filename string) error {
|
|
|
|
var q *QRCode
|
|
|
|
q, err := New(content, level)
|
|
|
|
q.BackgroundColor = background
|
|
q.ForegroundColor = foreground
|
|
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return q.WriteFile(size, filename)
|
|
}
|
|
|
|
// A QRCode represents a valid encoded QRCode.
|
|
type QRCode struct {
|
|
// Original content encoded.
|
|
Content string
|
|
|
|
// QR Code type.
|
|
Level RecoveryLevel
|
|
VersionNumber int
|
|
|
|
// User settable drawing options.
|
|
ForegroundColor color.Color
|
|
BackgroundColor color.Color
|
|
|
|
encoder *dataEncoder
|
|
version qrCodeVersion
|
|
|
|
data *bitset.Bitset
|
|
symbol *symbol
|
|
mask int
|
|
}
|
|
|
|
// New constructs a QRCode.
|
|
//
|
|
// var q *qrcode.QRCode
|
|
// q, err := qrcode.New("my content", qrcode.Medium)
|
|
//
|
|
// An error occurs if the content is too long.
|
|
func New(content string, level RecoveryLevel) (*QRCode, error) {
|
|
encoders := []dataEncoderType{dataEncoderType1To9, dataEncoderType10To26,
|
|
dataEncoderType27To40}
|
|
|
|
var encoder *dataEncoder
|
|
var encoded *bitset.Bitset
|
|
var chosenVersion *qrCodeVersion
|
|
var err error
|
|
|
|
for _, t := range encoders {
|
|
encoder = newDataEncoder(t)
|
|
encoded, err = encoder.encode([]byte(content))
|
|
|
|
if err != nil {
|
|
continue
|
|
}
|
|
|
|
chosenVersion = chooseQRCodeVersion(level, encoder, encoded.Len())
|
|
|
|
if chosenVersion != nil {
|
|
break
|
|
}
|
|
}
|
|
|
|
if err != nil {
|
|
return nil, err
|
|
} else if chosenVersion == nil {
|
|
return nil, errors.New("content too long to encode")
|
|
}
|
|
|
|
q := &QRCode{
|
|
Content: content,
|
|
|
|
Level: level,
|
|
VersionNumber: chosenVersion.version,
|
|
|
|
ForegroundColor: color.Black,
|
|
BackgroundColor: color.White,
|
|
|
|
encoder: encoder,
|
|
data: encoded,
|
|
version: *chosenVersion,
|
|
}
|
|
|
|
q.encode(chosenVersion.numTerminatorBitsRequired(encoded.Len()))
|
|
|
|
return q, nil
|
|
}
|
|
|
|
func newWithForcedVersion(content string, version int, level RecoveryLevel) (*QRCode, error) {
|
|
var encoder *dataEncoder
|
|
|
|
switch {
|
|
case version >= 1 && version <= 9:
|
|
encoder = newDataEncoder(dataEncoderType1To9)
|
|
case version >= 10 && version <= 26:
|
|
encoder = newDataEncoder(dataEncoderType10To26)
|
|
case version >= 27 && version <= 40:
|
|
encoder = newDataEncoder(dataEncoderType27To40)
|
|
default:
|
|
log.Fatalf("Invalid version %d (expected 1-40 inclusive)", version)
|
|
}
|
|
|
|
var encoded *bitset.Bitset
|
|
encoded, err := encoder.encode([]byte(content))
|
|
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
chosenVersion := getQRCodeVersion(level, version)
|
|
|
|
if chosenVersion == nil {
|
|
return nil, errors.New("cannot find QR Code version")
|
|
}
|
|
|
|
q := &QRCode{
|
|
Content: content,
|
|
|
|
Level: level,
|
|
VersionNumber: chosenVersion.version,
|
|
|
|
ForegroundColor: color.Black,
|
|
BackgroundColor: color.White,
|
|
|
|
encoder: encoder,
|
|
data: encoded,
|
|
version: *chosenVersion,
|
|
}
|
|
|
|
q.encode(chosenVersion.numTerminatorBitsRequired(encoded.Len()))
|
|
|
|
return q, nil
|
|
}
|
|
|
|
// Bitmap returns the QR Code as a 2D array of 1-bit pixels.
|
|
//
|
|
// bitmap[y][x] is true if the pixel at (x, y) is set.
|
|
//
|
|
// The bitmap includes the required "quiet zone" around the QR Code to aid
|
|
// decoding.
|
|
func (q *QRCode) Bitmap() [][]bool {
|
|
return q.symbol.bitmap()
|
|
}
|
|
|
|
// Image returns the QR Code as an image.Image.
|
|
//
|
|
// A positive size sets a fixed image width and height (e.g. 256 yields an
|
|
// 256x256px image).
|
|
//
|
|
// Depending on the amount of data encoded, fixed size images can have different
|
|
// amounts of padding (white space around the QR Code). As an alternative, a
|
|
// variable sized image can be generated instead:
|
|
//
|
|
// A negative size causes a variable sized image to be returned. The image
|
|
// returned is the minimum size required for the QR Code. Choose a larger
|
|
// negative number to increase the scale of the image. e.g. a size of -5 causes
|
|
// each module (QR Code "pixel") to be 5px in size.
|
|
func (q *QRCode) Image(size int) image.Image {
|
|
// Minimum pixels (both width and height) required.
|
|
realSize := q.symbol.size
|
|
|
|
// Variable size support.
|
|
if size < 0 {
|
|
size = size * -1 * realSize
|
|
}
|
|
|
|
// Actual pixels available to draw the symbol. Automatically increase the
|
|
// image size if it's not large enough.
|
|
if size < realSize {
|
|
size = realSize
|
|
}
|
|
|
|
// Size of each module drawn.
|
|
pixelsPerModule := size / realSize
|
|
|
|
// Center the symbol within the image.
|
|
offset := (size - realSize*pixelsPerModule) / 2
|
|
|
|
rect := image.Rectangle{Min: image.Point{0, 0}, Max: image.Point{size, size}}
|
|
|
|
// Saves a few bytes to have them in this order
|
|
p := color.Palette([]color.Color{q.BackgroundColor, q.ForegroundColor})
|
|
img := image.NewPaletted(rect, p)
|
|
fgClr := uint8(img.Palette.Index(q.ForegroundColor))
|
|
|
|
bitmap := q.symbol.bitmap()
|
|
for y, row := range bitmap {
|
|
for x, v := range row {
|
|
if v {
|
|
startX := x*pixelsPerModule + offset
|
|
startY := y*pixelsPerModule + offset
|
|
for i := startX; i < startX+pixelsPerModule; i++ {
|
|
for j := startY; j < startY+pixelsPerModule; j++ {
|
|
pos := img.PixOffset(i, j)
|
|
img.Pix[pos] = fgClr
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return img
|
|
}
|
|
|
|
// PNG returns the QR Code as a PNG image.
|
|
//
|
|
// size is both the image width and height in pixels. If size is too small then
|
|
// a larger image is silently returned. Negative values for size cause a
|
|
// variable sized image to be returned: See the documentation for Image().
|
|
func (q *QRCode) PNG(size int) ([]byte, error) {
|
|
img := q.Image(size)
|
|
|
|
encoder := png.Encoder{CompressionLevel: png.BestCompression}
|
|
|
|
var b bytes.Buffer
|
|
err := encoder.Encode(&b, img)
|
|
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return b.Bytes(), nil
|
|
}
|
|
|
|
// Write writes the QR Code as a PNG image to io.Writer.
|
|
//
|
|
// size is both the image width and height in pixels. If size is too small then
|
|
// a larger image is silently written. Negative values for size cause a
|
|
// variable sized image to be written: See the documentation for Image().
|
|
func (q *QRCode) Write(size int, out io.Writer) error {
|
|
var png []byte
|
|
|
|
png, err := q.PNG(size)
|
|
|
|
if err != nil {
|
|
return err
|
|
}
|
|
_, err = out.Write(png)
|
|
return err
|
|
}
|
|
|
|
// WriteFile writes the QR Code as a PNG image to the specified file.
|
|
//
|
|
// size is both the image width and height in pixels. If size is too small then
|
|
// a larger image is silently written. Negative values for size cause a
|
|
// variable sized image to be written: See the documentation for Image().
|
|
func (q *QRCode) WriteFile(size int, filename string) error {
|
|
var png []byte
|
|
|
|
png, err := q.PNG(size)
|
|
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return ioutil.WriteFile(filename, png, os.FileMode(0644))
|
|
}
|
|
|
|
// encode completes the steps required to encode the QR Code. These include
|
|
// adding the terminator bits and padding, splitting the data into blocks and
|
|
// applying the error correction, and selecting the best data mask.
|
|
func (q *QRCode) encode(numTerminatorBits int) {
|
|
q.addTerminatorBits(numTerminatorBits)
|
|
q.addPadding()
|
|
|
|
encoded := q.encodeBlocks()
|
|
|
|
const numMasks int = 8
|
|
penalty := 0
|
|
|
|
for mask := 0; mask < numMasks; mask++ {
|
|
var s *symbol
|
|
var err error
|
|
|
|
s, err = buildRegularSymbol(q.version, mask, encoded)
|
|
|
|
if err != nil {
|
|
log.Panic(err.Error())
|
|
}
|
|
|
|
numEmptyModules := s.numEmptyModules()
|
|
if numEmptyModules != 0 {
|
|
log.Panicf("bug: numEmptyModules is %d (expected 0) (version=%d)",
|
|
numEmptyModules, q.VersionNumber)
|
|
}
|
|
|
|
p := s.penaltyScore()
|
|
|
|
//log.Printf("mask=%d p=%3d p1=%3d p2=%3d p3=%3d p4=%d\n", mask, p, s.penalty1(), s.penalty2(), s.penalty3(), s.penalty4())
|
|
|
|
if q.symbol == nil || p < penalty {
|
|
q.symbol = s
|
|
q.mask = mask
|
|
penalty = p
|
|
}
|
|
}
|
|
}
|
|
|
|
// addTerminatorBits adds final terminator bits to the encoded data.
|
|
//
|
|
// The number of terminator bits required is determined when the QR Code version
|
|
// is chosen (which itself depends on the length of the data encoded). The
|
|
// terminator bits are thus added after the QR Code version
|
|
// is chosen, rather than at the data encoding stage.
|
|
func (q *QRCode) addTerminatorBits(numTerminatorBits int) {
|
|
q.data.AppendNumBools(numTerminatorBits, false)
|
|
}
|
|
|
|
// encodeBlocks takes the completed (terminated & padded) encoded data, splits
|
|
// the data into blocks (as specified by the QR Code version), applies error
|
|
// correction to each block, then interleaves the blocks together.
|
|
//
|
|
// The QR Code's final data sequence is returned.
|
|
func (q *QRCode) encodeBlocks() *bitset.Bitset {
|
|
// Split into blocks.
|
|
type dataBlock struct {
|
|
data *bitset.Bitset
|
|
ecStartOffset int
|
|
}
|
|
|
|
block := make([]dataBlock, q.version.numBlocks())
|
|
|
|
start := 0
|
|
end := 0
|
|
blockID := 0
|
|
|
|
for _, b := range q.version.block {
|
|
for j := 0; j < b.numBlocks; j++ {
|
|
start = end
|
|
end = start + b.numDataCodewords*8
|
|
|
|
// Apply error correction to each block.
|
|
numErrorCodewords := b.numCodewords - b.numDataCodewords
|
|
block[blockID].data = reedsolomon.Encode(q.data.Substr(start, end), numErrorCodewords)
|
|
block[blockID].ecStartOffset = end - start
|
|
|
|
blockID++
|
|
}
|
|
}
|
|
|
|
// Interleave the blocks.
|
|
|
|
result := bitset.New()
|
|
|
|
// Combine data blocks.
|
|
working := true
|
|
for i := 0; working; i += 8 {
|
|
working = false
|
|
|
|
for j, b := range block {
|
|
if i >= block[j].ecStartOffset {
|
|
continue
|
|
}
|
|
|
|
result.Append(b.data.Substr(i, i+8))
|
|
|
|
working = true
|
|
}
|
|
}
|
|
|
|
// Combine error correction blocks.
|
|
working = true
|
|
for i := 0; working; i += 8 {
|
|
working = false
|
|
|
|
for j, b := range block {
|
|
offset := i + block[j].ecStartOffset
|
|
if offset >= block[j].data.Len() {
|
|
continue
|
|
}
|
|
|
|
result.Append(b.data.Substr(offset, offset+8))
|
|
|
|
working = true
|
|
}
|
|
}
|
|
|
|
// Append remainder bits.
|
|
result.AppendNumBools(q.version.numRemainderBits, false)
|
|
|
|
return result
|
|
}
|
|
|
|
// max returns the maximum of a and b.
|
|
func max(a int, b int) int {
|
|
if a > b {
|
|
return a
|
|
}
|
|
|
|
return b
|
|
}
|
|
|
|
// addPadding pads the encoded data upto the full length required.
|
|
func (q *QRCode) addPadding() {
|
|
numDataBits := q.version.numDataBits()
|
|
|
|
if q.data.Len() == numDataBits {
|
|
return
|
|
}
|
|
|
|
// Pad to the nearest codeword boundary.
|
|
q.data.AppendNumBools(q.version.numBitsToPadToCodeword(q.data.Len()), false)
|
|
|
|
// Pad codewords 0b11101100 and 0b00010001.
|
|
padding := [2]*bitset.Bitset{
|
|
bitset.New(true, true, true, false, true, true, false, false),
|
|
bitset.New(false, false, false, true, false, false, false, true),
|
|
}
|
|
|
|
// Insert pad codewords alternately.
|
|
i := 0
|
|
for numDataBits-q.data.Len() >= 8 {
|
|
q.data.Append(padding[i])
|
|
|
|
i = 1 - i // Alternate between 0 and 1.
|
|
}
|
|
|
|
if q.data.Len() != numDataBits {
|
|
log.Panicf("BUG: got len %d, expected %d", q.data.Len(), numDataBits)
|
|
}
|
|
}
|
|
|
|
// ToString produces a multi-line string that forms a QR-code image.
|
|
func (q *QRCode) ToString(inverseColor bool) string {
|
|
bits := q.Bitmap()
|
|
var buf bytes.Buffer
|
|
for y := range bits {
|
|
for x := range bits[y] {
|
|
if bits[y][x] != inverseColor {
|
|
buf.WriteString(" ")
|
|
} else {
|
|
buf.WriteString("██")
|
|
}
|
|
}
|
|
buf.WriteString("\n")
|
|
}
|
|
return buf.String()
|
|
}
|
|
|
|
// ToSmallString produces a multi-line string that forms a QR-code image, a
|
|
// factor two smaller in x and y then ToString.
|
|
func (q *QRCode) ToSmallString(inverseColor bool) string {
|
|
bits := q.Bitmap()
|
|
var buf bytes.Buffer
|
|
// if there is an odd number of rows, the last one needs special treatment
|
|
for y := 0; y < len(bits)-1; y += 2 {
|
|
for x := range bits[y] {
|
|
if bits[y][x] == bits[y+1][x] {
|
|
if bits[y][x] != inverseColor {
|
|
buf.WriteString(" ")
|
|
} else {
|
|
buf.WriteString("█")
|
|
}
|
|
} else {
|
|
if bits[y][x] != inverseColor {
|
|
buf.WriteString("▄")
|
|
} else {
|
|
buf.WriteString("▀")
|
|
}
|
|
}
|
|
}
|
|
buf.WriteString("\n")
|
|
}
|
|
// special treatment for the last row if odd
|
|
if len(bits)%2 == 1 {
|
|
y := len(bits) - 1
|
|
for x := range bits[y] {
|
|
if bits[y][x] != inverseColor {
|
|
buf.WriteString(" ")
|
|
} else {
|
|
buf.WriteString("▀")
|
|
}
|
|
}
|
|
buf.WriteString("\n")
|
|
}
|
|
return buf.String()
|
|
}
|