git.sr.ht/~pingoo/stdx@v0.0.0-20240218134121-094174641f6e/barcode/qr/encoder.go (about) 1 // Package qr can be used to create QR barcodes. 2 package qr 3 4 import ( 5 "image" 6 7 "git.sr.ht/~pingoo/stdx/barcode" 8 "git.sr.ht/~pingoo/stdx/barcode/utils" 9 ) 10 11 type encodeFn func(content string, eccLevel ErrorCorrectionLevel) (*utils.BitList, *versionInfo, error) 12 13 // Encoding mode for QR Codes. 14 type Encoding byte 15 16 const ( 17 // Auto will choose ths best matching encoding 18 Auto Encoding = iota 19 // Numeric encoding only encodes numbers [0-9] 20 Numeric 21 // AlphaNumeric encoding only encodes uppercase letters, numbers and [Space], $, %, *, +, -, ., /, : 22 AlphaNumeric 23 // Unicode encoding encodes the string as utf-8 24 Unicode 25 // only for testing purpose 26 unknownEncoding 27 ) 28 29 func (e Encoding) getEncoder() encodeFn { 30 switch e { 31 case Auto: 32 return encodeAuto 33 case Numeric: 34 return encodeNumeric 35 case AlphaNumeric: 36 return encodeAlphaNumeric 37 case Unicode: 38 return encodeUnicode 39 } 40 return nil 41 } 42 43 func (e Encoding) String() string { 44 switch e { 45 case Auto: 46 return "Auto" 47 case Numeric: 48 return "Numeric" 49 case AlphaNumeric: 50 return "AlphaNumeric" 51 case Unicode: 52 return "Unicode" 53 } 54 return "" 55 } 56 57 // Encode returns a QR barcode with the given content, error correction level and uses the given encoding 58 func Encode(content string, level ErrorCorrectionLevel, mode Encoding) (barcode.Barcode, error) { 59 bits, vi, err := mode.getEncoder()(content, level) 60 if err != nil { 61 return nil, err 62 } 63 64 blocks := splitToBlocks(bits.IterateBytes(), vi) 65 data := blocks.interleave(vi) 66 result := render(data, vi) 67 result.content = content 68 return result, nil 69 } 70 71 func render(data []byte, vi *versionInfo) *qrcode { 72 dim := vi.modulWidth() 73 results := make([]*qrcode, 8) 74 for i := 0; i < 8; i++ { 75 results[i] = newBarcode(dim) 76 } 77 78 occupied := newBarcode(dim) 79 80 setAll := func(x int, y int, val bool) { 81 occupied.Set(x, y, true) 82 for i := 0; i < 8; i++ { 83 results[i].Set(x, y, val) 84 } 85 } 86 87 drawFinderPatterns(vi, setAll) 88 drawAlignmentPatterns(occupied, vi, setAll) 89 90 //Timing Pattern: 91 var i int 92 for i = 0; i < dim; i++ { 93 if !occupied.Get(i, 6) { 94 setAll(i, 6, i%2 == 0) 95 } 96 if !occupied.Get(6, i) { 97 setAll(6, i, i%2 == 0) 98 } 99 } 100 // Dark Module 101 setAll(8, dim-8, true) 102 103 drawVersionInfo(vi, setAll) 104 drawFormatInfo(vi, -1, occupied.Set) 105 for i := 0; i < 8; i++ { 106 drawFormatInfo(vi, i, results[i].Set) 107 } 108 109 // Write the data 110 var curBitNo int 111 112 for pos := range iterateModules(occupied) { 113 var curBit bool 114 if curBitNo < len(data)*8 { 115 curBit = ((data[curBitNo/8] >> uint(7-(curBitNo%8))) & 1) == 1 116 } else { 117 curBit = false 118 } 119 120 for i := 0; i < 8; i++ { 121 setMasked(pos.X, pos.Y, curBit, i, results[i].Set) 122 } 123 curBitNo++ 124 } 125 126 lowestPenalty := ^uint(0) 127 lowestPenaltyIdx := -1 128 for i := 0; i < 8; i++ { 129 p := results[i].calcPenalty() 130 if p < lowestPenalty { 131 lowestPenalty = p 132 lowestPenaltyIdx = i 133 } 134 } 135 return results[lowestPenaltyIdx] 136 } 137 138 func setMasked(x, y int, val bool, mask int, set func(int, int, bool)) { 139 switch mask { 140 case 0: 141 val = val != (((y + x) % 2) == 0) 142 break 143 case 1: 144 val = val != ((y % 2) == 0) 145 break 146 case 2: 147 val = val != ((x % 3) == 0) 148 break 149 case 3: 150 val = val != (((y + x) % 3) == 0) 151 break 152 case 4: 153 val = val != (((y/2 + x/3) % 2) == 0) 154 break 155 case 5: 156 val = val != (((y*x)%2)+((y*x)%3) == 0) 157 break 158 case 6: 159 val = val != ((((y*x)%2)+((y*x)%3))%2 == 0) 160 break 161 case 7: 162 val = val != ((((y+x)%2)+((y*x)%3))%2 == 0) 163 } 164 set(x, y, val) 165 } 166 167 func iterateModules(occupied *qrcode) <-chan image.Point { 168 result := make(chan image.Point) 169 allPoints := make(chan image.Point) 170 go func() { 171 curX := occupied.dimension - 1 172 curY := occupied.dimension - 1 173 isUpward := true 174 175 for true { 176 if isUpward { 177 allPoints <- image.Pt(curX, curY) 178 allPoints <- image.Pt(curX-1, curY) 179 curY-- 180 if curY < 0 { 181 curY = 0 182 curX -= 2 183 if curX == 6 { 184 curX-- 185 } 186 if curX < 0 { 187 break 188 } 189 isUpward = false 190 } 191 } else { 192 allPoints <- image.Pt(curX, curY) 193 allPoints <- image.Pt(curX-1, curY) 194 curY++ 195 if curY >= occupied.dimension { 196 curY = occupied.dimension - 1 197 curX -= 2 198 if curX == 6 { 199 curX-- 200 } 201 isUpward = true 202 if curX < 0 { 203 break 204 } 205 } 206 } 207 } 208 209 close(allPoints) 210 }() 211 go func() { 212 for pt := range allPoints { 213 if !occupied.Get(pt.X, pt.Y) { 214 result <- pt 215 } 216 } 217 close(result) 218 }() 219 return result 220 } 221 222 func drawFinderPatterns(vi *versionInfo, set func(int, int, bool)) { 223 dim := vi.modulWidth() 224 drawPattern := func(xoff int, yoff int) { 225 for x := -1; x < 8; x++ { 226 for y := -1; y < 8; y++ { 227 val := (x == 0 || x == 6 || y == 0 || y == 6 || (x > 1 && x < 5 && y > 1 && y < 5)) && (x <= 6 && y <= 6 && x >= 0 && y >= 0) 228 229 if x+xoff >= 0 && x+xoff < dim && y+yoff >= 0 && y+yoff < dim { 230 set(x+xoff, y+yoff, val) 231 } 232 } 233 } 234 } 235 drawPattern(0, 0) 236 drawPattern(0, dim-7) 237 drawPattern(dim-7, 0) 238 } 239 240 func drawAlignmentPatterns(occupied *qrcode, vi *versionInfo, set func(int, int, bool)) { 241 drawPattern := func(xoff int, yoff int) { 242 for x := -2; x <= 2; x++ { 243 for y := -2; y <= 2; y++ { 244 val := x == -2 || x == 2 || y == -2 || y == 2 || (x == 0 && y == 0) 245 set(x+xoff, y+yoff, val) 246 } 247 } 248 } 249 positions := vi.alignmentPatternPlacements() 250 251 for _, x := range positions { 252 for _, y := range positions { 253 if occupied.Get(x, y) { 254 continue 255 } 256 drawPattern(x, y) 257 } 258 } 259 } 260 261 var formatInfos = map[ErrorCorrectionLevel]map[int][]bool{ 262 L: { 263 0: []bool{true, true, true, false, true, true, true, true, true, false, false, false, true, false, false}, 264 1: []bool{true, true, true, false, false, true, false, true, true, true, true, false, false, true, true}, 265 2: []bool{true, true, true, true, true, false, true, true, false, true, false, true, false, true, false}, 266 3: []bool{true, true, true, true, false, false, false, true, false, false, true, true, true, false, true}, 267 4: []bool{true, true, false, false, true, true, false, false, false, true, false, true, true, true, true}, 268 5: []bool{true, true, false, false, false, true, true, false, false, false, true, true, false, false, false}, 269 6: []bool{true, true, false, true, true, false, false, false, true, false, false, false, false, false, true}, 270 7: []bool{true, true, false, true, false, false, true, false, true, true, true, false, true, true, false}, 271 }, 272 M: { 273 0: []bool{true, false, true, false, true, false, false, false, false, false, true, false, false, true, false}, 274 1: []bool{true, false, true, false, false, false, true, false, false, true, false, false, true, false, true}, 275 2: []bool{true, false, true, true, true, true, false, false, true, true, true, true, true, false, false}, 276 3: []bool{true, false, true, true, false, true, true, false, true, false, false, true, false, true, true}, 277 4: []bool{true, false, false, false, true, false, true, true, true, true, true, true, false, false, true}, 278 5: []bool{true, false, false, false, false, false, false, true, true, false, false, true, true, true, false}, 279 6: []bool{true, false, false, true, true, true, true, true, false, false, true, false, true, true, true}, 280 7: []bool{true, false, false, true, false, true, false, true, false, true, false, false, false, false, false}, 281 }, 282 Q: { 283 0: []bool{false, true, true, false, true, false, true, false, true, false, true, true, true, true, true}, 284 1: []bool{false, true, true, false, false, false, false, false, true, true, false, true, false, false, false}, 285 2: []bool{false, true, true, true, true, true, true, false, false, true, true, false, false, false, true}, 286 3: []bool{false, true, true, true, false, true, false, false, false, false, false, false, true, true, false}, 287 4: []bool{false, true, false, false, true, false, false, true, false, true, true, false, true, false, false}, 288 5: []bool{false, true, false, false, false, false, true, true, false, false, false, false, false, true, true}, 289 6: []bool{false, true, false, true, true, true, false, true, true, false, true, true, false, true, false}, 290 7: []bool{false, true, false, true, false, true, true, true, true, true, false, true, true, false, true}, 291 }, 292 H: { 293 0: []bool{false, false, true, false, true, true, false, true, false, false, false, true, false, false, true}, 294 1: []bool{false, false, true, false, false, true, true, true, false, true, true, true, true, true, false}, 295 2: []bool{false, false, true, true, true, false, false, true, true, true, false, false, true, true, true}, 296 3: []bool{false, false, true, true, false, false, true, true, true, false, true, false, false, false, false}, 297 4: []bool{false, false, false, false, true, true, true, false, true, true, false, false, false, true, false}, 298 5: []bool{false, false, false, false, false, true, false, false, true, false, true, false, true, false, true}, 299 6: []bool{false, false, false, true, true, false, true, false, false, false, false, true, true, false, false}, 300 7: []bool{false, false, false, true, false, false, false, false, false, true, true, true, false, true, true}, 301 }, 302 } 303 304 func drawFormatInfo(vi *versionInfo, usedMask int, set func(int, int, bool)) { 305 var formatInfo []bool 306 307 if usedMask == -1 { 308 formatInfo = []bool{true, true, true, true, true, true, true, true, true, true, true, true, true, true, true} // Set all to true cause -1 --> occupied mask. 309 } else { 310 formatInfo = formatInfos[vi.Level][usedMask] 311 } 312 313 if len(formatInfo) == 15 { 314 dim := vi.modulWidth() 315 set(0, 8, formatInfo[0]) 316 set(1, 8, formatInfo[1]) 317 set(2, 8, formatInfo[2]) 318 set(3, 8, formatInfo[3]) 319 set(4, 8, formatInfo[4]) 320 set(5, 8, formatInfo[5]) 321 set(7, 8, formatInfo[6]) 322 set(8, 8, formatInfo[7]) 323 set(8, 7, formatInfo[8]) 324 set(8, 5, formatInfo[9]) 325 set(8, 4, formatInfo[10]) 326 set(8, 3, formatInfo[11]) 327 set(8, 2, formatInfo[12]) 328 set(8, 1, formatInfo[13]) 329 set(8, 0, formatInfo[14]) 330 331 set(8, dim-1, formatInfo[0]) 332 set(8, dim-2, formatInfo[1]) 333 set(8, dim-3, formatInfo[2]) 334 set(8, dim-4, formatInfo[3]) 335 set(8, dim-5, formatInfo[4]) 336 set(8, dim-6, formatInfo[5]) 337 set(8, dim-7, formatInfo[6]) 338 set(dim-8, 8, formatInfo[7]) 339 set(dim-7, 8, formatInfo[8]) 340 set(dim-6, 8, formatInfo[9]) 341 set(dim-5, 8, formatInfo[10]) 342 set(dim-4, 8, formatInfo[11]) 343 set(dim-3, 8, formatInfo[12]) 344 set(dim-2, 8, formatInfo[13]) 345 set(dim-1, 8, formatInfo[14]) 346 } 347 } 348 349 var versionInfoBitsByVersion = map[byte][]bool{ 350 7: []bool{false, false, false, true, true, true, true, true, false, false, true, false, false, true, false, true, false, false}, 351 8: []bool{false, false, true, false, false, false, false, true, false, true, true, false, true, true, true, true, false, false}, 352 9: []bool{false, false, true, false, false, true, true, false, true, false, true, false, false, true, true, false, false, true}, 353 10: []bool{false, false, true, false, true, false, false, true, false, false, true, true, false, true, false, false, true, true}, 354 11: []bool{false, false, true, false, true, true, true, false, true, true, true, true, true, true, false, true, true, false}, 355 12: []bool{false, false, true, true, false, false, false, true, true, true, false, true, true, false, false, false, true, false}, 356 13: []bool{false, false, true, true, false, true, true, false, false, false, false, true, false, false, false, true, true, true}, 357 14: []bool{false, false, true, true, true, false, false, true, true, false, false, false, false, false, true, true, false, true}, 358 15: []bool{false, false, true, true, true, true, true, false, false, true, false, false, true, false, true, false, false, false}, 359 16: []bool{false, true, false, false, false, false, true, false, true, true, false, true, true, true, true, false, false, false}, 360 17: []bool{false, true, false, false, false, true, false, true, false, false, false, true, false, true, true, true, false, true}, 361 18: []bool{false, true, false, false, true, false, true, false, true, false, false, false, false, true, false, true, true, true}, 362 19: []bool{false, true, false, false, true, true, false, true, false, true, false, false, true, true, false, false, true, false}, 363 20: []bool{false, true, false, true, false, false, true, false, false, true, true, false, true, false, false, true, true, false}, 364 21: []bool{false, true, false, true, false, true, false, true, true, false, true, false, false, false, false, false, true, true}, 365 22: []bool{false, true, false, true, true, false, true, false, false, false, true, true, false, false, true, false, false, true}, 366 23: []bool{false, true, false, true, true, true, false, true, true, true, true, true, true, false, true, true, false, false}, 367 24: []bool{false, true, true, false, false, false, true, true, true, false, true, true, false, false, false, true, false, false}, 368 25: []bool{false, true, true, false, false, true, false, false, false, true, true, true, true, false, false, false, false, true}, 369 26: []bool{false, true, true, false, true, false, true, true, true, true, true, false, true, false, true, false, true, true}, 370 27: []bool{false, true, true, false, true, true, false, false, false, false, true, false, false, false, true, true, true, false}, 371 28: []bool{false, true, true, true, false, false, true, true, false, false, false, false, false, true, true, false, true, false}, 372 29: []bool{false, true, true, true, false, true, false, false, true, true, false, false, true, true, true, true, true, true}, 373 30: []bool{false, true, true, true, true, false, true, true, false, true, false, true, true, true, false, true, false, true}, 374 31: []bool{false, true, true, true, true, true, false, false, true, false, false, true, false, true, false, false, false, false}, 375 32: []bool{true, false, false, false, false, false, true, false, false, true, true, true, false, true, false, true, false, true}, 376 33: []bool{true, false, false, false, false, true, false, true, true, false, true, true, true, true, false, false, false, false}, 377 34: []bool{true, false, false, false, true, false, true, false, false, false, true, false, true, true, true, false, true, false}, 378 35: []bool{true, false, false, false, true, true, false, true, true, true, true, false, false, true, true, true, true, true}, 379 36: []bool{true, false, false, true, false, false, true, false, true, true, false, false, false, false, true, false, true, true}, 380 37: []bool{true, false, false, true, false, true, false, true, false, false, false, false, true, false, true, true, true, false}, 381 38: []bool{true, false, false, true, true, false, true, false, true, false, false, true, true, false, false, true, false, false}, 382 39: []bool{true, false, false, true, true, true, false, true, false, true, false, true, false, false, false, false, false, true}, 383 40: []bool{true, false, true, false, false, false, true, true, false, false, false, true, true, false, true, false, false, true}, 384 } 385 386 func drawVersionInfo(vi *versionInfo, set func(int, int, bool)) { 387 versionInfoBits, ok := versionInfoBitsByVersion[vi.Version] 388 389 if ok && len(versionInfoBits) > 0 { 390 for i := 0; i < len(versionInfoBits); i++ { 391 x := (vi.modulWidth() - 11) + i%3 392 y := i / 3 393 set(x, y, versionInfoBits[len(versionInfoBits)-i-1]) 394 set(y, x, versionInfoBits[len(versionInfoBits)-i-1]) 395 } 396 } 397 398 } 399 400 func addPaddingAndTerminator(bl *utils.BitList, vi *versionInfo) { 401 for i := 0; i < 4 && bl.Len() < vi.totalDataBytes()*8; i++ { 402 bl.AddBit(false) 403 } 404 405 for bl.Len()%8 != 0 { 406 bl.AddBit(false) 407 } 408 409 for i := 0; bl.Len() < vi.totalDataBytes()*8; i++ { 410 if i%2 == 0 { 411 bl.AddByte(236) 412 } else { 413 bl.AddByte(17) 414 } 415 } 416 }