DIAMOND: A QR Code Story That Started With Rounded Corners
6 min read
It started in the most boring way possible.
I wanted a QR Code as SVG.
And I wanted the corners rounded.
That’s it.
No art. No manifesto. No “reinventing standards”. Just a practical need: a clean vector QR that behaves well in print, scales infinitely, and doesn’t look like a harsh black-and-white grid.
But QR Codes have a funny habit: the moment you touch rendering, you end up thinking about structure.
So I split the problem in my head:
- One layer generates the QR matrix (the truth).
- Another layer renders that matrix as visual language (the interpretation).
That separation turned out to be the whole game.
The “core vs render” decision that unlocked everything
If you treat QR generation and QR rendering as one blob, you get one output style and call it a day.
If you treat them as separate layers, you suddenly get freedom:
- Generate once, render many times.
- Keep correctness and determinism in core.
- Experiment aggressively in render without risking the math.
That’s where qr-core came from: a deterministic encoder that outputs a binary module matrix and metadata, nothing else.
https://jsr.io/@goker/qr-core
And that’s where @goker/qr-code came from: a renderer that takes the matrix and produces SVG—optionally merging modules, rounding corners, rotating the whole symbol, or rotating modules individually.
https://jsr.io/@goker/qr-code
Friday night: meeting Nayuki, getting tempted
At some point on Friday I bumped into Nayuki’s QR generator repo.
It’s one of those projects where you read the README and you can feel the intent: absolute correctness first. Multiple languages, consistent behavior, clean spec alignment.
https://github.com/nayuki/QR-Code-generator
My reaction was basically:
“This is excellent.
…But I want a TypeScript-first core that I can extend, benchmark, and evolve in my own direction.”
So Saturday morning, I started laying the foundations of qr-core.
https://github.com/gokerDEV/qr-core
Saturday morning: qr-core becomes real
I needed a core that behaves like infrastructure:
- Deterministic output (same input + options ⇒ same matrix, same mask, same version)
- Strict TypeScript, predictable API surface
- Read-only matrix (don’t let consumers accidentally mutate the QR truth)
- Zero dependencies, runtime-agnostic
Publishing it to both npm and JSR made it immediately portable across Node/Deno/Bun/browsers.
And yes—benchmarking happened early, because “fast enough” is not a feeling.
The repo layout explicitly includes a bench/ directory and related configs.
Saturday afternoon: rendering begins, and the QR starts whispering
Once the core matrix existed, I moved to the renderer.
The renderer’s initial goal was still humble:
toSvgString()- module grouping (row/col/blob)
- corner rounding
All things you’d expect from an SVG QR library.
Then I built a demo page to tweak params quickly.
The repo literally has a demo/ folder for it.
https://github.com/gokerDEV/qr-code
And while playing with those knobs, something clicked:
QR Codes are designed to be detected under rotation and distortion.
Finder patterns exist for position, size, inclination.
So why are we still presenting them like a rigid square grid?
The DIAMOND moment: “What if the square stays… but the vibe changes?”
Rotating the entire QR by 45 degrees is one thing.
But DIAMOND wasn’t just about rotating the whole symbol.
The deeper idea came as a sequence of uncomfortable questions:
Why is grouping always row/col/blob?
Why not diagonal?
Why do modules have to feel like static squares?
Why not rotate the module primitive itself?
That’s the part where DIAMOND became not a theme, but a rendering strategy.
And it’s now explicitly part of @goker/qr-code’s feature set.
https://jsr.io/@goker/qr-code
The real technical novelty: diagonal grouping and “ungrouped rotation”
A lot of QR styling libraries can do rounding, circles, blobs. That’s familiar territory.
DIAMOND’s unusual bits are these:
Diagonal grouping ("45" and "-45")
Grouping strategies include "45" and "-45" directly in the API.
This is a different axis of organization: instead of compressing the grid along X/Y, you slice it along diagonals.
It changes the texture.
The QR stops looking like a matrix and starts looking like woven pattern logic.
Module rotation (moduleRotationDeg)
There’s also moduleRotationDeg: rotating individual square modules, even when you’re not merging them into blobs.
This is the “ungrouped square modules rotated” idea—and yes, this is not the usual QR styling playbook.
Combine diagonal grouping + module rotation + global rotation, and you get a visual language that feels closer to ornament than utility—without touching the core matrix.
The “kilim” surprise, and the scan reality check
At some point the output started resembling Turkish kilim patterns—unexpectedly and consistently.
The proper way to validate this is an actual scan-robustness study (different devices, lighting, ECC levels, print sizes).
I didn’t go that deep this weekend.
But I did the most honest quick test: pulled out an iPhone and scanned.
It worked without drama.
That was enough to confirm: the idea is not only pretty; it’s also viable as a direction.
The artistic exploration and visual outcomes live separately in the goker.art post (“Taking QR Codes from Square to Diamond”).
https://goker.art/qrcode
This post stays on the engineering side.
The libraries: what exists today
qr-core is the encoder.
It outputs the matrix + metadata. It doesn’t care how you render.
https://github.com/gokerDEV/qr-core
@goker/qr-code is the renderer.
It can encode+render in one call (toSvgString) or render a pre-encoded matrix (renderSvg).
It supports grouping strategies, rounding, and DIAMOND rendering.
https://github.com/gokerDEV/qr-code
Minimal example:
import { toSvgString } from "@goker/qr-code";const svg = toSvgString("https://example.com", {ecc: "M",version: "auto",mask: "auto",render: {moduleSize: 10,margin: 4,viewBox: true,cornerRadius: 2}});console.log(svg);
And the “DIAMOND knobs” (diagonal grouping + module rotation) are first-class options:
import { encode } from "qr-core";import { renderSvg } from "@goker/qr-code";const qr = encode("https://example.com", { ecc: "H" });const svg = renderSvg(qr, {moduleSize: 6,margin: 4,lightColor: "transparent",grouping: "45",rotateDeg: 45,moduleShape: "square",moduleRotationDeg: 45,cornerRadius: 1});console.log(svg);
All of those option names and supported grouping values are documented in the package docs.
A side quest: the old Chrome QR generator deserves a revival
This whole thing also reminded me of a much older need: generating large vector QRs for print.
Years ago I built a Chrome App that generates a vector (SVG) QR from the current tab URL. It’s sitting there as gokerDEV/QRCoder, forked from an older HTML5 QR generator project.
It’s time to pull it out of the “deprecated museum” and modernize it.
DIAMOND made that urgency obvious.
Closing
This is the kind of project that starts as “I just want rounded corners” and ends as “why does QR have to look square if the standard only requires the structure to be square?”
The matrix remains sacred. The rendering becomes expressive.
That’s the whole philosophy behind DIAMOND—without pretending to change the QR standard, and without leaning on “error correction will forgive anything” aesthetics.
If you want the visual/art angle, the goker.art write-up is the right place. This post is the engineering origin story.