parent
3dbe04fe04
commit
d039b6bd02
|
@ -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")
|
||||
}
|
|
@ -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()
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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")
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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++
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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]]
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
|
|
@ -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:]
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
Loading…
Reference in New Issue