Search code examples
javascriptmatrixencodingqr-code

Why are undefined modules left during my QR code generation?


I try to generate QR codes with JavaScript. According to some tutorials, the generation of the bit string that has to be placed inside the QR matrix works, my bit strings (consisting of the interleaved data codewords, the interleaved error correction codewords and the remainder bits) euqal the examples of the tutorials. Therefore I believe I made a mistake when placing the modules of the QR code, done by the function addDataToMatrix.

For QR codes from version one to five everything seems to work. But for QR code versions higher than six, not all modules of the two-dimensional QR matrix will be filled, some of them will be left "undefined".

Here is an example:

// input string: Why are these damned undefined modules left after the placement?
// alternative string: Hello, world!

// error correction level: H
let version=7;
// version for alternative input / bit string: 2

// generated bit string (interleaved data codewords, interleaved error correction codewords, remainder bits)
let bitString="01000100001101100101011000110010010001100000010101010010011001100000011010000110011101100000011010010110110001100101001010000111010001101110011001010110000001111001001000010110010101100110011100000110000001101101011001000010010000101100011000010111111001100000011000000110000101100010011001010110110101100001011000110110010100100100001011110110011001110101011000000111000001110100011101000110110101100100011001010110010101100101011101010110100001101110011011000110001000101110011101010111010001100101011100000111010000111111000011011100001100010001110000110010011010101110100101101100011000100101101001001010010100000000010100001000011110101000100110011100111000101000011001011010110100110001100101011101110111110011111000100010011100110110011010001110010100111001111110011010011011010010110100010011110011101100001010000101110111110100101111000011010101011111001001000011010011111101010110101111000001010110100111010011011001101111001111111011010101101111010011010101001100010010000101100101101110011101100111001100010010100101010101100011010011100110000100111000000101000000010101101010111010011100111010100100101010111110100001111101101100110001100101010001101001010011101110000001101000010000100010010110111000111000000010110100000111110110111010100111101010001001100001001010010100100110010100110010011011100011100011001100100100000000100111010110111000101100010011110000011110011111011001011011110101101000000000010101000110000011000011010111101111010110010101011110011000100010111111111000101000000001000011001011110101001010001010010111110100011011011110110011";
// alternative bit string: 01000000110101001000011001010110110001101100011011110010110000100000011101110110111101110010011011000110010000100001000011101100011011110111111010111111101011111101001111001100010110100111111000100110000110001111111111101100011000000001011001011100100010011010110110100000010011011111000111000000111000001000111111000011000000110011100111110010000011010000000

const ALIGNMENT_PATTERN_COORDINATES={
    coordinates1: [18, 22, 26, 30, 34, 22, 24, 26, 28, 30, 32, 34, 26, 26, 26, 30, 30, 30, 34, 28, 26, 30, 28, 32, 30, 34, 26, 30, 26, 30, 34, 30, 34, 30, 24, 28, 32, 26, 30],
    coordinates2: [undefined, undefined, undefined, undefined, undefined, 38, 42, 46, 50, 54, 58, 62, 46, 48, 50, 54, 56, 58, 62, 50, 50, 54, 54, 58, 58, 62, 50, 54, 52, 56, 60, 58, 62, 54, 50, 54, 58, 54, 58],
    coordinates3: [undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, 66, 70, 74, 78, 82, 86, 90, 72, 74, 78, 80, 84, 86, 90, 74, 78, 78, 82, 86, 86, 90, 78, 76, 80, 84, 82, 86],
    coordinates4: [undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, 94, 98, 102, 106, 110, 114, 118, 98, 102, 104, 108, 112, 114, 118, 102, 102, 106, 110, 110, 114],
    coordinates5: [undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, 122, 126, 130, 134, 138, 142, 146, 126, 128, 132, 136, 138, 142],
    coordinates6: [undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, 150, 154, 158, 162, 166, 170]
};

let matrix=generateMatrix(version);
generateSeparators(matrix);
generateFinderPatterns(matrix);
generateAlignmentPatterns(version, matrix);
generateTimingPatterns(matrix);
reserveAreas(version, matrix);
addDataToMatrix(matrix, bitString);
drawMatrix(matrix);

function generateMatrix(version){
    let size=21+(version-1)*4;
  let matrix=[];
    for(let i=0; i<size; i++){
        matrix[i]=[];
        for(let j=0; j<size; j++) matrix[i][j]=undefined;
    }
  matrix[version*4+9][8]=1;
  return matrix;
}

function generateSeparators(matrix){
  for(let i=0; i<8; i++){
        for(let j=0; j<8; j++){
            matrix[i][j]=0;
            matrix[i][matrix[i].length-1-j]=0;
            matrix[matrix[i].length-1-i][j]=0;
        }
    }
}

function generateFinderPatterns(matrix){
    for(let i=0; i<7; i++){
        for(let j=0; j<7; j++){
            matrix[i][j]=1;
            matrix[i][matrix[i].length-1-j]=1;
            matrix[matrix[i].length-1-i][j]=1;
        }
    }
    for(let i=1; i<6; i++){
        for(let j=1; j<6; j++){
            matrix[i][j]=0;
            matrix[i][matrix[i].length-1-j]=0;
            matrix[matrix[i].length-1-i][j]=0;
        }
    }
    for(let i=2; i<5; i++){
        for(let j=2; j<5; j++){
            matrix[i][j]=1;
            matrix[i][matrix[i].length-1-j]=1;
            matrix[matrix[i].length-1-i][j]=1;
        }
    }
}

function generateAlignmentPatterns(version, matrix){
    if(version>1){
        let coordinates=[6, ALIGNMENT_PATTERN_COORDINATES.coordinates1[version-2], ALIGNMENT_PATTERN_COORDINATES.coordinates2[version-2], ALIGNMENT_PATTERN_COORDINATES.coordinates3[version-2], ALIGNMENT_PATTERN_COORDINATES.coordinates4[version-2], ALIGNMENT_PATTERN_COORDINATES.coordinates5[version-2], ALIGNMENT_PATTERN_COORDINATES.coordinates6[version-2]].filter(Number);
        let moduleCenters=[];
        for(let i=0; i<coordinates.length; i++) moduleCenters.push([coordinates[i], coordinates[i]]);
        for(let i=0; i<coordinates.length-1; i++){
            for(let j=i+1; j<coordinates.length; j++){
                moduleCenters.push([coordinates[i], coordinates[j]]);
                moduleCenters.push([coordinates[j], coordinates[i]]);
            }
        }
    for(let i=0; i<moduleCenters.length; i++){
            let isEmpty=true;
            for(let j=-2; j<3; j++){
                for(let k=-2; k<3; k++){
                    if(matrix[moduleCenters[i][0]+j][moduleCenters[i][1]+k]!==undefined) isEmpty=false;
                }
            }
            if(isEmpty){
                for(let j=-2; j<3; j++){
                    for(let k=-2; k<3; k++) matrix[moduleCenters[i][0]+j][moduleCenters[i][1]+k]=1;
                }
                for(let j=-1; j<2; j++){
                    for(let k=-1; k<2; k++) matrix[moduleCenters[i][0]+j][moduleCenters[i][1]+k]=0;
                }
                matrix[moduleCenters[i][0]][moduleCenters[i][1]]=1;
            }
        }
    }
}

function generateTimingPatterns(matrix){
    for(let i=8; i<matrix.length-8; i++){
        matrix[6][i]=(i%2==0)?1:0;
        matrix[i][6]=(i%2==0)?1:0;
    }
}

function reserveAreas(version, matrix){
    if(version<7){
        for(let i=0; i<9; i++){
            if(matrix[i][8]===undefined) matrix[i][8]="reserved";
            if(matrix[8][i]===undefined) matrix[8][i]="reserved";
        }
        for(let i=0; i<8; i++) matrix[8][matrix[8].length-1-i]="reserved";
        for(let i=0; i<7; i++) matrix[matrix[8].length-1-i][8]="reserved";
    }
    else{
        for(let i=0; i<6; i++){
            for(let j=0; j<3; j++){
                matrix[i][matrix[i].length-9-j]="reserved";
                matrix[matrix[i].length-9-j][i]="reserved";
            }
        }
    }
}

function addDataToMatrix(matrix, bitString){
    let j=matrix[0].length-1;
    while(bitString.length>0){
        for(let i=0; i<matrix.length; i++){
            if(bitString.length>0&&matrix[matrix.length-1-i][j]===undefined){
                matrix[matrix.length-1-i][j]=bitString.charAt(bitString.length-1);
                bitString=bitString.slice(0, -1);
            }
            if(bitString.length>0&&matrix[matrix.length-1-i][j-1]===undefined){
                matrix[matrix.length-1-i][j-1]=bitString.charAt(bitString.length-1);
                bitString=bitString.slice(0, -1);
            }
        }
        j-=2;
        if(j==6) j--;
        for(let i=0; i<matrix.length; i++){
            if(bitString.length>0&&matrix[i][j]===undefined){
                matrix[i][j]=bitString.charAt(bitString.length-1);
                bitString=bitString.slice(0, -1);
            }
            if(bitString.length>0&&matrix[i][j-1]===undefined){
                matrix[i][j-1]=bitString.charAt(bitString.length-1);
                bitString=bitString.slice(0, -1);
            }
        }
        j-=2;
    }
}

function drawMatrix(matrix){
    let cssClass;
    let code="";
    for(let i=0; i<matrix.length; i++){
        for(let j=0; j<matrix[i].length; j++){
            if(matrix[i][j]===undefined) cssClass="undefined";
            if(matrix[i][j]=="reserved") cssClass="reserved";
            if(matrix[i][j]==0) cssClass="white";
            if(matrix[i][j]==1) cssClass="black";
            code+='<div class="module '+cssClass+'"></div>';
            if(j==matrix[i].length-1) code+='<div class="break"></div>';
        }
    }
    document.getElementById("result").innerHTML=code;
}
*{
    box-sizing: border-box;
}

#result{
    border-width: 1px;
    border-style: solid;
    border-color: lightgray;
    overflow: auto;
    padding: 28px;
    width: -webkit-max-content;
    width: -moz-max-content;
    width: max-content;
}

.module{
    border-width: 1px;
    border-style: solid;
    border-color: lightgray;
    float: left;
    height: 7px;
    width: 7px;
}
.module.undefined{
    background-color: lightslategray;
}
.module.reserved{
    background-color: lightskyblue;
}
.module.white{
    background-color: white;
}
.module.black{
    background-color: black;
}

.break{
    display: block;
}
<!DOCTYPE html>

<html>

<head>
    <title>QR Generator</title>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
</head>

<body>
  <div id="result"></div>
</body>

</html>

As you can see, the input "Why are these damned undefined modules left after the placement?" resulting in the bit string results in some undefined grey modules. Trying another input, for example "Hello, world!" there are no undefined modules left.

Can someone explain where I made a mistake? I hope my example is good enough.

EDIT:

I figured out that the length of the bit string given to the function addDataToMatrix is exactly 30 characters shorter than the total count of undefined modules inside the matrix from version 7 to version 40.


Solution

  • I found the error. I misunderstood the format and version bits placement rule. For versions one to six the format information is needed. For versions seven to fourty, version information AND format information is needed. I placed only the version information for QR codes with versions seven to fourty.