From e939aa76fc6e58b15736573fd4b9e93286e8ae8c Mon Sep 17 00:00:00 2001 From: Kyle Zheng Date: Wed, 2 Oct 2024 21:29:20 -0400 Subject: [PATCH] remove presets build step w/ raw? + rewrite import in dev --- README.md | 13 +- app.config.ts | 43 +++- examples/ui1.png | Bin 89485 -> 0 bytes package.json | 2 +- presets/Alien.js | 2 +- presets/Basic.js | 2 +- presets/Blocks.js | 2 +- presets/Bubbles.js | 2 +- presets/Camo.js | 2 +- presets/Circle.js | 2 +- presets/Dots.js | 2 +- presets/Drawing.js | 2 +- presets/Glass.js | 2 +- presets/Halftone.js | 8 +- presets/Layers.js | 2 +- presets/Minimal.js | 2 +- presets/Mondrian.js | 2 +- presets/Neon.js | 2 +- presets/Quantum.js | 2 +- presets/Tile.js | 2 +- presets/Tutorial.js | 3 +- src/components/editor/QrEditor.tsx | 3 +- src/lib/presets.ts | 32 +-- src/lib/presets/Alien.ts | 145 ------------ src/lib/presets/Basic.ts | 224 ------------------- src/lib/presets/Blocks.ts | 198 ----------------- src/lib/presets/Bubbles.ts | 172 --------------- src/lib/presets/Camo.ts | 289 ------------------------ src/lib/presets/Circle.ts | 221 ------------------- src/lib/presets/Dots.ts | 119 ---------- src/lib/presets/Drawing.ts | 339 ----------------------------- src/lib/presets/Glass.ts | 278 ----------------------- src/lib/presets/Halftone.ts | 173 --------------- src/lib/presets/Layers.ts | 106 --------- src/lib/presets/Minimal.ts | 70 ------ src/lib/presets/Mondrian.ts | 128 ----------- src/lib/presets/Neon.ts | 328 ---------------------------- src/lib/presets/Quantum.ts | 180 --------------- src/lib/presets/Tile.ts | 163 -------------- src/lib/presets/Tutorial.ts | 68 ------ tsconfig.json | 2 +- updatePresets.js | 41 ---- 42 files changed, 87 insertions(+), 3291 deletions(-) delete mode 100644 examples/ui1.png delete mode 100644 src/lib/presets/Alien.ts delete mode 100644 src/lib/presets/Basic.ts delete mode 100644 src/lib/presets/Blocks.ts delete mode 100644 src/lib/presets/Bubbles.ts delete mode 100644 src/lib/presets/Camo.ts delete mode 100644 src/lib/presets/Circle.ts delete mode 100644 src/lib/presets/Dots.ts delete mode 100644 src/lib/presets/Drawing.ts delete mode 100644 src/lib/presets/Glass.ts delete mode 100644 src/lib/presets/Halftone.ts delete mode 100644 src/lib/presets/Layers.ts delete mode 100644 src/lib/presets/Minimal.ts delete mode 100644 src/lib/presets/Mondrian.ts delete mode 100644 src/lib/presets/Neon.ts delete mode 100644 src/lib/presets/Quantum.ts delete mode 100644 src/lib/presets/Tile.ts delete mode 100644 src/lib/presets/Tutorial.ts delete mode 100644 updatePresets.js diff --git a/README.md b/README.md index 39c7067..e80370b 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ Blatantly inspired by [QRBTF](https://qrbtf.com) and [Anthony Fu's QR Toolkit](h > [!CAUTION] > These example QR codes may not be reliably scannable! Results may vary drastically based on device and scanner! -THIS PROJECT IS A TOOL TO MAKE DESIGNS, THESE ARE ONLY EXAMPLES +This project is a tool to create designs! These are only examples! @@ -105,6 +105,10 @@ THIS PROJECT IS A TOOL TO MAKE DESIGNS, THESE ARE ONLY EXAMPLES
+## Create/modify designs with code + +![code and parameter editor ui](./examples/ui2.png) + ## Features - Customize data: @@ -121,13 +125,6 @@ THIS PROJECT IS A TOOL TO MAKE DESIGNS, THESE ARE ONLY EXAMPLES - Generated SVGs are not sanitized. This is an impossible task and attempting it breaks perfectly fine SVGs, makes debugging harder, and adds latency to previewing changes. - These should be non-issues, but even if you copy-and-paste and run malware there's no secrets to leak. -### Use existing presets - -![style select ui](./examples/ui1.png) - -### Customizable parameters defined in code - -![code and parameter editor ui](./examples/ui2.png) ## Creating a preset diff --git a/app.config.ts b/app.config.ts index d67c770..5e2e755 100644 --- a/app.config.ts +++ b/app.config.ts @@ -4,10 +4,12 @@ import UnoCSS from "unocss/vite"; import wasmpack from "vite-plugin-wasm-pack"; export default defineConfig({ - server: { preset: "vercel" }, + server: { + preset: "vercel", + }, ssr: true, vite: { - plugins: [UnoCSS(), wasmpack([], ["fuqr"])], + plugins: [UnoCSS(), wasmpack([], ["fuqr"]), blobRewriter()], resolve: { alias: { // https://christopher.engineering/en/blog/lucide-icons-with-vite-dev-server/ @@ -21,3 +23,40 @@ export default defineConfig({ }, }, }); + +// Rewrites imports inside blobs in dev mode +function blobRewriter() { + const virtualModuleId = "virtual:blob-rewriter"; + const resolvedVirtualModuleId = "\0" + virtualModuleId; + "help".replace(/test/, "help"); + + return { + name: "blob-rewriter", + resolveId(id) { + if (id === virtualModuleId) { + return resolvedVirtualModuleId; + } + }, + load(id) { + if (id === resolvedVirtualModuleId) { + if (process.env.NODE_ENV !== 'development') { + return 'export {}' + } + + return ` + if (!import.meta.env.SSR) { + const originalBlob = window.Blob; + window.Blob = function(array, options) { + if (options.type === "text/javascript") { + array = array.map(item => { + return item.replace("https://qrframe.kylezhe.ng", "http://localhost:3000"); + }); + } + return new originalBlob(array, options); + } + } + `; + } + }, + }; +} diff --git a/examples/ui1.png b/examples/ui1.png deleted file mode 100644 index 0d6fc64098652f4a96801cd164d98a87e77e2b20..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 89485 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 WU*h|@j>ixS^XP$$M2`3ium1~%Z3$)o diff --git a/package.json b/package.json index 63203ee..2d35208 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "qrframe", "type": "module", "scripts": { - "dev": "nr presets dev && vinxi dev", + "dev": "vinxi dev", "build": "vinxi build", "start": "vinxi start", "presets": "node updatePresets" diff --git a/presets/Alien.js b/presets/Alien.js index dac347f..c9d0e4c 100644 --- a/presets/Alien.js +++ b/presets/Alien.js @@ -1,6 +1,6 @@ // Based on QRBTF's Line style // https://github.com/CPunisher/react-qrbtf/blob/master/src/components/QRLine.tsx -import { Module, getSeededRand } from "REPLACE_URL/utils.js"; +import { Module, getSeededRand } from "https://qrframe.kylezhe.ng/utils.js"; export const paramsSchema = { Margin: { diff --git a/presets/Basic.js b/presets/Basic.js index 4beea70..7b5970c 100644 --- a/presets/Basic.js +++ b/presets/Basic.js @@ -1,4 +1,4 @@ -import { Module } from "REPLACE_URL/utils.js"; +import { Module } from "https://qrframe.kylezhe.ng/utils.js"; export const paramsSchema = { Margin: { diff --git a/presets/Blocks.js b/presets/Blocks.js index 1f32d42..d5da30a 100644 --- a/presets/Blocks.js +++ b/presets/Blocks.js @@ -1,6 +1,6 @@ // Based on QRBTF's DSJ style // https://github.com/CPunisher/react-qrbtf/blob/master/src/components/QRDsj.tsx -import { Module } from "REPLACE_URL/utils.js"; +import { Module } from "https://qrframe.kylezhe.ng/utils.js"; export const paramsSchema = { Margin: { diff --git a/presets/Bubbles.js b/presets/Bubbles.js index ffca90d..5f768f4 100644 --- a/presets/Bubbles.js +++ b/presets/Bubbles.js @@ -1,6 +1,6 @@ // Based on QRBTF's Bubble style // https://github.com/CPunisher/react-qrbtf/blob/master/src/components/QRBubble.tsx -import { Module, getSeededRand } from "REPLACE_URL/utils.js"; +import { Module, getSeededRand } from "https://qrframe.kylezhe.ng/utils.js"; export const paramsSchema = { Margin: { diff --git a/presets/Camo.js b/presets/Camo.js index 0a03f12..4a1cc7c 100644 --- a/presets/Camo.js +++ b/presets/Camo.js @@ -1,4 +1,4 @@ -import { Module, getSeededRand } from "REPLACE_URL/utils.js"; +import { Module, getSeededRand } from "https://qrframe.kylezhe.ng/utils.js"; export const paramsSchema = { Foreground: { diff --git a/presets/Circle.js b/presets/Circle.js index c02908f..8fb8269 100644 --- a/presets/Circle.js +++ b/presets/Circle.js @@ -1,4 +1,4 @@ -import { Module, getSeededRand } from "REPLACE_URL/utils.js"; +import { Module, getSeededRand } from "https://qrframe.kylezhe.ng/utils.js"; export const paramsSchema = { Margin: { diff --git a/presets/Dots.js b/presets/Dots.js index 348ea24..7924899 100644 --- a/presets/Dots.js +++ b/presets/Dots.js @@ -1,4 +1,4 @@ -import { Module, getSeededRand } from "REPLACE_URL/utils.js"; +import { Module, getSeededRand } from "https://qrframe.kylezhe.ng/utils.js"; export const paramsSchema = { Margin: { diff --git a/presets/Drawing.js b/presets/Drawing.js index ac4329f..7231a0f 100644 --- a/presets/Drawing.js +++ b/presets/Drawing.js @@ -1,4 +1,4 @@ -import { Module, getSeededRand } from "REPLACE_URL/utils.js"; +import { Module, getSeededRand } from "https://qrframe.kylezhe.ng/utils.js"; import rough from "https://esm.sh/roughjs"; export const paramsSchema = { diff --git a/presets/Glass.js b/presets/Glass.js index af5e44d..1c80a7c 100644 --- a/presets/Glass.js +++ b/presets/Glass.js @@ -1,4 +1,4 @@ -import { Module, getSeededRand } from "REPLACE_URL/utils.js"; +import { Module, getSeededRand } from "https://qrframe.kylezhe.ng/utils.js"; export const paramsSchema = { Margin: { diff --git a/presets/Halftone.js b/presets/Halftone.js index c6dbcab..e84691e 100644 --- a/presets/Halftone.js +++ b/presets/Halftone.js @@ -1,4 +1,4 @@ -import { Module } from "REPLACE_URL/utils.js"; +import { Module } from "https://qrframe.kylezhe.ng/utils.js"; export const paramsSchema = { Image: { @@ -92,10 +92,10 @@ export async function renderCanvas(qr, params, canvas) { } ctx.filter = `brightness(${params["Brightness"]}) contrast(${params["Contrast"]})`; - const imgScale = params["Image scale"] + const imgScale = params["Image scale"]; const imgSize = Math.floor(imgScale * canvasSize); - const imgOffset = Math.floor((canvasSize - imgSize) / 2) - ctx.drawImage(image, imgOffset, imgOffset,imgSize, imgSize); + const imgOffset = Math.floor((canvasSize - imgSize) / 2); + ctx.drawImage(image, imgOffset, imgOffset, imgSize, imgSize); ctx.filter = "none"; const imageData = ctx.getImageData(0, 0, canvasSize, canvasSize); diff --git a/presets/Layers.js b/presets/Layers.js index 23bc3f8..6e229c0 100644 --- a/presets/Layers.js +++ b/presets/Layers.js @@ -1,4 +1,4 @@ -import { Module } from "REPLACE_URL/utils.js"; +import { Module } from "https://qrframe.kylezhe.ng/utils.js"; export const paramsSchema = { Margin: { diff --git a/presets/Minimal.js b/presets/Minimal.js index 614601c..5e2b05e 100644 --- a/presets/Minimal.js +++ b/presets/Minimal.js @@ -1,4 +1,4 @@ -import { Module } from "REPLACE_URL/utils.js"; +import { Module } from "https://qrframe.kylezhe.ng/utils.js"; export const paramsSchema = { Margin: { diff --git a/presets/Mondrian.js b/presets/Mondrian.js index 81c8adc..0707a98 100644 --- a/presets/Mondrian.js +++ b/presets/Mondrian.js @@ -1,4 +1,4 @@ -import { Module, getSeededRand } from "REPLACE_URL/utils.js"; +import { Module, getSeededRand } from "https://qrframe.kylezhe.ng/utils.js"; export const paramsSchema = { Margin: { diff --git a/presets/Neon.js b/presets/Neon.js index 120493c..3e80cc5 100644 --- a/presets/Neon.js +++ b/presets/Neon.js @@ -1,4 +1,4 @@ -import { Module, getSeededRand } from "REPLACE_URL/utils.js"; +import { Module, getSeededRand } from "https://qrframe.kylezhe.ng/utils.js"; export const paramsSchema = { Foreground: { diff --git a/presets/Quantum.js b/presets/Quantum.js index 46b4b19..8339828 100644 --- a/presets/Quantum.js +++ b/presets/Quantum.js @@ -1,6 +1,6 @@ // Based on QRBTF's A1P style // https://github.com/CPunisher/react-qrbtf/blob/master/src/components/QRNormal.tsx -import { Module, getSeededRand } from "REPLACE_URL/utils.js"; +import { Module, getSeededRand } from "https://qrframe.kylezhe.ng/utils.js"; export const paramsSchema = { Margin: { diff --git a/presets/Tile.js b/presets/Tile.js index dab6a0a..0a8f018 100644 --- a/presets/Tile.js +++ b/presets/Tile.js @@ -1,4 +1,4 @@ -import { Module } from "REPLACE_URL/utils.js"; +import { Module } from "https://qrframe.kylezhe.ng/utils.js"; export const paramsSchema = { Margin: { diff --git a/presets/Tutorial.js b/presets/Tutorial.js index 180d64d..a579eb4 100644 --- a/presets/Tutorial.js +++ b/presets/Tutorial.js @@ -1,4 +1,5 @@ -import { Module } from "REPLACE_URL/utils.js"; +import { Module } from "https://qrframe.kylezhe.ng/utils.js"; + export const paramsSchema = { Margin: { type: "number", diff --git a/src/components/editor/QrEditor.tsx b/src/components/editor/QrEditor.tsx index a6818af..a69f668 100644 --- a/src/components/editor/QrEditor.tsx +++ b/src/components/editor/QrEditor.tsx @@ -17,8 +17,9 @@ import { TextInput, TextareaInput } from "../TextInput"; import { CodeEditor } from "./CodeEditor"; import { Settings } from "./Settings"; import { ParamsEditor } from "./ParamsEditor"; -import { Tutorial } from "~/lib/presets/Tutorial"; +import Tutorial from "../../../presets/Tutorial?raw"; import { ContentMenuTrigger, ContextMenuProvider } from "../ContextMenu"; +import "virtual:blob-rewriter" type Props = { class?: string; diff --git a/src/lib/presets.ts b/src/lib/presets.ts index 491e98b..ca530ef 100644 --- a/src/lib/presets.ts +++ b/src/lib/presets.ts @@ -1,19 +1,19 @@ -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"; -import { Circle } from "./presets/Circle"; -import { Dots } from "./presets/Dots"; -import { Drawing } from "./presets/Drawing"; -import { Glass } from "./presets/Glass"; -import { Halftone } from "./presets/Halftone"; -import { Layers } from "./presets/Layers"; -import { Minimal } from "./presets/Minimal"; -import { Mondrian } from "./presets/Mondrian"; -import { Neon } from "./presets/Neon"; -import { Quantum } from "./presets/Quantum"; -import { Tile } from "./presets/Tile"; +import Alien from "../../presets/Alien?raw"; +import Basic from "../../presets/Basic?raw"; +import Blocks from "../../presets/Blocks?raw"; +import Bubbles from "../../presets/Bubbles?raw"; +import Camo from "../../presets/Camo?raw"; +import Circle from "../../presets/Circle?raw"; +import Dots from "../../presets/Dots?raw"; +import Drawing from "../../presets/Drawing?raw"; +import Glass from "../../presets/Glass?raw"; +import Halftone from "../../presets/Halftone?raw"; +import Layers from "../../presets/Layers?raw"; +import Minimal from "../../presets/Minimal?raw"; +import Mondrian from "../../presets/Mondrian?raw"; +import Neon from "../../presets/Neon?raw"; +import Quantum from "../../presets/Quantum?raw"; +import Tile from "../../presets/Tile?raw"; export const PRESET_CODE = { Basic, diff --git a/src/lib/presets/Alien.ts b/src/lib/presets/Alien.ts deleted file mode 100644 index 3fe3eb3..0000000 --- a/src/lib/presets/Alien.ts +++ /dev/null @@ -1,145 +0,0 @@ -export const Alien = `// Based on QRBTF's Line style -// https://github.com/CPunisher/react-qrbtf/blob/master/src/components/QRLine.tsx -import { Module, getSeededRand } from "https://qrframe.kylezhe.ng/utils.js"; - -export const paramsSchema = { - Margin: { - type: "number", - min: 0, - max: 10, - step: 0.1, - default: 2, - }, - Background: { - type: "color", - default: "#ffffff", - }, - Dots: { - type: "color", - default: "#000000", - }, - Lines: { - type: "color", - default: "#000000", - }, - Seed: { - type: "number", - min: 1, - max: 100, - default: 1, - }, -}; - -export function renderSVG(qr, params) { - const rand = getSeededRand(params["Seed"]); - const rangeStr = (min, max) => (rand() * (max - min) + min).toFixed(2); - - const matrixWidth = qr.version * 4 + 17; - const margin = params["Margin"]; - const bg = params["Background"]; - const dots = params["Dots"]; - const lines = params["Lines"]; - - const size = matrixWidth + 2 * margin; - let svg = \`\`; - svg += \`\`; - - let linesLayer = \`\`; - let dotsLayer = \`\`; - - function matrix(x, y) { - return qr.matrix[y * matrixWidth + x]; - } - - const rightVisited = Array(matrixWidth * matrixWidth).fill(false); - const leftVisited = Array(matrixWidth * matrixWidth).fill(false); - function visited1(x, y) { - return rightVisited[y * matrixWidth + x]; - } - function visited2(x, y) { - return leftVisited[y * matrixWidth + x]; - } - function setVisited1(x, y) { - rightVisited[y * matrixWidth + x] = true; - } - function setVisited2(x, y) { - leftVisited[y * matrixWidth + x] = true; - } - - for (const [x, y] of [ - [0, 0], - [matrixWidth - 7, 0], - [0, matrixWidth - 7], - ]) { - dotsLayer += \`\`; - - dotsLayer += \`\`; - dotsLayer += \`\`; - dotsLayer += \`\`; - - dotsLayer += \`\`; - dotsLayer += \`\`; - - dotsLayer += \`\`; - dotsLayer += \`\`; - dotsLayer += \`\`; - - linesLayer += \`\`; - linesLayer += \`\`; - } - - for (let y = 0; y < matrixWidth; y++) { - for (let x = 0; x < matrixWidth; x++) { - const module = matrix(x, y); - if (module & Module.FINDER) continue; - - if (!(module & Module.ON)) continue; - dotsLayer += \`\`; - - if (!visited1(x, y)) { - let nx = x + 1; - let ny = y + 1; - while ( - nx < matrixWidth && - ny < matrixWidth && - matrix(nx, ny) & Module.ON && - !visited1(nx, ny) - ) { - setVisited1(nx, ny); - nx++; - ny++; - } - if (ny - y > 1) { - linesLayer += \`\`; - } - } - - if (!visited2(x, y)) { - let nx = x - 1; - let ny = y + 1; - while ( - nx >= 0 && - ny < matrixWidth && - matrix(nx, ny) & Module.ON && - !visited2(nx, ny) - ) { - setVisited2(nx, ny); - nx--; - ny++; - } - if (ny - y > 1) { - linesLayer += \`\`; - } - } - } - } - - linesLayer += \`\`; - svg += linesLayer; - dotsLayer += \`\`; - svg += dotsLayer; - svg += \`\`; - - return svg; -} -` diff --git a/src/lib/presets/Basic.ts b/src/lib/presets/Basic.ts deleted file mode 100644 index ada9c73..0000000 --- a/src/lib/presets/Basic.ts +++ /dev/null @@ -1,224 +0,0 @@ -export const Basic = `import { Module } from "https://qrframe.kylezhe.ng/utils.js"; - -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", - }, - Shape: { - type: "select", - options: ["Square-Circle", "Diamond-Squircle"], - }, - Frame: { - type: "select", - options: ["None", "Corners"], - }, - Roundness: { - type: "number", - min: 0, - max: 1, - step: 0.01, - default: 0, - }, - "Pixel 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, - }, - "Show data behind logo": { - type: "boolean", - }, -}; - -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 defaultShape = params["Shape"] === "Square-Circle"; - const roundness = params["Roundness"]; - const file = params["Logo"]; - const logoRatio = params["Logo size"]; - const showLogoData = params["Show data behind logo"]; - - const size = matrixWidth + 2 * margin; - let svg = \`\`; - svg += \`\`; - - svg += \`\`; - - if (params["Frame"] === "Corners") { - const bracketRadius = 2.2 * roundness; - const bracketStraight = 5 + margin / 2 - bracketRadius; - svg += brackets( - -margin / 2, - -margin / 2, - size - margin, - bracketRadius, - bracketStraight, - fg - ); - } - - svg += \`\`; - - const dataSize = params["Pixel size"]; - const dataRadius = (roundness * dataSize) / 2; - const dataOffset = (1 - dataSize) / 2; - - if (!defaultShape || !roundness) svg += \`\`; - } else { - svg += \`M\${x + dataOffset},\${y + dataOffset}h\${dataSize}v\${dataSize}h-\${dataSize}z\`; - } - } else { - svg += squircle( - x + dataOffset, - y + dataOffset, - dataSize, - dataRadius, - true - ); - } - } - } - if (!defaultShape || !roundness) svg += \`"/>\`; - svg += \`\`; - - if (file != null) { - const bytes = new Uint8Array(await file.arrayBuffer()); - const b64 = btoa( - Array.from(bytes, (byte) => String.fromCodePoint(byte)).join("") - ); - const logoSize = fmt(logoRatio * size); - const logoOffset = fmt(((1 - logoRatio) * size) / 2 - margin); - svg += \`\`; - } - - svg += \`\`; - return svg; -} - -// reduce file bloat from floating point math -const fmt = (n) => n.toFixed(2).replace(/.00$/, ""); - -function squircle(x, y, width, handle, cw) { - const half = fmt(width / 2); - - if (handle === 0) { - return cw - ? \`M\${fmt(x + width / 2)},\${fmt(y)}l\${half},\${half}l-\${half},\${half}l-\${half},-\${half}z\` - : \`M\${fmt(x + width / 2)},\${fmt(y)}l-\${half},\${half}l\${half},\${half}l\${half},-\${half}z\`; - } - - const h = fmt(handle); - const hInv1 = fmt(half - handle); - const hInv2 = fmt(-(half - handle)); - return cw - ? \`M\${fmt(x + width / 2)},\${fmt(y)}c\${h},0 \${half},\${hInv1} \${half},\${half}s\${hInv2},\${half} -\${half},\${half}s-\${half},\${hInv2} -\${half},-\${half}s\${hInv1},-\${half} \${half},-\${half}\` - : \`M\${fmt(x + width / 2)},\${fmt(y)}c-\${h},0 -\${half},\${hInv1} -\${half},\${half}s\${hInv1},\${half} \${half},\${half}s\${half},\${hInv2} \${half},-\${half}s\${hInv2},-\${half} -\${half},-\${half}\`; -} - -function roundedRect(x, y, width, radius, cw) { - if (radius === 0) { - return cw - ? \`M\${fmt(x)},\${fmt(y)}h\${width}v\${width}h-\${width}z\` - : \`M\${fmt(x)},\${fmt(y)}v\${width}h\${width}v-\${width}z\`; - } - - if (radius === width / 2) { - const r = fmt(radius); - const cwFlag = cw ? "1" : "0"; - return \`M\${fmt(x + radius)},\${fmt(y)}a\${r},\${r} 0,0,\${cwFlag} 0,\${width}a\${r},\${r} 0,0,\${cwFlag} \${0},-\${width}\`; - } - - const r = fmt(radius); - const side = fmt(width - 2 * radius); - return cw - ? \`M\${fmt(x + radius)},\${fmt(y)}h\${side}a\${r},\${r} 0,0,1 \${r},\${r}v\${side}a\${r},\${r} 0,0,1 -\${r},\${r}h-\${side}a\${r},\${r} 0,0,1 -\${r},-\${r}v-\${side}a\${r},\${r} 0,0,1 \${r},-\${r}\` - : \`M\${fmt(x + radius)},\${fmt(y)}a\${r},\${r} 0,0,0 -\${r},\${r}v\${side}a\${r},\${r} 0,0,0 \${r},\${r}h\${side}a\${r},\${r} 0,0,0 \${r},-\${r}v-\${side}a\${r},\${r} 0,0,0 -\${r},-\${r}\`; -} - -function brackets(x, y, width, radius, straight, stroke) { - const bracket = radius + straight; - const side = fmt(width - 2 * bracket); - const r = fmt(radius); - - const cap = radius === 0 ? "square" : "round"; - - let svg = \`\`; - return svg; -} -` diff --git a/src/lib/presets/Blocks.ts b/src/lib/presets/Blocks.ts deleted file mode 100644 index ea9ab70..0000000 --- a/src/lib/presets/Blocks.ts +++ /dev/null @@ -1,198 +0,0 @@ -export const Blocks = `// Based on QRBTF's DSJ style -// https://github.com/CPunisher/react-qrbtf/blob/master/src/components/QRDsj.tsx -import { Module } from "https://qrframe.kylezhe.ng/utils.js"; - -export const paramsSchema = { - Margin: { - type: "number", - min: 0, - max: 10, - step: 0.1, - default: 2, - }, - Background: { - type: "color", - default: "#ffffff", - }, - Finder: { - type: "color", - default: "#131d87", - }, - Horizontal: { - type: "color", - default: "#dc9c07", - }, - Vertical: { - type: "color", - default: "#d21313", - }, - Cross: { - type: "color", - default: "#131d87", - }, - "Horizontal thickness": { - type: "number", - min: 0, - max: 1, - step: 0.1, - default: 0.7, - }, - "Vertical thickness": { - type: "number", - min: 0, - max: 1, - step: 0.1, - default: 0.7, - }, - "Cross thickness": { - type: "number", - min: 0, - max: 1, - step: 0.1, - default: 0.7, - }, -}; - -export function renderSVG(qr, params) { - const matrixWidth = qr.version * 4 + 17; - const margin = params["Margin"]; - const bg = params["Background"]; - const fc = params["Finder"]; - - const hc = params["Horizontal"]; - const ht = params["Horizontal thickness"]; - const ho = (1 - ht) / 2; - - const vc = params["Vertical"]; - const vt = params["Vertical thickness"]; - const vo = (1 - vt) / 2; - - const cc = params["Cross"]; - const ct = params["Cross thickness"]; - const co = ct / Math.sqrt(8); // offset - - const size = matrixWidth + 2 * margin; - let svg = \`\`; - svg += \`\`; - - let crossLayer = \`\`; - let vLayer = \`\`; - let hLayer = \`\`; - - function matrix(x, y) { - return qr.matrix[y * matrixWidth + x]; - } - - const visitedMatrix = Array(matrixWidth * matrixWidth).fill(false); - function visited(x, y) { - return visitedMatrix[y * matrixWidth + x]; - } - function setVisited(x, y) { - visitedMatrix[y * matrixWidth + x] = true; - } - - svg += \`\`; - for (const [x, y] of [ - [0, 0], - [matrixWidth - 7, 0], - [0, matrixWidth - 7], - ]) { - svg += \`\`; - svg += \`\`; - svg += \`\`; - svg += \`\`; - svg += \`\`; - } - svg += \`\`; - - for (let y = 0; y < matrixWidth; y++) { - for (let x = 0; x < matrixWidth; x++) { - const module = matrix(x, y); - if (module & Module.FINDER) continue; - if (!(module & Module.ON)) continue; - if (visited(x, y)) continue; - setVisited(x, y); - - if ( - y < matrixWidth - 2 && - x < matrixWidth - 2 && - matrix(x + 2, y) & - matrix(x, y + 2) & - matrix(x + 1, y + 1) & - matrix(x + 2, y + 2) & - 1 - ) { - if ( - !visited(x + 1, y) && - !visited(x + 2, y) && - !visited(x, y + 1) && - !visited(x + 2, y + 1) - ) { - crossLayer += \`\`; - crossLayer += \`\`; - crossLayer += \`\`; - crossLayer += \`\`; - - setVisited(x + 2, y); - setVisited(x, y + 2); - setVisited(x + 1, y + 1); - setVisited(x + 2, y + 2); - continue; - } - } - if ( - y < matrixWidth - 1 && - x < matrixWidth - 1 && - matrix(x + 1, y) & matrix(x, y + 1) & matrix(x + 1, y + 1) & Module.ON - ) { - if ( - !visited(x + 1, y) && - !visited(x + 1, y + 1) && - !visited(x, y + 1) - ) { - crossLayer += \`\`; - crossLayer += \`\`; - crossLayer += \`\`; - crossLayer += \`\`; - - setVisited(x + 1, y); - setVisited(x, y + 1); - setVisited(x + 1, y + 1); - continue; - } - } - - let ny = y + 1; - while (ny < matrixWidth && matrix(x, ny) & Module.ON && !visited(x, ny)) { - ny++; - } - if (ny - y > 2) { - vLayer += \`\`; - vLayer += \`\`; - for (let i = y + 1; i < ny; i++) { - setVisited(x, i); - } - continue; - } - - let nx = x + 1; - while (nx < matrixWidth && matrix(nx, y) & Module.ON && !visited(nx, y)) { - setVisited(nx, y); - nx++; - } - hLayer += \`\`; - } - } - - vLayer += \`\`; - svg += vLayer; - hLayer += \`\`; - svg += hLayer; - crossLayer += \`\`; - svg += crossLayer; - - svg += \`\`; - - return svg; -} -` diff --git a/src/lib/presets/Bubbles.ts b/src/lib/presets/Bubbles.ts deleted file mode 100644 index 28e9a81..0000000 --- a/src/lib/presets/Bubbles.ts +++ /dev/null @@ -1,172 +0,0 @@ -export const Bubbles = `// Based on QRBTF's Bubble style -// https://github.com/CPunisher/react-qrbtf/blob/master/src/components/QRBubble.tsx -import { Module, getSeededRand } from "https://qrframe.kylezhe.ng/utils.js"; - -export const paramsSchema = { - Margin: { - type: "number", - min: 0, - max: 10, - step: 0.1, - default: 2, - }, - Background: { - type: "color", - default: "#ffffff", - }, - Finder: { - type: "color", - default: "#141e92", - }, - "Large circle": { - type: "color", - default: "#10a8e9", - }, - "Medium circle": { - type: "color", - default: "#1aa8cc", - }, - "Small circle": { - type: "color", - default: "#0f8bdd", - }, - "Tiny circle": { - type: "color", - default: "#012c8f", - }, - "Randomize circle size": { - type: "boolean", - }, - Seed: { - type: "number", - min: 1, - max: 100, - default: 1, - }, -}; - -export function renderSVG(qr, params) { - const rand = getSeededRand(params["Seed"]); - - const rangeStr = params["Randomize circle size"] - ? (min, max) => (rand() * (max - min) + min).toFixed(2) - : (min, max) => ((max - min) / 2 + min).toFixed(2); - - const matrixWidth = qr.version * 4 + 17; - const margin = params["Margin"]; - const bg = params["Background"]; - - const size = matrixWidth + 2 * margin; - let svg = \`\`; - svg += \`\`; - - let layer1 = \`\`; - let layer2 = \`\`; - let layer3 = \`\`; - let layer4 = \`\`; - - function matrix(x, y) { - return qr.matrix[y * matrixWidth + x]; - } - - const visitedMatrix = Array(matrixWidth * matrixWidth).fill(false); - function visited(x, y) { - return visitedMatrix[y * matrixWidth + x]; - } - function setVisited(x, y) { - visitedMatrix[y * matrixWidth + x] = true; - } - - const fc = params["Finder"]; - for (const [x, y] of [ - [0, 0], - [matrixWidth - 7, 0], - [0, matrixWidth - 7], - ]) { - svg += \`\`; - svg += \`\`; - } - - for (let y = 0; y < matrixWidth; y++) { - for (let x = 0; x < matrixWidth; x++) { - const module = matrix(x, y); - if (module & Module.FINDER) continue; - if (visited(x, y)) continue; - - if ( - y < matrixWidth - 2 && - x < matrixWidth - 2 && - matrix(x + 1, y) & - matrix(x, y + 1) & - matrix(x + 2, y + 1) & - matrix(x + 1, y + 2) & - 1 && - !visited(x + 1, y) && - !visited(x + 2, y) && - !visited(x + 1, y + 1) && - !visited(x + 2, y + 1) - ) { - layer1 += \`\`; - - setVisited(x + 1, y); - setVisited(x, y + 1); - setVisited(x + 2, y + 1); - setVisited(x + 1, y + 2); - continue; - } - if (!(module & Module.ON)) continue; - setVisited(x, y); - - if ( - y < matrixWidth - 1 && - x < matrixWidth - 1 && - matrix(x + 1, y) & - matrix(x, y + 1) & - matrix(x + 1, y + 1) & - Module.ON && - !visited(x + 1, y) && - !visited(x + 1, y + 1) - ) { - layer2 += \`\`; - setVisited(x + 1, y); - setVisited(x, y + 1); - setVisited(x + 1, y + 1); - continue; - } - if ( - x < matrixWidth - 1 && - matrix(x + 1, y) & Module.ON && - !visited(x + 1, y) - ) { - layer3 += \`\`; - setVisited(x + 1, y); - continue; - } - if ( - y < matrixWidth - 1 && - matrix(x, y + 1) & Module.ON && - !visited(x, y + 1) - ) { - layer3 += \`\`; - setVisited(x, y + 1); - continue; - } - - layer4 += \`\`; - } - } - - layer1 += \`\`; - svg += layer1; - layer2 += \`\`; - svg += layer2; - layer3 += \`\`; - svg += layer3; - layer4 += \`\`; - svg += layer4; - - svg += \`\`; - - return svg; -} -` diff --git a/src/lib/presets/Camo.ts b/src/lib/presets/Camo.ts deleted file mode 100644 index 929681c..0000000 --- a/src/lib/presets/Camo.ts +++ /dev/null @@ -1,289 +0,0 @@ -export const Camo = `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 += \`\`; - svg += \`\`; - - 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(\`\`; - }); - - svg += \`\`; - - return svg; -} -` diff --git a/src/lib/presets/Circle.ts b/src/lib/presets/Circle.ts deleted file mode 100644 index f506203..0000000 --- a/src/lib/presets/Circle.ts +++ /dev/null @@ -1,221 +0,0 @@ -export const Circle = `import { Module, getSeededRand } from "https://qrframe.kylezhe.ng/utils.js"; - -export const paramsSchema = { - Margin: { - type: "number", - min: 0, - max: 20, - step: 0.1, - default: 8, - }, - "Radius offset": { - type: "number", - min: -10, - max: 10, - default: 0, - }, - Foreground: { - type: "color", - default: "#000000", - }, - Background: { - type: "color", - default: "#ffffff", - }, - "Frame thickness": { - type: "number", - min: 0, - max: 10, - step: 0.1, - }, - "Finder pattern": { - type: "select", - options: ["Default", "Circle", "Square"], - }, - "Alignment pattern": { - type: "select", - options: ["Default", "Circle", "Square"], - }, - Logo: { - type: "file", - accept: ".jpeg, .jpg, .png, .svg", - }, - "Logo size": { - type: "number", - min: 0, - max: 1, - step: 0.01, - default: 0.25, - }, - "Show data behind logo": { - type: "boolean", - }, - "Pixel size": { - type: "select", - options: ["None", "Center", "Edge", "Random"], - }, - Seed: { - type: "number", - min: 1, - max: 100, - default: 1, - }, -}; - -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 rOffset = params["Radius offset"]; - const file = params["Logo"]; - const logoRatio = params["Logo size"]; - const showLogoData = params["Show data behind logo"]; - const rand = getSeededRand(params["Seed"]); - const range = (min, max) => rand() * (max - min) + min; - - const size = matrixWidth + 2 * margin; - - let svg = \`\`; - svg += \`\`; - - // nearest odd number - let diameter = Math.round(Math.sqrt(2) * matrixWidth) + 2 * rOffset; - if (!(diameter & 1)) diameter += 1; - - const frameThick = params["Frame thickness"]; - if (frameThick) { - const frameR = diameter / 2 + 1 + frameThick / 2; - svg += \`\`; - if (rOffset < -1) { - const c = matrixWidth / 2; - const offset = (frameR * Math.sqrt(2)) / 2; - const r = (-rOffset + 1) * Math.max(frameThick / 2, 1); - svg += \`\`; - svg += \`\`; - svg += \`\`; - if (rOffset < -2) { - svg += \`\`; - } - } - } - - if (params["Finder pattern"] !== "Default") { - for (const [x, y] of [ - [0, 0], - [matrixWidth - 7, 0], - [0, matrixWidth - 7], - ]) { - if (params["Finder pattern"] === "Circle") { - svg += \`\`; - svg += \`\`; - } else { - svg += \`\`; - } - } - } - svg += \` diameter / 2) { - continue; - } else if (rand() > 0.5) { - continue; - } - - let ratio; - switch (params["Pixel size"]) { - case "Center": - ratio = 1 - dist / maxDist + 0.8; - break; - case "Edge": - ratio = dist / maxDist + 0.8; - break; - case "Random": - ratio = range(0.8, 1.2); - break; - default: - ratio = 1; - } - - const radius = fmt(0.5 * ratio); - - svg += \`M\${x + 0.5},\${y + 0.5 - radius}a\${radius},\${radius} 0,0,0 0,\${2 * radius}a\${radius},\${radius} 0,0,0 0,\${-2 * radius}\`; - } - } - svg += \`"/>\`; - - if (file != null) { - const bytes = new Uint8Array(await file.arrayBuffer()); - const b64 = btoa( - Array.from(bytes, (byte) => String.fromCodePoint(byte)).join("") - ); - const logoSize = fmt(logoRatio * size); - const logoOffset = fmt(((1 - logoRatio) * size) / 2 - margin); - svg += \`\`; - } - svg += \`\`; - - return svg; -} -` diff --git a/src/lib/presets/Dots.ts b/src/lib/presets/Dots.ts deleted file mode 100644 index e4e06ea..0000000 --- a/src/lib/presets/Dots.ts +++ /dev/null @@ -1,119 +0,0 @@ -export const Dots = `import { Module, getSeededRand } from "https://qrframe.kylezhe.ng/utils.js"; - -export const paramsSchema = { - Margin: { - type: "number", - min: 0, - max: 10, - step: 0.1, - default: 2, - }, - Density: { - type: "number", - min: 2, - max: 10, - default: 4, - }, - "Finder clarity": { - type: "number", - min: 1, - max: 1.5, - step: 0.1, - default: 1.3, - }, - Foreground: { - type: "array", - resizable: true, - props: { - type: "color", - }, - default: ["#f7158b", "#02d1fd", "#1f014b"], - }, - Background: { - 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", - options: [ - "normal", - "multiply", - "screen", - "overlay", - "darken", - "lighten", - "color-dodge", - "color-burn", - "hard-light", - "soft-light", - "difference", - "exclusion", - "hue", - "saturation", - "color", - "luminosity", - "plus-darker", - "plus-lighter", - ], - }, - Seed: { - type: "number", - min: 1, - max: 100, - default: 1, - }, -}; - -export function renderSVG(qr, params) { - const unit = params["Density"]; - - const rand = getSeededRand(params["Seed"]); - const rangeStr = (min, max) => (rand() * (max - min) + min).toFixed(2); - const matrixWidth = qr.version * 4 + 17; - const margin = params["Margin"] * unit; - const colors = params["Foreground"]; - const bg = params["Background"]; - - const size = matrixWidth * unit + 2 * margin; - let svg = \`\`; - - const center = (matrixWidth * unit) / 2; - svg += \`\`; - - const dotRadius = 1; - const dotSpace = 2.2; - const maxRadius = Math.sqrt((unit * unit * matrixWidth * matrixWidth) / 2); - for (let r = 0.1; r < maxRadius; r += dotSpace) { - const angleInc = dotSpace / r; - for (let theta = 0; theta < 2 * Math.PI - angleInc / 2; theta += angleInc) { - const x = r * Math.cos(theta); - const y = r * Math.sin(theta); - const qx = Math.floor((x + center) / unit); - const qy = Math.floor((y + center) / unit); - if (qx >= 0 && qx < matrixWidth && qy >= 0 && qy < matrixWidth) { - if (qr.matrix[qy * matrixWidth + qx] & Module.ON) { - const rad = - qr.matrix[qy * matrixWidth + qx] & Module.FINDER - ? params["Finder clarity"] - : dotRadius; - svg += \`\`; - } - } - } - } - svg += \`\`; - svg += \`\`; - - svg += \`\`; - colors.forEach( - (color) => - (svg += \`\`) - ); - svg += \`\`; - - svg += \`\`; - return svg; -} -` diff --git a/src/lib/presets/Drawing.ts b/src/lib/presets/Drawing.ts deleted file mode 100644 index 96e1849..0000000 --- a/src/lib/presets/Drawing.ts +++ /dev/null @@ -1,339 +0,0 @@ -export const Drawing = `import { Module, getSeededRand } from "https://qrframe.kylezhe.ng/utils.js"; -import rough from "https://esm.sh/roughjs"; - -export const paramsSchema = { - Margin: { - type: "number", - min: 0, - max: 10, - default: 2, - }, - "Fill style": { - type: "select", - options: [ - "Hachure", - "Solid", - "Zigzag", - "Cross-hatch", - "Dots", - "Dashed", - "Zigzag-line", - ], - default: "Zigzag", - }, - Fill: { - type: "color", - default: "#ffffff", - }, - "Fill weight": { - type: "number", - min: 0, - max: 10, - default: 2, - }, - "Fill gap": { - type: "number", - min: 1, - max: 10, - default: 4, - }, - Stroke: { - type: "color", - default: "#ffffff", - }, - "Stroke width": { - type: "number", - min: 0, - max: 10, - default: 1, - }, - Invert: { - type: "boolean", - default: true, - }, - Roughness: { - type: "number", - min: 0, - max: 10, - default: 1, - }, - Bowing: { - type: "number", - min: 0, - max: 10, - default: 1, - }, - Background: { - type: "color", - default: "#222222", - }, - Seed: { - type: "number", - min: 1, - max: 100, - default: 1, - }, -}; - -const domMock = { - ownerDocument: { - createElementNS: (_ns, tagName) => { - const children = []; - const attributes = {}; - return { - tagName, - attributes, - setAttribute: (key, value) => (attributes[key] = value), - appendChild: (node) => children.push(node), - children, - }; - }, - }, -}; - -export function renderSVG(qr, params) { - const roughSVG = rough.svg(domMock, { - options: { - roughness: params["Roughness"], - bowing: params["Bowing"], - fillStyle: params["Fill style"].toLowerCase(), - fillWeight: params["Fill weight"], - fill: params["Fill weight"] === 0 ? "none" : params["Fill"], - strokeWidth: params["Stroke width"], - stroke: params["Stroke width"] === 0 ? "none" : params["Stroke"], - hachureGap: params["Fill gap"], - seed: params["Seed"], - fixedDecimalPlaceDigits: 2, - }, - }); - - let matrix = qr.matrix; - let matrixWidth = qr.version * 4 + 17; - - if (params["Invert"]) { - matrixWidth += 2; - matrix = []; - for (let y = 0; y < matrixWidth; y++) { - for (let x = 0; x < matrixWidth; x++) { - if ( - x === 0 || - y === 0 || - x === matrixWidth - 1 || - y === matrixWidth - 1 - ) { - matrix.push(0); - } else { - matrix.push(qr.matrix[(y - 1) * (matrixWidth - 2) + x - 1]); - } - } - } - } - - const visited = new Uint16Array(matrixWidth * matrixWidth); - const unit = 10; - const margin = params["Margin"] * unit; - const size = matrixWidth * unit + 2 * margin; - - let svg = \`\`; - - svg += \`\`; - - const xMax = matrixWidth - 1; - const yMax = matrixWidth - 1; - - let baseX; - let baseY; - - const on = params["Invert"] - ? (x, y) => (matrix[y * matrixWidth + x] & Module.ON) === 0 - : (x, y) => (matrix[y * matrixWidth + x] & Module.ON) !== 0; - - function goRight(x, y, shape, cw) { - let sx = x; - - let vert = false; - visited[y * matrixWidth + x] = shape; - 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] = shape; - } - - paths[shape] += \`h\${(x - sx + 1) * unit}\`; - if (vert) { - goUp(x + 1, y - 1, shape, cw); - } else { - goDown(x, y, shape, 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 - 1) * unit}\`; - if (vert) { - goDown(x - 1, y + 1, shape, cw); - } else { - 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 - 1) * unit}\`; - if (horz) { - goLeft(x - 1, y - 1, shape, cw); - } else { - 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 + 1) * unit}\`; - if (horz) { - goRight(x + 1, y + 1, shape, cw); - } else { - 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 * unit},\${y * unit}\`; - - 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(\`M\${x * unit},\${y * unit}\`); - - baseY = y; - baseX = x; - - goRight(x, y, paths.length - 1, true); - } - } - - function domToString(node) { - const attrs = Object.entries(node.attributes) - .map(([key, value]) => \`\${key}="\${value}"\`) - .join(" "); - svg += \`<\${node.tagName} \${attrs}>\`; - node.children.forEach(domToString); - svg += \`\`; - } - - paths.forEach((path, i) => { - if (i === 0) return; - const g = roughSVG.path(path); - domToString(g); - }); - - svg += \`\`; - return svg; -} -` diff --git a/src/lib/presets/Glass.ts b/src/lib/presets/Glass.ts deleted file mode 100644 index a0f0052..0000000 --- a/src/lib/presets/Glass.ts +++ /dev/null @@ -1,278 +0,0 @@ -export const Glass = `import { Module, getSeededRand } from "https://qrframe.kylezhe.ng/utils.js"; - -export const paramsSchema = { - Margin: { - type: "number", - min: 0, - max: 10, - default: 2, - }, - Foreground: { - type: "color", - default: "#000000", - }, - Background: { - type: "color", - default: "#fcb9ff", - }, - Shapes: { - type: "number", - min: 1, - max: 400, - default: 100, - }, - "Stroke width": { - type: "number", - min: 0, - max: 4, - step: 0.1, - default: 1.5, - }, - "Shape gap": { - type: "number", - min: -4, - max: 4, - default: 0, - }, - "Shape opacity": { - type: "number", - min: 0, - max: 1, - step: 0.1, - default: 0.3, - }, - "QR layer": { - type: "select", - options: ["Above", "Below"], - }, - Seed: { - type: "number", - min: 1, - max: 100, - default: 1, - }, -}; - -export function renderSVG(qr, params) { - const rand = getSeededRand(params["Seed"]); - const margin = params["Margin"]; - - const unit = 4; - const offset = params["Shape gap"] / 2; - const thin = unit - params["Shape gap"]; - const matrixWidth = qr.version * 4 + 17 + 2 * margin; - const size = matrixWidth * unit; - - let svg = \`\`; - svg += \`\`; - - function getRGB() { - const r = Math.floor(rand() * 255); - const g = Math.floor(rand() * 255); - const b = Math.floor(rand() * 255); - return \`#\${r.toString(16).padStart(2, "0")}\${g.toString(16).padStart(2, "0")}\${b.toString(16).padStart(2, "0")}\`; - } - - const groups = params["Shapes"]; - - let groupLayer = ""; - - const group = Array.from({ length: matrixWidth * matrixWidth }).fill(0); - const visited = Array.from({ length: matrixWidth * matrixWidth }).fill(false); - - const queue = []; - while (queue.length < groups) { - const x = Math.floor(rand() * matrixWidth); - const y = Math.floor(rand() * matrixWidth); - if (queue.some((seed) => seed.x === x && seed.y === y)) { - continue; - } - queue.push([x, y]); - group[y * matrixWidth + x] = queue.length; - } - while (queue.length) { - const [x, y] = queue.shift(); - const id = group[y * matrixWidth + x]; - if (x > 0 && !group[y * matrixWidth + x - 1]) { - queue.push([x - 1, y]); - group[y * matrixWidth + x - 1] = id; - } - if (y > 0 && !group[(y - 1) * matrixWidth + x]) { - queue.push([x, y - 1]); - group[(y - 1) * matrixWidth + x] = id; - } - if (x < matrixWidth - 1 && !group[y * matrixWidth + x + 1]) { - queue.push([x + 1, y]); - group[y * matrixWidth + x + 1] = id; - } - if (y < matrixWidth - 1 && !group[(y + 1) * matrixWidth + x]) { - queue.push([x, y + 1]); - group[(y + 1) * matrixWidth + x] = id; - } - } - - const xMax = matrixWidth - 1; - const yMax = matrixWidth - 1; - - let baseX; - let baseY; - - function goRight(x, y, id) { - let sx = x; - let vert = false; - visited[y * matrixWidth + x] = true; - while (x < xMax) { - const right = group[x + 1 + y * matrixWidth] === id; - const vertRight = y > 0 && group[x + 1 + (y - 1) * matrixWidth] === id; - if (!right || vertRight) { - vert = right && vertRight; - break; - } - x++; - visited[y * matrixWidth + x] = true; - } - if (vert) { - groupLayer += \`h\${(x - sx + 1) * unit}v\${-2 * offset}\`; - goUp(x + 1, y - 1, id); - } else { - groupLayer += \`h\${(x - sx) * unit + thin}\`; - goDown(x, y, id); - } - } - - function goLeft(x, y, id) { - let sx = x; - let vert = false; - visited[y * matrixWidth + x] = true; - while (x > 0) { - const left = group[x - 1 + y * matrixWidth] === id; - const vertLeft = y < yMax && group[x - 1 + (y + 1) * matrixWidth] === id; - if (!left || vertLeft) { - vert = left && vertLeft; - break; - } - x--; - visited[y * matrixWidth + x] = true; - } - if (vert) { - groupLayer += \`h\${(x - sx - 1) * unit}v\${2 * offset}\`; - goDown(x - 1, y + 1, id); - } else { - groupLayer += \`h\${(x - sx) * unit - thin}\`; - goUp(x, y, id); - } - } - - function goUp(x, y, id) { - let sy = y; - let horz = false; - visited[y * matrixWidth + x] = true; - while (y > 0) { - const up = group[x + (y - 1) * matrixWidth] === id; - const horzUp = x > 0 && group[x - 1 + (y - 1) * matrixWidth] === id; - if (!up || horzUp) { - horz = up && horzUp; - break; - } - y--; - visited[y * matrixWidth + x] = true; - } - - if (x === baseX && y === baseY) { - groupLayer += "z"; - return; - } - if (horz) { - groupLayer += \`v\${(y - sy - 1) * unit}h\${-2 * offset}\`; - goLeft(x - 1, y - 1, id); - } else { - groupLayer += \`v\${(y - sy) * unit - thin}\`; - goRight(x, y, id); - } - } - - function goDown(x, y, id) { - let sy = y; - let horz = false; - visited[y * matrixWidth + x] = true; - while (y < yMax) { - const down = group[x + (y + 1) * matrixWidth] === id; - const horzDown = x < xMax && group[x + 1 + (y + 1) * matrixWidth] === id; - if (!down || horzDown) { - horz = down && horzDown; - break; - } - y++; - visited[y * matrixWidth + x] = true; - } - if (horz) { - groupLayer += \`v\${(y - sy + 1) * unit}h\${2 * offset}\`; - goRight(x + 1, y + 1, id); - } else { - groupLayer += \`v\${(y - sy) * unit + thin}\`; - goLeft(x, y, id); - } - } - - const matrix = Array.from({ length: matrixWidth * matrixWidth }).fill(0); - const qrWidth = qr.version * 4 + 17; - - for (let y = 0; y < matrixWidth; y++) { - for (let x = 0; x < matrixWidth; x++) { - if ( - y >= margin && - y < matrixWidth - margin && - x >= margin && - x < matrixWidth - margin - ) { - matrix[y * matrixWidth + x] = - qr.matrix[(y - margin) * qrWidth + (x - margin)]; - } - } - } - - let qrLayer = \`\`; - } - } - if (params["QR layer"] === "Below") svg += qrLayer + \`"/>\`; - svg += groupLayer; - if (params["QR layer"] === "Above") svg += qrLayer + \`"/>\`; - svg += \`\`; - - return svg; -} -` diff --git a/src/lib/presets/Halftone.ts b/src/lib/presets/Halftone.ts deleted file mode 100644 index f9c165d..0000000 --- a/src/lib/presets/Halftone.ts +++ /dev/null @@ -1,173 +0,0 @@ -export const Halftone = `import { Module } from "https://qrframe.kylezhe.ng/utils.js"; - -export const paramsSchema = { - Image: { - type: "file", - }, - "Image scale": { - type: "number", - min: 0, - max: 1, - step: 0.01, - default: 1, - }, - Contrast: { - type: "number", - min: 0, - max: 10, - step: 0.1, - default: 1, - }, - Brightness: { - type: "number", - min: 0, - max: 5, - step: 0.1, - default: 1.8, - }, - "QR background": { - type: "boolean", - }, - "Alignment pattern": { - type: "boolean", - default: true, - }, - "Timing pattern": { - type: "boolean", - }, - Margin: { - type: "number", - min: 0, - max: 10, - default: 2, - }, - Foreground: { - type: "color", - default: "#000000", - }, - Background: { - type: "color", - default: "#ffffff", - }, -}; - -export async function renderCanvas(qr, params, canvas) { - const unit = 3; - const pixel = 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"]; - let file = params["Image"]; - if (file == null) { - file = await fetch( - "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 pixelWidth = matrixWidth + 2 * margin; - const canvasSize = pixelWidth * unit; - const ctx = canvas.getContext("2d"); - ctx.canvas.width = canvasSize; - ctx.canvas.height = canvasSize; - - ctx.fillStyle = bg; - ctx.fillRect(0, 0, canvasSize, canvasSize); - if (params["QR background"]) { - 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 & Module.ON) { - const px = x + margin; - const py = y + margin; - ctx.fillRect(px * unit, py * unit, unit, unit); - } - } - } - } - - ctx.filter = \`brightness(\${params["Brightness"]}) contrast(\${params["Contrast"]})\`; - const imgScale = params["Image scale"] - const imgSize = Math.floor(imgScale * canvasSize); - const imgOffset = Math.floor((canvasSize - imgSize) / 2) - ctx.drawImage(image, imgOffset, imgOffset,imgSize, imgSize); - ctx.filter = "none"; - - const imageData = ctx.getImageData(0, 0, canvasSize, canvasSize); - const data = imageData.data; - - for (let y = imgOffset; y < imgOffset + imgSize; y++) { - for (let x = imgOffset; x < imgOffset + imgSize; 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 * pixel, y * pixel, pixel, pixel); - - 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 = (unit - pixel) / 2; - - for (let y = 0; y < matrixWidth; y++) { - for (let x = 0; x < matrixWidth; x++) { - const module = qr.matrix[y * matrixWidth + x]; - if (module & Module.ON) { - ctx.fillStyle = fg; - } else { - ctx.fillStyle = bg; - } - - const px = x + margin; - const py = y + margin; - - if ( - module & Module.FINDER || - (alignment && module & Module.ALIGNMENT) || - (timing && module & Module.TIMING) - ) { - ctx.fillRect(px * unit, py * unit, unit, unit); - } else { - ctx.fillRect( - px * unit + dataOffset, - py * unit + dataOffset, - pixel, - pixel - ); - } - } - } -} -` diff --git a/src/lib/presets/Layers.ts b/src/lib/presets/Layers.ts deleted file mode 100644 index ce060c4..0000000 --- a/src/lib/presets/Layers.ts +++ /dev/null @@ -1,106 +0,0 @@ -export const Layers = `import { Module } from "https://qrframe.kylezhe.ng/utils.js"; - -export const paramsSchema = { - Margin: { - type: "number", - min: 0, - max: 10, - step: 0.1, - default: 2, - }, - Background: { - type: "color", - default: "#163157", - }, - // See browser compatibility issues here - // https://developer.mozilla.org/en-US/docs/Web/CSS/mix-blend-mode - "Mix blend mode": { - type: "select", - options: [ - "normal", - "multiply", - "screen", - "overlay", - "darken", - "lighten", - "color-dodge", - "color-burn", - "hard-light", - "soft-light", - "difference", - "exclusion", - "hue", - "saturation", - "color", - "luminosity", - "plus-darker", - "plus-lighter", - ], - default: "difference", - }, - Foreground: { - type: "array", - resizable: true, - props: { - type: "color", - }, - default: ["#e80004", "#000000", "#ca70cf", "#000000", "#ffffff"], - }, - "Offset x": { - type: "array", - resizable: true, - props: { - type: "number", - min: -1, - step: 0.1, - max: 1, - }, - default: [0.6, 0.4, 0.2, 0, -0.2], - }, - "Offset y": { - type: "array", - resizable: true, - props: { - type: "number", - min: -1, - step: 0.1, - max: 1, - }, - default: [0.6, 0.4, 0.2, 0, -0.2], - }, -}; - -export function renderSVG(qr, params) { - const matrixWidth = qr.version * 4 + 17; - const margin = params["Margin"]; - const colors = params["Foreground"]; - const offsetX = params["Offset x"]; - const offsetY = params["Offset y"]; - const bg = params["Background"]; - - const size = matrixWidth + 2 * margin; - let svg = \`\`; - svg += \`\`; - - svg += \`\`; - svg += \`\`; - svg += \`\`; - - svg += \`\`; - colors.forEach((color, i) => { - svg += \`\`; - }); - svg += \`\`; - svg += \`\`; - return svg; -} -` diff --git a/src/lib/presets/Minimal.ts b/src/lib/presets/Minimal.ts deleted file mode 100644 index f592dd4..0000000 --- a/src/lib/presets/Minimal.ts +++ /dev/null @@ -1,70 +0,0 @@ -export const Minimal = `import { Module } from "https://qrframe.kylezhe.ng/utils.js"; - -export const paramsSchema = { - Margin: { - type: "number", - min: 0, - max: 10, - default: 2, - }, - "Data pixel size": { - type: "number", - min: 1, - max: 20, - default: 3, - }, - Background: { - type: "boolean", - }, -}; - -export function renderSVG(qr, params) { - const matrixWidth = qr.version * 4 + 17; - const unit = 10; - const dataSize = params["Data pixel size"]; - const margin = params["Margin"] * unit; - - const fg = "#000"; - const bg = "#fff"; - - const size = matrixWidth * unit + 2 * margin; - let svg = \`\`; - if (params["Background"]) { - svg += \`\`; - } - svg += \`\`; - - return svg; -} -` diff --git a/src/lib/presets/Mondrian.ts b/src/lib/presets/Mondrian.ts deleted file mode 100644 index 0b5e085..0000000 --- a/src/lib/presets/Mondrian.ts +++ /dev/null @@ -1,128 +0,0 @@ -export const Mondrian = `import { Module, getSeededRand } from "https://qrframe.kylezhe.ng/utils.js"; - -export const paramsSchema = { - Margin: { - type: "number", - min: 0, - max: 10, - default: 2, - }, - Foreground: { - type: "array", - props: { - type: "color", - }, - resizable: true, - default: ["#860909", "#0e21a0", "#95800f"], - }, - Background: { - type: "color", - default: "#ffffff", - }, - Lines: { - type: "color", - default: "#000000", - }, - "Line thickness": { - type: "number", - min: -10, - max: 10, - default: 2, - }, - Seed: { - type: "number", - min: 1, - max: 100, - default: 1, - }, -}; - -export function renderSVG(qr, params) { - const rand = getSeededRand(params["Seed"]); - const margin = params["Margin"]; - - const unit = 20; - const matrixWidth = qr.version * 4 + 17 + 2 * margin; - const size = matrixWidth * unit; - - const gap = params["Line thickness"]; - const offset = gap / 2; - - let svg = \`\`; - svg += \`\`; - - let lightLayer = \` \` (svg += layer + \`"/>\`)); - svg += lightLayer + \`"/>\`; - svg += \`\`; - - return svg; -} -` diff --git a/src/lib/presets/Neon.ts b/src/lib/presets/Neon.ts deleted file mode 100644 index e016c50..0000000 --- a/src/lib/presets/Neon.ts +++ /dev/null @@ -1,328 +0,0 @@ -export const Neon = `import { Module, getSeededRand } from "https://qrframe.kylezhe.ng/utils.js"; - -export const paramsSchema = { - Foreground: { - type: "array", - props: { - type: "color", - }, - resizable: true, - default: ["#fb51dd", "#f2cffa", "#aefdfd", "#54a9fe"], - }, - Background: { - type: "color", - default: "#101529", - }, - Margin: { - type: "number", - min: 0, - max: 10, - default: 4, - }, - "Quiet zone": { - type: "select", - options: ["Minimal", "Full"], - }, - Invert: { - type: "boolean", - }, - "Line thickness": { - type: "number", - min: 1, - max: 4, - default: 2, - }, - "Finder thickness": { - type: "number", - min: 1, - max: 4, - default: 4, - }, - "Glow strength": { - type: "number", - min: 0, - max: 4, - step: 0.1, - default: 2, - }, - Seed: { - type: "number", - min: 1, - max: 100, - default: 1, - }, -}; - -export function renderSVG(qr, params) { - const rand = getSeededRand(params["Seed"]); - const margin = params["Margin"]; - const colors = 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 - 1; y++) { - for (let x = 0; x < matrixWidth; x++) { - if (rand() > 0.5) newMatrix[y * matrixWidth + x] = Module.ON; - } - } - for (let y = margin - 1; y < margin + qrWidth + 1; y++) { - for (let x = 0; x < margin - 1; 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 + 1; x < matrixWidth; x++) { - if (rand() > 0.5) newMatrix[y * matrixWidth + x] = Module.ON; - } - } - for (let y = margin + qrWidth + 1; y < matrixWidth; y++) { - for (let x = 0; x < matrixWidth; x++) { - if (rand() > 0.5) newMatrix[y * matrixWidth + x] = Module.ON; - } - } - if (params["Quiet zone"] === "Minimal") { - for (let x = margin + 8; x < matrixWidth - margin - 8; x++) { - if (rand() > 0.5) newMatrix[(margin - 1) * matrixWidth + x] = Module.ON; - } - for (let y = margin + 8; y < matrixWidth - margin; y++) { - if (y < matrixWidth - margin - 8) { - if (rand() > 0.5) newMatrix[y * matrixWidth + margin - 1] = Module.ON; - } - if (rand() > 0.5) - newMatrix[y * matrixWidth + matrixWidth - margin] = Module.ON; - } - for (let x = margin + 8; x < matrixWidth - margin + 1; x++) { - if (rand() > 0.5) - newMatrix[(matrixWidth - margin) * matrixWidth + x] = Module.ON; - } - } - - const unit = 4; - let thin = params["Line thickness"]; - let offset = (unit - thin) / 2; - const size = matrixWidth * unit - 2 * offset; - - let svg = \`\`; - - svg += \` - - -\`; - - svg += \`\`; - - 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, shape, cw) { - let sx = x; - - let vert = false; - visited[y * matrixWidth + x] = shape; - 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] = shape; - } - - if (vert) { - paths[shape] += \`h\${(x - sx + 1) * unit}v\${-2 * offset}\`; - goUp(x + 1, y - 1, shape, cw); - } else { - paths[shape] += \`h\${(x - sx) * unit + thin}\`; - goDown(x, y, shape, 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; - } - - if (vert) { - paths[shape] += \`h\${(x - sx - 1) * unit}v\${2 * offset}\`; - goDown(x - 1, y + 1, shape, cw); - } else { - paths[shape] += \`h\${(x - sx) * unit - thin}\`; - 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; - } - - if (horz) { - paths[shape] += \`v\${(y - sy - 1) * unit}h\${-2 * offset}\`; - goLeft(x - 1, y - 1, shape, cw); - } else { - paths[shape] += \`v\${(y - sy) * unit - thin}\`; - 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; - } - - if (horz) { - paths[shape] += \`v\${(y - sy + 1) * unit}h\${2 * offset}\`; - goRight(x + 1, y + 1, shape, cw); - } else { - paths[shape] += \`v\${(y - sy) * unit + thin}\`; - 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 (newMatrix[y * matrixWidth + x] & Module.FINDER) { - thin = params["Finder thickness"]; - offset = (unit - thin) / 2; - } else { - thin = params["Line thickness"]; - offset = (unit - thin) / 2; - } - - if (!on(x, y)) { - const shape = visited[y * matrixWidth + x - 1]; - paths[shape] += - \`M\${x * unit - offset},\${y * unit - offset}v\${2 * offset}\`; - - 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; - } - - const color = colors[Math.floor(rand() * colors.length)]; - paths.push( - \`\`; - }); - svg += \`\`; - return svg; -} -` diff --git a/src/lib/presets/Quantum.ts b/src/lib/presets/Quantum.ts deleted file mode 100644 index 5d1f6e6..0000000 --- a/src/lib/presets/Quantum.ts +++ /dev/null @@ -1,180 +0,0 @@ -export const Quantum = `// Based on QRBTF's A1P style -// https://github.com/CPunisher/react-qrbtf/blob/master/src/components/QRNormal.tsx -import { Module, getSeededRand } from "https://qrframe.kylezhe.ng/utils.js"; - -export const paramsSchema = { - Margin: { - type: "number", - min: 0, - max: 10, - step: 0.1, - default: 2, - }, - Background: { - type: "color", - default: "#ffffff", - }, - Foreground: { - type: "color", - default: "#000000", - }, - "Finder pattern": { - type: "select", - options: ["Atom", "Planet"], - }, - Particles: { - type: "boolean", - default: true, - }, - Seed: { - type: "number", - min: 1, - max: 100, - default: 1, - }, -}; - -export function renderSVG(qr, params) { - const rand = getSeededRand(params["Seed"]); - const range = (min, max) => - Math.trunc(100 * (rand() * (max - min) + min)) / 100; - - const matrixWidth = qr.version * 4 + 17; - const margin = params["Margin"]; - const bg = params["Background"]; - const fg = params["Foreground"]; - - const size = matrixWidth + 2 * margin; - let svg = \`\`; - svg += \`\`; - - for (const [x, y] of [ - [0, 0], - [matrixWidth - 7, 0], - [0, matrixWidth - 7], - ]) { - svg += \`\`; - svg += \`\`; - svg += \`\`; - svg += \`\`; - svg += \`\`; - svg += \`\`; - - switch (params["Finder pattern"]) { - case "Atom": - let r1 = 0.98; - let r2 = 1.5; - - const a = 0.87 * r2; - const b = 0.5 * r2; - svg += \`\`; - } - - let linesLayer = \`\`; - - function on(x, y) { - return (qr.matrix[y * matrixWidth + x] & Module.ON) !== 0; - } - - const visitArray = Array.from({ length: matrixWidth * matrixWidth }).fill( - false - ); - - function visited(x, y) { - return visitArray[y * matrixWidth + x]; - } - function visitCenter(x, y) { - visitArray[y * matrixWidth + x] = true; - dotsLayer += \`\`; - } - function visit(x, y) { - visitArray[y * matrixWidth + x] = true; - dotsLayer += \`\`; - } - - for (let y = 0; y < matrixWidth; y++) { - for (let x = 0; x < matrixWidth; x++) { - const module = qr.matrix[y * matrixWidth + x]; - if (module & Module.FINDER) continue; - - if (params["Particles"] && y < matrixWidth - 2 && x < matrixWidth - 2) { - let xCross = false; - let tCross = false; - - let a = range(-10, 10); - if ( - on(x, y) && - !visited(x, y) && - on(x + 2, y) && - !visited(x + 2, y) && - on(x + 1, y + 1) && - !visited(x + 1, y + 1) && - on(x, y + 2) && - !visited(x, y + 2) && - on(x + 2, y + 2) && - !visited(x + 2, y + 2) - ) { - linesLayer += \`M\${x + 0.5},\${y + 0.5}a1.4,.35 \${45 + a},0,1 2,2a1.4,.35 \${45 + a},0,1 -2,-2\`; - linesLayer += \`M\${x + 2.5},\${y + 0.5}a.35,1.4 \${45 + a},0,1 -2,2a.35,1.4 \${45 + a},0,1 2,-2\`; - xCross = true; - } - if ( - on(x + 1, y) && - !visited(x + 1, y) && - on(x, y + 1) && - !visited(x, y + 1) && - on(x + 1, y + 1) && - !visited(x + 1, y + 1) && - on(x + 2, y + 1) && - !visited(x + 2, y + 1) && - on(x + 1, y + 2) && - !visited(x + 1, y + 2) - ) { - linesLayer += \`M\${x},\${y + 1.55}a1,.35 \${a},0,1 3,0a1,.35 \${a},0,1 -3,0\`; - linesLayer += \`M\${x + 1.5},\${y}a.35,1 \${a},0,1 0,3a.35,1 \${a},0,1 0,-3\`; - tCross = true; - } - if (xCross) { - visit(x, y); - visit(x + 2, y); - visitCenter(x + 1, y + 1); - visit(x, y + 2); - visit(x + 2, y + 2); - } - if (tCross) { - visit(x + 1, y); - visit(x, y + 1); - visitCenter(x + 1, y + 1); - visit(x + 2, y + 1); - visit(x + 1, y + 2); - } - } - - if (!visited(x, y) && on(x, y)) { - dotsLayer += \`\`; - } - } - } - - linesLayer += \`"/>\`; - svg += linesLayer; - dotsLayer += \`\`; - svg += dotsLayer; - svg += \`\`; - - return svg; -} -` diff --git a/src/lib/presets/Tile.ts b/src/lib/presets/Tile.ts deleted file mode 100644 index 413436d..0000000 --- a/src/lib/presets/Tile.ts +++ /dev/null @@ -1,163 +0,0 @@ -export const Tile = `import { Module } from "https://qrframe.kylezhe.ng/utils.js"; - -export const paramsSchema = { - Margin: { - type: "number", - min: 0, - max: 20, - default: 10, - }, - "Inner square": { - type: "number", - min: 0, - max: 10, - default: 2, - }, - "Outer square": { - type: "number", - min: 0, - max: 10, - default: 6, - }, - Foreground: { - type: "color", - default: "#000000", - }, - Background: { - type: "color", - default: "#ffffff", - }, - Grout: { - type: "color", - default: "#b3b8fd", - }, -}; - -export function renderSVG(qr, params) { - const unit = 16; - const gap = 2; - const offset = gap / 2; - - const margin = params["Margin"]; - const qrWidth = qr.version * 4 + 17; - const matrixWidth = qrWidth + 2 * margin; - - const fg = params["Foreground"]; - const bg = params["Background"]; - const grout = params["Grout"]; - - const newMatrix = Array.from({ length: matrixWidth * matrixWidth }).fill(0); - - const start = margin; - const end = matrixWidth - 1 - margin; - const inner = params["Inner square"]; - const outer = params["Outer square"]; - for (let y = 0; y < matrixWidth; y++) { - for (let x = 0; x < matrixWidth; x++) { - // outer square - if (y === start - outer && x >= start - outer && x <= end + outer) { - newMatrix[y * matrixWidth + x] = Module.ON; - } else if ( - (x === start - outer || x === end + outer) && - y >= start - outer + 1 && - y <= end + outer - 1 - ) { - newMatrix[y * matrixWidth + x] = Module.ON; - newMatrix[y * matrixWidth + x] = Module.ON; - } else if (y === end + outer && x >= start - outer && x <= end + outer) { - newMatrix[y * matrixWidth + x] = Module.ON; - } - // inner square - else if (y === start - inner && x >= start - inner && x <= end + inner) { - newMatrix[y * matrixWidth + x] = Module.ON; - } else if ( - (x === start - inner || x === end + inner) && - y >= start - inner + 1 && - y <= end + inner - 1 - ) { - newMatrix[y * matrixWidth + x] = Module.ON; - newMatrix[y * matrixWidth + x] = Module.ON; - } else if (y === end + inner && x >= start - inner && x <= end + inner) { - newMatrix[y * matrixWidth + x] = Module.ON; - } - // qr code w/ quiet zone - else if ( - y >= margin - inner && - y < matrixWidth - margin + inner && - x >= margin - inner && - x < matrixWidth - margin + inner - ) { - if ( - y >= margin && - y < matrixWidth - margin && - x >= margin && - x < matrixWidth - margin - ) { - newMatrix[y * matrixWidth + x] = - qr.matrix[(y - margin) * qrWidth + x - margin]; - } - } - // between squares - else if ( - y > start - outer && - y < end + outer && - x > start - outer && - x < end + outer - ) { - if ((x + y) & 1) { - newMatrix[y * matrixWidth + x] = Module.ON; - } - // outside squares - } else { - if (x % 4 && y % 4) { - if ((x % 8 < 4 && y % 8 < 4) || (x % 8 > 4 && y % 8 > 4)) { - newMatrix[y * matrixWidth + x] = Module.ON; - } - } else { - newMatrix[y * matrixWidth + x] = Module.ON; - } - } - } - } - - const size = matrixWidth * unit; - let svg = \`\`; - svg += \`\`; - svg += \`\`; - svg += layer + \`"/>\`; - svg += \`\`; - - return svg; -} -` diff --git a/src/lib/presets/Tutorial.ts b/src/lib/presets/Tutorial.ts deleted file mode 100644 index e3c735c..0000000 --- a/src/lib/presets/Tutorial.ts +++ /dev/null @@ -1,68 +0,0 @@ -export const Tutorial = `import { Module } from "https://qrframe.kylezhe.ng/utils.js"; -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", - }, -}; - -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 += \`\`; - svg += \`\`; - - 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 & Module.ON) { -// ctx.fillRect((x + margin) * unit, (y + margin) * unit, unit, unit) -// } -// } -// } -// } -` diff --git a/tsconfig.json b/tsconfig.json index 2b7f63e..0ac386b 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -14,7 +14,7 @@ "types": ["vinxi/types/client"], "isolatedModules": true, "paths": { - "~/*": ["./src/*"] + "~/*": ["./src/*"], } } } diff --git a/updatePresets.js b/updatePresets.js deleted file mode 100644 index 0f5a4f7..0000000 --- a/updatePresets.js +++ /dev/null @@ -1,41 +0,0 @@ -import { promises as fs } from "fs"; -import path from "path"; - -const dev = process.argv[2] === "dev"; -const url = dev ? "http://localhost:3000" : "https://qrframe.kylezhe.ng"; - -async function convertTsToJs(inputDir, outputDir) { - try { - // Ensure output directory exists - await fs.mkdir(outputDir, { recursive: true }); - - const files = await fs.readdir(inputDir); - const jsFiles = files.filter((file) => file.endsWith(".js")); - - for (const file of jsFiles) { - const inputPath = path.join(inputDir, file); - const outputPath = path.join(outputDir, file.slice(0, -3) + ".ts"); - - let code = await fs.readFile(inputPath, "utf-8"); - - code = code.replace("REPLACE_URL", url); - code = code.replaceAll("`", "\\`"); - code = code.replaceAll("${", "\\${"); - - await fs.writeFile( - outputPath, - `export const ${file.slice(0, -3)} = \`${code.slice(0, -1)}\n\`\n`, - "utf-8" - ); - if (!dev) console.log(`Converted and formatted: ${inputPath} -> ${outputPath}`); - } - - if (!dev) console.log("Conversion and formatting completed successfully."); - } catch (error) { - console.error("An error occurred:", error); - } -} - -const inputDir = "./presets"; -const outputDir = "./src/lib/presets"; -convertTsToJs(inputDir, outputDir);