python-qrcodeのマスク処理結果の評価方法が若干間違っている

PythonQRコードを生成するのに便利なライブラリ

lincolnloop/python-qrcode: Python QR Code image generator

このライブラリはマスク処理結果の評価方法が若干仕様と間違っているため、他のライブラリとはできあがったQRコードが違う結果になることがある。 間違っているのはQRコードの仕様書の次の部分。

1:1:3:1:1 比率パターンの前又は後ろに比率4の幅以上の明パターンが存在する。 qrcode_specification_ja

python-qrcodeの該当部分は下記になるが、1:1:3:1:1 比率パターンの出現しか評価していない。

def _lost_point_level3(modules, modules_count):
    modules_range_short = xrange(modules_count-6)

    lost_point = 0
    for row in xrange(modules_count):
        this_row = modules[row]
        for col in modules_range_short:
            if (this_row[col]
                    and not this_row[col + 1]
                    and this_row[col + 2]
                    and this_row[col + 3]
                    and this_row[col + 4]
                    and not this_row[col + 5]
                    and this_row[col + 6]):
                lost_point += 40

    for col in xrange(modules_count):
        for row in modules_range_short:
            if (modules[row][col]
                    and not modules[row + 1][col]
                    and modules[row + 2][col]
                    and modules[row + 3][col]
                    and modules[row + 4][col]
                    and not modules[row + 5][col]
                    and modules[row + 6][col]):
                lost_point += 40

    return lost_point

一方、golangQRコードのライブラリ

skip2/go-qrcode: QR Code encoder (Go)

では、下記のようになっており、1:1:3:1:1 比率パターンの出現だけではなく、その前後に比率4の幅以上の明パターンが存在するかどうかまできちんと評価している。

func (m *symbol) penalty3() int {
    penalty := 0

    for y := 0; y < m.symbolSize; y++ {
        var bitBuffer int16 = 0x00

        for x := 0; x < m.symbolSize; x++ {
            bitBuffer <<= 1
            if v := m.get(x, y); v {
                bitBuffer |= 1
            }

            switch bitBuffer & 0x7ff {
            // 0b000 0101 1101 or 0b10111010000
            // 0x05d           or 0x5d0
            case 0x05d, 0x5d0:
                penalty += penaltyWeight3
                bitBuffer = 0xFF
            default:
                if x == m.symbolSize-1 && (bitBuffer&0x7f) == 0x5d {
                    penalty += penaltyWeight3
                    bitBuffer = 0xFF
                }
            }
        }
    }

    for x := 0; x < m.symbolSize; x++ {
        var bitBuffer int16 = 0x00

        for y := 0; y < m.symbolSize; y++ {
            bitBuffer <<= 1
            if v := m.get(x, y); v {
                bitBuffer |= 1
            }

            switch bitBuffer & 0x7ff {
            // 0b000 0101 1101 or 0b10111010000
            // 0x05d           or 0x5d0
            case 0x05d, 0x5d0:
                penalty += penaltyWeight3
                bitBuffer = 0xFF
            default:
                if y == m.symbolSize-1 && (bitBuffer&0x7f) == 0x5d {
                    penalty += penaltyWeight3
                    bitBuffer = 0xFF
                }
            }
        }
    }

    return penalty
}

しかし、適用されるマスクパターンが間違っているぐらいではQRコードが読めなかったりはしないので実用上は問題なかったりする。

追記 2017/03/31

この件を修正するPull Requestが送られているようだ。

Change penalty rules. Small optimization. Add optional argument mask_pattern. by cryptogun · Pull Request #127 · lincolnloop/python-qrcode