Spec & internals
Rendering a QR code from a matrix
Rendering starts from a boolean matrix, for each dark module, draw a square of moduleSize pixels at (column + quietZone, row + quietZone) × moduleSize. Add four modules of quiet zone on every side, multiply canvas dimensions by devicePixelRatio to stay sharp, and export PNG at final display size or larger.
The hand-off point
Everything covered in how QR codes are generated
(modes, Reed–Solomon, masking) ends at a data structure, not an image: a square boolean
matrix, true for dark, typically row-major. UseQR's core returns exactly this
(modules: boolean[][], with size counting modules per side, quiet zone not
included). Rendering is the separate, dumber, easier-to-get-wrong job of turning that
matrix into pixels or vectors.
The core loop
Three numbers govern the geometry:
- moduleSize: pixels per module (UseQR defaults to 8);
- quietZone: blank modules on every side (default 4, the spec minimum);
- total, the output edge:
(size + 2 × quietZone) × moduleSize.
The loop itself:
total = (size + 2*quietZone) * moduleSize
fill entire total × total area with background // opaque, not assumed
for row in 0..size-1:
for col in 0..size-1:
if matrix[row][col]:
x = (col + quietZone) * moduleSize
y = (row + quietZone) * moduleSize
fillRect(x, y, moduleSize, moduleSize) // foreground
Note col maps to x and row to y: transposing them mirrors the code, which
often still scans and therefore survives testing until it
ships. Keep moduleSize an integer: fractional module widths force modules onto
fractional pixel boundaries, and anti-aliasing turns crisp edges into grey gradients that
cost contrast exactly where the decoder samples.
Painting the background explicitly matters too. A transparent-background PNG dropped onto a dark page becomes a polarity accident; if you want transparency, treat it as a deliberate feature with known placement, per transparent background QR codes.
Canvas and the device pixel ratio
On the web, a canvas has two sizes: its CSS layout size and its pixel buffer. On a 2× or 3× display, drawing a 320 px code into a 320-buffer canvas displayed at 320 CSS pixels renders soft, the browser upscales your buffer. The standard correction:
dpr = window.devicePixelRatio || 1
canvas.style.width = cssSize + "px"
canvas.style.height = cssSize + "px"
canvas.width = cssSize * dpr
canvas.height = cssSize * dpr
ctx.scale(dpr, dpr)
// …then draw in CSS pixel coordinates as normal
A blurry on-screen QR code usually still scans (phone cameras are forgiving of soft edges at screen sizes), but it looks broken, and screenshots of it inherit the blur.
PNG or SVG
The loop above describes raster thinking, but the same traversal emits vectors: instead of
fillRect, append a rectangle sub-path per dark module and fill once, the approach
dissected in QR code SVG path structure.
The decision rule is destination, not preference. Fixed-size screen display: PNG (or canvas directly). Anything that will be scaled, printed or handed to a designer: SVG, because raster scaling is where codes go blurry or pixelated. If you must ship raster for print, render at the final physical size at 300 DPI or more: the arithmetic lives on vector vs raster.
However rendered, the output is a claim, not a fact, until a decoder reads it back: /validate does render-then-decode in one step.
FAQ
How do I draw a QR code from a matrix of booleans?
Iterate rows and columns; for each true cell, fill a square of moduleSize pixels at ((column + quietZone) × moduleSize, (row + quietZone) × moduleSize). Paint the background first and include four quiet-zone modules on every side.
Why is my canvas QR code blurry on Retina screens?
The canvas pixel buffer matches CSS size, so high-DPI displays upscale it. Multiply canvas.width and canvas.height by devicePixelRatio, keep the CSS size unchanged, and scale the drawing context by the same factor before drawing.
Do I add the quiet zone before or after rendering?
During. Offset every module by the quiet-zone width and size the output as (modules + 2 × quiet zone) × moduleSize, so the margin is baked into the file. Margins left to whoever places the image have a way of disappearing.
Should moduleSize be an integer?
Yes, for raster output. Fractional module sizes put module edges between pixels, and the resulting anti-aliasing greys out edges and shrinks effective contrast. Pick an integer pixel count per module and let overall image size follow from it.
Try it: free, no signup
Related
- QR code anatomy: every region of the symbol, A labelled tour of a QR code: finder patterns, separators, timing, alignment, format and version information, the data region and the quiet zone.
- QR code SVG path structure, How a module matrix becomes SVG: one merged path versus thousands of rects, crisp-edges rendering, integer coordinates, and why seams appear.
- How a QR code is generated, step by step, Encoding runs in eight steps: choose the mode, choose the version, build the bit stream, split into codewords, compute Reed–Solomon error correction,…
- Generate a QR code in JavaScript and React, For a QR code in a browser or React app, either point an <img> at the keyless API (one line, no dependency), or use a client-side library such as qrcode…