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  }