lowercase param types + replace square with basic and tutorial

main
Kyle Zheng 2024-08-31 20:21:10 -04:00
rodzic f6bafd0e0f
commit 7a7c3853a3
37 zmienionych plików z 499 dodań i 170 usunięć

Wyświetl plik

@ -9,15 +9,15 @@ export const paramsSchema = {
default: 2,
},
Background: {
type: "Color",
type: "color",
default: "#ffffff",
},
Dots: {
type: "Color",
type: "color",
default: "#000000",
},
Lines: {
type: "Color",
type: "color",
default: "#000000",
},
Seed: {

126
presets/Basic.js 100644
Wyświetl plik

@ -0,0 +1,126 @@
export const paramsSchema = {
Margin: {
type: "number",
min: 0,
max: 10,
step: 0.1,
default: 2,
},
Foreground: {
type: "color",
default: "#000000",
},
Background: {
type: "color",
default: "#ffffff",
},
Roundness: {
type: "number",
min: 0,
max: 1,
step: 0.01,
default: 0,
},
"Data size": {
type: "number",
min: 0.5,
max: 1.5,
step: 0.1,
default: 1,
},
Logo: {
type: "file",
accept: ".jpeg, .jpg, .png, .svg",
},
"Logo size": {
type: "number",
min: 0,
max: 1,
step: 0.01,
default: 0.25,
},
};
const Module = {
DataOFF: 0,
DataON: 1,
FinderOFF: 2,
FinderON: 3,
AlignmentOFF: 4,
AlignmentON: 5,
TimingOFF: 6,
TimingON: 7,
FormatOFF: 8,
FormatON: 9,
VersionOFF: 10,
VersionON: 11,
SeparatorOFF: 12,
};
// unformatted floats can bloat file size
const fmt = (n) => n.toFixed(2).replace(/\.00$/, "");
export async function renderSVG(qr, params) {
const matrixWidth = qr.version * 4 + 17;
const margin = params["Margin"];
const fg = params["Foreground"];
const bg = params["Background"];
const roundness = params["Roundness"];
const file = params["Logo"];
const size = matrixWidth + 2 * margin;
let svg = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="${-margin} ${-margin} ${size} ${size}">`;
svg += `<rect x="${-margin}" y="${-margin}" width="${size}" height="${size}" fill="${bg}"/>`;
svg += `<g fill="${fg}">`;
svg += `<path d="`;
const lgRadius = 3.5 * roundness;
const mdRadius = 2.5 * roundness;
const smRadius = 1.5 * roundness;
const lgSide = fmt(7 - 2 * lgRadius);
const mdSide = fmt(5 - 2 * mdRadius);
const smSide = fmt(3 - 2 * smRadius);
const corner = (radius, xDir, yDir, cw) =>
`a${fmt(radius)},${fmt(radius)} 0,0,${cw ? "1" : "0"} ${fmt(xDir * radius)},${fmt(yDir * radius)}`;
for (const [x, y] of [
[0, 0],
[matrixWidth - 7, 0],
[0, matrixWidth - 7],
]) {
svg += `M${fmt(x + lgRadius)},${y}h${lgSide}${corner(lgRadius, 1, 1, true)}v${lgSide}${corner(lgRadius, -1, 1, true)}h-${lgSide}${corner(lgRadius, -1, -1, true)}v-${lgSide}${corner(lgRadius, 1, -1, true)}`;
svg += `M${fmt(x + 1 + mdRadius)},${y + 1}${corner(mdRadius, -1, 1, false)}v${mdSide}${corner(mdRadius, 1, 1, false)}h${mdSide}${corner(mdRadius, 1, -1, false)}v-${mdSide}${corner(mdRadius, -1, -1, false)}`;
svg += `M${fmt(x + 2 + smRadius)},${y + 2}h${smSide}${corner(smRadius, 1, 1, true)}v${smSide}${corner(smRadius, -1, 1, true)}h-${smSide}${corner(smRadius, -1, -1, true)}v-${smSide}${corner(smRadius, 1, -1, true)}`;
}
svg += `"/>`;
const dataSize = params["Data size"];
const dataRadius = fmt((roundness * dataSize) / 2);
const dataOffset = (1 - 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) {
if (module === Module.FinderON) continue;
svg += `<rect x="${fmt(x + dataOffset)}" y="${fmt(y + dataOffset)}" width="${dataSize}" height="${dataSize}" rx="${dataRadius}"/>`;
}
}
}
svg += `</g>`;
if (file != null) {
const bytes = await file.bytes();
const b64 = btoa(
Array.from(bytes, (byte) => String.fromCodePoint(byte)).join("")
);
const logoSize = fmt(params["Logo size"] * size);
const logoOffset = fmt(((1 - params["Logo size"]) * size) / 2 - margin);
svg += `<image x="${logoOffset}" y="${logoOffset}" width="${logoSize}" height="${logoSize}" href="data:${file.type};base64,${b64}"/>`;
}
svg += `</svg>`;
return svg;
}

Wyświetl plik

@ -9,23 +9,23 @@ export const paramsSchema = {
default: 2,
},
Background: {
type: "Color",
type: "color",
default: "#ffffff",
},
Finder: {
type: "Color",
type: "color",
default: "#131d87",
},
Horizontal: {
type: "Color",
type: "color",
default: "#dc9c07",
},
Vertical: {
type: "Color",
type: "color",
default: "#d21313",
},
Cross: {
type: "Color",
type: "color",
default: "#131d87",
},
"Horizontal thickness": {

Wyświetl plik

@ -9,27 +9,27 @@ export const paramsSchema = {
default: 2,
},
Background: {
type: "Color",
type: "color",
default: "#ffffff",
},
Finder: {
type: "Color",
type: "color",
default: "#141e92",
},
"Large circle": {
type: "Color",
type: "color",
default: "#10a8e9",
},
"Medium circle": {
type: "Color",
type: "color",
default: "#1aa8cc",
},
"Small circle": {
type: "Color",
type: "color",
default: "#0f8bdd",
},
"Tiny circle": {
type: "Color",
type: "color",
default: "#012c8f",
},
"Randomize circle size": {

Wyświetl plik

@ -1,10 +1,10 @@
export const paramsSchema = {
Foreground: {
type: "Color",
type: "color",
default: "#1c4a1a",
},
Background: {
type: "Color",
type: "color",
default: "#e3d68a",
},
Margin: {

Wyświetl plik

@ -13,23 +13,23 @@ export const paramsSchema = {
default: 0,
},
Foreground: {
type: "Color",
type: "color",
default: "#000000",
},
Background: {
type: "Color",
type: "color",
default: "#ffffff",
},
"Finder pattern": {
type: "Select",
type: "select",
options: ["Default", "Circle", "Square"],
},
"Alignment pattern": {
type: "Select",
type: "select",
options: ["Default", "Circle", "Square"],
},
"Scale direction": {
type: "Select",
type: "select",
options: ["None", "Center", "Edge"],
},
Seed: {

Wyświetl plik

@ -20,21 +20,21 @@ export const paramsSchema = {
default: 1.3,
},
Foreground: {
type: "Array",
type: "array",
resizable: true,
props: {
type: "Color",
type: "color",
},
default: ["#f7158b", "#02d1fd", "#1f014b"],
},
Background: {
type: "Color",
type: "color",
default: "#ffffff",
},
// See browser compatibility issues here
// https://developer.mozilla.org/en-US/docs/Web/CSS/mix-blend-mode
"Mix blend mode": {
type: "Select",
type: "select",
options: [
"normal",
"multiply",

Wyświetl plik

@ -8,7 +8,7 @@ export const paramsSchema = {
default: 2,
},
"Fill style": {
type: "Select",
type: "select",
options: [
"Hachure",
"Solid",
@ -21,7 +21,7 @@ export const paramsSchema = {
default: "Zigzag",
},
Fill: {
type: "Color",
type: "color",
default: "#ffffff",
},
"Fill weight": {
@ -37,7 +37,7 @@ export const paramsSchema = {
default: 4,
},
Stroke: {
type: "Color",
type: "color",
default: "#ffffff",
},
"Stroke width": {
@ -63,7 +63,7 @@ export const paramsSchema = {
default: 1,
},
Background: {
type: "Color",
type: "color",
default: "#222222",
},
Seed: {

Wyświetl plik

@ -6,11 +6,11 @@ export const paramsSchema = {
default: 2,
},
Foreground: {
type: "Color",
type: "color",
default: "#000000",
},
Background: {
type: "Color",
type: "color",
default: "#fcb9ff",
},
Shapes: {
@ -40,7 +40,7 @@ export const paramsSchema = {
default: 0.3,
},
"QR layer": {
type: "Select",
type: "select",
options: ["Above", "Below"],
},
Seed: {

Wyświetl plik

@ -1,7 +1,7 @@
// Halftone is a misnomer, but that's what this type of QR code is known as
export const paramsSchema = {
Image: {
type: "File",
type: "file",
},
Contrast: {
type: "number",
@ -34,11 +34,11 @@ export const paramsSchema = {
default: 2,
},
Foreground: {
type: "Color",
type: "color",
default: "#000000",
},
Background: {
type: "Color",
type: "color",
default: "#ffffff",
},
};
@ -75,7 +75,7 @@ export async function renderCanvas(qr, params, canvas) {
"https://upload.wikimedia.org/wikipedia/commons/1/14/The_Widow_%28Boston_Public_Library%29_%28cropped%29.jpg"
).then((res) => res.blob());
}
const image = await createImageBitmap(file)
const image = await createImageBitmap(file);
const pixelWidth = matrixWidth + 2 * margin;
const canvasSize = pixelWidth * unit;

Wyświetl plik

@ -6,19 +6,19 @@ export const paramsSchema = {
default: 2,
},
Foreground: {
type: "Array",
type: "array",
props: {
type: "Color",
type: "color",
},
resizable: true,
default: ["#860909","#0e21a0","#95800f"]
default: ["#860909", "#0e21a0", "#95800f"],
},
Background: {
type: "Color",
type: "color",
default: "#ffffff",
},
Lines: {
type: "Color",
type: "color",
default: "#000000",
},
"Line thickness": {
@ -62,7 +62,9 @@ export function renderSVG(qr, params) {
svg += `<rect width="${size}" height="${size}" fill="${params["Lines"]}"/>`;
let lightLayer = `<path fill="${params["Background"]}" d="`;
const darkLayers = params["Foreground"].map((color) => `<path fill="${color}" d="`)
const darkLayers = params["Foreground"].map(
(color) => `<path fill="${color}" d="`
);
const visited = Array.from({ length: matrixWidth * matrixWidth }).fill(false);
const matrix = Array.from({ length: matrixWidth * matrixWidth }).fill(0);
@ -127,7 +129,7 @@ export function renderSVG(qr, params) {
}
}
}
darkLayers.forEach((layer) => svg += layer + `"/>`)
darkLayers.forEach((layer) => (svg += layer + `"/>`));
svg += lightLayer + `"/>`;
svg += `</svg>`;

Wyświetl plik

@ -1,15 +1,15 @@
export const paramsSchema = {
Foreground: {
type: "Array" ,
type: "array",
props: {
type: "Color",
type: "color",
},
resizable: true,
default: [ "#fb51dd", "#f2cffa", "#aefdfd", "#54a9fe" ]
default: ["#fb51dd", "#f2cffa", "#aefdfd", "#54a9fe"],
},
Background: {
type: "Color",
default: "#101529"
type: "color",
default: "#101529",
},
Margin: {
type: "number",
@ -18,7 +18,7 @@ export const paramsSchema = {
default: 4,
},
"Quiet zone": {
type: "Select",
type: "select",
options: ["Minimal", "Full"],
},
Invert: {

Wyświetl plik

@ -9,15 +9,15 @@ export const paramsSchema = {
default: 2,
},
Background: {
type: "Color",
type: "color",
default: "#ffffff",
},
Foreground: {
type: "Color",
type: "color",
default: "#000000",
},
"Finder pattern": {
type: "Select",
type: "select",
options: ["Atom", "Planet"],
},
Particles: {

Wyświetl plik

@ -18,15 +18,15 @@ export const paramsSchema = {
default: 6,
},
Foreground: {
type: "Color",
type: "color",
default: "#000000",
},
Background: {
type: "Color",
type: "color",
default: "#ffffff",
},
Grout: {
type: "Color",
type: "color",
default: "#b3b8fd",
},
};

Wyświetl plik

@ -7,11 +7,11 @@ export const paramsSchema = {
default: 2,
},
Foreground: {
type: "Color",
type: "color",
default: "#000000",
},
Background: {
type: "Color",
type: "color",
default: "#ffffff",
},
};
@ -39,3 +39,28 @@ export function renderSVG(qr, params) {
return svg;
}
// export function renderCanvas(qr, params, canvas) {
// const matrixWidth = qr.version * 4 + 17;
// const margin = params["Margin"];
// const fg = params["Foreground"];
// const bg = params["Background"];
// const unit = 10;
// const size = (matrixWidth + 2 * margin) * unit;
// canvas.width = size;
// canvas.height = size;
// const ctx = canvas.getContext("2d");
// ctx.fillStyle = bg;
// ctx.fillRect(0, 0, size, size)
// 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) * unit, (y + margin) * unit, unit, unit)
// }
// }
// }
// }

Wyświetl plik

@ -5,9 +5,10 @@ import { FlatButton } from "./Button";
type Props = {
value: File | null;
setValue: (f: File | null) => void;
accept?: string;
};
export function ImageInput(props: Props) {
export function FileInput(props: Props) {
let input: HTMLInputElement;
return (
<div class="inline-flex items-center gap-1">
@ -15,7 +16,7 @@ export function ImageInput(props: Props) {
class="border rounded-md text-sm px-1 py-2 file:(bg-transparent border-none text-fore-base) bg-back-base hover:bg-fore-base/5 focus-visible:(outline-none ring-2 ring-fore-base ring-offset-2 ring-offset-back-base)"
ref={input!}
type="file"
accept=".jpeg, .jpg, .png"
accept={props.accept}
onChange={(e) => {
// @ts-expect-error onChange is called so files exists
props.setValue(e.target.files[0]);

Wyświetl plik

@ -7,20 +7,23 @@ import { createSignal } from "solid-js";
import { FillButton } from "./Button";
type Props = {
onClick: (width, height) => void;
onClick: (resizeWidth, resizeHeight) => void;
};
export function SplitButton(props: Props) {
const [customWidth, setCustomWidth] = createSignal(1000);
const [customHeight, setCustomHeight] = createSignal(1000);
const onClick = (width, height) => {
props.onClick(width, height);
const onClick = (resizeWidth, resizeHeight) => {
props.onClick(resizeWidth, resizeHeight);
setOpen(false);
};
const [open, setOpen] = createSignal(false);
return (
<div class="leading-tight flex">
<Button class="border border-e-none rounded-md rounded-e-none hover:bg-fore-base/5 focus-visible:(outline-none ring-2 ring-fore-base ring-offset-2 ring-offset-back-base) inline-flex justify-center items-center gap-1 flex-1 px-6 py-2">
<Button
class="border border-e-none rounded-md rounded-e-none hover:bg-fore-base/5 focus-visible:(outline-none ring-2 ring-fore-base ring-offset-2 ring-offset-back-base) inline-flex justify-center items-center gap-1 flex-1 px-6 py-2"
onClick={() => onClick(0, 0)}
>
<Download size={20} />
PNG
</Button>

Wyświetl plik

@ -23,7 +23,7 @@ export function ParamsEditor() {
<div class="flex flex-col gap-2 mb-4">
<For each={Object.entries(paramsSchema())}>
{([label, { type, ...other }]) => {
if (type === "Array") {
if (type === "array") {
return <ArrayParam label={label} other={other} />;
}
return (

Wyświetl plik

@ -1,18 +1,8 @@
import Pencil from "lucide-solid/icons/pencil";
import Trash2 from "lucide-solid/icons/trash-2";
import {
For,
Index,
Show,
batch,
createSignal,
onMount,
type JSX,
} from "solid-js";
import { For, Show, batch, createSignal, onMount, type JSX } from "solid-js";
import { createStore } from "solid-js/store";
import { Dynamic } from "solid-js/web";
import {
PARAM_COMPONENTS,
defaultParams,
deepEqualObj,
parseParamsSchema,
@ -27,9 +17,8 @@ import { TextInput, TextareaInput } from "../TextInput";
import { CodeEditor } from "./CodeEditor";
import { Settings } from "./Settings";
import { clearToasts, toastError } from "../ErrorToasts";
import Minus from "lucide-solid/icons/minus";
import Plus from "lucide-solid/icons/plus";
import { ParamsEditor } from "./ParamsEditor";
import { Tutorial } from "~/lib/presets/Tutorial";
type Props = {
class?: string;
@ -62,7 +51,7 @@ export function Editor(props: Props) {
setRender,
} = useQrContext();
const [code, setCode] = createSignal(PRESET_CODE.Square);
const [code, setCode] = createSignal(PRESET_CODE.Basic);
const [funcKeys, _setFuncKeys] = createStore<string[]>([]);
const [thumbs, setThumbs] = createStore<Thumbs>({} as Thumbs);
@ -413,9 +402,7 @@ export function Editor(props: Props) {
)}
</For>
<Preview
onClick={() =>
createAndSelectFunc("custom", PRESET_CODE.Square)
}
onClick={() => createAndSelectFunc("custom", Tutorial)}
label="Create new"
active={false}
>
@ -429,7 +416,7 @@ export function Editor(props: Props) {
</Preview>
</div>
</div>
<ParamsEditor/>
<ParamsEditor />
<CodeEditor
initialValue={code()}
onSave={(code, updateThumbnail) => {

Wyświetl plik

@ -185,14 +185,34 @@ function RenderedQrCode() {
<div class="font-bold text-sm pb-2">Downloads</div>
<div class="grid grid-cols-2 gap-2">
<SplitButton
onClick={async (width, height) => {
onClick={async (resizeWidth, resizeHeight) => {
let outCanvas;
if (render()?.type === "canvas") {
download(canvas.toDataURL("image/png"), `${filename()}.png`);
if (resizeWidth === 0 && resizeHeight === 0) {
outCanvas = canvas;
} else {
const bmp = await createImageBitmap(canvas, {
// resizeQuality unsupported in ff 8/31/24
// https://developer.mozilla.org/en-US/docs/Web/API/createImageBitmap
resizeQuality: "pixelated",
resizeWidth,
resizeHeight,
});
outCanvas = document.createElement("canvas");
outCanvas.width = resizeWidth;
outCanvas.height = resizeHeight;
const ctx = outCanvas.getContext("bitmaprenderer")!;
ctx.transferFromImageBitmap(bmp);
}
} else {
const canvas = document.createElement("canvas");
const ctx = canvas.getContext("2d")!;
canvas.width = width;
canvas.height = height;
if (resizeWidth === 0 && resizeHeight === 0) {
resizeWidth = 300;
resizeHeight = 300;
}
outCanvas = document.createElement("canvas");
outCanvas.width = resizeWidth;
outCanvas.height = resizeHeight;
const ctx = outCanvas.getContext("2d")!;
const url = URL.createObjectURL(
new Blob([svgParent.innerHTML], { type: "image/svg+xml" })
@ -200,11 +220,22 @@ function RenderedQrCode() {
const img = new Image();
img.src = url;
await img.decode();
ctx.drawImage(img, 0, 0, width, height);
download(canvas.toDataURL("image/png"), `${filename()}.png`);
ctx.drawImage(img, 0, 0, resizeWidth, resizeHeight);
URL.revokeObjectURL(url);
}
outCanvas.toBlob((blob) => {
if (blob == null) {
toastError(
"Failed to create image",
"idk bro this shouldn't happen"
);
return;
}
const url = URL.createObjectURL(blob);
download(url, `${filename()}.png`);
URL.revokeObjectURL(url);
});
}}
/>
<Show when={render()?.type === "svg"}>

Wyświetl plik

@ -1,35 +1,35 @@
import { ColorInput } from "~/components/ColorInput";
import { ImageInput } from "~/components/ImageInput";
import { FileInput } from "~/components/ImageInput";
import { NumberInput } from "~/components/NumberInput";
import { Select } from "~/components/Select";
import { Switch } from "~/components/Switch";
const PARAM_TYPES = ["boolean", "number", "Color", "Select", "File", "Array"];
const PARAM_TYPES = ["boolean", "number", "color", "select", "file", "array"];
export const PARAM_COMPONENTS = {
boolean: Switch,
number: NumberInput,
Color: ColorInput,
Select: Select,
File: ImageInput,
color: ColorInput,
select: Select,
file: FileInput,
};
export const PARAM_DEFAULTS = {
boolean: false,
number: 0,
Color: "#000000",
// Select: //default is first option
File: null,
Array: [],
color: "#000000",
// select: //default is first option
file: null,
array: [],
};
type PARAM_VALUE_TYPES = {
boolean: boolean;
number: number;
Color: string;
Select: any;
File: File | null;
Array: any[];
color: string;
select: any;
file: File | null;
array: any[];
};
export type Params = {
@ -94,7 +94,7 @@ function parseField(value: any) {
!PARAM_TYPES.includes(value.type)
) {
return null;
} else if (value.type === "Select") {
} else if (value.type === "select") {
if (
!("options" in value) ||
!Array.isArray(value.options) ||
@ -102,7 +102,7 @@ function parseField(value: any) {
) {
return null;
}
} else if (value.type === "Array") {
} else if (value.type === "array") {
if (!("props" in value)) {
return null;
}
@ -115,9 +115,9 @@ function parseField(value: any) {
// === undefined b/c null is a valid default value for ImageInput
if (value.default === undefined) {
if (value.type === "Select") {
if (value.type === "select") {
value.default = value.options[0];
} else if (value.type === "Array") {
} else if (value.type === "array") {
value.default = Array.from(
{ length: value.defaultLength ?? 1 },
() => value.props.default

Wyświetl plik

@ -1,4 +1,5 @@
import { Alien } from "./presets/Alien";
import { Basic } from "./presets/Basic";
import { Blocks } from "./presets/Blocks";
import { Bubbles } from "./presets/Bubbles";
import { Camo } from "./presets/Camo";
@ -11,11 +12,10 @@ import { Minimal } from "./presets/Minimal";
import { Mondrian } from "./presets/Mondrian";
import { Neon } from "./presets/Neon";
import { Quantum } from "./presets/Quantum";
import { Square } from "./presets/Square";
import { Tile } from "./presets/Tile";
export const PRESET_CODE = {
Square,
Basic,
Circle,
Camo,
Neon,

Wyświetl plik

@ -9,15 +9,15 @@ export const paramsSchema = {
default: 2,
},
Background: {
type: "Color",
type: "color",
default: "#ffffff",
},
Dots: {
type: "Color",
type: "color",
default: "#000000",
},
Lines: {
type: "Color",
type: "color",
default: "#000000",
},
Seed: {

Wyświetl plik

@ -0,0 +1,127 @@
export const Basic = `export const paramsSchema = {
Margin: {
type: "number",
min: 0,
max: 10,
step: 0.1,
default: 2,
},
Foreground: {
type: "color",
default: "#000000",
},
Background: {
type: "color",
default: "#ffffff",
},
Roundness: {
type: "number",
min: 0,
max: 1,
step: 0.01,
default: 0,
},
"Data size": {
type: "number",
min: 0.5,
max: 1.5,
step: 0.1,
default: 1,
},
Logo: {
type: "file",
accept: ".jpeg, .jpg, .png, .svg",
},
"Logo size": {
type: "number",
min: 0,
max: 1,
step: 0.01,
default: 0.25,
},
};
const Module = {
DataOFF: 0,
DataON: 1,
FinderOFF: 2,
FinderON: 3,
AlignmentOFF: 4,
AlignmentON: 5,
TimingOFF: 6,
TimingON: 7,
FormatOFF: 8,
FormatON: 9,
VersionOFF: 10,
VersionON: 11,
SeparatorOFF: 12,
};
// unformatted floats can bloat file size
const fmt = (n) => n.toFixed(2).replace(/\.00$/, "");
export async function renderSVG(qr, params) {
const matrixWidth = qr.version * 4 + 17;
const margin = params["Margin"];
const fg = params["Foreground"];
const bg = params["Background"];
const roundness = params["Roundness"];
const file = params["Logo"];
const size = matrixWidth + 2 * margin;
let svg = \`<svg xmlns="http://www.w3.org/2000/svg" viewBox="\${-margin} \${-margin} \${size} \${size}">\`;
svg += \`<rect x="\${-margin}" y="\${-margin}" width="\${size}" height="\${size}" fill="\${bg}"/>\`;
svg += \`<g fill="\${fg}">\`;
svg += \`<path d="\`;
const lgRadius = 3.5 * roundness;
const mdRadius = 2.5 * roundness;
const smRadius = 1.5 * roundness;
const lgSide = fmt(7 - 2 * lgRadius);
const mdSide = fmt(5 - 2 * mdRadius);
const smSide = fmt(3 - 2 * smRadius);
const corner = (radius, xDir, yDir, cw) =>
\`a\${fmt(radius)},\${fmt(radius)} 0,0,\${cw ? "1" : "0"} \${fmt(xDir * radius)},\${fmt(yDir * radius)}\`;
for (const [x, y] of [
[0, 0],
[matrixWidth - 7, 0],
[0, matrixWidth - 7],
]) {
svg += \`M\${fmt(x + lgRadius)},\${y}h\${lgSide}\${corner(lgRadius, 1, 1, true)}v\${lgSide}\${corner(lgRadius, -1, 1, true)}h-\${lgSide}\${corner(lgRadius, -1, -1, true)}v-\${lgSide}\${corner(lgRadius, 1, -1, true)}\`;
svg += \`M\${fmt(x + 1 + mdRadius)},\${y + 1}\${corner(mdRadius, -1, 1, false)}v\${mdSide}\${corner(mdRadius, 1, 1, false)}h\${mdSide}\${corner(mdRadius, 1, -1, false)}v-\${mdSide}\${corner(mdRadius, -1, -1, false)}\`;
svg += \`M\${fmt(x + 2 + smRadius)},\${y + 2}h\${smSide}\${corner(smRadius, 1, 1, true)}v\${smSide}\${corner(smRadius, -1, 1, true)}h-\${smSide}\${corner(smRadius, -1, -1, true)}v-\${smSide}\${corner(smRadius, 1, -1, true)}\`;
}
svg += \`"/>\`;
const dataSize = params["Data size"];
const dataRadius = fmt((roundness * dataSize) / 2);
const dataOffset = (1 - 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) {
if (module === Module.FinderON) continue;
svg += \`<rect x="\${fmt(x + dataOffset)}" y="\${fmt(y + dataOffset)}" width="\${dataSize}" height="\${dataSize}" rx="\${dataRadius}"/>\`;
}
}
}
svg += \`</g>\`;
if (file != null) {
const bytes = await file.bytes();
const b64 = btoa(
Array.from(bytes, (byte) => String.fromCodePoint(byte)).join("")
);
const logoSize = fmt(params["Logo size"] * size);
const logoOffset = fmt(((1 - params["Logo size"]) * size) / 2 - margin);
svg += \`<image x="\${logoOffset}" y="\${logoOffset}" width="\${logoSize}" height="\${logoSize}" href="data:\${file.type};base64,\${b64}"/>\`;
}
svg += \`</svg>\`;
return svg;
}
`

Wyświetl plik

@ -9,23 +9,23 @@ export const paramsSchema = {
default: 2,
},
Background: {
type: "Color",
type: "color",
default: "#ffffff",
},
Finder: {
type: "Color",
type: "color",
default: "#131d87",
},
Horizontal: {
type: "Color",
type: "color",
default: "#dc9c07",
},
Vertical: {
type: "Color",
type: "color",
default: "#d21313",
},
Cross: {
type: "Color",
type: "color",
default: "#131d87",
},
"Horizontal thickness": {

Wyświetl plik

@ -9,27 +9,27 @@ export const paramsSchema = {
default: 2,
},
Background: {
type: "Color",
type: "color",
default: "#ffffff",
},
Finder: {
type: "Color",
type: "color",
default: "#141e92",
},
"Large circle": {
type: "Color",
type: "color",
default: "#10a8e9",
},
"Medium circle": {
type: "Color",
type: "color",
default: "#1aa8cc",
},
"Small circle": {
type: "Color",
type: "color",
default: "#0f8bdd",
},
"Tiny circle": {
type: "Color",
type: "color",
default: "#012c8f",
},
"Randomize circle size": {

Wyświetl plik

@ -1,10 +1,10 @@
export const Camo = `export const paramsSchema = {
Foreground: {
type: "Color",
type: "color",
default: "#1c4a1a",
},
Background: {
type: "Color",
type: "color",
default: "#e3d68a",
},
Margin: {

Wyświetl plik

@ -13,23 +13,23 @@ export const Circle = `export const paramsSchema = {
default: 0,
},
Foreground: {
type: "Color",
type: "color",
default: "#000000",
},
Background: {
type: "Color",
type: "color",
default: "#ffffff",
},
"Finder pattern": {
type: "Select",
type: "select",
options: ["Default", "Circle", "Square"],
},
"Alignment pattern": {
type: "Select",
type: "select",
options: ["Default", "Circle", "Square"],
},
"Scale direction": {
type: "Select",
type: "select",
options: ["None", "Center", "Edge"],
},
Seed: {

Wyświetl plik

@ -20,21 +20,21 @@ export const Dots = `export const paramsSchema = {
default: 1.3,
},
Foreground: {
type: "Array",
type: "array",
resizable: true,
props: {
type: "Color",
type: "color",
},
default: ["#f7158b", "#02d1fd", "#1f014b"],
},
Background: {
type: "Color",
type: "color",
default: "#ffffff",
},
// See browser compatibility issues here
// https://developer.mozilla.org/en-US/docs/Web/CSS/mix-blend-mode
"Mix blend mode": {
type: "Select",
type: "select",
options: [
"normal",
"multiply",

Wyświetl plik

@ -8,7 +8,7 @@ export const paramsSchema = {
default: 2,
},
"Fill style": {
type: "Select",
type: "select",
options: [
"Hachure",
"Solid",
@ -21,7 +21,7 @@ export const paramsSchema = {
default: "Zigzag",
},
Fill: {
type: "Color",
type: "color",
default: "#ffffff",
},
"Fill weight": {
@ -37,7 +37,7 @@ export const paramsSchema = {
default: 4,
},
Stroke: {
type: "Color",
type: "color",
default: "#ffffff",
},
"Stroke width": {
@ -63,7 +63,7 @@ export const paramsSchema = {
default: 1,
},
Background: {
type: "Color",
type: "color",
default: "#222222",
},
Seed: {

Wyświetl plik

@ -6,11 +6,11 @@ export const Glass = `export const paramsSchema = {
default: 2,
},
Foreground: {
type: "Color",
type: "color",
default: "#000000",
},
Background: {
type: "Color",
type: "color",
default: "#fcb9ff",
},
Shapes: {
@ -40,7 +40,7 @@ export const Glass = `export const paramsSchema = {
default: 0.3,
},
"QR layer": {
type: "Select",
type: "select",
options: ["Above", "Below"],
},
Seed: {

Wyświetl plik

@ -1,7 +1,7 @@
export const Halftone = `// Halftone is a misnomer, but that's what this type of QR code is known as
export const paramsSchema = {
Image: {
type: "File",
type: "file",
},
Contrast: {
type: "number",
@ -34,11 +34,11 @@ export const paramsSchema = {
default: 2,
},
Foreground: {
type: "Color",
type: "color",
default: "#000000",
},
Background: {
type: "Color",
type: "color",
default: "#ffffff",
},
};
@ -75,7 +75,7 @@ export async function renderCanvas(qr, params, canvas) {
"https://upload.wikimedia.org/wikipedia/commons/1/14/The_Widow_%28Boston_Public_Library%29_%28cropped%29.jpg"
).then((res) => res.blob());
}
const image = await createImageBitmap(file)
const image = await createImageBitmap(file);
const pixelWidth = matrixWidth + 2 * margin;
const canvasSize = pixelWidth * unit;

Wyświetl plik

@ -6,26 +6,26 @@ export const Mondrian = `export const paramsSchema = {
default: 2,
},
Foreground: {
type: "Array",
type: "array",
props: {
type: "Color",
type: "color",
},
resizable: true,
default: ["#ad1010","#172fce","#b39906"]
default: ["#860909", "#0e21a0", "#95800f"],
},
Background: {
type: "Color",
type: "color",
default: "#ffffff",
},
Lines: {
type: "Color",
type: "color",
default: "#000000",
},
"Line thickness": {
type: "number",
min: 0,
min: -10,
max: 10,
default: 4,
default: 2,
},
Seed: {
type: "number",
@ -62,7 +62,9 @@ export function renderSVG(qr, params) {
svg += \`<rect width="\${size}" height="\${size}" fill="\${params["Lines"]}"/>\`;
let lightLayer = \`<path fill="\${params["Background"]}" d="\`;
const darkLayers = params["Foreground"].map((color) => \`<path fill="\${color}" d="\`)
const darkLayers = params["Foreground"].map(
(color) => \`<path fill="\${color}" d="\`
);
const visited = Array.from({ length: matrixWidth * matrixWidth }).fill(false);
const matrix = Array.from({ length: matrixWidth * matrixWidth }).fill(0);
@ -127,7 +129,7 @@ export function renderSVG(qr, params) {
}
}
}
darkLayers.forEach((layer) => svg += layer + \`"/>\`)
darkLayers.forEach((layer) => (svg += layer + \`"/>\`));
svg += lightLayer + \`"/>\`;
svg += \`</svg>\`;

Wyświetl plik

@ -1,15 +1,15 @@
export const Neon = `export const paramsSchema = {
Foreground: {
type: "Array" ,
type: "array",
props: {
type: "Color",
type: "color",
},
resizable: true,
default: [ "#fb51dd", "#f2cffa", "#aefdfd", "#54a9fe" ]
default: ["#fb51dd", "#f2cffa", "#aefdfd", "#54a9fe"],
},
Background: {
type: "Color",
default: "#101529"
type: "color",
default: "#101529",
},
Margin: {
type: "number",
@ -18,7 +18,7 @@ export const Neon = `export const paramsSchema = {
default: 4,
},
"Quiet zone": {
type: "Select",
type: "select",
options: ["Minimal", "Full"],
},
Invert: {

Wyświetl plik

@ -9,15 +9,15 @@ export const paramsSchema = {
default: 2,
},
Background: {
type: "Color",
type: "color",
default: "#ffffff",
},
Foreground: {
type: "Color",
type: "color",
default: "#000000",
},
"Finder pattern": {
type: "Select",
type: "select",
options: ["Atom", "Planet"],
},
Particles: {

Wyświetl plik

@ -18,15 +18,15 @@ export const Tile = `export const paramsSchema = {
default: 6,
},
Foreground: {
type: "Color",
type: "color",
default: "#000000",
},
Background: {
type: "Color",
type: "color",
default: "#ffffff",
},
Grout: {
type: "Color",
type: "color",
default: "#b3b8fd",
},
};

Wyświetl plik

@ -1,4 +1,4 @@
export const Square = `export const paramsSchema = {
export const Tutorial = `export const paramsSchema = {
Margin: {
type: "number",
min: 0,
@ -7,11 +7,11 @@ export const Square = `export const paramsSchema = {
default: 2,
},
Foreground: {
type: "Color",
type: "color",
default: "#000000",
},
Background: {
type: "Color",
type: "color",
default: "#ffffff",
},
};
@ -39,4 +39,29 @@ export function renderSVG(qr, params) {
return svg;
}
// export function renderCanvas(qr, params, canvas) {
// const matrixWidth = qr.version * 4 + 17;
// const margin = params["Margin"];
// const fg = params["Foreground"];
// const bg = params["Background"];
// const unit = 10;
// const size = (matrixWidth + 2 * margin) * unit;
// canvas.width = size;
// canvas.height = size;
// const ctx = canvas.getContext("2d");
// ctx.fillStyle = bg;
// ctx.fillRect(0, 0, size, size)
// 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) * unit, (y + margin) * unit, unit, unit)
// }
// }
// }
// }
`