kopia lustrzana https://github.com/zhengkyl/qrframe
fuck, minify breaks function toString
rodzic
dadb2cb282
commit
ae20e21f27
|
@ -156,9 +156,114 @@ const code = `export const paramsSchema = ${objString(paramsSchema)};
|
|||
|
||||
const Module = ${objString(Module)};
|
||||
|
||||
${splitmix32.toString()}
|
||||
function splitmix32(a) {
|
||||
return function () {
|
||||
a |= 0;
|
||||
a = (a + 0x9e3779b9) | 0;
|
||||
let t = a ^ (a >>> 16);
|
||||
t = Math.imul(t, 0x21f0aaad);
|
||||
t = t ^ (t >>> 15);
|
||||
t = Math.imul(t, 0x735a2d97);
|
||||
return ((t = t ^ (t >>> 15)) >>> 0) / 4294967296;
|
||||
};
|
||||
}
|
||||
|
||||
export ${renderCanvas.toString()}
|
||||
export function renderCanvas(qr, params, ctx) {
|
||||
const seededRand = splitmix32(params["Seed"]);
|
||||
const margin = params["Margin"];
|
||||
const quietZone = params["Quiet zone"];
|
||||
|
||||
const pixelSize = 10;
|
||||
const radius = pixelSize / 2;
|
||||
const qrWidth = qr.version * 4 + 17;
|
||||
const matrixWidth = qrWidth + 2 * margin;
|
||||
const canvasSize = matrixWidth * pixelSize;
|
||||
|
||||
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 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 = matrixWidth - 1;
|
||||
const yMax = matrixWidth - 1;
|
||||
|
||||
for (let y = 0; y < matrixWidth; y++) {
|
||||
for (let x = 0; x < matrixWidth; x++) {
|
||||
const module = newMatrix[y * matrixWidth + x];
|
||||
|
||||
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;
|
||||
|
||||
if (module & 1) {
|
||||
ctx.beginPath();
|
||||
ctx.roundRect(x * pixelSize, y * pixelSize, pixelSize, pixelSize, [
|
||||
(!left && !top && radius) || 0,
|
||||
(!top && !right && radius) || 0,
|
||||
(!right && !bottom && radius) || 0,
|
||||
(!bottom && !left && radius) || 0,
|
||||
]);
|
||||
ctx.fill();
|
||||
} else {
|
||||
// Draw rounded concave corners
|
||||
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();
|
||||
ctx.fillStyle = bg;
|
||||
ctx.roundRect(x * pixelSize, y * pixelSize, pixelSize, pixelSize, [
|
||||
(left && top && topLeft && radius) || 0,
|
||||
(top && right && topRight && radius) || 0,
|
||||
(right && bottom && bottomRight && radius) || 0,
|
||||
(bottom && left && bottomLeft && radius) || 0,
|
||||
]);
|
||||
ctx.fill();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export default {
|
||||
|
|
|
@ -181,7 +181,149 @@ const code = `export const paramsSchema = ${objString(paramsSchema)};
|
|||
|
||||
const Module = ${objString(Module)};
|
||||
|
||||
export ${renderCanvas.toString()}
|
||||
export function renderCanvas(qr, params, ctx) {
|
||||
const pixelSize = 10;
|
||||
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);
|
||||
|
||||
const gradient = ctx.createRadialGradient(
|
||||
ctx.canvas.width / 2,
|
||||
ctx.canvas.height / 2,
|
||||
2 * pixelSize,
|
||||
ctx.canvas.width / 2,
|
||||
ctx.canvas.height / 2,
|
||||
20 * pixelSize
|
||||
);
|
||||
|
||||
gradient.addColorStop(0, "red");
|
||||
gradient.addColorStop(1, "blue");
|
||||
|
||||
ctx.fillStyle = gradient;
|
||||
|
||||
const radius = pixelSize / 2;
|
||||
|
||||
const finderPos = [
|
||||
[margin, margin],
|
||||
[margin + matrixWidth - 7, margin],
|
||||
[margin, margin + matrixWidth - 7],
|
||||
];
|
||||
|
||||
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 = matrixWidth / 2;
|
||||
const yMid = matrixWidth / 2;
|
||||
const maxDist = Math.sqrt(xMid * xMid + yMid * yMid);
|
||||
|
||||
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 (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) * 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(
|
||||
xPos * pixelSize,
|
||||
yPos * pixelSize,
|
||||
2.5 * pixelSize,
|
||||
0,
|
||||
2 * Math.PI
|
||||
);
|
||||
ctx.fill();
|
||||
|
||||
ctx.fillStyle = "rgb(255, 255, 255)";
|
||||
ctx.beginPath();
|
||||
ctx.arc(
|
||||
xPos * pixelSize,
|
||||
yPos * pixelSize,
|
||||
1.5 * pixelSize,
|
||||
0,
|
||||
2 * Math.PI
|
||||
);
|
||||
ctx.fill();
|
||||
|
||||
ctx.fillStyle = gradient;
|
||||
ctx.beginPath();
|
||||
ctx.arc(
|
||||
xPos * pixelSize,
|
||||
yPos * pixelSize,
|
||||
0.5 * pixelSize,
|
||||
0,
|
||||
2 * Math.PI
|
||||
);
|
||||
ctx.fill();
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
const xCenter = (x + margin) * pixelSize + radius;
|
||||
const yCenter = (y + margin) * pixelSize + radius;
|
||||
|
||||
const xDist = Math.abs(xMid - x);
|
||||
const yDist = Math.abs(yMid - y);
|
||||
const scale =
|
||||
(Math.sqrt(xDist * xDist + yDist * yDist) / maxDist) * 0.7 + 0.5;
|
||||
|
||||
ctx.beginPath();
|
||||
ctx.arc(xCenter, yCenter, radius * scale, 0, 2 * Math.PI);
|
||||
ctx.fill();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export default {
|
||||
|
|
|
@ -203,7 +203,138 @@ const code = `export const paramsSchema = ${objString(paramsSchema)};
|
|||
|
||||
const Module = ${objString(Module)};
|
||||
|
||||
export ${renderCanvas.toString()}
|
||||
export async function renderCanvas(qr, params, ctx) {
|
||||
const moduleSize = 3;
|
||||
const pixelSize = 1;
|
||||
|
||||
const matrixWidth = qr.version * 4 + 17;
|
||||
const margin = params["Margin"];
|
||||
const fg = params["Foreground"];
|
||||
const bg = params["Background"];
|
||||
const alignment = params["Alignment pattern"];
|
||||
const timing = params["Timing pattern"];
|
||||
const file = params["Image"];
|
||||
|
||||
const pixelWidth = matrixWidth + 2 * margin;
|
||||
const canvasSize = pixelWidth * moduleSize;
|
||||
ctx.canvas.width = canvasSize;
|
||||
ctx.canvas.height = canvasSize;
|
||||
|
||||
if (params["QR background"]) {
|
||||
ctx.fillStyle = bg;
|
||||
ctx.fillRect(0, 0, canvasSize, canvasSize);
|
||||
ctx.fillStyle = fg;
|
||||
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 + margin) * moduleSize,
|
||||
(y + margin) * moduleSize,
|
||||
moduleSize,
|
||||
moduleSize
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const image = new Image();
|
||||
|
||||
if (file != null) {
|
||||
image.src = URL.createObjectURL(file);
|
||||
} else {
|
||||
// if canvas tainted, need to reload
|
||||
// https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_enabled_image
|
||||
image.crossOrigin = "anonymous";
|
||||
image.src =
|
||||
"https://upload.wikimedia.org/wikipedia/commons/1/14/The_Widow_%28Boston_Public_Library%29_%28cropped%29.jpg";
|
||||
}
|
||||
await image.decode();
|
||||
|
||||
ctx.filter = \`brightness(\${params["Brightness"]}) contrast(\${params["Contrast"]})\`;
|
||||
ctx.drawImage(image, 0, 0, canvasSize, canvasSize);
|
||||
ctx.filter = "none";
|
||||
|
||||
if (file != null) {
|
||||
URL.revokeObjectURL(image.src);
|
||||
}
|
||||
|
||||
const imageData = ctx.getImageData(0, 0, canvasSize, canvasSize);
|
||||
const data = imageData.data;
|
||||
|
||||
for (let y = 0; y < canvasSize; y++) {
|
||||
for (let x = 0; x < canvasSize; x++) {
|
||||
const i = (y * canvasSize + x) * 4;
|
||||
|
||||
if (data[i + 3] === 0) continue;
|
||||
// Convert to grayscale and normalize to 0-255
|
||||
const oldPixel =
|
||||
(data[i] * 0.299 + data[i + 1] * 0.587 + data[i + 2] * 0.114) | 0;
|
||||
|
||||
let newPixel;
|
||||
if (oldPixel < 128) {
|
||||
newPixel = 0;
|
||||
ctx.fillStyle = fg;
|
||||
} else {
|
||||
newPixel = 255;
|
||||
ctx.fillStyle = bg;
|
||||
}
|
||||
ctx.fillRect(x * pixelSize, y * pixelSize, pixelSize, pixelSize);
|
||||
|
||||
data[i] = data[i + 1] = data[i + 2] = newPixel;
|
||||
const error = oldPixel - newPixel;
|
||||
|
||||
// Distribute error to neighboring pixels
|
||||
if (x < canvasSize - 1) {
|
||||
data[i + 4] += (error * 7) / 16;
|
||||
}
|
||||
if (y < canvasSize - 1) {
|
||||
if (x > 0) {
|
||||
data[i + canvasSize * 4 - 4] += (error * 3) / 16;
|
||||
}
|
||||
data[i + canvasSize * 4] += (error * 5) / 16;
|
||||
if (x < canvasSize - 1) {
|
||||
data[i + canvasSize * 4 + 4] += (error * 1) / 16;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const dataOffset = (moduleSize - pixelSize) / 2;
|
||||
|
||||
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.fillStyle = fg;
|
||||
} else {
|
||||
ctx.fillStyle = bg;
|
||||
}
|
||||
|
||||
const type = module | 1;
|
||||
if (
|
||||
type === Module.FinderON ||
|
||||
(alignment && type === Module.AlignmentON) ||
|
||||
(timing && type === Module.TimingON)
|
||||
) {
|
||||
ctx.fillRect(
|
||||
(x + margin) * moduleSize,
|
||||
(y + margin) * moduleSize,
|
||||
moduleSize,
|
||||
moduleSize
|
||||
);
|
||||
} else {
|
||||
ctx.fillRect(
|
||||
(x + margin) * moduleSize + dataOffset,
|
||||
(y + margin) * moduleSize + dataOffset,
|
||||
pixelSize,
|
||||
pixelSize
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export default {
|
||||
|
|
|
@ -102,7 +102,67 @@ const code = `export const paramsSchema = ${objString(paramsSchema)};
|
|||
|
||||
const Module = ${objString(Module)};
|
||||
|
||||
export ${renderSVG.toString()}
|
||||
export function renderSVG(qr, params) {
|
||||
const matrixWidth = qr.version * 4 + 17;
|
||||
const margin = params["Margin"];
|
||||
const dataSize = params["Data pixel size"];
|
||||
const moduleSize = 10;
|
||||
const fg = "#000";
|
||||
const bg = "#fff";
|
||||
|
||||
const finderPos = [
|
||||
[margin, margin],
|
||||
[matrixWidth + margin - 7, margin],
|
||||
[margin, matrixWidth + margin - 7],
|
||||
];
|
||||
|
||||
const svgSize = (matrixWidth + 2 * margin) * moduleSize;
|
||||
|
||||
let svg = \`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 \${svgSize} \${svgSize}">\`;
|
||||
if (params["Background"]) {
|
||||
svg += \`<rect width="\${svgSize}" height="\${svgSize}" fill="\${bg}"/>\`;
|
||||
}
|
||||
svg += \`<path fill="\${fg}" d="\`;
|
||||
|
||||
for (const [x, y] of finderPos) {
|
||||
svg += \`M\${(x + 3) * moduleSize},\${
|
||||
y * moduleSize
|
||||
}h\${moduleSize}v\${moduleSize}h-\${moduleSize}z\`;
|
||||
svg += \`M\${x * moduleSize},\${
|
||||
(y + 3) * moduleSize
|
||||
}h\${moduleSize}v\${moduleSize}h-\${moduleSize}z\`;
|
||||
svg += \`M\${(x + 6) * moduleSize},\${
|
||||
(y + 3) * moduleSize
|
||||
}h\${moduleSize}v\${moduleSize}h-\${moduleSize}z\`;
|
||||
svg += \`M\${(x + 3) * moduleSize},\${
|
||||
(y + 6) * moduleSize
|
||||
}h\${moduleSize}v\${moduleSize}h-\${moduleSize}z\`;
|
||||
|
||||
svg += \`M\${(x + 2) * moduleSize},\${(y + 2) * moduleSize}h\${
|
||||
moduleSize * 3
|
||||
}v\${moduleSize * 3}h-\${moduleSize * 3}z\`;
|
||||
}
|
||||
|
||||
const offset = (moduleSize - dataSize) / 2;
|
||||
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) {
|
||||
const sx = (x + margin) * moduleSize + offset;
|
||||
const sy = (y + margin) * moduleSize + offset;
|
||||
svg += \`M\${sx},\${sy}h\${dataSize}v\${dataSize}h-\${dataSize}z\`;
|
||||
}
|
||||
}
|
||||
}
|
||||
svg += \`"/></svg>\`;
|
||||
|
||||
return svg;
|
||||
}
|
||||
`;
|
||||
|
||||
export default {
|
||||
|
|
|
@ -47,7 +47,30 @@ function renderSVG(qr: OutputQr, params: Params<typeof paramsSchema>) {
|
|||
|
||||
const code = `export const paramsSchema = ${objString(paramsSchema)};
|
||||
|
||||
export ${renderSVG.toString()}
|
||||
export function renderSVG(qr, params) {
|
||||
const matrixWidth = qr.version * 4 + 17;
|
||||
const margin = params["Margin"];
|
||||
const fg = params["Foreground"];
|
||||
const bg = params["Background"];
|
||||
|
||||
const size = matrixWidth + 2 * margin;
|
||||
|
||||
let svg = \`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 \${size} \${size}">\`;
|
||||
svg += \`<rect width="\${size}" height="\${size}" fill="\${bg}"/>\`;
|
||||
svg += \`<path fill="\${fg}" d="\`;
|
||||
|
||||
for (let y = 0; y < matrixWidth; y++) {
|
||||
for (let x = 0; x < matrixWidth; x++) {
|
||||
const module = qr.matrix[y * matrixWidth + x];
|
||||
if (module & 1) {
|
||||
svg += \`M\${x + margin},\${y + margin}h1v1h-1z\`;
|
||||
}
|
||||
}
|
||||
}
|
||||
svg += \`"/></svg>\`;
|
||||
|
||||
return svg;
|
||||
}
|
||||
`;
|
||||
|
||||
export default {
|
||||
|
|
Ładowanie…
Reference in New Issue