From d039b6bd02e8dbca886dcccee3fd8d841851fc1c Mon Sep 17 00:00:00 2001 From: boombuler Date: Mon, 26 Dec 2016 22:16:28 +0100 Subject: [PATCH] finished aztec code generator. fixes #10 --- aztec/aztec_test.go | 94 ++++++++++ aztec/azteccode.go | 66 +++++++ aztec/encoder.go | 267 +++++++++++++++++++++++++++++ aztec/encoder_test.go | 58 +++++++ aztec/errorcorrection.go | 61 +++++++ datamatrix/errorcorrection.go | 54 +----- datamatrix/errorcorrection_test.go | 33 ---- qr/errorcorrection.go | 53 ++---- qr/errorcorrection_test.go | 13 -- utils/bitlist.go | 2 +- utils/galoisfield.go | 29 ++-- utils/galoisfield_test.go | 2 +- utils/gfpoly.go | 26 +-- utils/reedsolomon.go | 44 +++++ 14 files changed, 642 insertions(+), 160 deletions(-) create mode 100644 aztec/aztec_test.go create mode 100644 aztec/azteccode.go create mode 100644 aztec/encoder.go create mode 100644 aztec/encoder_test.go create mode 100644 aztec/errorcorrection.go create mode 100644 utils/reedsolomon.go diff --git a/aztec/aztec_test.go b/aztec/aztec_test.go new file mode 100644 index 0000000..598192a --- /dev/null +++ b/aztec/aztec_test.go @@ -0,0 +1,94 @@ +package aztec + +import ( + "testing" +) + +func encodeTest(t *testing.T, data, wanted string) { + result, err := Encode([]byte(data), DEFAULT_EC_PERCENT, DEFAULT_LAYERS) + if err != nil { + t.Error(err) + } else { + ac, ok := result.(*aztecCode) + if !ok { + t.Error("returned barcode is no aztec code...") + } else if draw := ac.string(); draw != wanted { + t.Errorf("Invalid Barcode returned:\n%s", draw) + } + + } +} + +func Test_Encode1(t *testing.T) { + encodeTest(t, "This is an example Aztec symbol for Wikipedia.", + "X X X X X X X X \n"+ + "X X X X X X X X X X \n"+ + "X X X X X X X X X X X \n"+ + "X X X X X X X X X X X \n"+ + " X X X X X X X X X X X \n"+ + " X X X X X X X X X X X X X \n"+ + " X X X X X X X X X X X X \n"+ + "X X X X X X X X X X X X X X X X \n"+ + "X X X X X X X X X X X \n"+ + "X X X X X X X X X X X X X X X X \n"+ + "X X X X X X X X X X \n"+ + "X X X X X X X X X X \n"+ + " X X X X X X X X X X \n"+ + " X X X X X X X X X X X X X X X X X X \n"+ + " X X X X X X X X X X X X \n"+ + " X X X X X X X X X X X X X X X X \n"+ + " X X X X X X X X X X X \n"+ + " X X X X X X X X \n"+ + " X X X X X X X X X X X X X X X X \n"+ + " X X X X X X X X X X X X \n"+ + " X X X \n"+ + " X X X X X X X X X X \n"+ + " X X X X X X X X X X \n") +} + +func Test_Encode2(t *testing.T) { + encodeTest(t, "Aztec Code is a public domain 2D matrix barcode symbology"+ + " of nominally square symbols built on a square grid with a "+ + "distinctive square bullseye pattern at their center.", + " X X X X X X X X X X X X X X X \n"+ + " X X X X X X X X X X X X X X X \n"+ + " X X X X X X X X X X X X X X X X X X X \n"+ + "X X X X X X X X X X X X X X \n"+ + "X X X X X X X X X X X X X X X X X X X X X \n"+ + " X X X X X X X X X X X X X X X X \n"+ + "X X X X X X X X X X X X X X X X X X X X \n"+ + " X X X X X X X X X X X X X X X X X X X X X X \n"+ + "X X X X X X X X X X X X X X X X X X X X X X X X X X X X \n"+ + " X X X X X X X X X X X X X X X X X X X X \n"+ + " X X X X X X X X X X X X X X X X X X X X \n"+ + " X X X X X X X X X X X X X X X X X X X X X X X \n"+ + "X X X X X X X X X X X X X X X X X X X X X \n"+ + " X X X X X X X X X X X X X X X \n"+ + " X X X X X X X X X X X X X X X X X X X X X X X X X X X \n"+ + " X X X X X X X X X X X \n"+ + " X X X X X X X X X X X X X X X X X X X X X X X X X \n"+ + " X X X X X X X X X X X X X X X \n"+ + "X X X X X X X X X X X X X X X X X X X X X X X X X X \n"+ + "X X X X X X X X X X X X X X X X X X X X \n"+ + "X X X X X X X X X X X X X X X X X X X X X \n"+ + " X X X X X X X X X X X X \n"+ + " X X X X X X X X X X X X X X X X X X X X X X X \n"+ + "X X X X X X X X X X X X X X X X X \n"+ + " X X X X X X X X X X X X X X X X X X X X X X X X X X X \n"+ + " X X X X X X X X X X X X X X X X \n"+ + " X X X X X X X X X X X X X X X X X X X X X X X X X X X \n"+ + " X X X X X X X X X X X X X X X X X \n"+ + "X X X X X X X X X X X X X X X X X \n"+ + "X X X X X X X X X X X X X X X X X X X X X X X X \n"+ + " X X X X X X X X X X X X X X X X X X X X \n"+ + "X X X X X X X X X X X X X X X \n"+ + " X X X X X X X X X X X X X X X X X X X X X X X X X \n"+ + " X X X X X X X X X X X X X X X X X \n"+ + "X X X X X X X X X X X X X X X X X X \n"+ + "X X X X X X X X X X X X X X X X X X X X X X X \n"+ + "X X X X X X X X X X X X X X X X X X X X X \n"+ + "X X X X X X X X X X X X X X X X \n"+ + "X X X X X X X X X X X X X X X X X X X X X \n"+ + " X X X X X X X X X X X X X X X X \n"+ + "X X X X X X X X X X X X X \n") +} diff --git a/aztec/azteccode.go b/aztec/azteccode.go new file mode 100644 index 0000000..5a15975 --- /dev/null +++ b/aztec/azteccode.go @@ -0,0 +1,66 @@ +package aztec + +import ( + "bytes" + "image" + "image/color" + + "github.com/boombuler/barcode" + "github.com/boombuler/barcode/utils" +) + +type aztecCode struct { + *utils.BitList + size int + content []byte +} + +func newAztecCode(size int) *aztecCode { + return &aztecCode{utils.NewBitList(size * size), size, nil} +} + +func (c *aztecCode) Content() string { + return string(c.content) +} + +func (c *aztecCode) Metadata() barcode.Metadata { + return barcode.Metadata{"Aztec", 2} +} + +func (c *aztecCode) ColorModel() color.Model { + return color.Gray16Model +} + +func (c *aztecCode) Bounds() image.Rectangle { + return image.Rect(0, 0, c.size, c.size) +} + +func (c *aztecCode) At(x, y int) color.Color { + if c.GetBit(x*c.size + y) { + return color.Black + } + return color.White +} + +func (c *aztecCode) CheckSum() int { + return 0 +} + +func (c *aztecCode) set(x, y int) { + c.SetBit(x*c.size+y, true) +} + +func (c *aztecCode) string() string { + buf := new(bytes.Buffer) + for y := 0; y < c.size; y++ { + for x := 0; x < c.size; x++ { + if c.GetBit(x*c.size + y) { + buf.WriteString("X ") + } else { + buf.WriteString(" ") + } + } + buf.WriteRune('\n') + } + return buf.String() +} diff --git a/aztec/encoder.go b/aztec/encoder.go new file mode 100644 index 0000000..86fd08f --- /dev/null +++ b/aztec/encoder.go @@ -0,0 +1,267 @@ +package aztec + +import ( + "fmt" + + "github.com/boombuler/barcode" + "github.com/boombuler/barcode/utils" +) + +const ( + DEFAULT_EC_PERCENT = 33 + DEFAULT_LAYERS = 0 + max_nb_bits = 32 + max_nb_bits_compact = 4 +) + +var ( + word_size = []int{ + 4, 6, 6, 8, 8, 8, 8, 8, 8, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, + } +) + +func totalBitsInLayer(layers int, compact bool) int { + tmp := 112 + if compact { + tmp = 88 + } + return (tmp + 16*layers) * layers +} + +func stuffBits(bits *utils.BitList, wordSize int) *utils.BitList { + out := new(utils.BitList) + n := bits.Len() + mask := (1 << uint(wordSize)) - 2 + for i := 0; i < n; i += wordSize { + word := 0 + for j := 0; j < wordSize; j++ { + if i+j >= n || bits.GetBit(i+j) { + word |= 1 << uint(wordSize-1-j) + } + } + if (word & mask) == mask { + out.AddBits(word&mask, byte(wordSize)) + i-- + } else if (word & mask) == 0 { + out.AddBits(word|1, byte(wordSize)) + i-- + } else { + out.AddBits(word, byte(wordSize)) + } + } + return out +} + +func generateModeMessage(compact bool, layers, messageSizeInWords int) *utils.BitList { + modeMessage := new(utils.BitList) + if compact { + modeMessage.AddBits(layers-1, 2) + modeMessage.AddBits(messageSizeInWords-1, 6) + modeMessage = generateCheckWords(modeMessage, 28, 4) + } else { + modeMessage.AddBits(layers-1, 5) + modeMessage.AddBits(messageSizeInWords-1, 11) + modeMessage = generateCheckWords(modeMessage, 40, 4) + } + return modeMessage +} + +func drawModeMessage(matrix *aztecCode, compact bool, matrixSize int, modeMessage *utils.BitList) { + center := matrixSize / 2 + if compact { + for i := 0; i < 7; i++ { + offset := center - 3 + i + if modeMessage.GetBit(i) { + matrix.set(offset, center-5) + } + if modeMessage.GetBit(i + 7) { + matrix.set(center+5, offset) + } + if modeMessage.GetBit(20 - i) { + matrix.set(offset, center+5) + } + if modeMessage.GetBit(27 - i) { + matrix.set(center-5, offset) + } + } + } else { + for i := 0; i < 10; i++ { + offset := center - 5 + i + i/5 + if modeMessage.GetBit(i) { + matrix.set(offset, center-7) + } + if modeMessage.GetBit(i + 10) { + matrix.set(center+7, offset) + } + if modeMessage.GetBit(29 - i) { + matrix.set(offset, center+7) + } + if modeMessage.GetBit(39 - i) { + matrix.set(center-7, offset) + } + } + } +} + +func drawBullsEye(matrix *aztecCode, center, size int) { + for i := 0; i < size; i += 2 { + for j := center - i; j <= center+i; j++ { + matrix.set(j, center-i) + matrix.set(j, center+i) + matrix.set(center-i, j) + matrix.set(center+i, j) + } + } + matrix.set(center-size, center-size) + matrix.set(center-size+1, center-size) + matrix.set(center-size, center-size+1) + matrix.set(center+size, center-size) + matrix.set(center+size, center-size+1) + matrix.set(center+size, center+size-1) +} + +// Encode returns an aztec barcode with the given content +func Encode(data []byte, minECCPercent int, userSpecifiedLayers int) (barcode.Barcode, error) { + bits := highlevelEncode(data) + eccBits := ((bits.Len() * minECCPercent) / 100) + 11 + totalSizeBits := bits.Len() + eccBits + var layers, TotalBitsInLayer, wordSize int + var compact bool + var stuffedBits *utils.BitList + if userSpecifiedLayers != DEFAULT_LAYERS { + compact = userSpecifiedLayers < 0 + if compact { + layers = -userSpecifiedLayers + } else { + layers = userSpecifiedLayers + } + if (compact && layers > max_nb_bits_compact) || (!compact && layers > max_nb_bits) { + return nil, fmt.Errorf("Illegal value %d for layers", userSpecifiedLayers) + } + TotalBitsInLayer = totalBitsInLayer(layers, compact) + wordSize = word_size[layers] + usableBitsInLayers := TotalBitsInLayer - (TotalBitsInLayer % wordSize) + stuffedBits = stuffBits(bits, wordSize) + if stuffedBits.Len()+eccBits > usableBitsInLayers { + return nil, fmt.Errorf("Data to large for user specified layer") + } + if compact && stuffedBits.Len() > wordSize*64 { + return nil, fmt.Errorf("Data to large for user specified layer") + } + } else { + wordSize = 0 + stuffedBits = nil + // We look at the possible table sizes in the order Compact1, Compact2, Compact3, + // Compact4, Normal4,... Normal(i) for i < 4 isn't typically used since Compact(i+1) + // is the same size, but has more data. + for i := 0; ; i++ { + if i > max_nb_bits { + return nil, fmt.Errorf("Data too large for an aztec code") + } + compact = i <= 3 + layers = i + if compact { + layers = i + 1 + } + TotalBitsInLayer = totalBitsInLayer(layers, compact) + if totalSizeBits > TotalBitsInLayer { + continue + } + // [Re]stuff the bits if this is the first opportunity, or if the + // wordSize has changed + if wordSize != word_size[layers] { + wordSize = word_size[layers] + stuffedBits = stuffBits(bits, wordSize) + } + usableBitsInLayers := TotalBitsInLayer - (TotalBitsInLayer % wordSize) + if compact && stuffedBits.Len() > wordSize*64 { + // Compact format only allows 64 data words, though C4 can hold more words than that + continue + } + if stuffedBits.Len()+eccBits <= usableBitsInLayers { + break + } + } + } + messageBits := generateCheckWords(stuffedBits, TotalBitsInLayer, wordSize) + messageSizeInWords := stuffedBits.Len() / wordSize + modeMessage := generateModeMessage(compact, layers, messageSizeInWords) + + // allocate symbol + var baseMatrixSize int + if compact { + baseMatrixSize = 11 + layers*4 + } else { + baseMatrixSize = 14 + layers*4 + } + alignmentMap := make([]int, baseMatrixSize) + var matrixSize int + + if compact { + // no alignment marks in compact mode, alignmentMap is a no-op + matrixSize = baseMatrixSize + for i := 0; i < len(alignmentMap); i++ { + alignmentMap[i] = i + } + } else { + matrixSize = baseMatrixSize + 1 + 2*((baseMatrixSize/2-1)/15) + origCenter := baseMatrixSize / 2 + center := matrixSize / 2 + for i := 0; i < origCenter; i++ { + newOffset := i + i/15 + alignmentMap[origCenter-i-1] = center - newOffset - 1 + alignmentMap[origCenter+i] = center + newOffset + 1 + } + } + code := newAztecCode(matrixSize) + code.content = data + + // draw data bits + for i, rowOffset := 0, 0; i < layers; i++ { + rowSize := (layers - i) * 4 + if compact { + rowSize += 9 + } else { + rowSize += 12 + } + + for j := 0; j < rowSize; j++ { + columnOffset := j * 2 + for k := 0; k < 2; k++ { + if messageBits.GetBit(rowOffset + columnOffset + k) { + code.set(alignmentMap[i*2+k], alignmentMap[i*2+j]) + } + if messageBits.GetBit(rowOffset + rowSize*2 + columnOffset + k) { + code.set(alignmentMap[i*2+j], alignmentMap[baseMatrixSize-1-i*2-k]) + } + if messageBits.GetBit(rowOffset + rowSize*4 + columnOffset + k) { + code.set(alignmentMap[baseMatrixSize-1-i*2-k], alignmentMap[baseMatrixSize-1-i*2-j]) + } + if messageBits.GetBit(rowOffset + rowSize*6 + columnOffset + k) { + code.set(alignmentMap[baseMatrixSize-1-i*2-j], alignmentMap[i*2+k]) + } + } + } + rowOffset += rowSize * 8 + } + + // draw mode message + drawModeMessage(code, compact, matrixSize, modeMessage) + + // draw alignment marks + if compact { + drawBullsEye(code, matrixSize/2, 5) + } else { + drawBullsEye(code, matrixSize/2, 7) + for i, j := 0, 0; i < baseMatrixSize/2-1; i, j = i+15, j+16 { + for k := (matrixSize / 2) & 1; k < matrixSize; k += 2 { + code.set(matrixSize/2-j, k) + code.set(matrixSize/2+j, k) + code.set(k, matrixSize/2-j) + code.set(k, matrixSize/2+j) + } + } + } + return code, nil +} diff --git a/aztec/encoder_test.go b/aztec/encoder_test.go new file mode 100644 index 0000000..053e67d --- /dev/null +++ b/aztec/encoder_test.go @@ -0,0 +1,58 @@ +package aztec + +import ( + "strings" + "testing" + + "github.com/boombuler/barcode/utils" +) + +func Test_StuffBits(t *testing.T) { + testStuffBits := func(wordSize int, bits string, expected string) { + bl := new(utils.BitList) + for _, r := range bits { + if r == 'X' { + bl.AddBit(true) + } else if r == '.' { + bl.AddBit(false) + } + } + stuffed := stuffBits(bl, wordSize) + expectedBits := strings.Replace(expected, " ", "", -1) + result := bitStr(stuffed) + + if result != expectedBits { + t.Errorf("stuffBits failed for %q\nGot: %q", bits, result) + } + } + + testStuffBits(5, ".X.X. X.X.X .X.X.", + ".X.X. X.X.X .X.X.") + testStuffBits(5, ".X.X. ..... .X.X", + ".X.X. ....X ..X.X") + testStuffBits(3, "XX. ... ... ..X XXX .X. ..", + "XX. ..X ..X ..X ..X .XX XX. .X. ..X") + testStuffBits(6, ".X.X.. ...... ..X.XX", + ".X.X.. .....X. ..X.XX XXXX.") + testStuffBits(6, ".X.X.. ...... ...... ..X.X.", + ".X.X.. .....X .....X ....X. X.XXXX") + testStuffBits(6, ".X.X.. XXXXXX ...... ..X.XX", + ".X.X.. XXXXX. X..... ...X.X XXXXX.") + testStuffBits(6, + "...... ..XXXX X..XX. .X.... .X.X.X .....X .X.... ...X.X .....X ....XX ..X... ....X. X..XXX X.XX.X", + ".....X ...XXX XX..XX ..X... ..X.X. X..... X.X... ....X. X..... X....X X..X.. .....X X.X..X XXX.XX .XXXXX") +} + +func Test_ModeMessage(t *testing.T) { + testModeMessage := func(compact bool, layers, words int, expected string) { + result := bitStr(generateModeMessage(compact, layers, words)) + expectedBits := strings.Replace(expected, " ", "", -1) + if result != expectedBits { + t.Errorf("generateModeMessage(%v, %d, %d) failed.\nGot:%s", compact, layers, words, result) + } + } + testModeMessage(true, 2, 29, ".X .XXX.. ...X XX.. ..X .XX. .XX.X") + testModeMessage(true, 4, 64, "XX XXXXXX .X.. ...X ..XX .X.. XX..") + testModeMessage(false, 21, 660, "X.X.. .X.X..X..XX .XXX ..X.. .XXX. .X... ..XXX") + testModeMessage(false, 32, 4096, "XXXXX XXXXXXXXXXX X.X. ..... XXX.X ..X.. X.XXX") +} diff --git a/aztec/errorcorrection.go b/aztec/errorcorrection.go new file mode 100644 index 0000000..6ee0be3 --- /dev/null +++ b/aztec/errorcorrection.go @@ -0,0 +1,61 @@ +package aztec + +import ( + "github.com/boombuler/barcode/utils" +) + +func bitsToWords(stuffedBits *utils.BitList, wordSize int, wordCount int) []int { + message := make([]int, wordCount) + + for i := 0; i < wordCount; i++ { + value := 0 + for j := 0; j < wordSize; j++ { + if stuffedBits.GetBit(i*wordSize + j) { + value |= (1 << uint(wordSize-j-1)) + } + } + message[i] = value + } + return message +} + +func generateCheckWords(bits *utils.BitList, totalBits, wordSize int) *utils.BitList { + rs := utils.NewReedSolomonEncoder(getGF(wordSize)) + + // bits is guaranteed to be a multiple of the wordSize, so no padding needed + messageWordCount := bits.Len() / wordSize + totalWordCount := totalBits / wordSize + eccWordCount := totalWordCount - messageWordCount + + messageWords := bitsToWords(bits, wordSize, messageWordCount) + eccWords := rs.Encode(messageWords, eccWordCount) + startPad := totalBits % wordSize + + messageBits := new(utils.BitList) + messageBits.AddBits(0, byte(startPad)) + + for _, messageWord := range messageWords { + messageBits.AddBits(messageWord, byte(wordSize)) + } + for _, eccWord := range eccWords { + messageBits.AddBits(eccWord, byte(wordSize)) + } + return messageBits +} + +func getGF(wordSize int) *utils.GaloisField { + switch wordSize { + case 4: + return utils.NewGaloisField(0x13, 16, 1) + case 6: + return utils.NewGaloisField(0x43, 64, 1) + case 8: + return utils.NewGaloisField(0x012D, 256, 1) + case 10: + return utils.NewGaloisField(0x409, 1024, 1) + case 12: + return utils.NewGaloisField(0x1069, 4096, 1) + default: + return nil + } +} diff --git a/datamatrix/errorcorrection.go b/datamatrix/errorcorrection.go index ef5876b..9617e32 100644 --- a/datamatrix/errorcorrection.go +++ b/datamatrix/errorcorrection.go @@ -5,58 +5,18 @@ import ( ) type errorCorrection struct { - fld *utils.GaloisField - polynomes map[int][]int + rs *utils.ReedSolomonEncoder } var ec *errorCorrection = newErrorCorrection() func newErrorCorrection() *errorCorrection { - result := new(errorCorrection) - result.fld = utils.NewGaloisField(301) - result.polynomes = make(map[int][]int) - return result -} + gf := utils.NewGaloisField(301, 256, 1) -func (ec *errorCorrection) getPolynomial(count int) []int { - poly, ok := ec.polynomes[count] - if !ok { - idx := 1 - poly = make([]int, count+1) - poly[0] = 1 - for i := 1; i <= count; i++ { - poly[i] = 1 - for j := i - 1; j > 0; j-- { - if poly[j] != 0 { - poly[j] = ec.fld.ALogTbl[(int(ec.fld.LogTbl[poly[j]])+idx)%255] - } - poly[j] = ec.fld.AddOrSub(poly[j], poly[j-1]) - } - poly[0] = ec.fld.ALogTbl[(int(ec.fld.LogTbl[poly[0]])+idx)%255] - idx++ - } - poly = poly[0:count] - ec.polynomes[count] = poly - } - return poly -} - -func (ec *errorCorrection) calcECCBlock(data []byte, poly []int) []byte { - ecc := make([]byte, len(poly)+1) - - for i := 0; i < len(data); i++ { - k := ec.fld.AddOrSub(int(ecc[0]), int(data[i])) - for j := 0; j < len(ecc)-1; j++ { - ecc[j] = byte(ec.fld.AddOrSub(int(ecc[j+1]), ec.fld.Multiply(k, poly[len(ecc)-j-2]))) - } - } - return ecc + return &errorCorrection{utils.NewReedSolomonEncoder(gf)} } func (ec *errorCorrection) calcECC(data []byte, size *dmCodeSize) []byte { - - poly := ec.getPolynomial(size.ErrorCorrectionCodewordsPerBlock()) - dataSize := len(data) // make some space for error correction codes data = append(data, make([]byte, size.ECCCount)...) @@ -64,19 +24,19 @@ func (ec *errorCorrection) calcECC(data []byte, size *dmCodeSize) []byte { for block := 0; block < size.BlockCount; block++ { dataCnt := size.DataCodewordsForBlock(block) - buff := make([]byte, dataCnt) + buff := make([]int, dataCnt) // copy the data for the current block to buff j := 0 for i := block; i < dataSize; i += size.BlockCount { - buff[j] = data[i] + buff[j] = int(data[i]) j++ } // calc the error correction codes - ecc := ec.calcECCBlock(buff, poly) + ecc := ec.rs.Encode(buff, size.ErrorCorrectionCodewordsPerBlock()) // and append them to the result j = 0 for i := block; i < size.ErrorCorrectionCodewordsPerBlock()*size.BlockCount; i += size.BlockCount { - data[dataSize+i] = ecc[j] + data[dataSize+i] = byte(ecc[j]) j++ } } diff --git a/datamatrix/errorcorrection_test.go b/datamatrix/errorcorrection_test.go index 78de8f7..baaaffe 100644 --- a/datamatrix/errorcorrection_test.go +++ b/datamatrix/errorcorrection_test.go @@ -5,39 +5,6 @@ import ( "testing" ) -func Test_GetPolynomial(t *testing.T) { - var gf_polys map[int][]int = map[int][]int{ - 5: []int{228, 48, 15, 111, 62}, - 7: []int{23, 68, 144, 134, 240, 92, 254}, - 10: []int{28, 24, 185, 166, 223, 248, 116, 255, 110, 61}, - 11: []int{175, 138, 205, 12, 194, 168, 39, 245, 60, 97, 120}, - 12: []int{41, 153, 158, 91, 61, 42, 142, 213, 97, 178, 100, 242}, - 14: []int{156, 97, 192, 252, 95, 9, 157, 119, 138, 45, 18, 186, 83, 185}, - 18: []int{83, 195, 100, 39, 188, 75, 66, 61, 241, 213, 109, 129, 94, 254, 225, 48, 90, 188}, - 20: []int{15, 195, 244, 9, 233, 71, 168, 2, 188, 160, 153, 145, 253, 79, 108, 82, 27, 174, 186, 172}, - 24: []int{52, 190, 88, 205, 109, 39, 176, 21, 155, 197, 251, 223, 155, 21, 5, 172, 254, 124, 12, 181, 184, 96, 50, 193}, - 28: []int{211, 231, 43, 97, 71, 96, 103, 174, 37, 151, 170, 53, 75, 34, 249, 121, 17, 138, 110, 213, 141, 136, 120, 151, 233, 168, 93, 255}, - 36: []int{245, 127, 242, 218, 130, 250, 162, 181, 102, 120, 84, 179, 220, 251, 80, 182, 229, 18, 2, 4, 68, 33, 101, 137, 95, 119, 115, 44, 175, 184, 59, 25, 225, 98, 81, 112}, - 42: []int{77, 193, 137, 31, 19, 38, 22, 153, 247, 105, 122, 2, 245, 133, 242, 8, 175, 95, 100, 9, 167, 105, 214, 111, 57, 121, 21, 1, 253, 57, 54, 101, 248, 202, 69, 50, 150, 177, 226, 5, 9, 5}, - 48: []int{245, 132, 172, 223, 96, 32, 117, 22, 238, 133, 238, 231, 205, 188, 237, 87, 191, 106, 16, 147, 118, 23, 37, 90, 170, 205, 131, 88, 120, 100, 66, 138, 186, 240, 82, 44, 176, 87, 187, 147, 160, 175, 69, 213, 92, 253, 225, 19}, - 56: []int{175, 9, 223, 238, 12, 17, 220, 208, 100, 29, 175, 170, 230, 192, 215, 235, 150, 159, 36, 223, 38, 200, 132, 54, 228, 146, 218, 234, 117, 203, 29, 232, 144, 238, 22, 150, 201, 117, 62, 207, 164, 13, 137, 245, 127, 67, 247, 28, 155, 43, 203, 107, 233, 53, 143, 46}, - 62: []int{242, 93, 169, 50, 144, 210, 39, 118, 202, 188, 201, 189, 143, 108, 196, 37, 185, 112, 134, 230, 245, 63, 197, 190, 250, 106, 185, 221, 175, 64, 114, 71, 161, 44, 147, 6, 27, 218, 51, 63, 87, 10, 40, 130, 188, 17, 163, 31, 176, 170, 4, 107, 232, 7, 94, 166, 224, 124, 86, 47, 11, 204}, - 68: []int{220, 228, 173, 89, 251, 149, 159, 56, 89, 33, 147, 244, 154, 36, 73, 127, 213, 136, 248, 180, 234, 197, 158, 177, 68, 122, 93, 213, 15, 160, 227, 236, 66, 139, 153, 185, 202, 167, 179, 25, 220, 232, 96, 210, 231, 136, 223, 239, 181, 241, 59, 52, 172, 25, 49, 232, 211, 189, 64, 54, 108, 153, 132, 63, 96, 103, 82, 186}, - } - - for i, tst := range gf_polys { - res := ec.getPolynomial(i) - if len(res) != len(tst) { - t.Fail() - } - for i := 0; i < len(res); i++ { - if res[i] != tst[i] { - t.Fail() - } - } - } -} - func Test_CalcECC(t *testing.T) { data := []byte{142, 164, 186} var size *dmCodeSize = nil diff --git a/qr/errorcorrection.go b/qr/errorcorrection.go index 950fa82..08ebf0c 100644 --- a/qr/errorcorrection.go +++ b/qr/errorcorrection.go @@ -2,53 +2,28 @@ package qr import ( "github.com/boombuler/barcode/utils" - "sync" ) type errorCorrection struct { - fld *utils.GaloisField - - m *sync.Mutex - polynomes []*utils.GFPoly + rs *utils.ReedSolomonEncoder } -var ec = newGF() +var ec = newErrorCorrection() -func newGF() *errorCorrection { - fld := utils.NewGaloisField(285) - - return &errorCorrection{fld, - new(sync.Mutex), - []*utils.GFPoly{ - utils.NewGFPoly(fld, []byte{1}), - }, - } -} - -func (ec *errorCorrection) getPolynomial(degree int) *utils.GFPoly { - ec.m.Lock() - defer ec.m.Unlock() - - if degree >= len(ec.polynomes) { - last := ec.polynomes[len(ec.polynomes)-1] - for d := len(ec.polynomes); d <= degree; d++ { - next := last.Multiply(utils.NewGFPoly(ec.fld, []byte{1, byte(ec.fld.ALogTbl[d-1])})) - ec.polynomes = append(ec.polynomes, next) - last = next - } - } - return ec.polynomes[degree] +func newErrorCorrection() *errorCorrection { + fld := utils.NewGaloisField(285, 256, 0) + return &errorCorrection{utils.NewReedSolomonEncoder(fld)} } func (ec *errorCorrection) calcECC(data []byte, eccCount byte) []byte { - generator := ec.getPolynomial(int(eccCount)) - info := utils.NewGFPoly(ec.fld, data) - info = info.MultByMonominal(int(eccCount), 1) - - _, remainder := info.Divide(generator) - - result := make([]byte, eccCount) - numZero := int(eccCount) - len(remainder.Coefficients) - copy(result[numZero:], remainder.Coefficients) + dataInts := make([]int, len(data)) + for i := 0; i < len(data); i++ { + dataInts[i] = int(data[i]) + } + res := ec.rs.Encode(dataInts, int(eccCount)) + result := make([]byte, len(res)) + for i := 0; i < len(res); i++ { + result[i] = byte(res[i]) + } return result } diff --git a/qr/errorcorrection_test.go b/qr/errorcorrection_test.go index 56ed3a1..e88d92c 100644 --- a/qr/errorcorrection_test.go +++ b/qr/errorcorrection_test.go @@ -5,19 +5,6 @@ import ( "testing" ) -func Test_LogTables(t *testing.T) { - for i := 1; i <= 255; i++ { - tmp := ec.fld.LogTbl[i] - if i != ec.fld.ALogTbl[tmp] { - t.Errorf("Invalid LogTables: %d", i) - } - } - - if ec.fld.ALogTbl[11] != 232 || ec.fld.ALogTbl[87] != 127 || ec.fld.ALogTbl[225] != 36 { - t.Fail() - } -} - func Test_ErrorCorrection(t *testing.T) { doTest := func(b []byte, ecc []byte) { cnt := byte(len(ecc)) diff --git a/utils/bitlist.go b/utils/bitlist.go index 5cdb581..bb05e53 100644 --- a/utils/bitlist.go +++ b/utils/bitlist.go @@ -76,7 +76,7 @@ func (bl *BitList) AddByte(b byte) { // AddBits appends the last (LSB) 'count' bits of 'b' the the end of the list func (bl *BitList) AddBits(b int, count byte) { - for i := int(count - 1); i >= 0; i-- { + for i := int(count) - 1; i >= 0; i-- { bl.AddBit(((b >> uint(i)) & 1) == 1) } } diff --git a/utils/galoisfield.go b/utils/galoisfield.go index 6f077d7..68726fb 100644 --- a/utils/galoisfield.go +++ b/utils/galoisfield.go @@ -2,28 +2,31 @@ package utils // GaloisField encapsulates galois field arithmetics type GaloisField struct { + Size int + Base int ALogTbl []int LogTbl []int } -// NewGaloisField creates a new falois field -func NewGaloisField(pp int) *GaloisField { +// NewGaloisField creates a new galois field +func NewGaloisField(pp, fieldSize, b int) *GaloisField { result := new(GaloisField) - fldSize := 256 - result.ALogTbl = make([]int, fldSize) - result.LogTbl = make([]int, fldSize) + result.Size = fieldSize + result.Base = b + result.ALogTbl = make([]int, fieldSize) + result.LogTbl = make([]int, fieldSize) x := 1 - for i := 0; i < fldSize; i++ { + for i := 0; i < fieldSize; i++ { result.ALogTbl[i] = x x = x * 2 - if x >= fldSize { - x = (x ^ pp) & (fldSize - 1) + if x >= fieldSize { + x = (x ^ pp) & (fieldSize - 1) } } - for i := 0; i < fldSize; i++ { + for i := 0; i < fieldSize; i++ { result.LogTbl[result.ALogTbl[i]] = int(i) } @@ -31,7 +34,7 @@ func NewGaloisField(pp int) *GaloisField { } func (gf *GaloisField) Zero() *GFPoly { - return NewGFPoly(gf, []byte{0}) + return NewGFPoly(gf, []int{0}) } // AddOrSub add or substract two numbers @@ -44,7 +47,7 @@ func (gf *GaloisField) Multiply(a, b int) int { if a == 0 || b == 0 { return 0 } - return gf.ALogTbl[(gf.LogTbl[a]+gf.LogTbl[b])%255] + return gf.ALogTbl[(gf.LogTbl[a]+gf.LogTbl[b])%(gf.Size-1)] } // Divide divides two numbers @@ -54,9 +57,9 @@ func (gf *GaloisField) Divide(a, b int) int { } else if a == 0 { return 0 } - return gf.ALogTbl[(gf.LogTbl[a]-gf.LogTbl[b])%255] + return gf.ALogTbl[(gf.LogTbl[a]-gf.LogTbl[b])%(gf.Size-1)] } func (gf *GaloisField) Invers(num int) int { - return gf.ALogTbl[255-gf.LogTbl[num]] + return gf.ALogTbl[(gf.Size-1)-gf.LogTbl[num]] } diff --git a/utils/galoisfield_test.go b/utils/galoisfield_test.go index fc8134f..4a32656 100644 --- a/utils/galoisfield_test.go +++ b/utils/galoisfield_test.go @@ -43,7 +43,7 @@ func Test_GF(t *testing.T) { 3, 6, 12, 24, 48, 96, 192, 173, 119, 238, 241, 207, 179, 75, 150, 1, } - gf := NewGaloisField(301) + gf := NewGaloisField(301, 256, 1) if len(gf.LogTbl) != len(gf.ALogTbl) || len(gf.LogTbl) != len(log) { t.Fail() } diff --git a/utils/gfpoly.go b/utils/gfpoly.go index 54686de..c56bb40 100644 --- a/utils/gfpoly.go +++ b/utils/gfpoly.go @@ -2,7 +2,7 @@ package utils type GFPoly struct { gf *GaloisField - Coefficients []byte + Coefficients []int } func (gp *GFPoly) Degree() int { @@ -14,7 +14,7 @@ func (gp *GFPoly) Zero() bool { } // GetCoefficient returns the coefficient of x ^ degree -func (gp *GFPoly) GetCoefficient(degree int) byte { +func (gp *GFPoly) GetCoefficient(degree int) int { return gp.Coefficients[gp.Degree()-degree] } @@ -29,23 +29,23 @@ func (gp *GFPoly) AddOrSubstract(other *GFPoly) *GFPoly { if len(smallCoeff) > len(largeCoeff) { largeCoeff, smallCoeff = smallCoeff, largeCoeff } - sumDiff := make([]byte, len(largeCoeff)) + sumDiff := make([]int, len(largeCoeff)) lenDiff := len(largeCoeff) - len(smallCoeff) copy(sumDiff, largeCoeff[:lenDiff]) for i := lenDiff; i < len(largeCoeff); i++ { - sumDiff[i] = byte(gp.gf.AddOrSub(int(smallCoeff[i-lenDiff]), int(largeCoeff[i]))) + sumDiff[i] = int(gp.gf.AddOrSub(int(smallCoeff[i-lenDiff]), int(largeCoeff[i]))) } return NewGFPoly(gp.gf, sumDiff) } -func (gp *GFPoly) MultByMonominal(degree int, coeff byte) *GFPoly { +func (gp *GFPoly) MultByMonominal(degree int, coeff int) *GFPoly { if coeff == 0 { return gp.gf.Zero() } size := len(gp.Coefficients) - result := make([]byte, size+degree) + result := make([]int, size+degree) for i := 0; i < size; i++ { - result[i] = byte(gp.gf.Multiply(int(gp.Coefficients[i]), int(coeff))) + result[i] = int(gp.gf.Multiply(int(gp.Coefficients[i]), int(coeff))) } return NewGFPoly(gp.gf, result) } @@ -58,12 +58,12 @@ func (gp *GFPoly) Multiply(other *GFPoly) *GFPoly { aLen := len(aCoeff) bCoeff := other.Coefficients bLen := len(bCoeff) - product := make([]byte, aLen+bLen-1) + product := make([]int, aLen+bLen-1) for i := 0; i < aLen; i++ { ac := int(aCoeff[i]) for j := 0; j < bLen; j++ { bc := int(bCoeff[j]) - product[i+j] = byte(gp.gf.AddOrSub(int(product[i+j]), gp.gf.Multiply(ac, bc))) + product[i+j] = int(gp.gf.AddOrSub(int(product[i+j]), gp.gf.Multiply(ac, bc))) } } return NewGFPoly(gp.gf, product) @@ -77,7 +77,7 @@ func (gp *GFPoly) Divide(other *GFPoly) (quotient *GFPoly, remainder *GFPoly) { inversDenomLeadTerm := fld.Invers(int(denomLeadTerm)) for remainder.Degree() >= other.Degree() && !remainder.Zero() { degreeDiff := remainder.Degree() - other.Degree() - scale := byte(fld.Multiply(int(remainder.GetCoefficient(remainder.Degree())), inversDenomLeadTerm)) + scale := int(fld.Multiply(int(remainder.GetCoefficient(remainder.Degree())), inversDenomLeadTerm)) term := other.MultByMonominal(degreeDiff, scale) itQuot := NewMonominalPoly(fld, degreeDiff, scale) quotient = quotient.AddOrSubstract(itQuot) @@ -86,16 +86,16 @@ func (gp *GFPoly) Divide(other *GFPoly) (quotient *GFPoly, remainder *GFPoly) { return } -func NewMonominalPoly(field *GaloisField, degree int, coeff byte) *GFPoly { +func NewMonominalPoly(field *GaloisField, degree int, coeff int) *GFPoly { if coeff == 0 { return field.Zero() } - result := make([]byte, degree+1) + result := make([]int, degree+1) result[0] = coeff return NewGFPoly(field, result) } -func NewGFPoly(field *GaloisField, coefficients []byte) *GFPoly { +func NewGFPoly(field *GaloisField, coefficients []int) *GFPoly { for len(coefficients) > 1 && coefficients[0] == 0 { coefficients = coefficients[1:] } diff --git a/utils/reedsolomon.go b/utils/reedsolomon.go new file mode 100644 index 0000000..53af91a --- /dev/null +++ b/utils/reedsolomon.go @@ -0,0 +1,44 @@ +package utils + +import ( + "sync" +) + +type ReedSolomonEncoder struct { + gf *GaloisField + polynomes []*GFPoly + m *sync.Mutex +} + +func NewReedSolomonEncoder(gf *GaloisField) *ReedSolomonEncoder { + return &ReedSolomonEncoder{ + gf, []*GFPoly{NewGFPoly(gf, []int{1})}, new(sync.Mutex), + } +} + +func (rs *ReedSolomonEncoder) getPolynomial(degree int) *GFPoly { + rs.m.Lock() + defer rs.m.Unlock() + + if degree >= len(rs.polynomes) { + last := rs.polynomes[len(rs.polynomes)-1] + for d := len(rs.polynomes); d <= degree; d++ { + next := last.Multiply(NewGFPoly(rs.gf, []int{1, rs.gf.ALogTbl[d-1+rs.gf.Base]})) + rs.polynomes = append(rs.polynomes, next) + last = next + } + } + return rs.polynomes[degree] +} + +func (rs *ReedSolomonEncoder) Encode(data []int, eccCount int) []int { + generator := rs.getPolynomial(eccCount) + info := NewGFPoly(rs.gf, data) + info = info.MultByMonominal(eccCount, 1) + _, remainder := info.Divide(generator) + + result := make([]int, eccCount) + numZero := int(eccCount) - len(remainder.Coefficients) + copy(result[numZero:], remainder.Coefficients) + return result +}