Skip to content

Instantly share code, notes, and snippets.

@zpao
Last active June 10, 2018 01:20
Show Gist options
  • Save zpao/9e5ebcec2f13986d9001baf09ca78840 to your computer and use it in GitHub Desktop.
Save zpao/9e5ebcec2f13986d9001baf09ca78840 to your computer and use it in GitHub Desktop.
Making QRCodes with SVG smaller (using React)
// This will generate 31329 <rect>s for a level 40 QR Code (177x177).
// This approach is totally fine in <canvas> (but should learn from Smarter impl below)
// Lorem Ipsum test content was ~2MB
class QRCodeSVG extends React.Component<Props> {
render() {
var {value, size, level, bgColor, fgColor} = this.props;
var qrcode = new QRCodeImpl(-1, ErrorCorrectLevel[level]);
qrcode.addData(value);
qrcode.make();
var cells = qrcode.modules;
if (cells === null) {
return;
}
const modules = [];
cells.forEach(function(row, y) {
row.forEach(function(cell, x) {
modules.push(
<rect
key={`${x}-${y}`}
fill={cell ? fgColor : bgColor}
x={x}
y={y}
width={1}
height={1}
/>
);
});
});
return (
<svg
shapeRendering="crispEdges"
height={size}
width={size}
viewBox={`0 0 ${cells.length} ${cells.length}`}>
{modules}
</svg>
);
}
}
// Slightly smarter - single <rect> for bgcolor, then only draw dark modules
// Lorem Ipsum test content was unsurprisingly ~50% smaller.
class QRCodeSVG extends React.Component<Props> {
render() {
var {value, size, level, bgColor, fgColor} = this.props;
var qrcode = new QRCodeImpl(-1, ErrorCorrectLevel[level]);
qrcode.addData(value);
qrcode.make();
var cells = qrcode.modules;
if (cells === null) {
return;
}
const modules = [];
cells.forEach(function(row, y) {
row.forEach(function(cell, x) {
if (cell) {
modules.push(
<rect
key={`${x}-${y}`}
fill={fgColor}
x={x}
y={y}
width={1}
height={1}
/>
);
}
});
});
return (
<svg
shapeRendering="crispEdges"
height={size}
width={size}
viewBox={`0 0 ${cells.length} ${cells.length}`}>
<rect
fill={bgColor}
x={0}
y={0}
width={cells.length}
height={cells.length}
/>
{modules}
</svg>
);
}
}
// Way smarter. Still a single node for the background with dark modules on top.
// But instead of a rect per module, we can make this into a <path> with appropriate
// description. We only draw Nx1 rects within the path, across each row so it could be
// smarter still but requires way more work.
// Lorem Ipsum test content was 115KB with a total of 2 nodes.
// This component updates instantly when value changes, while Naive took a couple seconds.
class QRCodeSVG extends React.Component<Props> {
render() {
var {value, size, level, bgColor, fgColor} = this.props;
var qrcode = new QRCodeImpl(-1, ErrorCorrectLevel[level]);
qrcode.addData(value);
qrcode.make();
var cells = qrcode.modules;
if (cells === null) {
return;
}
// Drawing strategy: instead of a rect per module, we're going to create a
// single path for the dark modules and layer that on top of a light rect,
// for a total of 2 DOM nodes. We pay a bit more in string concat but that's
// way faster than DOM ops.
// For level 1, 441 nodes -> 2
// For level 40, 31329 -> 2
const ops = [];
cells.forEach(function(row, y) {
let lastIsDark = false;
let start = null;
row.forEach(function(cell, x) {
if (!cell && start !== null) {
// M0 0h7v1H0z injects the space with the move and dropd the comma,
// saving a char per operation
ops.push(`M${start} ${y}h${x - start}v1H${start}z`);
start = null;
return;
}
// end of row, clean up or skip
if (x === row.length - 1) {
if (!cell) {
// We would have closed the op above already so this can only mean
// 2+ light modules in a row.
return;
}
if (start === null) {
// Just a single dark module.
ops.push(`M${x},${y} h1v1H${x}z`);
} else {
// Otherwise finish the current line.
ops.push(`M${start},${y} h${x + 1 - start}v1H${start}z`);
}
return;
}
if (cell && start === null) {
start = x;
}
});
});
return (
<svg
shapeRendering="crispEdges"
height={size}
width={size}
viewBox={`0 0 ${cells.length} ${cells.length}`}>
<path fill={bgColor} d={`M0,0 h${cells.length}v${cells.length}H0z`} />
<path fill={fgColor} d={ops.join('')} />
</svg>
);
}
}
Display the source blob
Display the rendered blob
Raw
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Display the source blob
Display the rendered blob
Raw
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Display the source blob
Display the rendered blob
Raw
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment