evan.hong 4 年之前
父节点
当前提交
64ead27fac
共有 11 个文件被更改,包括 222 次插入46 次删除
  1. 18 12
      README.md
  2. 33 2
      encoder.go
  3. 85 3
      encoder_test.go
  4. 22 0
      example_test.go
  5. 3 0
      go.mod
  6. 43 24
      qrcode.go
  7. 5 0
      qrcode/main.go
  8. 2 2
      qrcode_decode_test.go
  9. 2 0
      qrcode_test.go
  10. 8 2
      regular_symbol.go
  11. 1 1
      regular_symbol_test.go

+ 18 - 12
README.md

@@ -18,26 +18,20 @@ A command-line tool `qrcode` will be built into `$GOPATH/bin/`.
 
     import qrcode "github.com/skip2/go-qrcode"
 
-- **Create a PNG image:**
+- **Create a 256x256 PNG image:**
 
         var png []byte
         png, err := qrcode.Encode("https://example.org", qrcode.Medium, 256)
 
-- **Create a PNG image and write to a file:**
+- **Create a 256x256 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:**
+- **Create a 256x256 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.
-
-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.
+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.
 
 ## Documentation
 
@@ -56,10 +50,13 @@ qrcode -- QR Code encoder in Go
 https://github.com/skip2/go-qrcode
 
 Flags:
+  -d	disable QR Code border
+  -i	invert black and white
   -o string
-        out PNG file prefix, empty for stdout
+    	out PNG file prefix, empty for stdout
   -s int
-        image size (pixel) (default 256)
+    	image size (pixel) (default 256)
+  -t	print as text-art on stdout
 
 Usage:
   1. Arguments except for flags are joined by " " and used to generate QR code.
@@ -71,7 +68,16 @@ Usage:
   2. Save to file if "display" not available:
 
        qrcode "homepage: https://github.com/skip2/go-qrcode" > out.png
+
 ```
+## Maximum capacity
+The maximum capacity of a QR Code varies according to the content encoded and the error recovery level. The maximum capacity is 2,953 bytes, 4,296 alphanumeric characters, 7,089 numeric digits, or a combination of these.
+
+## Borderless QR Codes
+
+To aid QR Code reading software, QR codes have a built in whitespace border.
+
+If you know what you're doing, and don't want a border, see https://gist.github.com/skip2/7e3d8a82f5317df9be437f8ec8ec0b7d for how to do it. It's still recommended you include a border manually.
 
 ## Links
 

+ 33 - 2
encoder.go

@@ -172,7 +172,7 @@ func (d *dataEncoder) encode(data []byte) (*bitset.Bitset, error) {
 	}
 
 	// Classify data into unoptimised segments.
-	d.classifyDataModes()
+	highestRequiredMode := d.classifyDataModes()
 
 	// Optimise segments.
 	err := d.optimiseDataModes()
@@ -180,6 +180,25 @@ func (d *dataEncoder) encode(data []byte) (*bitset.Bitset, error) {
 		return nil, err
 	}
 
+	// Check if a single byte encoded segment would be more efficient.
+	optimizedLength := 0
+	for _, s := range d.optimised {
+		length, err := d.encodedLength(s.dataMode, len(s.data))
+		if err != nil {
+			return nil, err
+		}
+		optimizedLength += length
+	}
+
+	singleByteSegmentLength, err := d.encodedLength(highestRequiredMode, len(d.data))
+	if err != nil {
+		return nil, err
+	}
+
+	if singleByteSegmentLength <= optimizedLength {
+		d.optimised = []segment{segment{dataMode: highestRequiredMode, data: d.data}}
+	}
+
 	// Encode data.
 	encoded := bitset.New()
 	for _, s := range d.optimised {
@@ -192,9 +211,15 @@ func (d *dataEncoder) encode(data []byte) (*bitset.Bitset, error) {
 // classifyDataModes classifies the raw data into unoptimised segments.
 // e.g. "123ZZ#!#!" =>
 // [numeric, 3, "123"] [alphanumeric, 2, "ZZ"] [byte, 4, "#!#!"].
-func (d *dataEncoder) classifyDataModes() {
+//
+// Returns the highest data mode needed to encode the data. e.g. for a mixed
+// numeric/alphanumeric input, the highest is alphanumeric.
+//
+// dataModeNone < dataModeNumeric < dataModeAlphanumeric < dataModeByte
+func (d *dataEncoder) classifyDataModes() dataMode {
 	var start int
 	mode := dataModeNone
+	highestRequiredMode := mode
 
 	for i, v := range d.data {
 		newMode := dataModeNone
@@ -217,9 +242,15 @@ func (d *dataEncoder) classifyDataModes() {
 
 			mode = newMode
 		}
+
+		if newMode > highestRequiredMode {
+			highestRequiredMode = newMode
+		}
 	}
 
 	d.actual = append(d.actual, segment{dataMode: mode, data: d.data[start:len(d.data)]})
+
+	return highestRequiredMode
 }
 
 // optimiseDataModes optimises the list of segments to reduce the overall output

+ 85 - 3
encoder_test.go

@@ -177,6 +177,19 @@ func TestOptimiseEncoding(t *testing.T) {
 			},
 		},
 		// Switch to more general dataMode.
+		{
+			dataEncoderType1To9,
+			[]testModeSegment{
+				{dataModeAlphanumeric, 100},
+				{dataModeByte, 1},
+				{dataModeNumeric, 1},
+			},
+			[]testModeSegment{
+				{dataModeAlphanumeric, 100},
+				{dataModeByte, 2},
+			},
+		},
+		// Sometimes encoding everything as bytes is more efficient.
 		{
 			dataEncoderType1To9,
 			[]testModeSegment{
@@ -185,12 +198,31 @@ func TestOptimiseEncoding(t *testing.T) {
 				{dataModeNumeric, 1},
 			},
 			[]testModeSegment{
+				{dataModeByte, 3},
+			},
+		},
+		// https://www.google.com/123456789012345678901234567890
+		// BBBBBAAABBBABBBBBBABBBANNNNNNNNNNNNNNNNNNNNNNNNNNNNNN
+		{
+			dataEncoderType1To9,
+			[]testModeSegment{
+				{dataModeByte, 5},
+				{dataModeAlphanumeric, 3},
+				{dataModeByte, 3},
 				{dataModeAlphanumeric, 1},
-				{dataModeByte, 2},
+				{dataModeByte, 6},
+				{dataModeAlphanumeric, 1},
+				{dataModeAlphanumeric, 4},
+				{dataModeNumeric, 30},
+			},
+			[]testModeSegment{
+				{dataModeByte, 23},
+				{dataModeNumeric, 30},
 			},
 		},
 		// https://www.google.com/123
 		// BBBBBAAABBBABBBBBBABBBANNN
+		// Small segments are inefficient because of additional metadata.
 		{
 			dataEncoderType1To9,
 			[]testModeSegment{
@@ -204,8 +236,7 @@ func TestOptimiseEncoding(t *testing.T) {
 				{dataModeNumeric, 3},
 			},
 			[]testModeSegment{
-				{dataModeByte, 23},
-				{dataModeNumeric, 3},
+				{dataModeByte, 26},
 			},
 		},
 		// HTTPS://WWW.GOOGLE.COM/123
@@ -236,6 +267,57 @@ func TestOptimiseEncoding(t *testing.T) {
 				{dataModeByte, 8},
 			},
 		},
+		// HTTPS://ABC.DE/Q/393AABB6998877XYZ0518AUQCRVJN25
+		// AAAAAAAAAAAAAAAAANNNAAAANNNNNNNAAANNNNAAAAAAAANN
+		// different to below---------^--------------------
+		{
+			dataEncoderType1To9,
+			[]testModeSegment{
+				{dataModeAlphanumeric, 17},
+				{dataModeNumeric, 3},
+				{dataModeAlphanumeric, 4},
+				{dataModeNumeric, 7},
+				{dataModeAlphanumeric, 3},
+				{dataModeNumeric, 4},
+				{dataModeAlphanumeric, 8},
+				{dataModeNumeric, 2},
+			},
+			[]testModeSegment{
+				{dataModeAlphanumeric, 48},
+			},
+		},
+		// HTTPS://ABC.DE/Q/393AABB699E877XYZ0518AUQCRVJN25
+		// AAAAAAAAAAAAAAAAANNNAAAANNNANNNAAANNNNAAAAAAAANN
+		// different to above---------^--------------------
+		{
+			dataEncoderType1To9,
+			[]testModeSegment{
+				{dataModeAlphanumeric, 17},
+				{dataModeNumeric, 3},
+				{dataModeAlphanumeric, 4},
+				{dataModeNumeric, 3},
+				{dataModeAlphanumeric, 1},
+				{dataModeNumeric, 3},
+				{dataModeAlphanumeric, 3},
+				{dataModeNumeric, 4},
+				{dataModeAlphanumeric, 8},
+				{dataModeNumeric, 2},
+			},
+			[]testModeSegment{
+				{dataModeAlphanumeric, 48},
+			},
+		},
+		// 0123456789
+		// NNNNNNNNNN
+		{
+			dataEncoderType1To9,
+			[]testModeSegment{
+				{dataModeNumeric, 10},
+			},
+			[]testModeSegment{
+				{dataModeNumeric, 10},
+			},
+		},
 	}
 
 	for _, test := range tests {

+ 22 - 0
example_test.go

@@ -9,6 +9,7 @@ package qrcode
 
 import (
 	"fmt"
+	"image/color"
 	"os"
 	"testing"
 )
@@ -29,3 +30,24 @@ func TestExampleWriteFile(t *testing.T) {
 		}
 	}
 }
+
+func TestExampleEncodeWithColourAndWithoutBorder(t *testing.T) {
+	q, err := New("https://example.org", Medium)
+	if err != nil {
+		t.Errorf("Error: %s", err)
+		return
+	}
+
+	// Optionally, disable the QR Code border.
+	q.DisableBorder = true
+
+	// Optionally, set the colours.
+	q.ForegroundColor = color.RGBA{R: 0x33, G: 0x33, B: 0x66, A: 0xff}
+	q.BackgroundColor = color.RGBA{R: 0xef, G: 0xef, B: 0xef, A: 0xff}
+
+	err = q.WriteFile(256, "example2.png")
+	if err != nil {
+		t.Errorf("Error: %s", err)
+		return
+	}
+}

+ 3 - 0
go.mod

@@ -0,0 +1,3 @@
+module github.com/skip2/go-qrcode
+
+go 1.13

+ 43 - 24
qrcode.go

@@ -51,6 +51,7 @@ package qrcode
 import (
 	"bytes"
 	"errors"
+	"fmt"
 	"image"
 	"image/color"
 	"image/png"
@@ -135,6 +136,9 @@ type QRCode struct {
 	ForegroundColor color.Color
 	BackgroundColor color.Color
 
+	// Disable the QR Code border.
+	DisableBorder bool
+
 	encoder *dataEncoder
 	version qrCodeVersion
 
@@ -193,12 +197,16 @@ func New(content string, level RecoveryLevel) (*QRCode, error) {
 		version: *chosenVersion,
 	}
 
-	q.encode(chosenVersion.numTerminatorBitsRequired(encoded.Len()))
-
 	return q, nil
 }
 
-func newWithForcedVersion(content string, version int, level RecoveryLevel) (*QRCode, error) {
+// NewWithForcedVersion constructs a QRCode of a specific version.
+//
+//	var q *qrcode.QRCode
+//	q, err := qrcode.NewWithForcedVersion("my content", 25, qrcode.Medium)
+//
+// An error occurs in case of invalid version.
+func NewWithForcedVersion(content string, version int, level RecoveryLevel) (*QRCode, error) {
 	var encoder *dataEncoder
 
 	switch {
@@ -209,7 +217,7 @@ func newWithForcedVersion(content string, version int, level RecoveryLevel) (*QR
 	case version >= 27 && version <= 40:
 		encoder = newDataEncoder(dataEncoderType27To40)
 	default:
-		log.Fatalf("Invalid version %d (expected 1-40 inclusive)", version)
+		return nil, fmt.Errorf("Invalid version %d (expected 1-40 inclusive)", version)
 	}
 
 	var encoded *bitset.Bitset
@@ -225,6 +233,13 @@ func newWithForcedVersion(content string, version int, level RecoveryLevel) (*QR
 		return nil, errors.New("cannot find QR Code version")
 	}
 
+	if encoded.Len() > chosenVersion.numDataBits() {
+		return nil, fmt.Errorf("Cannot encode QR code: content too large for fixed size QR Code version %d (encoded length is %d bits, maximum length is %d bits)",
+			version,
+			encoded.Len(),
+			chosenVersion.numDataBits())
+	}
+
 	q := &QRCode{
 		Content: content,
 
@@ -239,8 +254,6 @@ func newWithForcedVersion(content string, version int, level RecoveryLevel) (*QR
 		version: *chosenVersion,
 	}
 
-	q.encode(chosenVersion.numTerminatorBitsRequired(encoded.Len()))
-
 	return q, nil
 }
 
@@ -251,6 +264,9 @@ func newWithForcedVersion(content string, version int, level RecoveryLevel) (*QR
 // The bitmap includes the required "quiet zone" around the QR Code to aid
 // decoding.
 func (q *QRCode) Bitmap() [][]bool {
+	// Build QR code.
+	q.encode()
+
 	return q.symbol.bitmap()
 }
 
@@ -268,6 +284,9 @@ func (q *QRCode) Bitmap() [][]bool {
 // 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 {
+	// Build QR code.
+	q.encode()
+
 	// Minimum pixels (both width and height) required.
 	realSize := q.symbol.size
 
@@ -282,12 +301,7 @@ func (q *QRCode) Image(size int) image.Image {
 		size = realSize
 	}
 
-	// Size of each module drawn.
-	pixelsPerModule := size / realSize
-
-	// Center the symbol within the image.
-	offset := (size - realSize*pixelsPerModule) / 2
-
+	// Output image.
 	rect := image.Rectangle{Min: image.Point{0, 0}, Max: image.Point{size, size}}
 
 	// Saves a few bytes to have them in this order
@@ -295,18 +309,21 @@ func (q *QRCode) Image(size int) image.Image {
 	img := image.NewPaletted(rect, p)
 	fgClr := uint8(img.Palette.Index(q.ForegroundColor))
 
+	// QR code bitmap.
 	bitmap := q.symbol.bitmap()
-	for y, row := range bitmap {
-		for x, v := range row {
+
+	// Map each image pixel to the nearest QR code module.
+	modulesPerPixel := float64(realSize) / float64(size)
+	for y := 0; y < size; y++ {
+		y2 := int(float64(y) * modulesPerPixel)
+		for x := 0; x < size; x++ {
+			x2 := int(float64(x) * modulesPerPixel)
+
+			v := bitmap[y2][x2]
+
 			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
-					}
-				}
+				pos := img.PixOffset(x, y)
+				img.Pix[pos] = fgClr
 			}
 		}
 	}
@@ -371,7 +388,9 @@ func (q *QRCode) WriteFile(size int, filename string) error {
 // 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) {
+func (q *QRCode) encode() {
+	numTerminatorBits := q.version.numTerminatorBitsRequired(q.data.Len())
+
 	q.addTerminatorBits(numTerminatorBits)
 	q.addPadding()
 
@@ -384,7 +403,7 @@ func (q *QRCode) encode(numTerminatorBits int) {
 		var s *symbol
 		var err error
 
-		s, err = buildRegularSymbol(q.version, mask, encoded)
+		s, err = buildRegularSymbol(q.version, mask, encoded, !q.DisableBorder)
 
 		if err != nil {
 			log.Panic(err.Error())

+ 5 - 0
qrcode/main.go

@@ -17,6 +17,7 @@ func main() {
 	size := flag.Int("s", 256, "image size (pixel)")
 	textArt := flag.Bool("t", false, "print as text-art on stdout")
 	negative := flag.Bool("i", false, "invert black and white")
+	disableBorder := flag.Bool("d", false, "disable QR Code border")
 	flag.Usage = func() {
 		fmt.Fprintf(os.Stderr, `qrcode -- QR Code encoder in Go
 https://github.com/skip2/go-qrcode
@@ -52,6 +53,10 @@ Usage:
 	q, err = qrcode.New(content, qrcode.Highest)
 	checkError(err)
 
+	if *disableBorder {
+		q.DisableBorder = true
+	}
+
 	if *textArt {
 		art := q.ToString(*negative)
 		fmt.Println(art)

+ 2 - 2
qrcode_decode_test.go

@@ -93,7 +93,7 @@ func TestDecodeAllVersionLevels(t *testing.T) {
 				version,
 				level)
 
-			q, err := newWithForcedVersion(
+			q, err := NewWithForcedVersion(
 				fmt.Sprintf("v-%d l-%d", version, level), version, level)
 			if err != nil {
 				t.Fatal(err.Error())
@@ -196,7 +196,7 @@ func zbarimgDecode(q *QRCode) (string, error) {
 	}
 
 	cmd := exec.Command("zbarimg", "--quiet", "-Sdisable",
-		"-Sqrcode.enable", "/dev/stdin")
+		"-Sqrcode.enable", "-")
 
 	var out bytes.Buffer
 

+ 2 - 0
qrcode_test.go

@@ -151,6 +151,8 @@ func TestQRCodeISOAnnexIExample(t *testing.T) {
 			err.Error())
 	}
 
+	q.encode()
+
 	const expectedMask int = 2
 
 	if q.mask != 2 {

+ 8 - 2
regular_symbol.go

@@ -105,13 +105,19 @@ var (
 )
 
 func buildRegularSymbol(version qrCodeVersion, mask int,
-	data *bitset.Bitset) (*symbol, error) {
+	data *bitset.Bitset, includeQuietZone bool) (*symbol, error) {
+
+	quietZoneSize := 0
+	if includeQuietZone {
+		quietZoneSize = version.quietZoneSize()
+	}
+
 	m := &regularSymbol{
 		version: version,
 		mask:    mask,
 		data:    data,
 
-		symbol: newSymbol(version.symbolSize(), version.quietZoneSize()),
+		symbol: newSymbol(version.symbolSize(), quietZoneSize),
 		size:   version.symbolSize(),
 	}
 

+ 1 - 1
regular_symbol_test.go

@@ -19,7 +19,7 @@ func TestBuildRegularSymbol(t *testing.T) {
 			data.AppendNumBools(8, false)
 		}
 
-		s, err := buildRegularSymbol(*v, k, data)
+		s, err := buildRegularSymbol(*v, k, data, false)
 
 		if err != nil {
 			fmt.Println(err.Error())