5
0
mirror of https://github.com/cwinfo/matterbridge.git synced 2024-12-27 07:45:40 +00:00
matterbridge/vendor/github.com/skip2/go-qrcode/qrcode.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()
}