kopia lustrzana https://github.com/zhengkyl/qrframe
289 wiersze
7.5 KiB
JavaScript
289 wiersze
7.5 KiB
JavaScript
import { Module, getSeededRand } from "https://qrframe.kylezhe.ng/utils.js";
|
|
|
|
export const paramsSchema = {
|
|
Foreground: {
|
|
type: "color",
|
|
default: "#1c4a1a",
|
|
},
|
|
Background: {
|
|
type: "color",
|
|
default: "#e3d68a",
|
|
},
|
|
Margin: {
|
|
type: "number",
|
|
min: 0,
|
|
max: 10,
|
|
default: 3,
|
|
},
|
|
"Quiet zone": {
|
|
type: "number",
|
|
min: 0,
|
|
max: 10,
|
|
default: 1,
|
|
},
|
|
Invert: {
|
|
type: "boolean",
|
|
},
|
|
Seed: {
|
|
type: "number",
|
|
min: 1,
|
|
max: 100,
|
|
default: 1,
|
|
},
|
|
};
|
|
|
|
export function renderSVG(qr, params) {
|
|
const rand = getSeededRand(params["Seed"]);
|
|
const margin = params["Margin"];
|
|
const quietZone = params["Quiet zone"];
|
|
const fg = params["Foreground"];
|
|
const bg = params["Background"];
|
|
|
|
const qrWidth = qr.version * 4 + 17;
|
|
const matrixWidth = qrWidth + 2 * margin;
|
|
|
|
const newMatrix = Array(matrixWidth * matrixWidth).fill(0);
|
|
const visited = new Uint16Array(matrixWidth * matrixWidth);
|
|
|
|
// 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 (rand() > 0.5) newMatrix[y * matrixWidth + x] = Module.ON;
|
|
}
|
|
}
|
|
for (let y = margin - quietZone; y < margin + qrWidth + quietZone; y++) {
|
|
for (let x = 0; x < margin - quietZone; x++) {
|
|
if (rand() > 0.5) newMatrix[y * matrixWidth + x] = Module.ON;
|
|
}
|
|
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 (rand() > 0.5) newMatrix[y * matrixWidth + x] = Module.ON;
|
|
}
|
|
}
|
|
for (let y = margin + qrWidth + quietZone; y < matrixWidth; y++) {
|
|
for (let x = 0; x < matrixWidth; x++) {
|
|
if (rand() > 0.5) newMatrix[y * matrixWidth + x] = Module.ON;
|
|
}
|
|
}
|
|
if (quietZone === 0 && margin > 0) {
|
|
for (let x = margin; x < margin + 7; x++) {
|
|
newMatrix[(margin - 1) * matrixWidth + x] = 0;
|
|
newMatrix[(margin - 1) * matrixWidth + x + qrWidth - 7] = 0;
|
|
}
|
|
for (let y = margin; y < margin + 7; y++) {
|
|
newMatrix[y * matrixWidth + margin - 1] = 0;
|
|
newMatrix[y * matrixWidth + matrixWidth - margin] = 0;
|
|
}
|
|
for (let y = margin + qrWidth - 7; y < margin + qrWidth; y++) {
|
|
newMatrix[y * matrixWidth + margin - 1] = 0;
|
|
}
|
|
for (let x = margin; x < margin + 7; x++) {
|
|
newMatrix[(matrixWidth - margin) * matrixWidth + x] = 0;
|
|
}
|
|
}
|
|
|
|
let svg = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${matrixWidth} ${matrixWidth}">`;
|
|
svg += `<rect width="${matrixWidth}" height="${matrixWidth}" fill="${bg}"/>`;
|
|
svg += `<g fill="${fg}">`;
|
|
|
|
const xMax = matrixWidth - 1;
|
|
const yMax = matrixWidth - 1;
|
|
|
|
let baseX;
|
|
let baseY;
|
|
|
|
const on = params["Invert"]
|
|
? (x, y) => (newMatrix[y * matrixWidth + x] & Module.ON) === 0
|
|
: (x, y) => (newMatrix[y * matrixWidth + x] & Module.ON) !== 0;
|
|
|
|
function goRight(x, y, path, cw) {
|
|
let sx = x;
|
|
let vert = false;
|
|
visited[y * matrixWidth + x] = path;
|
|
while (x < xMax) {
|
|
const right = on(x + 1, y);
|
|
const vertRight = y > 0 && on(x + 1, y - 1);
|
|
if (!right || vertRight) {
|
|
vert = right && vertRight;
|
|
break;
|
|
}
|
|
x++;
|
|
visited[y * matrixWidth + x] = path;
|
|
}
|
|
paths[path] += `h${x - sx}`;
|
|
if (vert) {
|
|
paths[path] += `a.5.5 0,0,0 .5-.5`;
|
|
goUp(x + 1, y - 1, path, cw);
|
|
} else {
|
|
paths[path] += `a.5.5 0,0,1 .5.5`;
|
|
goDown(x, y, path, cw);
|
|
}
|
|
}
|
|
|
|
function goLeft(x, y, shape, cw) {
|
|
let sx = x;
|
|
let vert = false;
|
|
visited[y * matrixWidth + x] = shape;
|
|
while (x > 0) {
|
|
const left = on(x - 1, y);
|
|
const vertLeft = y < yMax && on(x - 1, y + 1);
|
|
if (!left || vertLeft) {
|
|
vert = left && vertLeft;
|
|
break;
|
|
}
|
|
x--;
|
|
visited[y * matrixWidth + x] = shape;
|
|
}
|
|
if (!cw && x === baseX && y === baseY) {
|
|
paths[shape] += "z";
|
|
return;
|
|
}
|
|
paths[shape] += `h${x - sx}`;
|
|
|
|
if (vert) {
|
|
paths[shape] += `a.5.5 0,0,0 -.5.5`;
|
|
goDown(x - 1, y + 1, shape, cw);
|
|
} else {
|
|
paths[shape] += `a.5.5 0,0,1 -.5-.5`;
|
|
goUp(x, y, shape, cw);
|
|
}
|
|
}
|
|
|
|
function goUp(x, y, shape, cw) {
|
|
let sy = y;
|
|
let horz = false;
|
|
visited[y * matrixWidth + x] = shape;
|
|
while (y > 0) {
|
|
const up = on(x, y - 1);
|
|
const horzUp = x > 0 && on(x - 1, y - 1);
|
|
if (!up || horzUp) {
|
|
horz = up && horzUp;
|
|
break;
|
|
}
|
|
y--;
|
|
visited[y * matrixWidth + x] = shape;
|
|
}
|
|
|
|
if (cw && x === baseX && y === baseY) {
|
|
paths[shape] += "z";
|
|
return;
|
|
}
|
|
paths[shape] += `v${y - sy}`;
|
|
if (horz) {
|
|
paths[shape] += `a.5.5 0,0,0 -.5-.5`;
|
|
goLeft(x - 1, y - 1, shape, cw);
|
|
} else {
|
|
paths[shape] += `a.5.5 0,0,1 .5-.5`;
|
|
goRight(x, y, shape, cw);
|
|
}
|
|
}
|
|
|
|
function goDown(x, y, shape, cw) {
|
|
let sy = y;
|
|
let horz = false;
|
|
visited[y * matrixWidth + x] = shape;
|
|
while (y < yMax) {
|
|
const down = on(x, y + 1);
|
|
const horzDown = x < xMax && on(x + 1, y + 1);
|
|
if (!down || horzDown) {
|
|
horz = down && horzDown;
|
|
break;
|
|
}
|
|
y++;
|
|
visited[y * matrixWidth + x] = shape;
|
|
}
|
|
paths[shape] += `v${y - sy}`;
|
|
if (horz) {
|
|
paths[shape] += `a.5.5 0,0,0 .5.5`;
|
|
goRight(x + 1, y + 1, shape, cw);
|
|
} else {
|
|
paths[shape] += `a.5.5 0,0,1 -.5.5`;
|
|
goLeft(x, y, shape, cw);
|
|
}
|
|
}
|
|
|
|
const stack = [];
|
|
for (let x = 0; x < matrixWidth; x++) {
|
|
if (!on(x, 0)) stack.push([x, 0]);
|
|
}
|
|
for (let y = 1; y < yMax; y++) {
|
|
if (!on(0, y)) stack.push([0, y]);
|
|
if (!on(xMax, y)) stack.push([xMax, y]);
|
|
}
|
|
for (let x = 0; x < matrixWidth; x++) {
|
|
if (!on(x, yMax)) stack.push([x, yMax]);
|
|
}
|
|
|
|
// recursion dfs limited to ~4000
|
|
// visit all whitespace connected to edges
|
|
function dfsOff() {
|
|
while (stack.length > 0) {
|
|
const [x, y] = stack.pop();
|
|
if (visited[y * matrixWidth + x]) continue;
|
|
visited[y * matrixWidth + x] = 1;
|
|
for (let dy = -1; dy <= 1; dy++) {
|
|
for (let dx = -1; dx <= 1; dx++) {
|
|
if (dy === 0 && dx === 0) continue;
|
|
let nx = x + dx;
|
|
let ny = y + dy;
|
|
if (nx < 0 || nx > xMax || ny < 0 || ny > yMax) continue;
|
|
if (on(nx, ny)) continue;
|
|
stack.push([nx, ny]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
dfsOff();
|
|
|
|
const paths = [""];
|
|
for (let y = 0; y < matrixWidth; y++) {
|
|
for (let x = 0; x < matrixWidth; x++) {
|
|
if (visited[y * matrixWidth + x]) continue;
|
|
|
|
if (!on(x, y)) {
|
|
const shape = visited[y * matrixWidth + x - 1];
|
|
paths[shape] += `M${x + 0.5},${y}a.5.5 0,0,0 -.5.5`;
|
|
|
|
// these indexes are correct, think about it
|
|
baseY = y - 1;
|
|
baseX = x;
|
|
goDown(x - 1, y, shape, false);
|
|
stack.push([x, y]);
|
|
dfsOff();
|
|
continue;
|
|
}
|
|
|
|
if (y > 0 && on(x, y - 1) && visited[(y - 1) * matrixWidth + x]) {
|
|
visited[y * matrixWidth + x] = visited[(y - 1) * matrixWidth + x];
|
|
continue;
|
|
}
|
|
if (x > 0 && on(x - 1, y) && visited[y * matrixWidth + x - 1]) {
|
|
visited[y * matrixWidth + x] = visited[y * matrixWidth + x - 1];
|
|
continue;
|
|
}
|
|
|
|
paths.push(`<path d="M${x},${y + 0.5}a.5.5 0,0,1 .5-.5`);
|
|
|
|
baseY = y;
|
|
baseX = x;
|
|
|
|
goRight(x, y, paths.length - 1, true);
|
|
}
|
|
}
|
|
|
|
paths.forEach((path, i) => {
|
|
if (i === 0) return;
|
|
svg += path;
|
|
svg += `"/>`;
|
|
});
|
|
|
|
svg += `</g></svg>`;
|
|
|
|
return svg;
|
|
}
|