Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
115 changes: 115 additions & 0 deletions coding/qr.go
Original file line number Diff line number Diff line change
Expand Up @@ -318,6 +318,121 @@ func (c *Code) Black(x, y int) bool {
c.Bitmap[y*c.Stride+x/8]&(1<<uint(7-x&7)) != 0
}

// Penalty returns the scoring penalty of a pixel grid.
func (c *Code) Penalty() int {
return c.adjacentPenalty() + c.blockPenalty() + c.patternPenalty() + c.proportionPenalty()
}

// adjacentPenalty penalizes adjacent modules of the
// same color in a row/column.
func (c *Code) adjacentPenalty() int {
const n1 = 3
var p int
for y := 0; y < c.Size; y++ {
last := c.Black(0, y)
bs := 0
for x := 1; x < c.Size; x++ {
b := c.Black(x, y)
if b != last {
if n := x - bs; n >= 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
Expand Down
40 changes: 40 additions & 0 deletions coding/qr_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}
29 changes: 18 additions & 11 deletions qr.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down