main
Kyle Zheng 2024-07-25 03:19:48 -04:00
rodzic d2efe998f1
commit 46fc279f58
7 zmienionych plików z 235 dodań i 209 usunięć

Wyświetl plik

@ -19,7 +19,7 @@
"@solidjs/start": "^1.0.0",
"@unocss/reset": "^0.59.4",
"codemirror": "^6.0.1",
"fuqr": "^0.0.5",
"fuqr": "^0.0.6",
"lucide-solid": "^0.378.0",
"qr-scanner-wechat": "^0.1.3",
"solid-js": "^1.8.17",

Wyświetl plik

@ -49,8 +49,8 @@ importers:
specifier: ^6.0.1
version: 6.0.1(@lezer/common@1.2.1)
fuqr:
specifier: ^0.0.5
version: 0.0.5
specifier: ^0.0.6
version: 0.0.6
lucide-solid:
specifier: ^0.378.0
version: 0.378.0(solid-js@1.8.17)
@ -1781,8 +1781,8 @@ packages:
function-bind@1.1.2:
resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==}
fuqr@0.0.5:
resolution: {integrity: sha512-KxIKlDZ4HQFTouhH8VuDuA6EdPZbLy2GklevRP2KRJ7FiBlh/fET+26IizsWIpkwfpNWb0QkDs9rdL8Nn6PbkA==}
fuqr@0.0.6:
resolution: {integrity: sha512-pYq22vaBSzVEJJ9lkh11kFMdDtv++saB09I/dGSB5gL3GUTVrV0M1l5FDiSIjLisylrUOKsBXuSfH70a3desjg==}
gauge@3.0.2:
resolution: {integrity: sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==}
@ -4790,7 +4790,7 @@ snapshots:
function-bind@1.1.2: {}
fuqr@0.0.5: {}
fuqr@0.0.6: {}
gauge@3.0.2:
dependencies:

Wyświetl plik

@ -159,7 +159,7 @@ export function Editor(props: Props) {
placeholder="https://qrcode.kylezhe.ng"
setValue={(s) => setInputQr("text", s)}
/>
<Collapsible trigger="Settings">
<Collapsible trigger="QR Code">
<Settings />
</Collapsible>
<Collapsible trigger="Rendering" defaultOpen>

Wyświetl plik

@ -1,4 +1,4 @@
import { For, type JSX } from "solid-js";
import { For } from "solid-js";
import { useQrContext } from "~/lib/QrContext";
import {
ECL_NAMES,
@ -18,7 +18,7 @@ export function Settings() {
const { inputQr, setInputQr } = useQrContext();
return (
<>
<div class="flex flex-col gap-2 py-2">
<div class="flex justify-between">
<div class="text-sm py-2">Encoding mode</div>
<Select
@ -27,15 +27,17 @@ export function Settings() {
setValue={(name) => setInputQr("mode", MODE_VALUE[name])}
/>
</div>
<Row title="Min version">
<div class="flex justify-between">
<div class="text-sm py-2 w-48">Min version</div>
<NumberInput
min={1}
max={40}
value={inputQr.minVersion}
setValue={(v) => setInputQr("minVersion", v)}
/>
</Row>
<Row title="Min error tolerance">
</div>
<div>
<div class="text-sm py-2">Min error tolerance</div>
<ButtonGroup
value={ECL_NAMES[inputQr.minEcl]}
setValue={(v) => setInputQr("minEcl", ECL_VALUE[v])}
@ -44,8 +46,9 @@ export function Settings() {
{(name) => <ButtonGroupItem value={name}>{name}</ButtonGroupItem>}
</For>
</ButtonGroup>
</Row>
<Row title="Mask pattern">
</div>
<div>
<div class="text-sm py-2">Mask pattern</div>
<ButtonGroup
value={MASK_KEY[inputQr.mask!]}
setValue={(name) => setInputQr("mask", MASK_VALUE[name])}
@ -56,32 +59,7 @@ export function Settings() {
)}
</For>
</ButtonGroup>
</Row>
<Row title="Margin">
<NumberInput
min={0}
max={10}
step={1}
value={inputQr.margin.top}
setValue={(v) =>
setInputQr("margin", { top: v, bottom: v, left: v, right: v })
}
/>
</Row>
</>
);
}
function Row(props: {
title: string;
children: JSX.Element;
}) {
return (
<div>
<div class="text-sm py-2">
{props.title}
</div>
{props.children}
</div>
);
}

Wyświetl plik

@ -69,18 +69,14 @@ export default function QrPreview(props: Props) {
* Running the effect in the ref function caused double rendering for future mounts.
*/
function RenderedQrCode() {
const { outputQr: _outputQr, renderFunc, renderFuncKey, params } = useQrContext();
const {
outputQr: _outputQr,
renderFunc,
renderFuncKey,
params,
} = useQrContext();
const outputQr = _outputQr as () => OutputQr;
const fullWidth = () => {
const output = outputQr();
return output.version * 4 + 17 + output.margin.left + output.margin.right;
};
const fullHeight = () => {
const output = outputQr();
return output.version * 4 + 17 + output.margin.top + output.margin.bottom;
};
let qrCanvas: HTMLCanvasElement;
const [runtimeError, setRuntimeError] = createSignal<string | null>(null);
@ -132,9 +128,10 @@ function RenderedQrCode() {
"background-image":
"repeating-conic-gradient(#ddd 0% 25%, #aaa 25% 50%)",
"background-position": "50%",
"background-size": `${(1 / fullWidth()) * 100}% ${
(1 / fullHeight()) * 100
}%`,
"background-size": `${
(1 / (outputQr().version * 4 + 17 + 4)) * 100
}% ${(1 / (outputQr().version * 4 + 17 + 4)) * 100}%
`,
}}
>
<canvas class="w-full h-full" ref={qrCanvas!}></canvas>
@ -153,9 +150,19 @@ function RenderedQrCode() {
<div class="text-center">
{canvasDims().width}x{canvasDims().height} px
</div>
<div class="px-2 grid grid-cols-3 gap-y-2 text-sm">
<div class="px-2 grid grid-cols-2 gap-y-2 text-sm">
<div class="">
Version <span class="font-bold text-base">{outputQr().version}</span>
Version
<div class="font-bold text-base">
{outputQr().version} ({outputQr().version * 4 + 17}x
{outputQr().version * 4 + 17} matrix)
</div>
</div>
<div class="">
Error tolerance{" "}
<div class="font-bold text-base whitespace-pre">
{ECL_NAMES[outputQr().ecl]} ({ECL_LABELS[outputQr().ecl]})
</div>
</div>
<div class="">
Mask{" "}
@ -165,24 +172,6 @@ function RenderedQrCode() {
Encoding{" "}
<span class="font-bold text-base">{MODE_KEY[outputQr().mode]}</span>
</div>
<div class="">
Symbol size{" "}
<div class="font-bold text-base whitespace-pre">
{outputQr().version * 4 + 17}x{outputQr().version * 4 + 17}
</div>
</div>
<div class="">
Size incl. margin{" "}
<div class="font-bold text-base whitespace-pre">
{outputQr().matrixWidth}x{outputQr().matrixHeight}
</div>
</div>
<div class="">
Error tolerance{" "}
<div class="font-bold text-base whitespace-pre">
{ECL_NAMES[outputQr().ecl]} ({ECL_LABELS[outputQr().ecl]})
</div>
</div>
</div>
<div class="flex gap-2">
<FlatButton

Wyświetl plik

@ -15,7 +15,6 @@ import {
Module,
QrOptions,
Version,
Margin,
get_matrix,
} from "fuqr";
import { createStore, type SetStoreFunction } from "solid-js/store";
@ -27,12 +26,6 @@ type InputQr = {
minEcl: ECL;
mode: Mode | null;
mask: Mask | null;
margin: {
top: number;
right: number;
bottom: number;
left: number;
};
};
export type OutputQr = {
@ -42,17 +35,8 @@ export type OutputQr = {
ecl: ECL;
mode: Mode;
mask: Mask;
/** Stored as value b/c Margin is a ptr which becomes null after use */
margin: {
top: number;
right: number;
bottom: number;
left: number;
};
/** Stored as value b/c Matrix is a ptr which becomes null after use */
matrix: Module[];
matrixWidth: number;
matrixHeight: number;
};
export const QrContext = createContext<{
@ -83,12 +67,6 @@ export function QrContextProvider(props: { children: JSX.Element }) {
minEcl: ECL.Low,
mode: null,
mask: null,
margin: {
top: 2,
right: 2,
bottom: 2,
left: 2,
},
});
const [outputQr, setOutputQr] = createSignal<OutputQr | QrError>(
@ -109,31 +87,16 @@ export function QrContextProvider(props: { children: JSX.Element }) {
.min_ecl(inputQr.minEcl)
.mask(inputQr.mask!) // null makes more sense than undefined
.mode(inputQr.mode!) // null makes more sense than undefined
.margin(
new Margin(0)
.setTop(inputQr.margin.top)
.setRight(inputQr.margin.right)
.setBottom(inputQr.margin.bottom)
.setLeft(inputQr.margin.left)
);
let m = get_matrix(inputQr.text, qrOptions);
setOutputQr({
text: inputQr.text,
matrix: m.value,
matrixWidth: m.version["0"] * 4 + 17 + m.margin.left + m.margin.right,
matrixHeight: m.version["0"] * 4 + 17 + m.margin.top + m.margin.bottom,
version: m.version["0"],
ecl: m.ecl,
mode: m.mode,
mask: m.mask,
margin: {
top: m.margin.top,
right: m.margin.right,
bottom: m.margin.bottom,
left: m.margin.left,
},
});
} catch (e) {
setOutputQr(e as QrError);

Wyświetl plik

@ -1,11 +1,10 @@
export const PRESET_FUNCS = {
Square: `export const paramsSchema = {
"Pixel size": {
"Margin": {
type: "number",
min: 1,
max: 20,
default: 10
min: 0,
max: 10,
default: 2,
},
"Foreground": {
type: "Color",
@ -14,31 +13,49 @@ export const PRESET_FUNCS = {
"Background": {
type: "Color",
default: "#ffffff"
}
},
}
export function renderCanvas(qr, params, ctx) {
const pixelSize = params["Pixel size"];
ctx.canvas.width = qr.matrixWidth * pixelSize;
ctx.canvas.height = qr.matrixHeight * pixelSize;
const pixelSize = 10;
const matrixWidth = qr.version * 4 + 17;
const canvasSize = (matrixWidth + 2 * params["Margin"]) * pixelSize;
ctx.canvas.width = canvasSize;
ctx.canvas.height = canvasSize;
ctx.fillStyle = params["Background"];
ctx.fillRect(0, 0, ctx.canvas.width, ctx.canvas.height);
ctx.fillStyle = params["Foreground"];
for (let y = 0; y < qr.matrixHeight; y++) {
for (let x = 0; x < qr.matrixWidth; x++) {
const module = qr.matrix[y * qr.matrixWidth + x];
for (let y = 0; y < matrixWidth; y++) {
for (let x = 0; x < matrixWidth; x++) {
const module = qr.matrix[y * matrixWidth + x];
if (module & 1) {
ctx.fillRect(x * pixelSize, y * pixelSize, pixelSize, pixelSize);
ctx.fillRect(
(x + params["Margin"]) * pixelSize,
(y + params["Margin"]) * pixelSize,
pixelSize,
pixelSize
);
}
}
}
}
`,
Circle: `// qr, ctx are args
Circle: `export const paramsSchema = {
"Circular finder pattern": {
type: "boolean",
default: true,
},
"Circular alignment pattern": {
type: "boolean",
default: true,
},
}
const Module = {
DataOFF: 0,
DataON: 1,
@ -52,12 +69,15 @@ const Module = {
FormatON: 9,
VersionOFF: 10,
VersionON: 11,
Unset: 12,
SeparatorOFF: 12,
}
export function renderCanvas(qr, params, ctx) {
const pixelSize = 10;
ctx.canvas.width = qr.matrixWidth * pixelSize;
ctx.canvas.height = qr.matrixHeight * pixelSize;
const margin = 2;
const matrixWidth = qr.version * 4 + 17;
const canvasSize = (matrixWidth + 2 * margin) * pixelSize;
ctx.canvas.width = canvasSize;
ctx.canvas.height = canvasSize;
ctx.fillStyle = "rgb(255, 255, 255)";
ctx.fillRect(0, 0, ctx.canvas.width, ctx.canvas.height);
@ -79,62 +99,67 @@ export function renderCanvas(qr, params, ctx) {
const radius = pixelSize / 2;
const finderPos = [
[qr.margin.left, qr.margin.top],
[qr.matrixWidth - qr.margin.right - 7, qr.margin.top],
[qr.margin.left, qr.matrixHeight - qr.margin.bottom - 7],
[margin, margin],
[margin + matrixWidth - 7, margin],
[margin, margin + matrixWidth - 7],
];
for (const [x, y] of finderPos) {
ctx.beginPath();
ctx.arc((x + 3.5) * pixelSize, (y + 3.5) * pixelSize, 3.5 * pixelSize, 0, 2 * Math.PI);
ctx.fill();
ctx.fillStyle = "rgb(255, 255, 255)";
ctx.beginPath();
ctx.arc((x + 3.5) * pixelSize, (y + 3.5) * pixelSize, 2.5 * pixelSize, 0, 2 * Math.PI);
ctx.fill();
ctx.fillStyle = gradient;
ctx.beginPath();
ctx.arc((x + 3.5) * pixelSize, (y + 3.5) * pixelSize, 1.5 * pixelSize, 0, 2 * Math.PI);
ctx.fill();
if (params["Circular finder pattern"]) {
for (const [x, y] of finderPos) {
ctx.beginPath();
ctx.arc((x + 3.5) * pixelSize, (y + 3.5) * pixelSize, 3.5 * pixelSize, 0, 2 * Math.PI);
ctx.fill();
ctx.fillStyle = "rgb(255, 255, 255)";
ctx.beginPath();
ctx.arc((x + 3.5) * pixelSize, (y + 3.5) * pixelSize, 2.5 * pixelSize, 0, 2 * Math.PI);
ctx.fill();
ctx.fillStyle = gradient;
ctx.beginPath();
ctx.arc((x + 3.5) * pixelSize, (y + 3.5) * pixelSize, 1.5 * pixelSize, 0, 2 * Math.PI);
ctx.fill();
}
}
const xMid = qr.matrixWidth / 2;
const yMid = qr.matrixHeight / 2;
const maxDist = Math.sqrt(xMid * xMid + yMid + yMid);
const xMid = matrixWidth / 2;
const yMid = matrixWidth / 2;
const maxDist = Math.sqrt(xMid * xMid + yMid * yMid);
for (let y = 0; y < qr.matrixHeight; y++) {
for (let x = 0; x < qr.matrixWidth; x++) {
const module = qr.matrix[y * qr.matrixWidth + x];
for (let y = 0; y < matrixWidth; y++) {
for (let x = 0; x < matrixWidth; x++) {
const module = qr.matrix[y * matrixWidth + x];
if (module & 1) {
if (module === Module.FinderON) continue;
if (module === Module.AlignmentON) {
if (params["Circular finder pattern"] && module === Module.FinderON) continue;
if (params["Circular alignment pattern"] && module === Module.AlignmentON) {
// Find top left corner of alignment square
if (qr.matrix[(y - 1) * qr.matrixWidth + x] !== Module.AlignmentON &&
qr.matrix[y * qr.matrixWidth + x - 1] !== Module.AlignmentON &&
qr.matrix[y * qr.matrixWidth + x + 1] === Module.AlignmentON
if (qr.matrix[(y - 1) * matrixWidth + x] !== Module.AlignmentON &&
qr.matrix[y * matrixWidth + x - 1] !== Module.AlignmentON &&
qr.matrix[y * matrixWidth + x + 1] === Module.AlignmentON
) {
const xPos = x + 2.5 + margin;
const yPos = y + 2.5 + margin;
ctx.beginPath();
ctx.arc((x + 2.5) * pixelSize, (y + 2.5) * pixelSize, 2.5 * pixelSize, 0, 2 * Math.PI);
ctx.arc(xPos * pixelSize, yPos * pixelSize, 2.5 * pixelSize, 0, 2 * Math.PI);
ctx.fill();
ctx.fillStyle = "rgb(255, 255, 255)";
ctx.beginPath();
ctx.arc((x + 2.5) * pixelSize, (y + 2.5) * pixelSize, 1.5 * pixelSize, 0, 2 * Math.PI);
ctx.arc(xPos * pixelSize, yPos * pixelSize, 1.5 * pixelSize, 0, 2 * Math.PI);
ctx.fill();
ctx.fillStyle = gradient;
ctx.beginPath();
ctx.arc((x + 2.5) * pixelSize, (y + 2.5) * pixelSize, 0.5 * pixelSize, 0, 2 * Math.PI);
ctx.arc(xPos * pixelSize, yPos * pixelSize, 0.5 * pixelSize, 0, 2 * Math.PI);
ctx.fill();
}
continue;
};
const xCenter = x * pixelSize + radius;
const yCenter = y * pixelSize + radius;
const xCenter = (x + margin) * pixelSize + radius;
const yCenter = (y + margin) * pixelSize + radius;
const xDist = Math.abs(xMid - x);
const yDist = Math.abs(yMid - y);
@ -148,7 +173,27 @@ export function renderCanvas(qr, params, ctx) {
}
}
`,
Camouflage: `// qr, ctx are args
Camouflage: `export const paramsSchema = {
"Margin": {
type: "number",
min: 0,
max: 10,
default: 3,
},
"Quiet zone": {
type: "number",
min: 0,
max: 10,
default: 1,
},
"Seed": {
type: "number",
min: 1,
max: 100,
default: 1,
},
}
const Module = {
DataOFF: 0,
DataON: 1,
@ -162,7 +207,7 @@ const Module = {
FormatON: 9,
VersionOFF: 10,
VersionON: 11,
Unset: 12,
SeparatorOFF: 12,
}
function splitmix32(a) {
@ -178,44 +223,63 @@ function splitmix32(a) {
}
export function renderCanvas(qr, params, ctx) {
const seededRand = splitmix32(1 /* change seed to change pattern */);
const seededRand = splitmix32(params["Seed"]);
const margin = params["Margin"];
const quietZone = params["Quiet zone"];
// Randomly set pixels in margin
for (let y = 0; y < qr.matrixHeight; y++) {
for (let x = 0; x < qr.matrixWidth; x++) {
if (y > qr.margin.top - 2 &&
y < qr.matrixHeight - qr.margin.bottom + 1 &&
x > qr.margin.left - 2 &&
x < qr.matrixWidth - qr.margin.right + 1) {
continue;
}
const pixelSize = 10;
const radius = pixelSize / 2;
const qrWidth = qr.version * 4 + 17;
const matrixWidth = qrWidth + 2 * margin;
const canvasSize = matrixWidth * pixelSize;
if (seededRand() > 0.5) qr.matrix[y * qr.matrixWidth + x] = Module.DataON;
const newMatrix = Array(matrixWidth * matrixWidth).fill(Module.SeparatorOFF);
// Copy qr to matrix with margin and randomly set pixels in margin
for (let y = 0; y < margin - quietZone; y++) {
for (let x = 0; x < matrixWidth; x++) {
if (seededRand() > 0.5) newMatrix[y * matrixWidth + x] = Module.DataON;
}
}
for (let y = margin - quietZone; y < margin + qrWidth + quietZone; y++) {
for (let x = 0; x < margin - quietZone; x++) {
if (seededRand() > 0.5) newMatrix[y * matrixWidth + x] = Module.DataON;
}
if (y >= margin && y < margin + qrWidth) {
for (let x = margin; x < matrixWidth - margin; x++) {
newMatrix[y * matrixWidth + x] = qr.matrix[(y - margin) * qrWidth + x - margin];
}
}
for (let x = margin + qrWidth + quietZone; x < matrixWidth; x++) {
if (seededRand() > 0.5) newMatrix[y * matrixWidth + x] = Module.DataON;
}
}
for (let y = margin + qrWidth + quietZone; y < matrixWidth; y++) {
for (let x = 0; x < matrixWidth; x++) {
if (seededRand() > 0.5) newMatrix[y * matrixWidth + x] = Module.DataON;
}
}
const pixelSize = 20;
const radius = pixelSize / 2;
ctx.canvas.width = qr.matrixWidth * pixelSize;
ctx.canvas.height = qr.matrixHeight * pixelSize;
const fg = "rgb(40, 70, 10)";
const bg = "rgb(200, 200, 100)";
ctx.canvas.width = canvasSize;
ctx.canvas.height = canvasSize;
ctx.fillStyle = bg;
ctx.fillRect(0, 0, ctx.canvas.width, ctx.canvas.height);
const xMax = qr.matrixWidth - 1;
const yMax = qr.matrixHeight - 1;
const xMax = matrixWidth - 1;
const yMax = matrixWidth - 1;
for (let y = 0; y < qr.matrixHeight; y++) {
for (let x = 0; x < qr.matrixWidth; x++) {
const module = qr.matrix[y * qr.matrixWidth + x];
for (let y = 0; y < matrixWidth; y++) {
for (let x = 0; x < matrixWidth; x++) {
const module = newMatrix[y * matrixWidth + x];
const top = y > 0 && (qr.matrix[(y - 1) * qr.matrixWidth + x] & 1);
const bottom = y < yMax && (qr.matrix[(y + 1) * qr.matrixWidth + x] & 1);
const left = x > 0 && (qr.matrix[y * qr.matrixWidth + x - 1] & 1);
const right = x < xMax && (qr.matrix[y * qr.matrixWidth + x + 1] & 1);
const top = y > 0 && (newMatrix[(y - 1) * matrixWidth + x] & 1);
const bottom = y < yMax && (newMatrix[(y + 1) * matrixWidth + x] & 1);
const left = x > 0 && (newMatrix[y * matrixWidth + x - 1] & 1);
const right = x < xMax && (newMatrix[y * matrixWidth + x + 1] & 1);
ctx.fillStyle = fg;
@ -236,10 +300,10 @@ export function renderCanvas(qr, params, ctx) {
ctx.fill();
} else {
// Draw rounded concave corners
const topLeft = y > 0 && x > 0 && (qr.matrix[(y - 1) * qr.matrixWidth + x - 1] & 1);
const topRight = y > 0 && x < xMax && (qr.matrix[(y - 1) * qr.matrixWidth + x + 1] & 1);
const bottomRight = y < yMax && x < xMax && (qr.matrix[(y + 1) * qr.matrixWidth + x + 1] & 1);
const bottomLeft = y < yMax && x > 0 && (qr.matrix[(y + 1) * qr.matrixWidth + x - 1] & 1);
const topLeft = y > 0 && x > 0 && (newMatrix[(y - 1) * matrixWidth + x - 1] & 1);
const topRight = y > 0 && x < xMax && (newMatrix[(y - 1) * matrixWidth + x + 1] & 1);
const bottomRight = y < yMax && x < xMax && (newMatrix[(y + 1) * matrixWidth + x + 1] & 1);
const bottomLeft = y < yMax && x > 0 && (newMatrix[(y + 1) * matrixWidth + x - 1] & 1);
ctx.fillRect(x * pixelSize, y * pixelSize, pixelSize, pixelSize);
ctx.beginPath();
@ -262,7 +326,27 @@ export function renderCanvas(qr, params, ctx) {
}
}
`,
Minimal: `export function renderCanvas(qr, params, ctx) {
Minimal: `export const paramsSchema = {
"Margin": {
type: "number",
min: 0,
max: 10,
default: 2,
},
"Pixel size": {
type: "number",
min: 0,
max: 20,
default: 8,
},
"Small pixel size": {
type: "number",
min: 1,
max: 20,
default: 4,
},
}
export function renderCanvas(qr, params, ctx) {
const Module = {
DataOFF: 0,
DataON: 1,
@ -276,17 +360,22 @@ export function renderCanvas(qr, params, ctx) {
FormatON: 9,
VersionOFF: 10,
VersionON: 11,
Unset: 12,
SeparatorOFF: 12,
}
const pixelSize = 12;
ctx.canvas.width = qr.matrixWidth * pixelSize;
ctx.canvas.height = qr.matrixHeight * pixelSize;
const margin = params["Margin"];
const pixelSize = params["Pixel size"];
const minSize = params["Small pixel size"];
const matrixWidth = qr.version * 4 + 17;
const canvasSize = (matrixWidth + 2 * margin) * pixelSize;
ctx.canvas.width = canvasSize;
ctx.canvas.height = canvasSize;
const finderPos = [
[qr.margin.left, qr.margin.top],
[qr.matrixWidth - qr.margin.right - 7, qr.margin.top],
[qr.margin.left, qr.matrixHeight - qr.margin.bottom - 7],
[margin, margin],
[matrixWidth + margin - 7, margin],
[margin, matrixWidth + margin - 7],
];
ctx.fillStyle = "rgb(0, 0, 0)";
@ -300,18 +389,22 @@ export function renderCanvas(qr, params, ctx) {
ctx.fillRect((x + 2) * pixelSize, (y + 2) * pixelSize, 3 * pixelSize, 3 * pixelSize);
}
const minSize = pixelSize / 2;
const offset = (pixelSize - minSize) / 2;
for (let y = 0; y < qr.matrixHeight; y++) {
for (let x = 0; x < qr.matrixWidth; x++) {
const module = qr.matrix[y * qr.matrixWidth + x];
for (let y = 0; y < matrixWidth; y++) {
for (let x = 0; x < matrixWidth; x++) {
const module = qr.matrix[y * matrixWidth + x];
if ((module | 1) === Module.FinderON) {
continue;
}
if (module & 1) {
ctx.fillRect(x * pixelSize + offset, y * pixelSize + offset, minSize, minSize);
ctx.fillRect(
(x + margin) * pixelSize + offset,
(y + margin) * pixelSize + offset,
minSize,
minSize
);
}
}
}
@ -319,8 +412,11 @@ export function renderCanvas(qr, params, ctx) {
`,
"Lover (Animated)": `export function renderCanvas(qr, params, ctx) {
const pixelSize = 10;
ctx.canvas.width = qr.matrixWidth * pixelSize;
ctx.canvas.height = qr.matrixHeight * pixelSize;
const margin = 2;
const matrixWidth = qr.version * 4 + 17;
const canvasSize = (matrixWidth + 2 * margin) * pixelSize;
ctx.canvas.width = canvasSize;
ctx.canvas.height = canvasSize;
const period = 3000; // ms
const amplitude = 0.8; // maxSize - minSize
@ -345,9 +441,9 @@ export function renderCanvas(qr, params, ctx) {
ctx.fillStyle = "rgb(0, 0, 0)";
ctx.fillRect(0, 0, ctx.canvas.width, ctx.canvas.height);
for (let y = 0; y < qr.matrixHeight; y++) {
for (let x = 0; x < qr.matrixWidth; x++) {
const module = qr.matrix[y * qr.matrixWidth + x];
for (let y = 0; y < matrixWidth; y++) {
for (let x = 0; x < matrixWidth; x++) {
const module = qr.matrix[y * matrixWidth + x];
if ((module & 1) === 0) continue;
const xBias = Math.abs(5 - (x % 10));
@ -360,7 +456,7 @@ export function renderCanvas(qr, params, ctx) {
const offset = (pixelSize - size) / 2;
ctx.fillStyle = \`rgb(\${100 + ratio * 150}, \${200 + xBias * 10}, 255)\`;
ctx.fillRect(x * pixelSize + offset, y * pixelSize + offset, size, size);
ctx.fillRect((x + margin) * pixelSize + offset, (y + margin) * pixelSize + offset, size, size);
}
}
req = requestAnimationFrame(frame);