diff --git a/coding/qr.go b/coding/qr.go index bfc3ea4..1f56d42 100644 --- a/coding/qr.go +++ b/coding/qr.go @@ -318,6 +318,121 @@ func (c *Code) Black(x, y int) bool { c.Bitmap[y*c.Stride+x/8]&(1<= 5 { + p += n1 + (n - 5) + } + bs = x + } + last = b + } + if n := c.Size - bs; n >= 5 { + p += n1 + (n - 5) + } + } + for x := 0; x < c.Size; x++ { + last := c.Black(x, 0) + bs := 0 + for y := 1; y < c.Size; y++ { + b := c.Black(x, y) + if b != last { + if n := y - bs; n >= 5 { + p += n1 + (n - 5) + } + bs = y + } + last = b + } + if n := c.Size - bs; n >= 5 { + p += n1 + (n - 5) + } + } + return p +} + +// blockPenalty penalizes blocks of modules in the same color. +func (c *Code) blockPenalty() int { + const n2 = 3 + var n int + for x := 0; x < c.Size-1; x++ { + for y := 0; y < c.Size-1; y++ { + // The spec does not specify how to + // count overlapping blocks, so just + // count all 2x2 blocks. + tl := c.Black(x, y) + if tl == c.Black(x+1, y) && tl == c.Black(x, y+1) && tl == c.Black(x+1, y+1) { + n++ + } + } + } + return n * n2 +} + +// patternPenalty penalizes the 1:1:3:1:1 ratio +// (dark:light:dark:light:dark) pattern in row/column. +func (c *Code) patternPenalty() int { + const n3 = 40 + pattern := []bool{true, false, true, true, true, false, true} + var n int + for y := 0; y < c.Size; y++ { + row: + for x := 0; x <= c.Size-7; x++ { + for i, d := range pattern { + if c.Black(x+i, y) != d { + continue row + } + } + n++ + } + } + for x := 0; x < c.Size; x++ { + col: + for y := 0; y <= c.Size-7; y++ { + for i, d := range pattern { + if c.Black(x, y+i) != d { + continue col + } + } + n++ + } + } + return n * n3 +} + +// proportionPenalty penalizes non-equal proportion +// of dark modules in the whole symbol. +func (c *Code) proportionPenalty() int { + const n4 = 10 + var black int + for x := 0; x < c.Size; x++ { + for y := 0; y < c.Size; y++ { + if c.Black(x, y) { + black++ + } + } + } + d := 10 - (20*black)/(c.Size*c.Size) + if d < 0 { + d = -d + } + return n4 * d +} + // A Mask describes a mask that is applied to the QR // code to avoid QR artifacts being interpreted as // alignment and timing patterns (such as the squares diff --git a/coding/qr_test.go b/coding/qr_test.go index c933bb8..7ca8b81 100644 --- a/coding/qr_test.go +++ b/coding/qr_test.go @@ -131,3 +131,43 @@ func TestEncode(t *testing.T) { t.Errorf("have %x want %x", out, check) } } + +func TestPenalty(t *testing.T) { + grid := &Code{ + // runs of 5, 6, 8, 6 -> 17 + // 11 2x2 blocks -> 33 + // 2 penalty patterns -> 80 + // 61% black -> 20 + Bitmap: []byte{ + 0xe0, // 11100000 + 0xe7, // 11100111 + 0x5c, // 01011100 + 0x79, // 01111001 + 0x5d, // 01011101 + 0xfd, // 11111101 + 0xe4, // 11100100 + 0xe9, // 11101001 + }, + Size: 8, + Stride: 1, + } + const ( + adjacentPenalty = 17 + blockPenalty = 33 + patternPenalty = 80 + proportionPenalty = 20 + ) + + if got := grid.adjacentPenalty(); got != adjacentPenalty { + t.Errorf("have adjacent penalty %d want %d", got, adjacentPenalty) + } + if got := grid.blockPenalty(); got != blockPenalty { + t.Errorf("have block penalty %d want %d", got, blockPenalty) + } + if got := grid.patternPenalty(); got != patternPenalty { + t.Errorf("have pattern penalty %d want %d", got, patternPenalty) + } + if got := grid.proportionPenalty(); got != proportionPenalty { + t.Errorf("have proportion penalty %d want %d", got, proportionPenalty) + } +} diff --git a/qr.go b/qr.go index ace7e6f..9837e58 100644 --- a/qr.go +++ b/qr.go @@ -53,19 +53,26 @@ func Encode(text string, level Level) (*Code, error) { } } - // Build and execute plan. - p, err := coding.NewPlan(v, l, 0) - if err != nil { - return nil, err - } - cc, err := p.Encode(enc) - if err != nil { - return nil, err + // Pick appropriate mask. + var best *coding.Code + lowPenalty := int(^uint(0) >> 1) + for m := coding.Mask(0); m < 7; m++ { + // Build and execute plan. + p, err := coding.NewPlan(v, l, m) + if err != nil { + return nil, err + } + cc, err := p.Encode(enc) + if err != nil { + return nil, err + } + if pen := cc.Penalty(); pen < lowPenalty { + best = cc + lowPenalty = pen + } } - // TODO: Pick appropriate mask. - - return &Code{cc.Bitmap, cc.Size, cc.Stride, 8}, nil + return &Code{best.Bitmap, best.Size, best.Stride, 8}, nil } // A Code is a square pixel grid.