Quick Type-sketching Quick-Response Codes

Table of Contents

Quick-Response Codes are Slow

Every year in the fall, the quad is filled with thousands of students from hundreds of clubs in the school. Technology has enabled club sign-up to be done with a quick scan of QR codes, but the delivery of such codes still relies on traditional medium -- paper.

Betraying its name, Quick Response codes often don't work quickly. Moreover, QR codes on curved surfaces are never read. Spending the day full of QR scans, you may find your phone album loaded with illegible curved QR codes. And to worsen the problem, current popular technology has no means to flatten the curved or read the curved QR code -- neither the iPhone camera nor any online QR code scanners can do it. The last resort comes as this: draw the QR code.

How to draw QR codes

Indeed, QR works in any media as long as its quadrilingual shape is maintained. Thus, it only takes imagination to choose a working method for a QR replica or correction. One method may be tying ones and zeros. For instance, type .(periods) and _(underscores) consecutively and substitute each into black and white <td>'s via regex substitutions.

An example of such might look like this:

...
_._
_..

Which is, of course, painstakingly slow. One thing to note is that there are often repeated patterns in the code. Such as the nested rectangles at the corners, which are, in most cases, 7x7.
Then the next thought was if there was a way to quickly draw the typed codes into actual QR. Javascript, in this case, seemed most appropriate, not only because of its supportability but also its handy graphics library.

Javascripting the QR

The idea, in one equation, was essentially (\.)(\d*)$1$$\ast$$max($2,1). Multiple blanks or dots shall be compactly written as letter times the multiplier, repeating the very letter for the given quantity.

parseLine(line)

Input Output
".3" "..."
"3" ""
"w3_2 ." "www___."

The initial approach was simple: double for loops. However, step zero was to devise functions for printing the substituted outputs.

Step 0: printQR()

For extreme convenience, we were not going to require a fixed 2d array. Instead, as the Javascript language itself allowed, size-flexible 2d arrays were the arguments for this function.

function drawQR(src) {
    var lines = parseQR(src);
    drawParsedQR(lines);
}

function drawParsedQR(lines) {
    var myCanvas = document.getElementById("myCanvas");
    var canvasLength = getMaxSquareLength();
    myCanvas.width = canvasLength;
    myCanvas.height = canvasLength;

    var ctx = myCanvas.getContext("2d");
    ctx.fillStyle = '#f1f1f1';
    ctx.fillRect(0, 0, canvasLength, canvasLength);

    var maxLength = maxArrayLength(lines);
    var cellWidth = canvasLength / maxLength;
    var fillColor = "";

    for (var y = 0; y < lines.length; y++) {
        for (var x = 0; x < lines[y].length; x++) {

            if (lines[y][x] == ' ') {
                fillColor = "white";
            } else {
                fillColor = "black";
            }

            ctx.fillStyle = fillColor;
            ctx.fillRect(x * cellWidth, y * cellWidth, cellWidth + 1, cellWidth + 1);
        }
    }
}

For this function, the input was at best standardized. This led to creating the below:

Step 0.1: Binarize

Any whitespace character is replaced to a blank, and any solid, non-digit character to period. This was done by a regex search.


function binarize(ch) {
    if (' \t\n\r\v_'.indexOf(ch) > -1) {
        return ' ';
    }
    return '.';
}

Step 1: parseLine()

When given a source input string like below,

...3
 .
.2

Each line must be split, and each line must be parsed using parseLine(line).

Basically, there were two schools of thought: double-loop, or stacking.

Double-loop method

The only time a character has to be repeated multiple times is when a number -- or a multiplier -- appears during a line. Thus, until when the first number appears, the program has to remember the latest non-number, or non-digit char it has uttered, hence var latest_nd.

The second and last thing was that a multiplier should be recorded. When a two-or-more-digit number appears during the line, each digit should be multiplied by ten and welcome the new digit -- in a sense, shifting one left every time.

The first establishment is non-digit detection, which again utilized a regex test.

function isDigit(ch) {
    return ch != null && ' \t\n\r\v_'.indexOf(ch) == -1 && +ch == ch;
}
function parseLine(line) {
    var i = 0;
    var latest_nd = '';
    var sarray = "";

    while (i < line.length) {
        var cur = line.charAt(i);
        var multiplier = 0;

        while (isDigit(line.charAt(i)) && i < line.length) {
            multiplier = multiplier * 10 + +line.charAt(i);
            i++;
        }

        if (multiplier == 0) {
            latest_nd = binarize(cur);
            multiplier = 1;
            i++;
        } else {
            multiplier--;
        }

        for (var j = 0; j < multiplier; j++) {
            sarray += latest_nd;
        }
    }
    return Array.from(sarray);
}

The multiplier variable also serves as a flag for non-repetition. If it remains 0, there was no multiplier, and thus the current char only has to appear once.

Whether repeated or not, the for loop repeats the character once or more and appends to the string array. Finally, the sarray is converted into a char[] and returned.

Stacking Method

Another method might be back-tracking or tracking backward in a stack. The Array in Javascript also serves as a Stack, having pop() and push() methods.

First, let it include all the chars from the source string by Array.from(line). Then pop one by one, seeking for number-multipliers.

Number tracking starts whenever the first number digit is found, adding current digit * powers of 10 like the former method, but only backward.

The digit tracking stops when the first non-digit char is met, and the code proceeds to repeat that first nd multiplier times. So for a non-digit character encounter with null multiplier, it will assume a one-digit character without multiplier and only print it once to move on.

function parseStack(line) {
    var parsed = "";
    var stack = Arrays.from(line);

    var multiplier = 0;
    var digitplacer = 0;

    for(var i=0; i<line.length; i++){
        var last = stack.pop();

        if(isDigit(last)){
            multiplier += +last * Math.pow(10, digitplacer);
            digitplacer ++;
        } else {
            if(multiplier == 0){
                multiplier = 1;
            }
            last = binarize(last);
            for(var j = 0; j < multiplier; j++){
                parsed = last + parsed;
            }
            multiplier = 0;
            digitplacer = 0;
        }
    }
    return parsed;
}

Conclusion

While anyone will wonder about the practicality of this program, the very act of practicing codes was the critical part. It is often not in practicality but in the aesthetics where my code lies.