├── .github └── workflows │ └── go.yml ├── .travis.yml ├── LICENSE ├── README.md ├── decoding.go ├── define.go ├── errors.go ├── example ├── main.go └── testdata │ ├── 000.jpg │ ├── 001.jpg │ ├── 002.jpg │ ├── 003.jpg │ ├── 004.png │ ├── 005.png │ ├── 006.png │ ├── 007.png │ └── 008.png ├── go.mod ├── go.sum ├── qr_const.go ├── qrcode.go ├── recognition.go ├── recognition_test.go ├── recognizer.go ├── utils.go └── version_db.go /.github/workflows/go.yml: -------------------------------------------------------------------------------- 1 | name: Go 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | 11 | build: 12 | name: Build 13 | runs-on: ubuntu-latest 14 | steps: 15 | 16 | - name: Set up Go 1.x 17 | uses: actions/setup-go@v2 18 | with: 19 | go-version: ^1.13 20 | id: go 21 | 22 | - name: Check out code into the Go module directory 23 | uses: actions/checkout@v2 24 | 25 | - name: Get dependencies 26 | run: | 27 | go get -v -t -d ./... 28 | if [ -f Gopkg.toml ]; then 29 | curl https://raw.githubusercontent.com/golang/dep/master/install.sh | sh 30 | dep ensure 31 | fi 32 | 33 | - name: Test 34 | run: go test -v -race -coverprofile=coverage.txt -covermode=atomic ./... 35 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | 3 | notifications: 4 | email: 5 | recipients: 6 | - liyue201@126.com 7 | on_success: change 8 | on_failure: always 9 | 10 | go: 11 | - 1.12 12 | 13 | install: 14 | - go get github.com/mattn/goveralls 15 | 16 | script: 17 | - go test ./... -v -covermode=count -coverprofile=coverage.out 18 | - $GOPATH/bin/goveralls -coverprofile=coverage.out -service=travis-ci -repotoken $COVERALLS_TOKEN 19 | 20 | env: 21 | global: 22 | - GO111MODULE=on 23 | - secure: "aOzEdb/ST82OpO4LqLpwrdRs6GnkmdLJbm3o9+2HmJCyiHkhqPY1bGY1TiYxQLl3GkjFkuVP+3RB+E0p2R/1f4xYkg4cIpPoLm+4xJ7c1NsEGTaWVS8rCNTicqdym7C9kJart6KcAeWWoElFxob3+lTdS6sz4kFaURrVQ/lpbzBESH0B5Rwa35u1WasmHkPxsHJ/2a7Xnknv+SerjgMUDiXUYM5IjUWt2fV2Zq/9blTSReng6+6Lj9EdziZcQfWNJxBK/NDBtpNIpOdY+bZEUDQi7InB3IioKpQeVjMGKSAzqhedqnGbZsaO734Z8VjRjcFRJPuuYMMkMw4hGoplDqqrTa2aFgln54NiNk2R6WIcpPKAFdLEL7rCPx6qRLMRq1EEn6h+lAy5mJ6NIpmJmY/IbDEVH9D4z4YVLJbHhU+yAN4Tk5mwW9N2bLceSNxIRSJcCcDlRiBNba5XLdNb/tHq7PEM2lX1oXp4WzdYgGh08a69OAweY/CRQjiTZrbhmbsZ5qo3UMLA34HTfEVWt8UWeAFry5fhtzeLqQa5h0BPwtFsy9bsdukEL3nAWcWF4HaQ2jh2CNO6OvpzRb8i45qj1QpIvGqy/lJbuO5WswFQaZcKK2nmPa2M4f7+EBGFe3VvmOJcJvAt2ZRYVZalbdTLDKMmMgcdYIL4jzzcSs4=" -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU LESSER GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | 9 | This version of the GNU Lesser General Public License incorporates 10 | the terms and conditions of version 3 of the GNU General Public 11 | License, supplemented by the additional permissions listed below. 12 | 13 | 0. Additional Definitions. 14 | 15 | As used herein, "this License" refers to version 3 of the GNU Lesser 16 | General Public License, and the "GNU GPL" refers to version 3 of the GNU 17 | General Public License. 18 | 19 | "The Library" refers to a covered work governed by this License, 20 | other than an Application or a Combined Work as defined below. 21 | 22 | An "Application" is any work that makes use of an interface provided 23 | by the Library, but which is not otherwise based on the Library. 24 | Defining a subclass of a class defined by the Library is deemed a mode 25 | of using an interface provided by the Library. 26 | 27 | A "Combined Work" is a work produced by combining or linking an 28 | Application with the Library. The particular version of the Library 29 | with which the Combined Work was made is also called the "Linked 30 | Version". 31 | 32 | The "Minimal Corresponding Source" for a Combined Work means the 33 | Corresponding Source for the Combined Work, excluding any source code 34 | for portions of the Combined Work that, considered in isolation, are 35 | based on the Application, and not on the Linked Version. 36 | 37 | The "Corresponding Application Code" for a Combined Work means the 38 | object code and/or source code for the Application, including any data 39 | and utility programs needed for reproducing the Combined Work from the 40 | Application, but excluding the System Libraries of the Combined Work. 41 | 42 | 1. Exception to Section 3 of the GNU GPL. 43 | 44 | You may convey a covered work under sections 3 and 4 of this License 45 | without being bound by section 3 of the GNU GPL. 46 | 47 | 2. Conveying Modified Versions. 48 | 49 | If you modify a copy of the Library, and, in your modifications, a 50 | facility refers to a function or data to be supplied by an Application 51 | that uses the facility (other than as an argument passed when the 52 | facility is invoked), then you may convey a copy of the modified 53 | version: 54 | 55 | a) under this License, provided that you make a good faith effort to 56 | ensure that, in the event an Application does not supply the 57 | function or data, the facility still operates, and performs 58 | whatever part of its purpose remains meaningful, or 59 | 60 | b) under the GNU GPL, with none of the additional permissions of 61 | this License applicable to that copy. 62 | 63 | 3. Object Code Incorporating Material from Library Header Files. 64 | 65 | The object code form of an Application may incorporate material from 66 | a header file that is part of the Library. You may convey such object 67 | code under terms of your choice, provided that, if the incorporated 68 | material is not limited to numerical parameters, data structure 69 | layouts and accessors, or small macros, inline functions and templates 70 | (ten or fewer lines in length), you do both of the following: 71 | 72 | a) Give prominent notice with each copy of the object code that the 73 | Library is used in it and that the Library and its use are 74 | covered by this License. 75 | 76 | b) Accompany the object code with a copy of the GNU GPL and this license 77 | document. 78 | 79 | 4. Combined Works. 80 | 81 | You may convey a Combined Work under terms of your choice that, 82 | taken together, effectively do not restrict modification of the 83 | portions of the Library contained in the Combined Work and reverse 84 | engineering for debugging such modifications, if you also do each of 85 | the following: 86 | 87 | a) Give prominent notice with each copy of the Combined Work that 88 | the Library is used in it and that the Library and its use are 89 | covered by this License. 90 | 91 | b) Accompany the Combined Work with a copy of the GNU GPL and this license 92 | document. 93 | 94 | c) For a Combined Work that displays copyright notices during 95 | execution, include the copyright notice for the Library among 96 | these notices, as well as a reference directing the user to the 97 | copies of the GNU GPL and this license document. 98 | 99 | d) Do one of the following: 100 | 101 | 0) Convey the Minimal Corresponding Source under the terms of this 102 | License, and the Corresponding Application Code in a form 103 | suitable for, and under terms that permit, the user to 104 | recombine or relink the Application with a modified version of 105 | the Linked Version to produce a modified Combined Work, in the 106 | manner specified by section 6 of the GNU GPL for conveying 107 | Corresponding Source. 108 | 109 | 1) Use a suitable shared library mechanism for linking with the 110 | Library. A suitable mechanism is one that (a) uses at run time 111 | a copy of the Library already present on the user's computer 112 | system, and (b) will operate properly with a modified version 113 | of the Library that is interface-compatible with the Linked 114 | Version. 115 | 116 | e) Provide Installation Information, but only if you would otherwise 117 | be required to provide such information under section 6 of the 118 | GNU GPL, and only to the extent that such information is 119 | necessary to install and execute a modified version of the 120 | Combined Work produced by recombining or relinking the 121 | Application with a modified version of the Linked Version. (If 122 | you use option 4d0, the Installation Information must accompany 123 | the Minimal Corresponding Source and Corresponding Application 124 | Code. If you use option 4d1, you must provide the Installation 125 | Information in the manner specified by section 6 of the GNU GPL 126 | for conveying Corresponding Source.) 127 | 128 | 5. Combined Libraries. 129 | 130 | You may place library facilities that are a work based on the 131 | Library side by side in a single library together with other library 132 | facilities that are not Applications and are not covered by this 133 | License, and convey such a combined library under terms of your 134 | choice, if you do both of the following: 135 | 136 | a) Accompany the combined library with a copy of the same work based 137 | on the Library, uncombined with any other library facilities, 138 | conveyed under the terms of this License. 139 | 140 | b) Give prominent notice with the combined library that part of it 141 | is a work based on the Library, and explaining where to find the 142 | accompanying uncombined form of the same work. 143 | 144 | 6. Revised Versions of the GNU Lesser General Public License. 145 | 146 | The Free Software Foundation may publish revised and/or new versions 147 | of the GNU Lesser General Public License from time to time. Such new 148 | versions will be similar in spirit to the present version, but may 149 | differ in detail to address new problems or concerns. 150 | 151 | Each version is given a distinguishing version number. If the 152 | Library as you received it specifies that a certain numbered version 153 | of the GNU Lesser General Public License "or any later version" 154 | applies to it, you have the option of following the terms and 155 | conditions either of that published version or of any later version 156 | published by the Free Software Foundation. If the Library as you 157 | received it does not specify a version number of the GNU Lesser 158 | General Public License, you may choose any version of the GNU Lesser 159 | General Public License ever published by the Free Software Foundation. 160 | 161 | If the Library as you received it specifies that a proxy can decide 162 | whether future versions of the GNU Lesser General Public License shall 163 | apply, that proxy's public statement of acceptance of any version is 164 | permanent authorization for you to choose that version for the 165 | Library. 166 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # goqr 2 | [![GoDoc](https://godoc.org/github.com/liyue201/goqr?status.svg)](https://godoc.org/github.com/liyue201/goqr) 3 | [![Go Report Card](https://goreportcard.com/badge/github.com/liyue201/goqr)](https://goreportcard.com/report/github.com/liyue201/goqr) 4 | [![Build Status](https://travis-ci.org/liyue201/goqr.svg?branch=master)](https://travis-ci.org/liyue201/goqr) 5 | [![Coverall](https://coveralls.io/repos/github/liyue201/goqr/badge.svg?branch=master)](https://coveralls.io/github/liyue201/goqr) 6 | [![License](https://img.shields.io/badge/license-GPLv3-brightgreen.svg)](/LICENSE) 7 | [![Example](https://img.shields.io/badge/learn-example-brightgreen.svg)](/example) 8 | 9 | 10 | This is a QR Code recognition and decoding library in pure go. It can recognize most of images into QR Code string. 11 | 12 | # Example 13 | 14 | ``` 15 | package main 16 | 17 | import ( 18 | "bytes" 19 | "fmt" 20 | "github.com/liyue201/goqr" 21 | "image" 22 | _ "image/jpeg" 23 | _ "image/png" 24 | "io/ioutil" 25 | ) 26 | 27 | func recognizeFile(path string) { 28 | fmt.Printf("recognize file: %v\n", path) 29 | imgdata, err := ioutil.ReadFile(path) 30 | if err != nil { 31 | fmt.Printf("%v\n", err) 32 | return 33 | } 34 | 35 | img, _, err := image.Decode(bytes.NewReader(imgdata)) 36 | if err != nil { 37 | fmt.Printf("image.Decode error: %v\n", err) 38 | return 39 | } 40 | qrCodes, err := goqr.Recognize(img) 41 | if err != nil { 42 | fmt.Printf("Recognize failed: %v\n", err) 43 | return 44 | } 45 | for _, qrCode := range qrCodes { 46 | fmt.Printf("qrCode text: %s\n", qrCode.Payload) 47 | } 48 | } 49 | 50 | func main() { 51 | recognizeFile("testdata/008.png") 52 | } 53 | 54 | ``` 55 | -------------------------------------------------------------------------------- /decoding.go: -------------------------------------------------------------------------------- 1 | package goqr 2 | 3 | import ( 4 | "math" 5 | ) 6 | 7 | const maxPoly = 64 8 | 9 | type galoisField struct { 10 | p int 11 | log []uint8 12 | exp []uint8 13 | } 14 | 15 | var gf16Exp = []uint8{ 16 | 0x01, 0x02, 0x04, 0x08, 0x03, 0x06, 0x0c, 0x0b, 17 | 0x05, 0x0a, 0x07, 0x0e, 0x0f, 0x0d, 0x09, 0x01, 18 | } 19 | 20 | var gf16Log = []uint8{ 21 | 0x00, 0x0f, 0x01, 0x04, 0x02, 0x08, 0x05, 0x0a, 22 | 0x03, 0x0e, 0x09, 0x07, 0x06, 0x0d, 0x0b, 0x0c, 23 | } 24 | 25 | var gf16 = galoisField{ 26 | p: 15, 27 | log: gf16Log, 28 | exp: gf16Exp, 29 | } 30 | 31 | var gf256Exp = [256]uint8{ 32 | 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 33 | 0x1d, 0x3a, 0x74, 0xe8, 0xcd, 0x87, 0x13, 0x26, 34 | 0x4c, 0x98, 0x2d, 0x5a, 0xb4, 0x75, 0xea, 0xc9, 35 | 0x8f, 0x03, 0x06, 0x0c, 0x18, 0x30, 0x60, 0xc0, 36 | 0x9d, 0x27, 0x4e, 0x9c, 0x25, 0x4a, 0x94, 0x35, 37 | 0x6a, 0xd4, 0xb5, 0x77, 0xee, 0xc1, 0x9f, 0x23, 38 | 0x46, 0x8c, 0x05, 0x0a, 0x14, 0x28, 0x50, 0xa0, 39 | 0x5d, 0xba, 0x69, 0xd2, 0xb9, 0x6f, 0xde, 0xa1, 40 | 0x5f, 0xbe, 0x61, 0xc2, 0x99, 0x2f, 0x5e, 0xbc, 41 | 0x65, 0xca, 0x89, 0x0f, 0x1e, 0x3c, 0x78, 0xf0, 42 | 0xfd, 0xe7, 0xd3, 0xbb, 0x6b, 0xd6, 0xb1, 0x7f, 43 | 0xfe, 0xe1, 0xdf, 0xa3, 0x5b, 0xb6, 0x71, 0xe2, 44 | 0xd9, 0xaf, 0x43, 0x86, 0x11, 0x22, 0x44, 0x88, 45 | 0x0d, 0x1a, 0x34, 0x68, 0xd0, 0xbd, 0x67, 0xce, 46 | 0x81, 0x1f, 0x3e, 0x7c, 0xf8, 0xed, 0xc7, 0x93, 47 | 0x3b, 0x76, 0xec, 0xc5, 0x97, 0x33, 0x66, 0xcc, 48 | 0x85, 0x17, 0x2e, 0x5c, 0xb8, 0x6d, 0xda, 0xa9, 49 | 0x4f, 0x9e, 0x21, 0x42, 0x84, 0x15, 0x2a, 0x54, 50 | 0xa8, 0x4d, 0x9a, 0x29, 0x52, 0xa4, 0x55, 0xaa, 51 | 0x49, 0x92, 0x39, 0x72, 0xe4, 0xd5, 0xb7, 0x73, 52 | 0xe6, 0xd1, 0xbf, 0x63, 0xc6, 0x91, 0x3f, 0x7e, 53 | 0xfc, 0xe5, 0xd7, 0xb3, 0x7b, 0xf6, 0xf1, 0xff, 54 | 0xe3, 0xdb, 0xab, 0x4b, 0x96, 0x31, 0x62, 0xc4, 55 | 0x95, 0x37, 0x6e, 0xdc, 0xa5, 0x57, 0xae, 0x41, 56 | 0x82, 0x19, 0x32, 0x64, 0xc8, 0x8d, 0x07, 0x0e, 57 | 0x1c, 0x38, 0x70, 0xe0, 0xdd, 0xa7, 0x53, 0xa6, 58 | 0x51, 0xa2, 0x59, 0xb2, 0x79, 0xf2, 0xf9, 0xef, 59 | 0xc3, 0x9b, 0x2b, 0x56, 0xac, 0x45, 0x8a, 0x09, 60 | 0x12, 0x24, 0x48, 0x90, 0x3d, 0x7a, 0xf4, 0xf5, 61 | 0xf7, 0xf3, 0xfb, 0xeb, 0xcb, 0x8b, 0x0b, 0x16, 62 | 0x2c, 0x58, 0xb0, 0x7d, 0xfa, 0xe9, 0xcf, 0x83, 63 | 0x1b, 0x36, 0x6c, 0xd8, 0xad, 0x47, 0x8e, 0x01, 64 | } 65 | 66 | var gf256Log = [256]uint8{ 67 | 0x00, 0xff, 0x01, 0x19, 0x02, 0x32, 0x1a, 0xc6, 68 | 0x03, 0xdf, 0x33, 0xee, 0x1b, 0x68, 0xc7, 0x4b, 69 | 0x04, 0x64, 0xe0, 0x0e, 0x34, 0x8d, 0xef, 0x81, 70 | 0x1c, 0xc1, 0x69, 0xf8, 0xc8, 0x08, 0x4c, 0x71, 71 | 0x05, 0x8a, 0x65, 0x2f, 0xe1, 0x24, 0x0f, 0x21, 72 | 0x35, 0x93, 0x8e, 0xda, 0xf0, 0x12, 0x82, 0x45, 73 | 0x1d, 0xb5, 0xc2, 0x7d, 0x6a, 0x27, 0xf9, 0xb9, 74 | 0xc9, 0x9a, 0x09, 0x78, 0x4d, 0xe4, 0x72, 0xa6, 75 | 0x06, 0xbf, 0x8b, 0x62, 0x66, 0xdd, 0x30, 0xfd, 76 | 0xe2, 0x98, 0x25, 0xb3, 0x10, 0x91, 0x22, 0x88, 77 | 0x36, 0xd0, 0x94, 0xce, 0x8f, 0x96, 0xdb, 0xbd, 78 | 0xf1, 0xd2, 0x13, 0x5c, 0x83, 0x38, 0x46, 0x40, 79 | 0x1e, 0x42, 0xb6, 0xa3, 0xc3, 0x48, 0x7e, 0x6e, 80 | 0x6b, 0x3a, 0x28, 0x54, 0xfa, 0x85, 0xba, 0x3d, 81 | 0xca, 0x5e, 0x9b, 0x9f, 0x0a, 0x15, 0x79, 0x2b, 82 | 0x4e, 0xd4, 0xe5, 0xac, 0x73, 0xf3, 0xa7, 0x57, 83 | 0x07, 0x70, 0xc0, 0xf7, 0x8c, 0x80, 0x63, 0x0d, 84 | 0x67, 0x4a, 0xde, 0xed, 0x31, 0xc5, 0xfe, 0x18, 85 | 0xe3, 0xa5, 0x99, 0x77, 0x26, 0xb8, 0xb4, 0x7c, 86 | 0x11, 0x44, 0x92, 0xd9, 0x23, 0x20, 0x89, 0x2e, 87 | 0x37, 0x3f, 0xd1, 0x5b, 0x95, 0xbc, 0xcf, 0xcd, 88 | 0x90, 0x87, 0x97, 0xb2, 0xdc, 0xfc, 0xbe, 0x61, 89 | 0xf2, 0x56, 0xd3, 0xab, 0x14, 0x2a, 0x5d, 0x9e, 90 | 0x84, 0x3c, 0x39, 0x53, 0x47, 0x6d, 0x41, 0xa2, 91 | 0x1f, 0x2d, 0x43, 0xd8, 0xb7, 0x7b, 0xa4, 0x76, 92 | 0xc4, 0x17, 0x49, 0xec, 0x7f, 0x0c, 0x6f, 0xf6, 93 | 0x6c, 0xa1, 0x3b, 0x52, 0x29, 0x9d, 0x55, 0xaa, 94 | 0xfb, 0x60, 0x86, 0xb1, 0xbb, 0xcc, 0x3e, 0x5a, 95 | 0xcb, 0x59, 0x5f, 0xb0, 0x9c, 0xa9, 0xa0, 0x51, 96 | 0x0b, 0xf5, 0x16, 0xeb, 0x7a, 0x75, 0x2c, 0xd7, 97 | 0x4f, 0xae, 0xd5, 0xe9, 0xe6, 0xe7, 0xad, 0xe8, 98 | 0x74, 0xd6, 0xf4, 0xea, 0xa8, 0x50, 0x58, 0xaf, 99 | } 100 | 101 | var gf256 = galoisField{ 102 | 255, 103 | gf256Log[:], 104 | gf256Exp[:], 105 | } 106 | 107 | /************************************************************************ 108 | * Polynomial operations 109 | */ 110 | 111 | func polyAdd(dst, src []uint8, c uint8, shift int, gf *galoisField) { 112 | 113 | logC := gf.log[c] 114 | if c == 0 { 115 | return 116 | } 117 | 118 | for i := 0; i < maxPoly; i++ { 119 | p := i + shift 120 | v := src[i] 121 | if p < 0 || p >= maxPoly { 122 | continue 123 | } 124 | if v == 0 { 125 | continue 126 | } 127 | pos := (int(gf.log[v]) + int(logC)) % gf.p 128 | 129 | dst[p] ^= gf.exp[pos] 130 | } 131 | } 132 | 133 | func polyEval(s []uint8, x uint8, gf *galoisField) uint8 { 134 | sum := uint8(0) 135 | logX := gf.log[x] 136 | 137 | if x == 0 { 138 | return s[0] 139 | } 140 | 141 | for i := 0; i < maxPoly; i++ { 142 | c := s[i] 143 | if c == 0 { 144 | continue 145 | } 146 | sum ^= gf.exp[(int(gf.log[c])+int(logX)*i)%gf.p] 147 | } 148 | return sum 149 | } 150 | 151 | /************************************************************************ 152 | * Berlekamp-Massey algorithm for finding error locator polynomials. 153 | */ 154 | 155 | func berlekampMassey(s []uint8, num int, gf *galoisField, sigma []uint8) { 156 | 157 | C := make([]uint8, maxPoly) 158 | B := make([]uint8, maxPoly) 159 | 160 | L := 0 161 | m := 1 162 | b := uint8(1) 163 | 164 | B[0] = 1 165 | C[0] = 1 166 | 167 | for n := 0; n < num; n++ { 168 | d := s[n] 169 | 170 | for i := 1; i <= L; i++ { 171 | if !(C[i] > 0 && s[n-i] > 0) { 172 | continue 173 | } 174 | d ^= gf.exp[(int(gf.log[C[i]])+int(gf.log[s[n-i]]))%gf.p] 175 | } 176 | 177 | mult := gf.exp[(gf.p-int(gf.log[b])+int(gf.log[d]))%gf.p] 178 | 179 | if d == 0 { 180 | m++ 181 | } else if L*2 <= n { 182 | T := make([]uint8, 0) 183 | T = append(T, C...) 184 | 185 | polyAdd(C, B, mult, m, gf) 186 | 187 | B = B[:0] 188 | B = append(B, T...) 189 | 190 | L = n + 1 - L 191 | b = d 192 | m = 1 193 | 194 | } else { 195 | polyAdd(C, B, mult, m, gf) 196 | m++ 197 | } 198 | } 199 | sigma = sigma[:0] 200 | sigma = append(sigma, C...) 201 | } 202 | 203 | /************************************************************************ 204 | * Code stream error correction 205 | * 206 | * Generator polynomial for GF(2^8) is x^8 + x^4 + x^3 + x^2 + 1 207 | */ 208 | 209 | func blockSyndromes(data []uint8, bs, npar int, s []uint8) int { 210 | nonzero := 0 211 | for i := 0; i < maxPoly; i++ { 212 | s[i] = 0 213 | } 214 | for i := 0; i < npar; i++ { 215 | for j := 0; j < bs; j++ { 216 | c := data[bs-j-1] 217 | if c == 0 { 218 | continue 219 | } 220 | s[i] ^= gf256Exp[((int)(gf256Log[c])+i*j)%255] 221 | } 222 | if s[i] != 0 { 223 | nonzero = 1 224 | } 225 | } 226 | return nonzero 227 | } 228 | 229 | func elocPoly(omega []uint8, s []uint8, sigma []uint8, npar int) { 230 | 231 | for i := 0; i < maxPoly; i++ { 232 | omega[i] = 0 233 | } 234 | 235 | for i := 0; i < npar; i++ { 236 | a := sigma[i] 237 | logA := gf256Log[a] 238 | 239 | if a == 0 { 240 | continue 241 | } 242 | 243 | for j := 0; j+1 < maxPoly; j++ { 244 | b := s[j+1] 245 | if i+j >= npar { 246 | break 247 | } 248 | 249 | if b == 0 { 250 | continue 251 | } 252 | omega[i+j] ^= gf256Exp[(int(logA)+int(gf256Log[b]))%255] 253 | } 254 | } 255 | } 256 | 257 | func correctBlock(data []uint8, ecc *qrRsParams) error { 258 | 259 | npar := ecc.bs - ecc.dw 260 | s := make([]uint8, maxPoly) 261 | 262 | // Compute syndrome vector 263 | if 0 == blockSyndromes(data, ecc.bs, npar, s) { 264 | return nil 265 | } 266 | 267 | sigma := make([]uint8, maxPoly) 268 | berlekampMassey(s, npar, &gf256, sigma) 269 | 270 | // Compute derivative of sigma 271 | sigmaDeriv := make([]uint8, maxPoly) 272 | for i := 0; i+1 < maxPoly; i += 2 { 273 | sigmaDeriv[i] = sigma[i+1] 274 | } 275 | 276 | // Compute error evaluator polynomial 277 | omega := make([]uint8, maxPoly) 278 | elocPoly(omega, s, sigma, npar-1) 279 | 280 | // Find error locations and magnitudes 281 | for i := 0; i < ecc.bs; i++ { 282 | xinv := gf256Exp[255-i] 283 | if 0 == polyEval(sigma, xinv, &gf256) { 284 | sdX := polyEval(sigmaDeriv, xinv, &gf256) 285 | omegaX := polyEval(omega, xinv, &gf256) 286 | error := gf256Exp[(255-int(gf256Log[sdX])+int(gf256Log[omegaX]))%255] 287 | data[ecc.bs-i-1] ^= error 288 | } 289 | } 290 | 291 | if blockSyndromes(data, ecc.bs, npar, s) != 0 { 292 | return ErrDataEcc 293 | } 294 | return nil 295 | } 296 | 297 | /************************************************************************ 298 | * Format value error correction 299 | * 300 | * Generator polynomial for GF(2^4) is x^4 + x + 1 301 | */ 302 | 303 | const formatMaxError = 3 304 | const formatSyndrome = formatMaxError * 2 305 | const formatBits = 15 306 | 307 | func formatSyndromes(u uint16, s []uint8) int { 308 | nonzero := 0 309 | for i := 0; i < maxPoly; i++ { 310 | s[i] = 0 311 | } 312 | for i := 0; i < formatSyndrome; i++ { 313 | s[i] = 0 314 | for j := 0; j < formatBits; j++ { 315 | if u&(uint16(1 << uint(j))) != 0 { 316 | s[i] ^= gf16Exp[((i+1)*j)%15] 317 | } 318 | } 319 | if s[i] != 0 { 320 | nonzero = 1 321 | } 322 | } 323 | return nonzero 324 | } 325 | 326 | func correctFormat(fRet *uint16) error { 327 | u := *fRet 328 | 329 | // Evaluate U (received codeword) at each of alpha_1 .. alpha_6 330 | // to get S_1 .. S_6 (but we index them from 0). 331 | 332 | s := make([]uint8, maxPoly) 333 | if 0 == formatSyndromes(u, s) { 334 | return nil 335 | } 336 | 337 | sigma := make([]uint8, maxPoly) 338 | berlekampMassey(s, formatSyndrome, &gf16, sigma) 339 | 340 | // Now, find the roots of the polynomial 341 | for i := 0; i < 15; i++ { 342 | if 0 == polyEval(sigma, gf16Exp[15-i], &gf16) { 343 | u ^= 1 << uint16(i) 344 | } 345 | } 346 | if 0 != formatSyndromes(u, s) { 347 | return ErrFormatEcc 348 | } 349 | 350 | *fRet = u 351 | return nil 352 | } 353 | 354 | /************************************************************************ 355 | * Decoder algorithm 356 | */ 357 | 358 | type datastream struct { 359 | raw [qrMaxPayload]uint8 360 | dataBits int 361 | ptr int 362 | data [qrMaxPayload]uint8 363 | } 364 | 365 | func maskBit(mask, i, j int) int { 366 | k := 0 367 | switch mask { 368 | case 0: 369 | k = (i + j) % 2 370 | case 1: 371 | k = i % 2 372 | case 2: 373 | k = j % 3 374 | case 3: 375 | k = (i + j) % 3 376 | case 4: 377 | k = ((i / 2) + (j / 3)) % 2 378 | case 5: 379 | k = (i*j)%2 + (i*j)%3 380 | case 6: 381 | k = ((i*j)%2 + (i*j)%3) % 2 382 | case 7: 383 | k = ((i*j)%3 + (i+j)%2) % 2 384 | default: 385 | return 0 386 | } 387 | if k != 0 { 388 | return 0 389 | } 390 | return 1 391 | } 392 | 393 | func reservedCell(version, i, j int) int { 394 | ver := &qrVersionDb[version] 395 | size := version*4 + 17 396 | ai := -1 397 | aj := -1 398 | a := 0 399 | 400 | // Finder + format: top left 401 | if i < 9 && j < 9 { 402 | return 1 403 | } 404 | 405 | // Finder + format: bottom left 406 | if i+8 >= size && j < 9 { 407 | return 1 408 | } 409 | 410 | // Finder + format: top right 411 | if i < 9 && j+8 >= size { 412 | return 1 413 | } 414 | // Exclude timing patterns 415 | if i == 6 || j == 6 { 416 | return 1 417 | } 418 | 419 | // Exclude Version info, if it exists. Version info sits adjacent to 420 | // the top-right and bottom-left finders in three rows, bounded by 421 | // the timing pattern. 422 | 423 | if version >= 7 { 424 | if i < 6 && j+11 >= size { 425 | return 1 426 | } 427 | 428 | if i+11 >= size && j < 6 { 429 | return 1 430 | } 431 | } 432 | // Exclude alignment patterns 433 | for a = 0; a < qrMaxAliment && ver.apat[a] != 0; a++ { 434 | p := ver.apat[a] 435 | 436 | if int(math.Abs(float64(p-i))) < 3 { 437 | ai = a 438 | } 439 | if int(math.Abs(float64(p-j))) < 3 { 440 | aj = a 441 | } 442 | } 443 | 444 | if ai >= 0 && aj >= 0 { 445 | a-- 446 | if ai > 0 && ai < a { 447 | return 1 448 | } 449 | if aj > 0 && aj < a { 450 | return 1 451 | } 452 | if aj == a && ai == a { 453 | return 1 454 | } 455 | } 456 | return 0 457 | } 458 | 459 | func codestreamEcc(data *QRData, ds *datastream) error { 460 | ver := &qrVersionDb[data.Version] 461 | sbEcc := &ver.ecc[data.EccLevel] 462 | var lbEcc qrRsParams 463 | lbCount := (ver.dataBytes - sbEcc.bs*sbEcc.ns) / (sbEcc.bs + 1) 464 | 465 | bc := lbCount + sbEcc.ns 466 | eccOffset := sbEcc.dw*bc + lbCount 467 | 468 | dstOffset := 0 469 | lbEcc = *sbEcc 470 | 471 | lbEcc.dw++ 472 | lbEcc.bs++ 473 | 474 | for i := 0; i < bc; i++ { 475 | dst := ds.data[dstOffset:] 476 | ecc := sbEcc 477 | if i < sbEcc.ns { 478 | ecc = sbEcc 479 | } else { 480 | ecc = &lbEcc 481 | } 482 | numEc := ecc.bs - ecc.dw 483 | 484 | for j := 0; j < ecc.dw; j++ { 485 | dst[j] = ds.raw[j*bc+i] 486 | } 487 | 488 | for j := 0; j < numEc; j++ { 489 | dst[ecc.dw+j] = ds.raw[eccOffset+j*bc+i] 490 | } 491 | 492 | err := correctBlock(dst, ecc) 493 | if err != nil { 494 | return err 495 | } 496 | 497 | dstOffset += ecc.dw 498 | } 499 | 500 | ds.dataBits = dstOffset * 8 501 | return nil 502 | } 503 | 504 | func bitsRemaining(ds *datastream) int { 505 | return ds.dataBits - ds.ptr 506 | } 507 | 508 | func takeBits(ds *datastream, len int) int { 509 | ret := 0 510 | for len > 0 && (ds.ptr < ds.dataBits) { 511 | b := ds.data[ds.ptr>>3] 512 | bitpos := ds.ptr & 7 513 | ret <<= 1 514 | if ((b << uint(bitpos)) & 0x80) != 0 { 515 | ret |= 1 516 | } 517 | ds.ptr++ 518 | len-- 519 | } 520 | return ret 521 | } 522 | 523 | func numericTuple(data *QRData, ds *datastream, bits, digits int) int { 524 | if bitsRemaining(ds) < bits { 525 | return -1 526 | } 527 | tuple := takeBits(ds, bits) 528 | 529 | for i := digits - 1; i >= 0; i-- { 530 | data.Payload = append(data.Payload, uint8(tuple%10)+uint8('0')) 531 | 532 | tuple /= 10 533 | } 534 | return 0 535 | } 536 | 537 | func decodeNumeric(data *QRData, ds *datastream) error { 538 | bits := 14 539 | if data.Version < 10 { 540 | bits = 10 541 | } else if data.Version < 27 { 542 | bits = 12 543 | } 544 | 545 | count := takeBits(ds, bits) 546 | if len(data.Payload)+count+1 > qrMaxPayload { 547 | return ErrDataOverflow 548 | } 549 | for count >= 3 { 550 | if numericTuple(data, ds, 10, 3) < 0 { 551 | return ErrDataUnderflow 552 | } 553 | count -= 3 554 | } 555 | if count >= 2 { 556 | if numericTuple(data, ds, 7, 2) < 0 { 557 | return ErrDataUnderflow 558 | 559 | } 560 | count -= 2 561 | } 562 | if count > 0 { 563 | if numericTuple(data, ds, 4, 1) < 0 { 564 | return ErrDataUnderflow 565 | } 566 | } 567 | return nil 568 | } 569 | 570 | var alphaMap = []byte("0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./:") 571 | 572 | func alphaTuple(data *QRData, ds *datastream, bits, digits int) int { 573 | if bitsRemaining(ds) < bits { 574 | return -1 575 | } 576 | tuple := takeBits(ds, bits) 577 | payloadLen := len(data.Payload) 578 | data.Payload = append(data.Payload, make([]uint8, digits)...) 579 | for i := 0; i < digits; i++ { 580 | data.Payload[payloadLen+digits-i-1] = alphaMap[tuple%45] 581 | tuple /= 45 582 | } 583 | return 0 584 | } 585 | 586 | func decodeAlpha(data *QRData, ds *datastream) error { 587 | bits := 13 588 | if data.Version < 10 { 589 | bits = 9 590 | } else if data.Version < 27 { 591 | bits = 11 592 | } 593 | count := takeBits(ds, bits) 594 | if len(data.Payload)+count+1 > qrMaxPayload { 595 | return ErrDataOverflow 596 | } 597 | for count >= 2 { 598 | if alphaTuple(data, ds, 11, 2) < 0 { 599 | return ErrDataUnderflow 600 | } 601 | count -= 2 602 | } 603 | if count > 0 { 604 | if alphaTuple(data, ds, 6, 1) < 0 { 605 | return ErrDataUnderflow 606 | } 607 | } 608 | return nil 609 | } 610 | 611 | func decodeByte(data *QRData, ds *datastream) error { 612 | bits := 16 613 | if data.Version < 10 { 614 | bits = 8 615 | } 616 | count := takeBits(ds, bits) 617 | if len(data.Payload)+count+1 > qrMaxPayload { 618 | return ErrDataOverflow 619 | } 620 | if bitsRemaining(ds) < count*8 { 621 | return ErrDataUnderflow 622 | } 623 | for i := 0; i < count; i++ { 624 | data.Payload = append(data.Payload, uint8(takeBits(ds, 8))) 625 | } 626 | return nil 627 | } 628 | 629 | func decodeKanji(data *QRData, ds *datastream) error { 630 | bits := 12 631 | 632 | if data.Version < 10 { 633 | bits = 8 634 | } else if data.Version < 27 { 635 | bits = 10 636 | } 637 | count := takeBits(ds, bits) 638 | 639 | if len(data.Payload)+count*2+1 > qrMaxPayload { 640 | return ErrDataOverflow 641 | } 642 | 643 | if bitsRemaining(ds) < count*13 { 644 | return ErrDataUnderflow 645 | } 646 | 647 | for i := 0; i < count; i++ { 648 | d := takeBits(ds, 13) 649 | msB := d / 0xc0 650 | lsB := d % 0xc0 651 | intermediate := uint16((msB << 8) | lsB) 652 | var sjw uint16 653 | 654 | if intermediate+0x8140 <= 0x9ffc { 655 | // bytes are in the range 0x8140 to 0x9FFC 656 | sjw = intermediate + 0x8140 657 | } else { 658 | // bytes are in the range 0xE040 to 0xEBBF 659 | sjw = intermediate + 0xc140 660 | } 661 | data.Payload = append(data.Payload, uint8(sjw>>8)) 662 | data.Payload = append(data.Payload, uint8(sjw&0xff)) 663 | } 664 | return nil 665 | } 666 | 667 | func decodeEci(data *QRData, ds *datastream) error { 668 | if bitsRemaining(ds) < 8 { 669 | return ErrDataOverflow 670 | } 671 | data.Eci = uint32(takeBits(ds, 8)) 672 | if (data.Eci & 0xc0) == 0x80 { 673 | if bitsRemaining(ds) < 8 { 674 | return ErrDataUnderflow 675 | } 676 | data.Eci = (data.Eci << 8) | uint32(takeBits(ds, 8)) 677 | } else if (data.Eci & 0xe0) == 0xc0 { 678 | if bitsRemaining(ds) < 16 { 679 | return ErrDataUnderflow 680 | } 681 | data.Eci = (data.Eci << 16) | uint32(takeBits(ds, 16)) 682 | } 683 | return nil 684 | } 685 | 686 | func decodePayload(data *QRData, ds *datastream) error { 687 | 688 | for bitsRemaining(ds) >= 4 { 689 | var err error 690 | _type := takeBits(ds, 4) 691 | switch _type { 692 | case qrDataTypeNumeric: 693 | err = decodeNumeric(data, ds) 694 | case qrDataTypeAlpha: 695 | err = decodeAlpha(data, ds) 696 | case qrDataTypeByte: 697 | err = decodeByte(data, ds) 698 | case qrDataTypeKanji: 699 | err = decodeKanji(data, ds) 700 | case 7: 701 | err = decodeEci(data, ds) 702 | default: 703 | goto done 704 | } 705 | 706 | if err != nil { 707 | return err 708 | } 709 | if 0 == (_type&(_type-1)) && (_type > data.DataType) { 710 | data.DataType = _type 711 | } 712 | } 713 | 714 | done: 715 | return nil 716 | } 717 | -------------------------------------------------------------------------------- /define.go: -------------------------------------------------------------------------------- 1 | package goqr 2 | 3 | const ( 4 | qrPixelWhite = 0 5 | qrPixelBlack = 1 6 | qrPixelRegion = 2 7 | qrMaxRegion = 254 8 | qrMaxCastones = 32 9 | qrMaxGrids = 8 10 | qrPerspectiveParams = 8 11 | ) 12 | 13 | type qrPixelType = uint8 14 | 15 | type point struct { 16 | x int 17 | y int 18 | } 19 | 20 | type qrRegion struct { 21 | seed point 22 | count int 23 | capstone int 24 | } 25 | 26 | type qrCapstone struct { 27 | ring int 28 | stone int 29 | corners [4]point 30 | center point 31 | c [qrPerspectiveParams]float64 32 | qrGrid int 33 | } 34 | 35 | type qrGrid struct { 36 | // Capstone indices 37 | caps [3]int 38 | 39 | // Alignment pattern region and corner 40 | alignRegion int 41 | align point 42 | 43 | // Timing pattern endpoints 44 | tpep [3]point 45 | hscan int 46 | vscan int 47 | 48 | // Grid size and perspective transform 49 | gridSize int 50 | c [qrPerspectiveParams]float64 51 | } 52 | 53 | /************************************************************************ 54 | * QR-code Version information database 55 | */ 56 | 57 | const ( 58 | qrMaxVersion = 40 59 | qrMaxAliment = 7 60 | ) 61 | 62 | type qrRsParams struct { 63 | bs int // Small block size 64 | dw int // Small data words 65 | ns int // Number of small blocks 66 | } 67 | 68 | type qrVersionInfo struct { 69 | dataBytes int 70 | apat [qrMaxAliment]int 71 | ecc [4]qrRsParams 72 | } 73 | 74 | type polygonScoreData struct { 75 | ref point 76 | scores [4]int 77 | corners []point 78 | } 79 | 80 | type neighbour struct { 81 | index int 82 | distance float64 83 | } 84 | -------------------------------------------------------------------------------- /errors.go: -------------------------------------------------------------------------------- 1 | package goqr 2 | 3 | import "errors" 4 | 5 | // Error definition 6 | var ( 7 | ErrNoQRCode = errors.New("no QR code in image") 8 | ErrInvalidGridSize = errors.New("invalid grid size") 9 | ErrInvalidVersion = errors.New("invalid version") 10 | ErrFormatEcc = errors.New("ecc format error") 11 | ErrDataEcc = errors.New("ecc data error") 12 | ErrUnknownDataType = errors.New("unknown data type") 13 | ErrDataOverflow = errors.New("data overflow") 14 | ErrDataUnderflow = errors.New("data underflow") 15 | ) 16 | -------------------------------------------------------------------------------- /example/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "github.com/liyue201/goqr" 7 | "image" 8 | _ "image/jpeg" 9 | _ "image/png" 10 | "io/ioutil" 11 | ) 12 | 13 | func recognizeFile(path string) { 14 | fmt.Printf("recognize file: %v\n", path) 15 | imgdata, err := ioutil.ReadFile(path) 16 | if err != nil { 17 | fmt.Printf("%v\n", err) 18 | return 19 | } 20 | 21 | img, _, err := image.Decode(bytes.NewReader(imgdata)) 22 | if err != nil { 23 | fmt.Printf("image.Decode error: %v\n", err) 24 | return 25 | } 26 | qrCodes, err := goqr.Recognize(img) 27 | if err != nil { 28 | fmt.Printf("Recognize failed: %v\n", err) 29 | return 30 | } 31 | for _, qrCode := range qrCodes { 32 | fmt.Printf("qrCode text: %s\n", qrCode.Payload) 33 | } 34 | } 35 | 36 | func main() { 37 | recognizeFile("testdata/001.png") 38 | } 39 | -------------------------------------------------------------------------------- /example/testdata/000.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liyue201/goqr/df443203d4ea90e6835916612ef0977809b06508/example/testdata/000.jpg -------------------------------------------------------------------------------- /example/testdata/001.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liyue201/goqr/df443203d4ea90e6835916612ef0977809b06508/example/testdata/001.jpg -------------------------------------------------------------------------------- /example/testdata/002.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liyue201/goqr/df443203d4ea90e6835916612ef0977809b06508/example/testdata/002.jpg -------------------------------------------------------------------------------- /example/testdata/003.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liyue201/goqr/df443203d4ea90e6835916612ef0977809b06508/example/testdata/003.jpg -------------------------------------------------------------------------------- /example/testdata/004.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liyue201/goqr/df443203d4ea90e6835916612ef0977809b06508/example/testdata/004.png -------------------------------------------------------------------------------- /example/testdata/005.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liyue201/goqr/df443203d4ea90e6835916612ef0977809b06508/example/testdata/005.png -------------------------------------------------------------------------------- /example/testdata/006.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liyue201/goqr/df443203d4ea90e6835916612ef0977809b06508/example/testdata/006.png -------------------------------------------------------------------------------- /example/testdata/007.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liyue201/goqr/df443203d4ea90e6835916612ef0977809b06508/example/testdata/007.png -------------------------------------------------------------------------------- /example/testdata/008.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liyue201/goqr/df443203d4ea90e6835916612ef0977809b06508/example/testdata/008.png -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/liyue201/goqr 2 | 3 | go 1.12 4 | 5 | require github.com/stretchr/testify v1.4.0 6 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= 2 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 3 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 4 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 5 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 6 | github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= 7 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 8 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 9 | gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= 10 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 11 | -------------------------------------------------------------------------------- /qr_const.go: -------------------------------------------------------------------------------- 1 | package goqr 2 | 3 | // Limits on the maximum size of QR-codes and their content 4 | const ( 5 | qrMaxBimap = 3917 6 | qrMaxPayload = 8896 7 | 8 | // QR-code ECC types 9 | qrEccLevelM = 0 10 | qrEccLevelL = 1 11 | qrEccLevelH = 2 12 | qrEccLevelQ = 3 13 | 14 | // QR-code data types 15 | qrDataTypeNumeric = 1 16 | qrDataTypeAlpha = 2 17 | qrDataTypeByte = 4 18 | qrDataTypeKanji = 8 19 | 20 | // Common character encodings 21 | qrEciIos8859_1 = 1 22 | qrEciIbm437 = 2 23 | qrEciIos8859_2 = 4 24 | qrEciIso8859_3 = 5 25 | qrEciIso8859_4 = 6 26 | qrEciIso8859_5 = 7 27 | qrEciIso8859_6 = 8 28 | qrEciIso8859_7 = 9 29 | qrEciIso8859_8 = 10 30 | qrEciIso8859_9 = 11 31 | qrEciWindows874 = 13 32 | qrEciIso8859_13 = 15 33 | qrEciIso8859_15 = 17 34 | qrEciShiftJis = 20 35 | qrEciUtf8 = 26 36 | ) 37 | -------------------------------------------------------------------------------- /qrcode.go: -------------------------------------------------------------------------------- 1 | package goqr 2 | 3 | // qrCode is used to return information about detected QR codes 4 | // in the input image. 5 | type qrCode struct { 6 | // The four corners of the QR-code, from top left, clockwise 7 | corners [4]point 8 | 9 | // The number of cells across in the QR-code. The cell bitmap 10 | // is a bitmask giving the actual values of cells. If the cell 11 | // at (x, y) is black, then the following bit is set: 12 | // 13 | // cell_bitmap[i >> 3] & (1 << (i & 7)) 14 | // 15 | // where i = (y * size) + x. 16 | // 17 | 18 | size int 19 | cellBitmap [qrMaxBimap]uint8 20 | } 21 | 22 | // QRData holds the decoded QR-code data 23 | type QRData struct { 24 | // Various parameters of the QR-code. These can mostly be 25 | // ignored if you only care about the data. 26 | Version int 27 | EccLevel int 28 | Mask int 29 | 30 | // This field is the highest-valued data type found in the QR code. 31 | DataType int 32 | 33 | // Data Payload. For the Kanji datatype, Payload is encoded as 34 | // Shift-JIS. For all other datatypes, Payload is ASCII text. 35 | Payload []uint8 36 | 37 | // ECI assignment number 38 | Eci uint32 39 | } 40 | 41 | func gridBit(code *qrCode, x, y int) int { 42 | p := uint(y*code.size + x) 43 | return int((code.cellBitmap[p>>3])>>(p&7)) & 1 44 | } 45 | 46 | func readFormat(code *qrCode, data *QRData, which int) error { 47 | format := uint16(0) 48 | if which != 0 { 49 | for i := 0; i < 7; i++ { 50 | format = (format << 1) | uint16(gridBit(code, 8, code.size-1-i)) 51 | } 52 | for i := 0; i < 8; i++ { 53 | format = (format << 1) | uint16(gridBit(code, code.size-8+i, 8)) 54 | } 55 | } else { 56 | xs := [15]int{8, 8, 8, 8, 8, 8, 8, 8, 7, 5, 4, 3, 2, 1, 0} 57 | ys := [15]int{0, 1, 2, 3, 4, 5, 7, 8, 8, 8, 8, 8, 8, 8, 8} 58 | for i := 14; i >= 0; i-- { 59 | format = (format << 1) | uint16(gridBit(code, xs[i], ys[i])) 60 | } 61 | } 62 | 63 | format ^= 0x5412 64 | err := correctFormat(&format) 65 | if err != nil { 66 | return err 67 | } 68 | 69 | fdata := format >> 10 70 | data.EccLevel = int(fdata) >> 3 71 | data.Mask = int(fdata) & 7 72 | return nil 73 | } 74 | 75 | func readBit(code *qrCode, data *QRData, ds *datastream, i, j int) { 76 | bitpos := ds.dataBits & 7 77 | bytepos := ds.dataBits >> 3 78 | v := gridBit(code, j, i) 79 | 80 | if maskBit(data.Mask, i, j) != 0 { 81 | v ^= 1 82 | } 83 | 84 | if v != 0 { 85 | ds.raw[bytepos] |= 0x80 >> uint32(bitpos) 86 | } 87 | ds.dataBits++ 88 | } 89 | 90 | func readData(code *qrCode, data *QRData, ds *datastream) { 91 | y := code.size - 1 92 | x := code.size - 1 93 | dir := -1 94 | for x > 0 { 95 | if x == 6 { 96 | x-- 97 | } 98 | 99 | if 0 == reservedCell(data.Version, y, x) { 100 | readBit(code, data, ds, y, x) 101 | } 102 | 103 | if 0 == reservedCell(data.Version, y, x-1) { 104 | readBit(code, data, ds, y, x-1) 105 | } 106 | y += dir 107 | if y < 0 || y >= code.size { 108 | dir = -dir 109 | x -= 2 110 | y += dir 111 | } 112 | } 113 | } 114 | 115 | func decode(code *qrCode, data *QRData) error { 116 | var ds datastream 117 | if (code.size-17)%4 != 0 { 118 | return ErrInvalidGridSize 119 | } 120 | data.Version = (code.size - 17) / 4 121 | 122 | if data.Version < 1 || data.Version > qrMaxVersion { 123 | return ErrInvalidVersion 124 | } 125 | 126 | // Read format information -- try both locations 127 | err := readFormat(code, data, 0) 128 | if err != nil { 129 | err = readFormat(code, data, 1) 130 | } 131 | if err != nil { 132 | return err 133 | } 134 | 135 | readData(code, data, &ds) 136 | 137 | err = codestreamEcc(data, &ds) 138 | if err != nil { 139 | return err 140 | } 141 | err = decodePayload(data, &ds) 142 | if err != nil { 143 | return err 144 | } 145 | return nil 146 | } 147 | -------------------------------------------------------------------------------- /recognition.go: -------------------------------------------------------------------------------- 1 | package goqr 2 | 3 | import ( 4 | "image" 5 | "image/color" 6 | "math" 7 | ) 8 | 9 | // Recognize recognizes the passed image and returns a slice of QRData. 10 | func Recognize(img image.Image) ([]*QRData, error) { 11 | b := img.Bounds() 12 | 13 | r := NewRecognizer(b.Max.X, b.Max.Y) 14 | r.Begin() 15 | switch m := img.(type) { 16 | case *image.Gray: 17 | off := 0 18 | for y := b.Min.Y; y < b.Max.Y; y++ { 19 | for x := b.Min.X; x < b.Max.X; x++ { 20 | gray := m.GrayAt(x, y) 21 | r.SetPixel(x-b.Min.X, y-b.Min.Y, byte(gray.Y)) 22 | off++ 23 | } 24 | } 25 | case *image.RGBA: 26 | off := 0 27 | for y := b.Min.Y; y < b.Max.Y; y++ { 28 | for x := b.Min.X; x < b.Max.X; x++ { 29 | pix := toGrayLuminance(m.At(x, y)) 30 | r.SetPixel(x-b.Min.X, y-b.Min.Y, byte(pix)) 31 | off++ 32 | } 33 | } 34 | default: 35 | off := 0 36 | for y := b.Min.Y; y < b.Max.Y; y++ { 37 | for x := b.Min.X; x < b.Max.X; x++ { 38 | rgba := color.RGBAModel.Convert(m.At(x, y)).(color.RGBA) 39 | pix := toGrayLuminance(rgba) 40 | r.SetPixel(x-b.Min.X, y-b.Min.Y, byte(pix)) 41 | off++ 42 | } 43 | } 44 | } 45 | r.End() 46 | 47 | count := r.Count() 48 | if count == 0 { 49 | return nil, ErrNoQRCode 50 | } 51 | 52 | qrCodes := make([]*QRData, 0) 53 | for i := 0; i < count; i++ { 54 | code, err := r.Decode(i) 55 | if err != nil { 56 | continue 57 | } 58 | qrCodes = append(qrCodes, code) 59 | } 60 | if len(qrCodes) == 0 { 61 | return nil, ErrNoQRCode 62 | } 63 | return qrCodes, nil 64 | } 65 | 66 | func toGrayLuminance(c color.Color) uint8 { 67 | rr, gg, bb, _ := c.RGBA() 68 | r := math.Pow(float64(rr), 2.2) 69 | g := math.Pow(float64(gg), 2.2) 70 | b := math.Pow(float64(bb), 2.2) 71 | y := math.Pow(0.2125*r+0.7154*g+0.0721*b, 1/2.2) 72 | Y := uint16(y + 0.5) 73 | return uint8(Y >> 8) 74 | } 75 | -------------------------------------------------------------------------------- /recognition_test.go: -------------------------------------------------------------------------------- 1 | package goqr 2 | 3 | import ( 4 | "bytes" 5 | "github.com/stretchr/testify/assert" 6 | "image" 7 | _ "image/jpeg" 8 | _ "image/png" 9 | "io/ioutil" 10 | 11 | "testing" 12 | ) 13 | 14 | func recognizeFile(path string) (string, error) { 15 | imgdata, err := ioutil.ReadFile(path) 16 | if err != nil { 17 | return "", err 18 | } 19 | 20 | img, _, err := image.Decode(bytes.NewReader(imgdata)) 21 | if err != nil { 22 | return "", err 23 | } 24 | qrCodes, err := Recognize(img) 25 | if err != nil { 26 | return "", nil 27 | } 28 | return string(qrCodes[0].Payload), nil 29 | } 30 | 31 | func TestRecognize(t *testing.T) { 32 | testCases := []struct { 33 | filePath string 34 | text string 35 | }{ 36 | { 37 | "example/testdata/000.jpg", 38 | "http://www.amazon.co.jp/gp/aw/rd.html?uid=NULLGWDOCOMO&url=/gp/aw/h.html&at=aw_intl6-22", 39 | }, 40 | { 41 | "example/testdata/001.jpg", 42 | "http://www.amazon.co.jp/gp/aw/rd.html?uid=NULLGWDOCOMO&url=/gp/aw/h.html&at=aw_intl6-22", 43 | }, 44 | { 45 | "example/testdata/002.jpg", 46 | "http://www.amazon.co.jp/gp/aw/rd.html?uid=NULLGWDOCOMO&url=/gp/aw/h.html&at=aw_intl6-22", 47 | }, 48 | { 49 | "example/testdata/003.jpg", 50 | "http://www.amazon.co.jp/gp/aw/rd.html?uid=NULLGWDOCOMO&url=/gp/aw/h.html&at=aw_intl6-22", 51 | }, 52 | { 53 | "example/testdata/004.png", 54 | "http://swtch.com/pjw/#523892624657510299353520120480795433576563223876200460867159368633143833417166162086873959805500633263545263286786346759633071266643358952888263169163143415896058956186071276000133287120333224396223386892286076080898690020480143143263415796162046552639449120143662", 55 | }, 56 | { 57 | "example/testdata/005.png", 58 | "https://github.com/", 59 | }, 60 | { 61 | "example/testdata/006.png", 62 | "https://github.com/liyue201", 63 | }, 64 | { 65 | "example/testdata/007.png", 66 | "https://github.com/", 67 | }, 68 | { 69 | "example/testdata/008.png", 70 | "https://github.com/liyue201/goqr", 71 | }, 72 | } 73 | for _, testCase := range testCases { 74 | text, err := recognizeFile(testCase.filePath) 75 | assert.Nil(t, err) 76 | assert.Equal(t, testCase.text, text) 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /recognizer.go: -------------------------------------------------------------------------------- 1 | package goqr 2 | 3 | import ( 4 | "errors" 5 | "math" 6 | ) 7 | 8 | const ( 9 | thresholdSMin = 1 10 | thresholdSDen = 8 11 | thresholdT = 5 12 | ) 13 | 14 | // Error definition 15 | var ( 16 | ErrOutOfRange = errors.New("out of range") 17 | ) 18 | 19 | // Recognizer is a Qr Code recognizer interface 20 | type Recognizer interface { 21 | SetPixel(x, y int, val uint8) 22 | Begin() 23 | End() 24 | Count() int 25 | Decode(index int) (*QRData, error) 26 | } 27 | 28 | type recognizer struct { 29 | pixels []uint8 30 | w int 31 | h int 32 | regions []qrRegion 33 | capstones []qrCapstone 34 | grids []qrGrid 35 | } 36 | 37 | // NewRecognizer news a recognizer 38 | func NewRecognizer(w, h int) Recognizer { 39 | if w <= 0 || h <= 0 { 40 | return nil 41 | } 42 | return &recognizer{ 43 | h: h, 44 | w: w, 45 | pixels: make([]qrPixelType, w*h), 46 | } 47 | } 48 | 49 | func (q *recognizer) SetPixel(x, y int, val uint8) { 50 | q.pixels[x+y*q.w] = val 51 | } 52 | 53 | func (q *recognizer) Count() int { 54 | return len(q.grids) 55 | } 56 | 57 | func (q *recognizer) Begin() { 58 | q.regions = make([]qrRegion, qrPixelRegion) 59 | } 60 | 61 | func (q *recognizer) End() { 62 | q.threshold() 63 | for i := 0; i < q.h; i++ { 64 | q.finderScan(i) 65 | } 66 | for i := 0; i < len(q.capstones); i++ { 67 | q.testGrouping(i) 68 | } 69 | } 70 | 71 | func (q *recognizer) extract(index int) (*qrCode, error) { 72 | code := &qrCode{} 73 | 74 | qr := &q.grids[index] 75 | 76 | if index < 0 || index >= len(q.grids) { 77 | return nil, ErrOutOfRange 78 | } 79 | 80 | perspectiveMap(qr.c[:], 0.0, 0.0, &code.corners[0]) 81 | perspectiveMap(qr.c[:], float64(qr.gridSize), 0.0, &code.corners[1]) 82 | perspectiveMap(qr.c[:], float64(qr.gridSize), float64(qr.gridSize), &code.corners[2]) 83 | perspectiveMap(qr.c[:], 0.0, float64(qr.gridSize), &code.corners[3]) 84 | 85 | code.size = qr.gridSize 86 | i := uint(0) 87 | for y := 0; y < qr.gridSize; y++ { 88 | for x := 0; x < qr.gridSize; x++ { 89 | if q.readCell(index, x, y) > 0 { 90 | code.cellBitmap[i>>3] |= uint8(1 << (i & 7)) 91 | } 92 | i++ 93 | } 94 | } 95 | return code, nil 96 | } 97 | 98 | func (q *recognizer) Decode(index int) (*QRData, error) { 99 | code, err := q.extract(index) 100 | if err != nil { 101 | return nil, err 102 | } 103 | var data QRData 104 | data.Payload = make([]uint8, 0) 105 | 106 | err = decode(code, &data) 107 | return &data, err 108 | } 109 | 110 | func (q *recognizer) threshold() { 111 | var x, y int 112 | avgW := 0 113 | avgU := 0 114 | thresholds := q.w / thresholdSDen 115 | 116 | // Ensure a sane, non-zero value for threshold_s. 117 | // threshold_s can be zero if the image width is small. We need to avoid 118 | // SIGFPE as it will be used as divisor. 119 | if thresholds < thresholdSMin { 120 | thresholds = thresholdSMin 121 | } 122 | 123 | for y = 0; y < q.h; y++ { 124 | row := q.pixels[q.w*y : q.w*(y+1)] 125 | rowAverage := make([]int, q.w) 126 | for x = 0; x < q.w; x++ { 127 | var w, u int 128 | if y&1 == 1 { 129 | w = x 130 | u = q.w - 1 - x 131 | } else { 132 | w = q.w - 1 - x 133 | u = x 134 | } 135 | 136 | avgW = (avgW*(thresholds-1))/thresholds + int(row[w]) 137 | avgU = (avgU*(thresholds-1))/thresholds + int(row[u]) 138 | 139 | rowAverage[w] += avgW 140 | rowAverage[u] += avgU 141 | } 142 | 143 | for x = 0; x < q.w; x++ { 144 | if int(row[x]) < rowAverage[x]*(100-thresholdT)/(200*thresholds) { 145 | row[x] = qrPixelBlack 146 | } else { 147 | row[x] = qrPixelWhite 148 | } 149 | } 150 | } 151 | } 152 | 153 | const floodFileMaxDepth = 4096 154 | 155 | type spanFunc func(userData interface{}, y, left, right int) 156 | 157 | func (q *recognizer) floodFillSeed(x, y, from, to int, span spanFunc, userData interface{}, depth int) { 158 | left := x 159 | right := x 160 | row := q.pixels[y*q.w : (y+1)*q.w] 161 | 162 | if depth >= floodFileMaxDepth { 163 | return 164 | } 165 | 166 | for left > 0 && int(row[left-1]) == from { 167 | left-- 168 | } 169 | 170 | for right < q.w-1 && int(row[right+1]) == from { 171 | right++ 172 | } 173 | 174 | // Fill the extent 175 | for i := left; i <= right; i++ { 176 | row[i] = qrPixelType(to) 177 | } 178 | 179 | if span != nil { 180 | span(userData, y, left, right) 181 | } 182 | 183 | // Seed new flood-fills 184 | if y > 0 { 185 | row = q.pixels[(y-1)*q.w : (y)*q.w] 186 | for i := left; i <= right; i++ { 187 | if int(row[i]) == from { 188 | q.floodFillSeed(i, y-1, from, to, span, userData, depth+1) 189 | } 190 | } 191 | } 192 | 193 | if y < q.h-1 { 194 | row = q.pixels[(y+1)*q.w : (y+2)*q.w] 195 | for i := left; i <= right; i++ { 196 | if int(row[i]) == from { 197 | q.floodFillSeed(i, y+1, from, to, span, userData, depth+1) 198 | } 199 | } 200 | } 201 | } 202 | 203 | func areaCount(userData interface{}, y, left, right int) { 204 | region := userData.(*qrRegion) 205 | region.count += right - left + 1 206 | } 207 | 208 | func (q *recognizer) regionCode(x, y int) int { 209 | if x < 0 || y < 0 || x >= q.w || y >= q.h { 210 | return -1 211 | } 212 | pixel := int(q.pixels[y*q.w+x]) 213 | if pixel >= qrPixelRegion { 214 | return pixel 215 | } 216 | if pixel == qrPixelWhite { 217 | return -1 218 | } 219 | if len(q.regions) >= qrMaxRegion { 220 | return -1 221 | } 222 | 223 | region := len(q.regions) 224 | q.regions = append(q.regions, qrRegion{}) 225 | box := &q.regions[region] 226 | 227 | box.seed.x = x 228 | box.seed.y = y 229 | box.count = 0 230 | box.capstone = -1 231 | 232 | q.floodFillSeed(x, y, pixel, region, areaCount, box, 0) 233 | return region 234 | } 235 | 236 | func findOneCorner(userData interface{}, y, left, right int) { 237 | psd := userData.(*polygonScoreData) 238 | xs := [2]int{left, right} 239 | dy := y - psd.ref.y 240 | 241 | for i := 0; i < 2; i++ { 242 | dx := xs[i] - psd.ref.x 243 | d := dx*dx + dy*dy 244 | if d > psd.scores[0] { 245 | psd.scores[0] = d 246 | psd.corners[0].x = xs[i] 247 | psd.corners[0].y = y 248 | } 249 | } 250 | } 251 | 252 | func findOtherCorners(userData interface{}, y, left, right int) { 253 | psd := userData.(*polygonScoreData) 254 | xs := [2]int{left, right} 255 | 256 | for i := 0; i < 2; i++ { 257 | up := xs[i]*psd.ref.x + y*psd.ref.y 258 | right := xs[i]*-psd.ref.y + y*psd.ref.x 259 | scores := [4]int{up, right, -up, -right} 260 | for j := 0; j < 4; j++ { 261 | if scores[j] > psd.scores[j] { 262 | psd.scores[j] = scores[j] 263 | psd.corners[j].x = xs[i] 264 | psd.corners[j].y = y 265 | } 266 | } 267 | } 268 | } 269 | 270 | func (q *recognizer) findRegionCorners(rcode int, ref *point, corners []point) { 271 | region := &q.regions[rcode] 272 | psd := polygonScoreData{} 273 | psd.corners = corners[:] 274 | 275 | psd.ref = *ref 276 | psd.scores[0] = -1 277 | 278 | q.floodFillSeed(region.seed.x, region.seed.y, rcode, qrPixelBlack, findOneCorner, &psd, 0) 279 | 280 | psd.ref.x = psd.corners[0].x - psd.ref.x 281 | psd.ref.y = psd.corners[0].y - psd.ref.y 282 | 283 | for i := 0; i < 4; i++ { 284 | psd.corners[i] = region.seed 285 | } 286 | 287 | i := region.seed.x*psd.ref.x + region.seed.y*psd.ref.y 288 | psd.scores[0] = i 289 | psd.scores[2] = -i 290 | 291 | i = region.seed.x*-psd.ref.y + region.seed.y*psd.ref.x 292 | psd.scores[1] = i 293 | psd.scores[3] = -i 294 | 295 | q.floodFillSeed(region.seed.x, region.seed.y, qrPixelBlack, rcode, findOtherCorners, &psd, 0) 296 | } 297 | 298 | func (q *recognizer) recordCapstone(ring, stone int) { 299 | 300 | stoneReg := &q.regions[stone] 301 | ringReg := &q.regions[ring] 302 | 303 | if len(q.capstones) >= qrMaxCastones { 304 | return 305 | } 306 | 307 | csIndex := len(q.capstones) 308 | q.capstones = append(q.capstones, qrCapstone{}) 309 | capstone := &q.capstones[csIndex] 310 | 311 | capstone.qrGrid = -1 312 | capstone.ring = ring 313 | capstone.stone = stone 314 | stoneReg.capstone = csIndex 315 | ringReg.capstone = csIndex 316 | 317 | // Find the corners of the ring 318 | q.findRegionCorners(ring, &stoneReg.seed, capstone.corners[:]) 319 | 320 | // Set up the perspective transform and find the center 321 | perspectiveSetup(capstone.c[:], capstone.corners[:], 7.0, 7.0) 322 | perspectiveMap(capstone.c[:], 3.5, 3.5, &capstone.center) 323 | 324 | } 325 | 326 | func perspectiveSetup(c []float64, rect []point, w, h float64) { 327 | x0 := float64(rect[0].x) 328 | y0 := float64(rect[0].y) 329 | x1 := float64(rect[1].x) 330 | y1 := float64(rect[1].y) 331 | x2 := float64(rect[2].x) 332 | y2 := float64(rect[2].y) 333 | x3 := float64(rect[3].x) 334 | y3 := float64(rect[3].y) 335 | wden := w * (x2*y3 - x3*y2 + (x3-x2)*y1 + x1*(y2-y3)) 336 | hden := h * (x2*y3 + x1*(y2-y3) - x3*y2 + (x3-x2)*y1) 337 | c[0] = (x1*(x2*y3-x3*y2) + x0*(-x2*y3+x3*y2+(x2-x3)*y1) + x1*(x3-x2)*y0) / wden 338 | c[1] = -(x0*(x2*y3+x1*(y2-y3)-x2*y1) - x1*x3*y2 + x2*x3*y1 + (x1*x3-x2*x3)*y0) / hden 339 | c[2] = x0 340 | c[3] = (y0*(x1*(y3-y2)-x2*y3+x3*y2) + y1*(x2*y3-x3*y2) + x0*y1*(y2-y3)) / wden 341 | c[4] = (x0*(y1*y3-y2*y3) + x1*y2*y3 - x2*y1*y3 + y0*(x3*y2-x1*y2+(x2-x3)*y1)) / hden 342 | c[5] = y0 343 | c[6] = (x1*(y3-y2) + x0*(y2-y3) + (x2-x3)*y1 + (x3-x2)*y0) / wden 344 | c[7] = (-x2*y3 + x1*y3 + x3*y2 + x0*(y1-y2) - x3*y1 + (x2-x1)*y0) / hden 345 | } 346 | 347 | func perspectiveMap(c []float64, u, v float64, ret *point) { 348 | den := c[6]*u + c[7]*v + 1.0 349 | x := (c[0]*u + c[1]*v + c[2]) / den 350 | y := (c[3]*u + c[4]*v + c[5]) / den 351 | ret.x = int(x + 0.5) 352 | ret.y = int(y + 0.5) 353 | } 354 | 355 | func perspectiveUnmap(c []float64, in *point, u, v *float64) { 356 | x := float64(in.x) 357 | y := float64(in.y) 358 | den := -c[0]*c[7]*y + c[1]*c[6]*y + (c[3]*c[7]-c[4]*c[6])*x + c[0]*c[4] - c[1]*c[3] 359 | *u = -(c[1]*(y-c[5]) - c[2]*c[7]*y + (c[5]*c[7]-c[4])*x + c[2]*c[4]) / den 360 | *v = (c[0]*(y-c[5]) - c[2]*c[6]*y + (c[5]*c[6]-c[3])*x + c[2]*c[3]) / den 361 | } 362 | 363 | func (q *recognizer) testCapstone(x, y int, pb []int) { 364 | 365 | ringRight := q.regionCode(x-pb[4], y) 366 | stone := q.regionCode(x-pb[4]-pb[3]-pb[2], y) 367 | ringLeft := q.regionCode(x-pb[4]-pb[3]-pb[2]-pb[1]-pb[0], y) 368 | 369 | if ringLeft < 0 || ringRight < 0 || stone < 0 { 370 | return 371 | } 372 | // Left and ring of ring should be connected 373 | if ringLeft != ringRight { 374 | return 375 | } 376 | // Ring should be disconnected from stone 377 | if ringLeft == stone { 378 | return 379 | } 380 | stoneReg := &q.regions[stone] 381 | ringReg := &q.regions[ringLeft] 382 | /* Already detected */ 383 | if stoneReg.capstone >= 0 || ringReg.capstone >= 0 { 384 | return 385 | } 386 | // Ratio should ideally be 37.5 387 | ratio := stoneReg.count * 100 / ringReg.count 388 | if ratio < 10 || ratio > 70 { 389 | return 390 | } 391 | q.recordCapstone(ringLeft, stone) 392 | } 393 | 394 | func (q *recognizer) finderScan(y int) { 395 | 396 | row := q.pixels[y*q.w : (y+1)*q.w] 397 | x := 0 398 | lastColor := 0 399 | runLength := 0 400 | runCount := 0 401 | pb := make([]int, 5) 402 | check := [5]int{1, 1, 3, 1, 1} 403 | 404 | for x = 0; x < q.w; x++ { 405 | color := 0 406 | if row[x] > 0 { 407 | color = 1 408 | } 409 | 410 | if x > 0 && color != lastColor { 411 | for i := 0; i < 4; i++ { 412 | pb[i] = pb[i+1] 413 | } 414 | pb[4] = runLength 415 | runLength = 0 416 | runCount++ 417 | 418 | if color == 0 && runCount >= 5 { 419 | var avg, err int 420 | ok := true 421 | avg = (pb[0] + pb[1] + pb[3] + pb[4]) / 4 422 | err = avg * 3 / 4 423 | for i := 0; i < 5; i++ { 424 | if pb[i] < check[i]*avg-err || pb[i] > check[i]*avg+err { 425 | ok = false 426 | break 427 | } 428 | } 429 | if ok { 430 | q.testCapstone(x, y, pb) 431 | } 432 | } 433 | } 434 | runLength++ 435 | lastColor = color 436 | } 437 | } 438 | 439 | func (q *recognizer) findAlignmentPattern(index int) { 440 | qr := &q.grids[index] 441 | c0 := &q.capstones[qr.caps[0]] 442 | c2 := &q.capstones[qr.caps[2]] 443 | 444 | var a, b, c point 445 | stepSize := 1 446 | dir := 0 447 | var u, v float64 448 | 449 | // Grab our previous estimate of the alignment pattern corner 450 | b = qr.align 451 | 452 | // Guess another two corners of the alignment pattern so that we 453 | // can estimate its size. 454 | 455 | perspectiveUnmap(c0.c[:], &b, &u, &v) 456 | perspectiveMap(c0.c[:], u, v+1.0, &a) 457 | perspectiveUnmap(c2.c[:], &b, &u, &v) 458 | perspectiveMap(c2.c[:], u+1.0, v, &c) 459 | sizeEstimate := int(math.Abs(float64((a.x-b.x)*-(c.y-b.y) + (a.y-b.y)*(c.x-b.x)))) 460 | 461 | // Spiral outwards from the estimate point until we find something 462 | // roughly the right size. Don't look too far from the estimate point 463 | 464 | for stepSize*stepSize < sizeEstimate*100 { 465 | dxMap := []int{1, 0, -1, 0} 466 | dyMap := []int{0, -1, 0, 1} 467 | 468 | for i := 0; i < stepSize; i++ { 469 | code := q.regionCode(b.x, b.y) 470 | 471 | if code >= 0 { 472 | reg := &q.regions[code] 473 | 474 | if reg.count >= sizeEstimate/2 && reg.count <= sizeEstimate*2 { 475 | qr.alignRegion = code 476 | return 477 | } 478 | } 479 | 480 | b.x += dxMap[dir] 481 | b.y += dyMap[dir] 482 | } 483 | 484 | dir = (dir + 1) % 4 485 | if (dir & 1) == 1 { 486 | stepSize++ 487 | } 488 | } 489 | } 490 | 491 | // readCell read a cell from a grid using the currently set perspective 492 | // transform. Returns +/- 1 for black/white, 0 for cells which are 493 | // out of image bounds. 494 | func (q *recognizer) readCell(index, x, y int) int { 495 | qr := &q.grids[index] 496 | var p point 497 | 498 | perspectiveMap(qr.c[:], float64(x)+0.5, float64(y)+0.5, &p) 499 | if p.y < 0 || p.y >= q.h || p.x < 0 || p.x >= q.w { 500 | return 0 501 | } 502 | if q.pixels[p.y*q.w+p.x] != 0 { 503 | return 1 504 | } 505 | return -1 506 | } 507 | 508 | func (q *recognizer) fitnessCell(index, x, y int) int { 509 | qr := &q.grids[index] 510 | score := 0 511 | offsets := []float64{0.3, 0.5, 0.7} 512 | for v := 0; v < 3; v++ { 513 | for u := 0; u < 3; u++ { 514 | var p point 515 | perspectiveMap(qr.c[:], float64(x)+offsets[u], float64(y)+offsets[v], &p) 516 | 517 | if p.y < 0 || p.y >= q.h || p.x < 0 || p.x >= q.w { 518 | continue 519 | } 520 | if q.pixels[p.y*q.w+p.x] != 0 { 521 | score++ 522 | } else { 523 | score-- 524 | } 525 | } 526 | } 527 | return score 528 | } 529 | 530 | func (q *recognizer) fitnessRing(index, cx, cy, radius int) int { 531 | score := 0 532 | for i := 0; i < radius*2; i++ { 533 | score += q.fitnessCell(index, cx-radius+i, cy-radius) 534 | score += q.fitnessCell(index, cx-radius, cy+radius-i) 535 | score += q.fitnessCell(index, cx+radius, cy-radius+i) 536 | score += q.fitnessCell(index, cx+radius-i, cy+radius) 537 | } 538 | return score 539 | } 540 | 541 | func (q *recognizer) fitnessApat(index, cx, cy int) int { 542 | return q.fitnessCell(index, cx, cy) - 543 | q.fitnessRing(index, cx, cy, 1) + 544 | q.fitnessRing(index, cx, cy, 2) 545 | } 546 | 547 | func (q *recognizer) fitnessCapstone(index, x, y int) int { 548 | x += 3 549 | y += 3 550 | return q.fitnessCell(index, x, y) + 551 | q.fitnessRing(index, x, y, 1) - 552 | q.fitnessRing(index, x, y, 2) + 553 | q.fitnessRing(index, x, y, 3) 554 | } 555 | 556 | // fitnessAll compute a fitness score for the currently configured perspective 557 | // transform, using the features we expect to find by scanning the 558 | // grid. 559 | func (q *recognizer) fitnessAll(index int) int { 560 | qr := &q.grids[index] 561 | version := (qr.gridSize - 17) / 4 562 | info := &qrVersionDb[version] 563 | score := 0 564 | 565 | // Check the timing pattern 566 | for i := 0; i < qr.gridSize-14; i++ { 567 | expect := 1 568 | if i&1 == 0 { 569 | expect = -1 570 | } 571 | score += q.fitnessCell(index, i+7, 6) * expect 572 | score += q.fitnessCell(index, 6, i+7) * expect 573 | } 574 | 575 | // Check capstones 576 | score += q.fitnessCapstone(index, 0, 0) 577 | score += q.fitnessCapstone(index, qr.gridSize-7, 0) 578 | score += q.fitnessCapstone(index, 0, qr.gridSize-7) 579 | 580 | if version < 0 || version > qrMaxVersion { 581 | return score 582 | } 583 | 584 | // Check alignment patterns 585 | apCount := 0 586 | for (apCount < qrMaxAliment) && info.apat[apCount] != 0 { 587 | apCount++ 588 | } 589 | 590 | for i := 1; i+1 < apCount; i++ { 591 | score += q.fitnessApat(index, 6, info.apat[i]) 592 | score += q.fitnessApat(index, info.apat[i], 6) 593 | } 594 | 595 | for i := 1; i < apCount; i++ { 596 | for j := 1; j < apCount; j++ { 597 | score += q.fitnessApat(index, info.apat[i], info.apat[j]) 598 | } 599 | } 600 | 601 | return score 602 | } 603 | 604 | func (q *recognizer) jigglePerspective(index int) { 605 | qr := &q.grids[index] 606 | best := q.fitnessAll(index) 607 | 608 | adjustments := make([]float64, 8) 609 | for i := 0; i < 8; i++ { 610 | adjustments[i] = qr.c[i] * 0.02 611 | } 612 | 613 | for pass := 0; pass < 5; pass++ { 614 | for i := 0; i < 16; i++ { 615 | j := i >> 1 616 | old := qr.c[j] 617 | step := adjustments[j] 618 | var new float64 619 | 620 | if i&1 == 1 { 621 | new = old + step 622 | } else { 623 | new = old - step 624 | } 625 | qr.c[j] = new 626 | test := q.fitnessAll(index) 627 | 628 | if test > best { 629 | best = test 630 | } else { 631 | qr.c[j] = old 632 | } 633 | } 634 | for i := 0; i < 8; i++ { 635 | adjustments[i] *= 0.5 636 | } 637 | } 638 | } 639 | 640 | // Once the capstones are in place and an alignment point has been chosen, 641 | // we call this function to set up a grid-reading perspective transform. 642 | func (q *recognizer) setupQrPerspective(index int) { 643 | qr := &q.grids[index] 644 | var rect [4]point 645 | 646 | /* Set up the perspective map for reading the grid */ 647 | rect[0] = q.capstones[qr.caps[1]].corners[0] 648 | rect[1] = q.capstones[qr.caps[2]].corners[0] 649 | rect[2] = qr.align 650 | rect[3] = q.capstones[qr.caps[0]].corners[0] 651 | 652 | perspectiveSetup(qr.c[:], rect[:], float64(qr.gridSize-7), float64(qr.gridSize-7)) 653 | q.jigglePerspective(index) 654 | } 655 | 656 | func rotateCapstone(cap *qrCapstone, h0, hd *point) { 657 | copy := [4]point{} 658 | 659 | var best int 660 | var bestScore int 661 | 662 | for j := 0; j < 4; j++ { 663 | p := &cap.corners[j] 664 | score := (p.x-h0.x)*-hd.y + (p.y-h0.y)*hd.x 665 | if j == 0 || score < bestScore { 666 | best = j 667 | bestScore = score 668 | } 669 | } 670 | ///* Rotate the capstone */ 671 | for j := 0; j < 4; j++ { 672 | copy[j] = cap.corners[(j+best)%4] 673 | } 674 | for j := 0; j < 4; j++ { 675 | cap.corners[j] = copy[j] 676 | } 677 | perspectiveSetup(cap.c[:], cap.corners[:], 7.0, 7.0) 678 | } 679 | 680 | func (q *recognizer) timingScan(p0, p1 *point) int { 681 | n := p1.x - p0.x 682 | d := p1.y - p0.y 683 | x := p0.x 684 | y := p0.y 685 | a := 0 686 | runlength := 0 687 | count := 0 688 | 689 | var dom, nondom *int 690 | var domStep int 691 | var nondomStep int 692 | 693 | if p0.x < 0 || p0.y < 0 || p0.x >= q.w || p0.y >= q.h { 694 | return -1 695 | } 696 | 697 | if p1.x < 0 || p1.y < 0 || p1.x >= q.w || p1.y >= q.h { 698 | return -1 699 | } 700 | 701 | if math.Abs(float64(n)) > math.Abs(float64(d)) { 702 | n, d = d, n 703 | dom = &x 704 | nondom = &y 705 | } else { 706 | dom = &y 707 | nondom = &x 708 | } 709 | 710 | if n < 0 { 711 | n = -n 712 | nondomStep = -1 713 | } else { 714 | nondomStep = 1 715 | } 716 | 717 | if d < 0 { 718 | d = -d 719 | domStep = -1 720 | } else { 721 | domStep = 1 722 | } 723 | 724 | x = p0.x 725 | y = p0.y 726 | for i := 0; i <= d; i++ { 727 | if y < 0 || y >= q.h || x < 0 || x >= q.w { 728 | break 729 | } 730 | pixel := q.pixels[y*q.w+x] 731 | 732 | if pixel > 0 { 733 | if runlength >= 2 { 734 | count++ 735 | } 736 | runlength = 0 737 | } else { 738 | runlength++ 739 | } 740 | 741 | a += n 742 | *dom += domStep 743 | if a >= d { 744 | *nondom += nondomStep 745 | a -= d 746 | } 747 | } 748 | return count 749 | } 750 | 751 | func findLeftMostToLine(userData interface{}, y, left, right int) { 752 | psd := userData.(*polygonScoreData) 753 | xs := []int{left, right} 754 | for i := 0; i < 2; i++ { 755 | d := -psd.ref.y*xs[i] + psd.ref.x*y 756 | if d < psd.scores[0] { 757 | psd.scores[0] = d 758 | psd.corners[0].x = xs[i] 759 | psd.corners[0].y = y 760 | } 761 | } 762 | } 763 | 764 | // Try the measure the timing pattern for a given QR code. This does 765 | // not require the global perspective to have been set up, but it 766 | // does require that the capstone corners have been set to their 767 | // canonical rotation. 768 | // 769 | // For each capstone, we find a point in the middle of the ring band 770 | // which is nearest the centre of the code. Using these points, we do 771 | // a horizontal and a vertical timing scan. 772 | func (q *recognizer) measureTimingPattern(index int) int { 773 | qr := &q.grids[index] 774 | for i := 0; i < 3; i++ { 775 | us := []float64{6.5, 6.5, 0.5} 776 | vs := []float64{0.5, 6.5, 6.5} 777 | cap := &q.capstones[qr.caps[i]] 778 | perspectiveMap(cap.c[:], us[i], vs[i], &qr.tpep[i]) 779 | } 780 | 781 | qr.hscan = q.timingScan(&qr.tpep[1], &qr.tpep[2]) 782 | qr.vscan = q.timingScan(&qr.tpep[1], &qr.tpep[0]) 783 | scan := qr.hscan 784 | if qr.vscan > scan { 785 | scan = qr.vscan 786 | } 787 | 788 | // If neither scan worked, we can't go any further. 789 | if scan < 0 { 790 | return -1 791 | } 792 | 793 | // Choose the nearest allowable grid size 794 | size := scan*2 + 13 795 | ver := (size - 15) / 4 796 | qr.gridSize = ver*4 + 17 797 | 798 | return 0 799 | } 800 | 801 | func (q *recognizer) recordQrGrid(a, b, c int) { 802 | 803 | if len(q.grids) >= qrMaxGrids { 804 | return 805 | } 806 | 807 | // Construct the hypotenuse line from A to C. B should be tothe left of this line. 808 | 809 | h0 := q.capstones[a].center 810 | var hd point 811 | hd.x = q.capstones[c].center.x - q.capstones[a].center.x 812 | hd.y = q.capstones[c].center.y - q.capstones[a].center.y 813 | 814 | // Make sure A-B-C is clockwise 815 | if (q.capstones[b].center.x-h0.x)*-hd.y+(q.capstones[b].center.y-h0.y)*hd.x > 0 { 816 | a, c = c, a 817 | hd.x = -hd.x 818 | hd.y = -hd.y 819 | } 820 | 821 | qrIndex := len(q.grids) 822 | q.grids = append(q.grids, qrGrid{}) 823 | qr := &q.grids[qrIndex] 824 | 825 | qr.caps[0] = a 826 | qr.caps[1] = b 827 | qr.caps[2] = c 828 | qr.alignRegion = -1 829 | 830 | // Rotate each capstone so that corner 0 is top-left with respect 831 | // to the grid. 832 | 833 | for i := 0; i < 3; i++ { 834 | cap := &q.capstones[qr.caps[i]] 835 | rotateCapstone(cap, &h0, &hd) 836 | cap.qrGrid = qrIndex 837 | } 838 | 839 | // Check the timing pattern. This doesn't require a perspective transform. 840 | 841 | if q.measureTimingPattern(qrIndex) < 0 { 842 | goto fail 843 | } 844 | 845 | // Make an estimate based for the alignment pattern based on extending lines from capstones A and C. 846 | if !lineIntersect(&q.capstones[a].corners[0], 847 | &q.capstones[a].corners[1], 848 | &q.capstones[c].corners[0], 849 | &q.capstones[c].corners[3], 850 | &qr.align) { 851 | 852 | goto fail 853 | } 854 | 855 | // On V2+ grids, we should use the alignment pattern. 856 | 857 | if qr.gridSize > 21 { 858 | // Try to find the actual location of the alignment pattern. 859 | 860 | q.findAlignmentPattern(qrIndex) 861 | 862 | // Find the point of the alignment pattern closest to the 863 | // top-left of the QR grid. 864 | 865 | if qr.alignRegion >= 0 { 866 | var psd polygonScoreData 867 | psd.corners = make([]point, 1) 868 | reg := &q.regions[qr.alignRegion] 869 | 870 | // Start from some point inside the alignment pattern 871 | qr.align = reg.seed 872 | psd.ref = hd 873 | psd.corners[0] = qr.align 874 | 875 | psd.scores[0] = -hd.y*qr.align.x + hd.x*qr.align.y 876 | 877 | q.floodFillSeed(reg.seed.x, reg.seed.y, qr.alignRegion, qrPixelBlack, nil, nil, 0) 878 | 879 | q.floodFillSeed(reg.seed.x, reg.seed.y, qrPixelBlack, qr.alignRegion, findLeftMostToLine, &psd, 0) 880 | qr.align = psd.corners[0] 881 | 882 | } 883 | } 884 | 885 | q.setupQrPerspective(qrIndex) 886 | return 887 | 888 | // We've been unable to complete setup for this grid. Undo what we've 889 | // recorded and pretend it never happened. 890 | 891 | fail: 892 | for i := 0; i < 3; i++ { 893 | q.capstones[qr.caps[i]].qrGrid = -1 894 | } 895 | q.grids = q.grids[:len(q.grids)-1] 896 | 897 | } 898 | 899 | func (q *recognizer) testNeighbours(i int, hlist []*neighbour, vlist []*neighbour) { 900 | bestScore := 0.0 901 | bestH := -1 902 | bestV := -1 903 | 904 | // Test each possible grouping 905 | 906 | for j := 0; j < len(hlist); j++ { 907 | hn := hlist[j] 908 | 909 | for k := 0; k < len(vlist); k++ { 910 | vn := vlist[k] 911 | score := math.Abs(1.0 - hn.distance/vn.distance) 912 | 913 | if score > 2.5 { 914 | continue 915 | } 916 | 917 | if bestH < 0 || score < bestScore { 918 | bestH = hn.index 919 | bestV = vn.index 920 | bestScore = score 921 | } 922 | } 923 | } 924 | 925 | if bestH < 0 || bestV < 0 { 926 | return 927 | } 928 | 929 | q.recordQrGrid(bestH, i, bestV) 930 | } 931 | 932 | func (q *recognizer) testGrouping(i int) { 933 | c1 := &q.capstones[i] 934 | 935 | hlist := make([]*neighbour, 0) 936 | vlist := make([]*neighbour, 0) 937 | 938 | if c1.qrGrid >= 0 { 939 | return 940 | } 941 | 942 | // Look for potential neighbours by examining the relative gradients 943 | // from this capstone to others. 944 | 945 | for j := 0; j < len(q.capstones); j++ { 946 | c2 := &q.capstones[j] 947 | 948 | if i == j || c2.qrGrid >= 0 { 949 | continue 950 | } 951 | var u, v float64 952 | 953 | perspectiveUnmap(c1.c[:], &c2.center, &u, &v) 954 | 955 | u = math.Abs(u - 3.5) 956 | v = math.Abs(v - 3.5) 957 | 958 | if u < 0.2*v { 959 | n := &neighbour{} 960 | n.index = j 961 | n.distance = v 962 | hlist = append(hlist, n) 963 | } 964 | if v < 0.2*u { 965 | n := &neighbour{} 966 | n.index = j 967 | n.distance = u 968 | vlist = append(vlist, n) 969 | } 970 | } 971 | 972 | if !(len(hlist) > 0 && len(vlist) > 0) { 973 | return 974 | } 975 | q.testNeighbours(i, hlist, vlist) 976 | } 977 | -------------------------------------------------------------------------------- /utils.go: -------------------------------------------------------------------------------- 1 | package goqr 2 | 3 | func lineIntersect(p0, p1, q0, q1, r *point) bool { 4 | // (a, b) is perpendicular to line p 5 | a := -(p1.y - p0.y) 6 | b := p1.x - p0.x 7 | 8 | // (c, d) is perpendicular to line q 9 | c := -(q1.y - q0.y) 10 | d := q1.x - q0.x 11 | 12 | // e and f are dot products of the respective vectors with p and q 13 | e := a*p1.x + b*p1.y 14 | f := c*q1.x + d*q1.y 15 | 16 | // Now we need to solve: 17 | // [a b] [rx] [e] 18 | // [c d] [ry] = [f] 19 | // 20 | // We do this by inverting the matrix and applying it to (e, f): 21 | // [ d -b] [e] [rx] 22 | // 1/det [-c a] [f] = [ry] 23 | // 24 | det := (a * d) - (b * c) 25 | if det == 0 { 26 | return false 27 | } 28 | r.x = (d*e - b*f) / det 29 | r.y = (-c*e + a*f) / det 30 | return true 31 | } 32 | -------------------------------------------------------------------------------- /version_db.go: -------------------------------------------------------------------------------- 1 | package goqr 2 | 3 | var qrVersionDb = [qrMaxVersion + 1]qrVersionInfo{ 4 | {}, 5 | { // Version 1 6 | 26, 7 | [qrMaxAliment]int{0}, 8 | [4]qrRsParams{ 9 | {26, 16, 1}, 10 | {26, 19, 1}, 11 | {26, 9, 1}, 12 | {26, 13, 1}, 13 | }, 14 | }, 15 | { // Version 2 16 | 44, 17 | [qrMaxAliment]int{6, 18, 0}, 18 | [4]qrRsParams{ 19 | {44, 28, 1}, 20 | {44, 34, 1}, 21 | {44, 16, 1}, 22 | {44, 22, 1}, 23 | }, 24 | }, 25 | { // Version 3 26 | 70, 27 | [qrMaxAliment]int{6, 22, 0}, 28 | [4]qrRsParams{ 29 | {70, 44, 1}, 30 | {70, 55, 1}, 31 | {35, 13, 2}, 32 | {35, 17, 2}, 33 | }, 34 | }, 35 | { // Version 4 36 | 100, 37 | [qrMaxAliment]int{6, 26, 0}, 38 | [4]qrRsParams{ 39 | {50, 32, 2}, 40 | {100, 80, 1}, 41 | {25, 9, 4}, 42 | {50, 24, 2}, 43 | }, 44 | }, 45 | { // Version 5 46 | 134, 47 | [qrMaxAliment]int{6, 30, 0}, 48 | [4]qrRsParams{ 49 | {67, 43, 2}, 50 | {134, 108, 1}, 51 | {33, 11, 2}, 52 | {33, 15, 2}, 53 | }, 54 | }, 55 | { // Version 6 56 | 172, 57 | [qrMaxAliment]int{6, 34, 0}, 58 | [4]qrRsParams{ 59 | {43, 27, 4}, 60 | {86, 68, 2}, 61 | {43, 15, 4}, 62 | {43, 19, 4}, 63 | }, 64 | }, 65 | { // Version 7 66 | 196, 67 | [qrMaxAliment]int{6, 22, 38, 0}, 68 | [4]qrRsParams{ 69 | {49, 31, 4}, 70 | {98, 78, 2}, 71 | {39, 13, 4}, 72 | {32, 14, 2}, 73 | }, 74 | }, 75 | { // Version 8 76 | 242, 77 | [qrMaxAliment]int{6, 24, 42, 0}, 78 | [4]qrRsParams{ 79 | {60, 38, 2}, 80 | {121, 97, 2}, 81 | {40, 14, 4}, 82 | {40, 18, 4}, 83 | }, 84 | }, 85 | { // Version 9 86 | 292, 87 | [qrMaxAliment]int{6, 26, 46, 0}, 88 | [4]qrRsParams{ 89 | {58, 36, 3}, 90 | {146, 116, 2}, 91 | {36, 12, 4}, 92 | {36, 16, 4}, 93 | }, 94 | }, 95 | { // Version 10 96 | 346, 97 | [qrMaxAliment]int{6, 28, 50, 0}, 98 | [4]qrRsParams{ 99 | {69, 43, 4}, 100 | {86, 68, 2}, 101 | {43, 15, 6}, 102 | {43, 19, 6}, 103 | }, 104 | }, 105 | { // Version 11 106 | 404, 107 | [qrMaxAliment]int{6, 30, 54, 0}, 108 | [4]qrRsParams{ 109 | {80, 50, 1}, 110 | {101, 81, 4}, 111 | {36, 12, 3}, 112 | {50, 22, 4}, 113 | }, 114 | }, 115 | { // Version 12 116 | 466, 117 | [qrMaxAliment]int{6, 32, 58, 0}, 118 | [4]qrRsParams{ 119 | {58, 36, 6}, 120 | {116, 92, 2}, 121 | {42, 14, 7}, 122 | {46, 20, 4}, 123 | }, 124 | }, 125 | { // Version 13 126 | 532, 127 | [qrMaxAliment]int{6, 34, 62, 0}, 128 | [4]qrRsParams{ 129 | {59, 37, 8}, 130 | {133, 107, 4}, 131 | {33, 11, 12}, 132 | {44, 20, 8}, 133 | }, 134 | }, 135 | { // Version 14 136 | 581, 137 | [qrMaxAliment]int{6, 26, 46, 66, 0}, 138 | [4]qrRsParams{ 139 | {64, 40, 4}, 140 | {145, 115, 3}, 141 | {36, 12, 11}, 142 | {36, 16, 11}, 143 | }, 144 | }, 145 | { // Version 15 146 | 655, 147 | [qrMaxAliment]int{6, 26, 48, 70, 0}, 148 | [4]qrRsParams{ 149 | {65, 41, 5}, 150 | {109, 87, 5}, 151 | {36, 12, 11}, 152 | {54, 24, 5}, 153 | }, 154 | }, 155 | { // Version 16 156 | 733, 157 | [qrMaxAliment]int{6, 26, 50, 74, 0}, 158 | [4]qrRsParams{ 159 | {73, 45, 7}, 160 | {122, 98, 5}, 161 | {45, 15, 3}, 162 | {43, 19, 15}, 163 | }, 164 | }, 165 | { // Version 17 166 | 815, 167 | [qrMaxAliment]int{6, 30, 54, 78, 0}, 168 | [4]qrRsParams{ 169 | {74, 46, 10}, 170 | {135, 107, 1}, 171 | {42, 14, 2}, 172 | {50, 22, 1}, 173 | }, 174 | }, 175 | { // Version 18 176 | 901, 177 | [qrMaxAliment]int{6, 30, 56, 82, 0}, 178 | [4]qrRsParams{ 179 | {69, 43, 9}, 180 | {150, 120, 5}, 181 | {42, 14, 2}, 182 | {50, 22, 17}, 183 | }, 184 | }, 185 | { // Version 19 186 | 991, 187 | [qrMaxAliment]int{6, 30, 58, 86, 0}, 188 | [4]qrRsParams{ 189 | {70, 44, 3}, 190 | {141, 113, 3}, 191 | {39, 13, 9}, 192 | {47, 21, 17}, 193 | }, 194 | }, 195 | { // Version 20 196 | 1085, 197 | [qrMaxAliment]int{6, 34, 62, 90, 0}, 198 | [4]qrRsParams{ 199 | {67, 41, 3}, 200 | {135, 107, 3}, 201 | {43, 15, 15}, 202 | {54, 24, 15}, 203 | }, 204 | }, 205 | { // Version 21 206 | 1156, 207 | [qrMaxAliment]int{6, 28, 50, 72, 92, 0}, 208 | [4]qrRsParams{ 209 | {68, 42, 17}, 210 | {144, 116, 4}, 211 | {46, 16, 19}, 212 | {50, 22, 17}, 213 | }, 214 | }, 215 | { // Version 22 216 | 1258, 217 | [qrMaxAliment]int{6, 26, 50, 74, 98, 0}, 218 | [4]qrRsParams{ 219 | {74, 46, 17}, 220 | {139, 111, 2}, 221 | {37, 13, 34}, 222 | {54, 24, 7}, 223 | }, 224 | }, 225 | { // Version 23 226 | 1364, 227 | [qrMaxAliment]int{6, 30, 54, 78, 102, 0}, 228 | [4]qrRsParams{ 229 | {75, 47, 4}, 230 | {151, 121, 4}, 231 | {45, 15, 16}, 232 | {54, 24, 11}, 233 | }, 234 | }, 235 | { // Version 24 236 | 1474, 237 | [qrMaxAliment]int{6, 28, 54, 80, 106, 0}, 238 | [4]qrRsParams{ 239 | {73, 45, 6}, 240 | {147, 117, 6}, 241 | {46, 16, 30}, 242 | {54, 24, 11}, 243 | }, 244 | }, 245 | { // Version 25 246 | 1588, 247 | [qrMaxAliment]int{6, 32, 58, 84, 110, 0}, 248 | [4]qrRsParams{ 249 | {75, 47, 8}, 250 | {132, 106, 8}, 251 | {45, 15, 22}, 252 | {54, 24, 7}, 253 | }, 254 | }, 255 | { // Version 26 256 | 1706, 257 | [qrMaxAliment]int{6, 30, 58, 86, 114, 0}, 258 | [4]qrRsParams{ 259 | {74, 46, 19}, 260 | {142, 114, 10}, 261 | {46, 16, 33}, 262 | {50, 22, 28}, 263 | }, 264 | }, 265 | { // Version 27 266 | 1828, 267 | [qrMaxAliment]int{6, 34, 62, 90, 118, 0}, 268 | [4]qrRsParams{ 269 | {73, 45, 22}, 270 | {152, 122, 8}, 271 | {45, 15, 12}, 272 | {53, 23, 8}, 273 | }, 274 | }, 275 | { // Version 28 276 | 1921, 277 | [qrMaxAliment]int{6, 26, 50, 74, 98, 122, 0}, 278 | [4]qrRsParams{ 279 | {73, 45, 3}, 280 | {147, 117, 3}, 281 | {45, 15, 11}, 282 | {54, 24, 4}, 283 | }, 284 | }, 285 | { // Version 29 286 | 2051, 287 | [qrMaxAliment]int{6, 30, 54, 78, 102, 126, 0}, 288 | [4]qrRsParams{ 289 | {73, 45, 21}, 290 | {146, 116, 7}, 291 | {45, 15, 19}, 292 | {53, 23, 1}, 293 | }, 294 | }, 295 | { // Version 30 296 | 2185, 297 | [qrMaxAliment]int{6, 26, 52, 78, 104, 130, 0}, 298 | [4]qrRsParams{ 299 | {75, 47, 19}, 300 | {145, 115, 5}, 301 | {45, 15, 23}, 302 | {54, 24, 15}, 303 | }, 304 | }, 305 | { // Version 31 306 | 2323, 307 | [qrMaxAliment]int{6, 30, 56, 82, 108, 134, 0}, 308 | [4]qrRsParams{ 309 | {74, 46, 2}, 310 | {145, 115, 13}, 311 | {45, 15, 23}, 312 | {54, 24, 42}, 313 | }, 314 | }, 315 | { // Version 32 316 | 2465, 317 | [qrMaxAliment]int{6, 34, 60, 86, 112, 138, 0}, 318 | [4]qrRsParams{ 319 | {74, 46, 10}, 320 | {145, 115, 17}, 321 | {45, 15, 19}, 322 | {54, 24, 10}, 323 | }, 324 | }, 325 | { // Version 33 326 | 2611, 327 | [qrMaxAliment]int{6, 30, 58, 86, 114, 142, 0}, 328 | [4]qrRsParams{ 329 | {74, 46, 14}, 330 | {145, 115, 17}, 331 | {45, 15, 11}, 332 | {54, 24, 29}, 333 | }, 334 | }, 335 | { // Version 34 336 | 2761, 337 | [qrMaxAliment]int{6, 34, 62, 90, 118, 146, 0}, 338 | [4]qrRsParams{ 339 | {74, 46, 14}, 340 | {145, 115, 13}, 341 | {46, 16, 59}, 342 | {54, 24, 44}, 343 | }, 344 | }, 345 | { // Version 35 346 | 2876, 347 | [qrMaxAliment]int{6, 30, 54, 78, 102, 126, 150}, 348 | [4]qrRsParams{ 349 | {75, 47, 12}, 350 | {151, 121, 12}, 351 | {45, 15, 22}, 352 | {54, 24, 39}, 353 | }, 354 | }, 355 | { // Version 36 356 | 3034, 357 | [qrMaxAliment]int{6, 24, 50, 76, 102, 128, 154}, 358 | [4]qrRsParams{ 359 | {75, 47, 6}, 360 | {151, 121, 6}, 361 | {45, 15, 2}, 362 | {54, 24, 46}, 363 | }, 364 | }, 365 | { // Version 37 366 | 3196, 367 | [qrMaxAliment]int{6, 28, 54, 80, 106, 132, 158}, 368 | [4]qrRsParams{ 369 | {74, 46, 29}, 370 | {152, 122, 17}, 371 | {45, 15, 24}, 372 | {54, 24, 49}, 373 | }, 374 | }, 375 | { // Version 38 376 | 3362, 377 | [qrMaxAliment]int{6, 32, 58, 84, 110, 136, 162}, 378 | [4]qrRsParams{ 379 | {74, 46, 13}, 380 | {152, 122, 4}, 381 | {45, 15, 42}, 382 | {54, 24, 48}, 383 | }, 384 | }, 385 | { // Version 39 386 | 3532, 387 | [qrMaxAliment]int{6, 26, 54, 82, 110, 138, 166}, 388 | [4]qrRsParams{ 389 | {75, 47, 40}, 390 | {147, 117, 20}, 391 | {45, 15, 10}, 392 | {54, 24, 43}, 393 | }, 394 | }, 395 | { // Version 40 396 | 3706, 397 | [qrMaxAliment]int{6, 30, 58, 86, 114, 142, 170}, 398 | [4]qrRsParams{ 399 | {75, 47, 18}, 400 | {148, 118, 19}, 401 | {45, 15, 20}, 402 | {54, 24, 34}, 403 | }, 404 | }, 405 | } 406 | --------------------------------------------------------------------------------