kopia lustrzana https://github.com/zhengkyl/qrframe
marginless
rodzic
d2efe998f1
commit
46fc279f58
|
@ -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",
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
Ładowanie…
Reference in New Issue