editable x y scaling

main
Kyle Zheng 2024-06-07 23:54:34 -04:00
rodzic c9370d62a6
commit da31679869
13 zmienionych plików z 673 dodań i 187 usunięć

132
fuqr/pkg/fuqr.d.ts vendored
Wyświetl plik

@ -39,14 +39,6 @@ export enum Toggle {
}
/**
*/
export enum ECL {
Low = 0,
Medium = 1,
Quartile = 2,
High = 3,
}
/**
*/
export enum Mask {
M0 = 0,
M1 = 1,
@ -59,9 +51,11 @@ export enum Mask {
}
/**
*/
export enum SvgError {
InvalidEncoding = 0,
ExceedsMaxCapacity = 1,
export enum ECL {
Low = 0,
Medium = 1,
Quartile = 2,
High = 3,
}
/**
*/
@ -72,23 +66,91 @@ export enum Mode {
}
/**
*/
export enum QrError {
InvalidEncoding = 0,
ExceedsMaxCapacity = 1,
}
/**
*/
export class Margin {
free(): void;
/**
* @param {number} margin
*/
constructor(margin: number);
/**
* @param {number} top
* @returns {Margin}
*/
setTop(top: number): Margin;
/**
* @param {number} right
* @returns {Margin}
*/
setRight(right: number): Margin;
/**
* @param {number} bottom
* @returns {Margin}
*/
setBottom(bottom: number): Margin;
/**
* @param {number} left
* @returns {Margin}
*/
setLeft(left: number): Margin;
/**
* @param {number} y
* @returns {Margin}
*/
y(y: number): Margin;
/**
* @param {number} x
* @returns {Margin}
*/
x(x: number): Margin;
/**
*/
bottom: number;
/**
*/
left: number;
/**
*/
right: number;
/**
*/
top: number;
}
/**
*/
export class Matrix {
free(): void;
/**
* @returns {number}
*/
width(): number;
/**
* @returns {number}
*/
height(): number;
/**
*/
ecl: ECL;
/**
*/
margin: Margin;
/**
*/
mask: Mask;
/**
*/
mode: Mode;
/**
*/
value: any[];
/**
*/
version: Version;
/**
*/
width: number;
}
/**
*/
@ -117,6 +179,11 @@ export class QrOptions {
* @returns {QrOptions}
*/
mask(mask?: Mask): QrOptions;
/**
* @param {Margin} margin
* @returns {QrOptions}
*/
margin(margin: Margin): QrOptions;
}
/**
*/
@ -146,20 +213,20 @@ export class SvgOptions {
*/
background(background: string): SvgOptions;
/**
* @param {Float64Array} scale_matrix
* @param {Uint8Array} scale_matrix
* @returns {SvgOptions}
*/
scale_x_matrix(scale_matrix: Float64Array): SvgOptions;
scale_x_matrix(scale_matrix: Uint8Array): SvgOptions;
/**
* @param {Float64Array} scale_matrix
* @param {Uint8Array} scale_matrix
* @returns {SvgOptions}
*/
scale_y_matrix(scale_matrix: Float64Array): SvgOptions;
scale_y_matrix(scale_matrix: Uint8Array): SvgOptions;
/**
* @param {Float64Array} scale_matrix
* @param {Uint8Array} scale_matrix
* @returns {SvgOptions}
*/
scale_matrix(scale_matrix: Float64Array): SvgOptions;
scale_matrix(scale_matrix: Uint8Array): SvgOptions;
/**
* @param {Toggle} toggle
* @returns {SvgOptions}
@ -203,23 +270,44 @@ export type InitInput = RequestInfo | URL | Response | BufferSource | WebAssembl
export interface InitOutput {
readonly memory: WebAssembly.Memory;
readonly __wbg_margin_free: (a: number) => void;
readonly __wbg_get_margin_top: (a: number) => number;
readonly __wbg_set_margin_top: (a: number, b: number) => void;
readonly __wbg_get_margin_right: (a: number) => number;
readonly __wbg_set_margin_right: (a: number, b: number) => void;
readonly __wbg_get_margin_bottom: (a: number) => number;
readonly __wbg_set_margin_bottom: (a: number, b: number) => void;
readonly __wbg_get_margin_left: (a: number) => number;
readonly __wbg_set_margin_left: (a: number, b: number) => void;
readonly margin_new: (a: number) => number;
readonly margin_setTop: (a: number, b: number) => number;
readonly margin_setRight: (a: number, b: number) => number;
readonly margin_setBottom: (a: number, b: number) => number;
readonly margin_setLeft: (a: number, b: number) => number;
readonly margin_y: (a: number, b: number) => number;
readonly margin_x: (a: number, b: number) => number;
readonly __wbg_matrix_free: (a: number) => void;
readonly __wbg_get_matrix_value: (a: number, b: number) => void;
readonly __wbg_set_matrix_value: (a: number, b: number, c: number) => void;
readonly __wbg_get_matrix_width: (a: number) => number;
readonly __wbg_set_matrix_width: (a: number, b: number) => void;
readonly __wbg_get_matrix_margin: (a: number) => number;
readonly __wbg_set_matrix_margin: (a: number, b: number) => void;
readonly __wbg_get_matrix_mode: (a: number) => number;
readonly __wbg_set_matrix_mode: (a: number, b: number) => void;
readonly __wbg_get_matrix_version: (a: number) => number;
readonly __wbg_set_matrix_version: (a: number, b: number) => void;
readonly __wbg_get_matrix_ecl: (a: number) => number;
readonly __wbg_set_matrix_ecl: (a: number, b: number) => void;
readonly __wbg_get_matrix_mask: (a: number) => number;
readonly __wbg_set_matrix_mask: (a: number, b: number) => void;
readonly matrix_width: (a: number) => number;
readonly matrix_height: (a: number) => number;
readonly __wbg_qroptions_free: (a: number) => void;
readonly qroptions_new: () => number;
readonly qroptions_min_version: (a: number, b: number) => number;
readonly qroptions_min_ecl: (a: number, b: number) => number;
readonly qroptions_mode: (a: number, b: number) => number;
readonly qroptions_mask: (a: number, b: number) => number;
readonly qroptions_margin: (a: number, b: number) => number;
readonly __wbg_svgoptions_free: (a: number) => void;
readonly svgoptions_new: () => number;
readonly svgoptions_margin: (a: number, b: number) => number;

Wyświetl plik

@ -17,18 +17,6 @@ function addHeapObject(obj) {
function getObject(idx) { return heap[idx]; }
function dropObject(idx) {
if (idx < 132) return;
heap[idx] = heap_next;
heap_next = idx;
}
function takeObject(idx) {
const ret = getObject(idx);
dropObject(idx);
return ret;
}
function isLikeNone(x) {
return x === undefined || x === null;
}
@ -51,6 +39,18 @@ function getInt32Memory0() {
return cachedInt32Memory0;
}
function dropObject(idx) {
if (idx < 132) return;
heap[idx] = heap_next;
heap_next = idx;
}
function takeObject(idx) {
const ret = getObject(idx);
dropObject(idx);
return ret;
}
const cachedTextDecoder = (typeof TextDecoder !== 'undefined' ? new TextDecoder('utf-8', { ignoreBOM: true, fatal: true }) : { decode: () => { throw Error('TextDecoder not available') } } );
if (typeof TextDecoder !== 'undefined') { cachedTextDecoder.decode(); };
@ -162,9 +162,9 @@ function passStringToWasm0(arg, malloc, realloc) {
return ptr;
}
function passArrayF64ToWasm0(arg, malloc) {
const ptr = malloc(arg.length * 8, 8) >>> 0;
getFloat64Memory0().set(arg, ptr / 8);
function passArray8ToWasm0(arg, malloc) {
const ptr = malloc(arg.length * 1, 1) >>> 0;
getUint8Memory0().set(arg, ptr / 1);
WASM_VECTOR_LEN = arg.length;
return ptr;
}
@ -229,16 +229,158 @@ export const Module = Object.freeze({ DataOFF:0,"0":"DataOFF",DataON:1,"1":"Data
export const Toggle = Object.freeze({ Background:0,"0":"Background",BackgroundPixels:1,"1":"BackgroundPixels",ForegroundPixels:2,"2":"ForegroundPixels", });
/**
*/
export const ECL = Object.freeze({ Low:0,"0":"Low",Medium:1,"1":"Medium",Quartile:2,"2":"Quartile",High:3,"3":"High", });
/**
*/
export const Mask = Object.freeze({ M0:0,"0":"M0",M1:1,"1":"M1",M2:2,"2":"M2",M3:3,"3":"M3",M4:4,"4":"M4",M5:5,"5":"M5",M6:6,"6":"M6",M7:7,"7":"M7", });
/**
*/
export const SvgError = Object.freeze({ InvalidEncoding:0,"0":"InvalidEncoding",ExceedsMaxCapacity:1,"1":"ExceedsMaxCapacity", });
export const ECL = Object.freeze({ Low:0,"0":"Low",Medium:1,"1":"Medium",Quartile:2,"2":"Quartile",High:3,"3":"High", });
/**
*/
export const Mode = Object.freeze({ Numeric:0,"0":"Numeric",Alphanumeric:1,"1":"Alphanumeric",Byte:2,"2":"Byte", });
/**
*/
export const QrError = Object.freeze({ InvalidEncoding:0,"0":"InvalidEncoding",ExceedsMaxCapacity:1,"1":"ExceedsMaxCapacity", });
const MarginFinalization = (typeof FinalizationRegistry === 'undefined')
? { register: () => {}, unregister: () => {} }
: new FinalizationRegistry(ptr => wasm.__wbg_margin_free(ptr >>> 0));
/**
*/
export class Margin {
static __wrap(ptr) {
ptr = ptr >>> 0;
const obj = Object.create(Margin.prototype);
obj.__wbg_ptr = ptr;
MarginFinalization.register(obj, obj.__wbg_ptr, obj);
return obj;
}
__destroy_into_raw() {
const ptr = this.__wbg_ptr;
this.__wbg_ptr = 0;
MarginFinalization.unregister(this);
return ptr;
}
free() {
const ptr = this.__destroy_into_raw();
wasm.__wbg_margin_free(ptr);
}
/**
* @returns {number}
*/
get top() {
const ret = wasm.__wbg_get_margin_top(this.__wbg_ptr);
return ret >>> 0;
}
/**
* @param {number} arg0
*/
set top(arg0) {
wasm.__wbg_set_margin_top(this.__wbg_ptr, arg0);
}
/**
* @returns {number}
*/
get right() {
const ret = wasm.__wbg_get_margin_right(this.__wbg_ptr);
return ret >>> 0;
}
/**
* @param {number} arg0
*/
set right(arg0) {
wasm.__wbg_set_margin_right(this.__wbg_ptr, arg0);
}
/**
* @returns {number}
*/
get bottom() {
const ret = wasm.__wbg_get_margin_bottom(this.__wbg_ptr);
return ret >>> 0;
}
/**
* @param {number} arg0
*/
set bottom(arg0) {
wasm.__wbg_set_margin_bottom(this.__wbg_ptr, arg0);
}
/**
* @returns {number}
*/
get left() {
const ret = wasm.__wbg_get_margin_left(this.__wbg_ptr);
return ret >>> 0;
}
/**
* @param {number} arg0
*/
set left(arg0) {
wasm.__wbg_set_margin_left(this.__wbg_ptr, arg0);
}
/**
* @param {number} margin
*/
constructor(margin) {
const ret = wasm.margin_new(margin);
this.__wbg_ptr = ret >>> 0;
return this;
}
/**
* @param {number} top
* @returns {Margin}
*/
setTop(top) {
const ptr = this.__destroy_into_raw();
const ret = wasm.margin_setTop(ptr, top);
return Margin.__wrap(ret);
}
/**
* @param {number} right
* @returns {Margin}
*/
setRight(right) {
const ptr = this.__destroy_into_raw();
const ret = wasm.margin_setRight(ptr, right);
return Margin.__wrap(ret);
}
/**
* @param {number} bottom
* @returns {Margin}
*/
setBottom(bottom) {
const ptr = this.__destroy_into_raw();
const ret = wasm.margin_setBottom(ptr, bottom);
return Margin.__wrap(ret);
}
/**
* @param {number} left
* @returns {Margin}
*/
setLeft(left) {
const ptr = this.__destroy_into_raw();
const ret = wasm.margin_setLeft(ptr, left);
return Margin.__wrap(ret);
}
/**
* @param {number} y
* @returns {Margin}
*/
y(y) {
const ptr = this.__destroy_into_raw();
const ret = wasm.margin_y(ptr, y);
return Margin.__wrap(ret);
}
/**
* @param {number} x
* @returns {Margin}
*/
x(x) {
const ptr = this.__destroy_into_raw();
const ret = wasm.margin_x(ptr, x);
return Margin.__wrap(ret);
}
}
const MatrixFinalization = (typeof FinalizationRegistry === 'undefined')
? { register: () => {}, unregister: () => {} }
@ -291,17 +433,32 @@ export class Matrix {
wasm.__wbg_set_matrix_value(this.__wbg_ptr, ptr0, len0);
}
/**
* @returns {number}
* @returns {Margin}
*/
get width() {
const ret = wasm.__wbg_get_matrix_width(this.__wbg_ptr);
return ret >>> 0;
get margin() {
const ret = wasm.__wbg_get_matrix_margin(this.__wbg_ptr);
return Margin.__wrap(ret);
}
/**
* @param {number} arg0
* @param {Margin} arg0
*/
set width(arg0) {
wasm.__wbg_set_matrix_width(this.__wbg_ptr, arg0);
set margin(arg0) {
_assertClass(arg0, Margin);
var ptr0 = arg0.__destroy_into_raw();
wasm.__wbg_set_matrix_margin(this.__wbg_ptr, ptr0);
}
/**
* @returns {Mode}
*/
get mode() {
const ret = wasm.__wbg_get_matrix_mode(this.__wbg_ptr);
return ret;
}
/**
* @param {Mode} arg0
*/
set mode(arg0) {
wasm.__wbg_set_matrix_mode(this.__wbg_ptr, arg0);
}
/**
* @returns {Version}
@ -344,6 +501,20 @@ export class Matrix {
set mask(arg0) {
wasm.__wbg_set_matrix_mask(this.__wbg_ptr, arg0);
}
/**
* @returns {number}
*/
width() {
const ret = wasm.matrix_width(this.__wbg_ptr);
return ret >>> 0;
}
/**
* @returns {number}
*/
height() {
const ret = wasm.matrix_height(this.__wbg_ptr);
return ret >>> 0;
}
}
const QrOptionsFinalization = (typeof FinalizationRegistry === 'undefined')
@ -417,6 +588,17 @@ export class QrOptions {
const ret = wasm.qroptions_mask(ptr, isLikeNone(mask) ? 8 : mask);
return QrOptions.__wrap(ret);
}
/**
* @param {Margin} margin
* @returns {QrOptions}
*/
margin(margin) {
const ptr = this.__destroy_into_raw();
_assertClass(margin, Margin);
var ptr0 = margin.__destroy_into_raw();
const ret = wasm.qroptions_margin(ptr, ptr0);
return QrOptions.__wrap(ret);
}
}
const SvgOptionsFinalization = (typeof FinalizationRegistry === 'undefined')
@ -493,34 +675,34 @@ export class SvgOptions {
return SvgOptions.__wrap(ret);
}
/**
* @param {Float64Array} scale_matrix
* @param {Uint8Array} scale_matrix
* @returns {SvgOptions}
*/
scale_x_matrix(scale_matrix) {
const ptr = this.__destroy_into_raw();
const ptr0 = passArrayF64ToWasm0(scale_matrix, wasm.__wbindgen_malloc);
const ptr0 = passArray8ToWasm0(scale_matrix, wasm.__wbindgen_malloc);
const len0 = WASM_VECTOR_LEN;
const ret = wasm.svgoptions_scale_x_matrix(ptr, ptr0, len0);
return SvgOptions.__wrap(ret);
}
/**
* @param {Float64Array} scale_matrix
* @param {Uint8Array} scale_matrix
* @returns {SvgOptions}
*/
scale_y_matrix(scale_matrix) {
const ptr = this.__destroy_into_raw();
const ptr0 = passArrayF64ToWasm0(scale_matrix, wasm.__wbindgen_malloc);
const ptr0 = passArray8ToWasm0(scale_matrix, wasm.__wbindgen_malloc);
const len0 = WASM_VECTOR_LEN;
const ret = wasm.svgoptions_scale_y_matrix(ptr, ptr0, len0);
return SvgOptions.__wrap(ret);
}
/**
* @param {Float64Array} scale_matrix
* @param {Uint8Array} scale_matrix
* @returns {SvgOptions}
*/
scale_matrix(scale_matrix) {
const ptr = this.__destroy_into_raw();
const ptr0 = passArrayF64ToWasm0(scale_matrix, wasm.__wbindgen_malloc);
const ptr0 = passArray8ToWasm0(scale_matrix, wasm.__wbindgen_malloc);
const len0 = WASM_VECTOR_LEN;
const ret = wasm.svgoptions_scale_matrix(ptr, ptr0, len0);
return SvgOptions.__wrap(ret);
@ -732,9 +914,6 @@ function __wbg_get_imports() {
const ret = arg0;
return addHeapObject(ret);
};
imports.wbg.__wbindgen_object_drop_ref = function(arg0) {
takeObject(arg0);
};
imports.wbg.__wbindgen_try_into_number = function(arg0) {
let result;
try { result = +getObject(arg0) } catch (e) { result = e }
@ -747,6 +926,9 @@ imports.wbg.__wbindgen_number_get = function(arg0, arg1) {
getFloat64Memory0()[arg0 / 8 + 1] = isLikeNone(ret) ? 0 : ret;
getInt32Memory0()[arg0 / 4 + 0] = !isLikeNone(ret);
};
imports.wbg.__wbindgen_object_drop_ref = function(arg0) {
takeObject(arg0);
};
imports.wbg.__wbg_new_abda76e883ba8a5f = function() {
const ret = new Error();
return addHeapObject(ret);

Plik binarny nie jest wyświetlany.

Wyświetl plik

@ -1,23 +1,44 @@
/* tslint:disable */
/* eslint-disable */
export const memory: WebAssembly.Memory;
export function __wbg_margin_free(a: number): void;
export function __wbg_get_margin_top(a: number): number;
export function __wbg_set_margin_top(a: number, b: number): void;
export function __wbg_get_margin_right(a: number): number;
export function __wbg_set_margin_right(a: number, b: number): void;
export function __wbg_get_margin_bottom(a: number): number;
export function __wbg_set_margin_bottom(a: number, b: number): void;
export function __wbg_get_margin_left(a: number): number;
export function __wbg_set_margin_left(a: number, b: number): void;
export function margin_new(a: number): number;
export function margin_setTop(a: number, b: number): number;
export function margin_setRight(a: number, b: number): number;
export function margin_setBottom(a: number, b: number): number;
export function margin_setLeft(a: number, b: number): number;
export function margin_y(a: number, b: number): number;
export function margin_x(a: number, b: number): number;
export function __wbg_matrix_free(a: number): void;
export function __wbg_get_matrix_value(a: number, b: number): void;
export function __wbg_set_matrix_value(a: number, b: number, c: number): void;
export function __wbg_get_matrix_width(a: number): number;
export function __wbg_set_matrix_width(a: number, b: number): void;
export function __wbg_get_matrix_margin(a: number): number;
export function __wbg_set_matrix_margin(a: number, b: number): void;
export function __wbg_get_matrix_mode(a: number): number;
export function __wbg_set_matrix_mode(a: number, b: number): void;
export function __wbg_get_matrix_version(a: number): number;
export function __wbg_set_matrix_version(a: number, b: number): void;
export function __wbg_get_matrix_ecl(a: number): number;
export function __wbg_set_matrix_ecl(a: number, b: number): void;
export function __wbg_get_matrix_mask(a: number): number;
export function __wbg_set_matrix_mask(a: number, b: number): void;
export function matrix_width(a: number): number;
export function matrix_height(a: number): number;
export function __wbg_qroptions_free(a: number): void;
export function qroptions_new(): number;
export function qroptions_min_version(a: number, b: number): number;
export function qroptions_min_ecl(a: number, b: number): number;
export function qroptions_mode(a: number, b: number): number;
export function qroptions_mask(a: number, b: number): number;
export function qroptions_margin(a: number, b: number): number;
export function __wbg_svgoptions_free(a: number): void;
export function svgoptions_new(): number;
export function svgoptions_margin(a: number, b: number): number;

Wyświetl plik

@ -4,29 +4,23 @@ import "virtual:uno.css";
import { Router } from "@solidjs/router";
import { FileRoutes } from "@solidjs/start/router";
import { Suspense } from "solid-js";
import { QrContextProvider } from "./lib/QrContext";
import { SvgContextProvider } from "./lib/SvgContext";
export default function App() {
return (
<>
<QrContextProvider>
<SvgContextProvider>
<Router root={(props) => <Suspense>{props.children}</Suspense>}>
<FileRoutes />
</Router>
<footer class="text-sm text-center p-4">
made with by{" "}
<a
class="font-semibold hover:text-fore-base/80 focus-visible:(outline-none ring-2 ring-fore-base ring-offset-2 ring-offset-back-base)"
href="https://kylezhe.ng"
target="_blank"
>
@zhengkyl
</a>
</footer>
</SvgContextProvider>
</QrContextProvider>
<Router root={(props) => <Suspense>{props.children}</Suspense>}>
<FileRoutes />
</Router>
<footer class="text-sm text-center p-4">
made with by{" "}
<a
class="font-semibold hover:text-fore-base/80 focus-visible:(outline-none ring-2 ring-fore-base ring-offset-2 ring-offset-back-base)"
href="https://kylezhe.ng"
target="_blank"
>
@zhengkyl
</a>
</footer>
</>
);
}

Wyświetl plik

@ -19,10 +19,14 @@ import { NumberInput } from "./NumberInput";
import { Select } from "./Select";
import { Switch } from "./Switch";
import { useSvgContext } from "~/lib/SvgContext";
import { usePaintContext } from "~/lib/PaintContext";
import { qroptions_margin } from "../../fuqr/pkg/fuqr_bg.wasm";
export function Editor(props: any) {
const { inputQr, setInputQr } = useQrContext();
const { inputQr, setInputQr, outputQr } = useQrContext();
const { svgOptions, setSvgOptions } = useSvgContext();
const { selections, scaleX, scaleY, setScaleXInPlace, setScaleYInPlace } =
usePaintContext();
// const [logoSize, setLogoSize] = createSignal(25);
@ -67,15 +71,15 @@ export function Editor(props: any) {
</For>
</ButtonGroup>
</Row>
<Row title="Margin">
{/* <Row title="Margin">
<NumberInput
min={0}
max={10}
step={1}
value={svgOptions.margin}
setValue={(v) => setSvgOptions("margin", v)}
value={inputQr.margin.top}
setValue={(v) => setInputQr("margin", (prev) => prev.setTop(v))}
/>
</Row>
</Row> */}
<Row title="Foreground">
<ColorInput
color={svgOptions.fgColor}
@ -132,6 +136,64 @@ export function Editor(props: any) {
/> */}
</div>
</Row>
<Row title="Scale X">
<NumberInput
min={0}
max={200}
step={1}
value={
selections().length
? scaleX()[
selections()[0].top * Math.sqrt(scaleX().length) +
selections()[0].left
]
: 100
}
setValue={(v) => {
if (!selections().length) return
setScaleXInPlace((prev) => {
let width = Math.sqrt(prev.length);
selections().forEach((sel) => {
for (let i = sel.top; i < sel.bot; i++) {
for (let j = sel.left; j < sel.right; j++) {
prev[i * width + j] = v;
}
}
});
return prev;
});
}}
/>
</Row>
<Row title="Scale Y">
<NumberInput
min={0}
max={200}
step={1}
value={
selections().length
? scaleY()[
selections()[0].top * Math.sqrt(scaleY().length) +
selections()[0].left
]
: 100
}
setValue={(v) => {
if (!selections().length) return
setScaleYInPlace((prev) => {
let width = Math.sqrt(prev.length);
selections().forEach((sel) => {
for (let i = sel.top; i < sel.bot; i++) {
for (let j = sel.left; j < sel.right; j++) {
prev[i * width + j] = v;
}
}
});
return prev;
});
}}
/>
</Row>
</div>
</div>
);

Wyświetl plik

@ -25,7 +25,12 @@ export function NumberInput(props: Props) {
) {
return;
}
props.setValue(value);
if (value !== props.value) {
// TODO, revisit this for x scale/y scale slider
// console.log("safe Set", value);
props.setValue(value);
}
};
const [focused, setFocused] = createSignal(false);

Wyświetl plik

@ -1,12 +1,13 @@
import { createEffect, createSignal, onCleanup } from "solid-js";
import { type SetStoreFunction } from "solid-js/store";
import { usePaintContext, type BoxSelection } from "~/lib/PaintContext";
type Props = {
width: number;
height: number;
color: number;
grid: number[];
setGrid: SetStoreFunction<number[]>;
// grid: number[];
// setGrid: SetStoreFunction<number[]>;
};
enum Mode {
@ -27,6 +28,8 @@ function clamp(a: number, min: number, max: number) {
}
export function RenderGrid(props: Props) {
const { selections, setSelectionsInPlace } = usePaintContext();
let canvas: HTMLCanvasElement;
let ctx: CanvasRenderingContext2D;
@ -45,17 +48,17 @@ export function RenderGrid(props: Props) {
};
const setPixel = (x: number, y: number) => {
if (props.color === props.grid[y * props.width + x]) return;
// if (props.color === props.grid[y * props.width + x]) return;
props.setGrid(y * props.width + x, props.color!);
if (props.color) {
ctx.fillRect(x * UNIT, y * UNIT, UNIT, UNIT);
} else {
ctx.clearRect(x * UNIT, y * UNIT, UNIT, UNIT);
}
// props.setGrid(y * props.width + x, props.color!);
// if (props.color) {
// ctx.fillRect(x * UNIT, y * UNIT, UNIT, UNIT);
// } else {
// ctx.clearRect(x * UNIT, y * UNIT, UNIT, UNIT);
// }
};
let selection: Selection | null;
let selection: BoxSelection | null;
function onMouseMove(e: MouseEvent) {
const { x, y } = getPos(e);
@ -66,7 +69,7 @@ export function RenderGrid(props: Props) {
selectCtx.clearRect(0, 0, props.width * UNIT, props.height * UNIT);
selectCtx.fillStyle = "rgb(59, 130, 246)";
selectedBoxes.forEach((sel) => {
selections().forEach((sel) => {
selectCtx.fillRect(
sel.left * UNIT,
sel.top * UNIT,
@ -142,7 +145,11 @@ export function RenderGrid(props: Props) {
const onMouseUp = () => {
if (selection != null) {
selectedBoxes.push(selection);
setSelectionsInPlace((prev) => {
// @ts-expect-error i'm right unless somehow this isn't synchronous
prev.push(selection);
return prev;
});
}
document.removeEventListener("mousemove", onMouseMove);
document.removeEventListener("mouseup", onMouseUp);
@ -157,19 +164,13 @@ export function RenderGrid(props: Props) {
const [brushSize, setBrushSize] = createSignal(1);
let selectedBoxes: Selection[] = [];
// let selectedBoxes: Selection[] = [];
let prevX: number;
let prevY: number;
let deselectIntent: boolean;
createEffect(() => {
props.width;
props.height;
selectedBoxes = [];
});
return (
<>
<canvas
@ -199,7 +200,7 @@ export function RenderGrid(props: Props) {
if (mode() === Mode.Select) {
deselectIntent = false;
if (!(e.shiftKey || e.ctrlKey)) {
deselectIntent = selectedBoxes.length > 0;
deselectIntent = selections().length > 0;
if (deselectIntent) {
selectCtx.clearRect(
@ -208,8 +209,8 @@ export function RenderGrid(props: Props) {
props.width * UNIT,
props.height * UNIT
);
selectedBoxes = [];
selection = null
setSelectionsInPlace([]);
selection = null;
}
}

Wyświetl plik

@ -1,9 +1,9 @@
import {
QrOptions,
SvgOptions,
Version,
get_svg,
SvgError,
QrError,
Version,
SvgResult,
} from "fuqr";
@ -17,45 +17,43 @@ import {
MODE_KEY,
MODE_NAMES,
} from "~/lib/options";
import { createStore } from "solid-js/store";
import { RenderGrid } from "../RenderGrid";
import { useQrContext } from "~/lib/QrContext";
import { useQrContext, type OutputQr } from "~/lib/QrContext";
import { useSvgContext } from "~/lib/SvgContext";
import { usePaintContext } from "~/lib/PaintContext";
const PIXELS_PER_MODULE = 20;
export default function SvgPreview() {
const { inputQr } = useQrContext();
const { inputQr, outputQr, setOutputQr } = useQrContext();
const { svgOptions } = useSvgContext();
const { scaleX, scaleY } = usePaintContext();
const [grid, setGrid] = createStore(Array(177 * 177).fill(100));
const svgResult = createMemo(() => {
console.log("svgResult", inputQr);
// what if iterate func return list of coords
// combine with list of true/false for placement
//
let output = outputQr();
if (typeof output === "number") {
// See <Show> component
// svgResult() only called when outputQr() is successful
return null as unknown as SvgResult;
}
const qrOptions = new QrOptions()
.min_version(new Version(inputQr.minVersion))
.min_ecl(inputQr.minEcl)
.mask(inputQr.mask!) // null makes more sense than undefined
.mode(inputQr.mode!); // null makes more sense than undefined
.min_version(new Version(output.version))
.min_ecl(output.ecl)
.mask(output.mask!) // wasm-bindgen types None as `undefined`, but null works
.mode(output.mode!); // wasm-bindgen types None as `undefined`, but null works
let svgOpts = new SvgOptions()
// .finder_pattern(props.finderPattern)
// .finder_roundness(props.finderRoundness)
// .margin(margin)
.foreground(svgOptions.fgColor)
.background(svgOptions.bgColor)
.scale_matrix(grid);
.scale_x_matrix(new Uint8Array(scaleX()))
.scale_y_matrix(new Uint8Array(scaleY()));
let t;
try {
t = get_svg(inputQr.text, qrOptions, svgOpts);
} catch (e) {
// <Show> doesn't play nicely with unions
// this requires one "as any" instead of 20
t = e as SvgResult;
}
console.log(typeof t);
return t;
// infallible b/c outputQr contains successful options
return get_svg(inputQr.text, qrOptions, svgOpts);
});
function download(href: string, name: string) {
@ -73,33 +71,31 @@ export default function SvgPreview() {
const bgSrc = () => URL.createObjectURL(svgOptions.bgImgFile!);
const logoSrc = () => URL.createObjectURL(svgOptions.fgImgFile!);
const fullWidth = () =>
svgResult().version["0"] * 4 + 17 + 2 * svgOptions.margin;
const checkerboardPixel = () => (1 / fullWidth()) * 100;
const fullWidth = () => {
const output = outputQr() as OutputQr;
return output.version * 4 + 17 + output.margin.left + output.margin.right;
};
const fullHeight = () => {
const output = outputQr() as OutputQr;
return output.version * 4 + 17 + output.margin.top + output.margin.bottom;
};
const [color, setColor] = createSignal(0);
// createEffect(() => {
// console.log("effect", svgResult());
// });
return (
<>
<Show
when={typeof svgResult() != "number" && svgResult() != null}
when={typeof outputQr() != "number"}
fallback={
<div class="aspect-[1/1] border rounded-md flex justify-center items-center">
<Switch>
<Match
when={(svgResult() as any) === SvgError.ExceedsMaxCapacity}
>
<Match when={outputQr() === QrError.ExceedsMaxCapacity}>
Data exceeds max capacity
</Match>
<Match when={(svgResult() as any) === SvgError.InvalidEncoding}>
<Match when={outputQr() === QrError.InvalidEncoding}>
{`Input cannot be encoded in ${
// @ts-expect-error props.mode not null b/c InvalidEncoding implies mode
MODE_NAMES[props.mode + 1]
MODE_NAMES[inputQr.mode + 1]
} mode`}
</Match>
</Switch>
@ -112,7 +108,9 @@ export default function SvgPreview() {
"background-image":
"repeating-conic-gradient(#ddd 0% 25%, #aaa 25% 50%)",
"background-position": "50%",
"background-size": `${checkerboardPixel()}% ${checkerboardPixel()}%`,
"background-size": `${(1 / fullWidth()) * 100}% ${
(1 / fullHeight()) * 100
}%`,
}}
>
<Show when={svgOptions.bgImgFile != null}>
@ -144,10 +142,8 @@ export default function SvgPreview() {
</Show>
<RenderGrid
width={fullWidth()}
height={fullWidth()}
height={fullHeight()}
color={color()}
grid={grid}
setGrid={setGrid}
/>
</div>
<div class="p-4 grid grid-cols-2 gap-y-2 text-sm text-left">
@ -181,10 +177,11 @@ export default function SvgPreview() {
<FlatButton
class="flex-1 px-3 py-2"
onClick={async () => {
const size = fullWidth() * PIXELS_PER_MODULE;
const width = fullWidth() * PIXELS_PER_MODULE;
const height = fullHeight() * PIXELS_PER_MODULE;
const canvas = document.createElement("canvas");
canvas.width = size;
canvas.height = size;
canvas.width = width;
canvas.height = height;
const ctx = canvas.getContext("2d");
if (svgOptions.bgImgFile != null) {
@ -192,13 +189,13 @@ export default function SvgPreview() {
const bgImg = new Image();
bgImg.src = bgSrc();
await bgImg.decode();
ctx!.drawImage(bgImg, 0, 0, size, size);
ctx!.drawImage(bgImg, 0, 0, width, height);
}
const svgImg = new Image();
svgImg.src = `data:image/svg+xml;base64,${btoa(svgResult().svg)}`;
await svgImg.decode();
ctx!.drawImage(svgImg, 0, 0, size, size);
ctx!.drawImage(svgImg, 0, 0, width, height);
if (svgOptions.fgImgFile != null) {
ctx!.imageSmoothingEnabled = !svgOptions.pixelateFgImg;
@ -207,8 +204,9 @@ export default function SvgPreview() {
await logoImg.decode();
// TODO logoSize
const logoSize = (fullWidth() / 4) * PIXELS_PER_MODULE;
const offset = (size - logoSize) / 2;
ctx!.drawImage(logoImg, offset, offset, logoSize, logoSize);
const xOffset = (width - logoSize) / 2;
const yOffset = (height - logoSize) / 2;
ctx!.drawImage(logoImg, xOffset, yOffset, logoSize, logoSize);
}
download(

Wyświetl plik

@ -0,0 +1,110 @@
import {
createContext,
createEffect,
createSignal,
untrack,
useContext,
type Accessor,
type JSX,
type Setter,
} from "solid-js";
import { useQrContext } from "./QrContext";
import { useSvgContext } from "./SvgContext";
import { Margin, QrOptions, Version, get_matrix, type QrError } from "fuqr";
export type BoxSelection = {
top: number;
bot: number;
left: number;
right: number;
};
export const PaintContext = createContext<{
selections: Accessor<BoxSelection[]>;
setSelectionsInPlace: Setter<BoxSelection[]>;
scaleX: Accessor<number[]>;
setScaleXInPlace: Setter<number[]>;
scaleY: Accessor<number[]>;
setScaleYInPlace: Setter<number[]>;
}>();
export function PaintContextProvider(props: { children: JSX.Element }) {
const { inputQr, outputQr, setOutputQr } = useQrContext();
const { svgOptions } = useSvgContext();
const [selections, setSelectionsInPlace] = createSignal<BoxSelection[]>([], {
equals: false,
});
const [scaleX, setScaleXInPlace] = createSignal<number[]>([], {
equals: false,
});
const [scaleY, setScaleYInPlace] = createSignal<number[]>([], {
equals: false,
});
createEffect(() => {
try {
// NOTE: Version and Margin cannot be reused, so must be created each time
const qrOptions = new QrOptions()
.min_version(new Version(inputQr.minVersion))
.min_ecl(inputQr.minEcl)
.mask(inputQr.mask!) // null makes more sense than undefined
.mode(inputQr.mode!) // null makes more sense than undefined
.margin(new Margin(10))
.margin(
new Margin(0)
.setTop(inputQr.margin.top)
.setRight(inputQr.margin.right)
.setBottom(inputQr.margin.bottom)
.setLeft(inputQr.margin.left)
);
let m = get_matrix(inputQr.text, qrOptions);
setOutputQr({
text: inputQr.text,
version: m.version["0"],
ecl: m.ecl,
mode: m.mode,
mask: m.mask,
margin: {
top: m.margin.top,
right: m.margin.right,
bottom: m.margin.bottom,
left: m.margin.left,
},
});
const matrixLength = m.width() * m.height();
if (matrixLength === untrack(scaleX).length) return;
setSelectionsInPlace([]);
setScaleXInPlace(Array(matrixLength).fill(100));
setScaleYInPlace(Array(matrixLength).fill(100));
} catch (e) {
setOutputQr(e as QrError);
}
});
return (
<PaintContext.Provider
value={{
selections,
setSelectionsInPlace,
scaleX,
setScaleXInPlace,
scaleY,
setScaleYInPlace,
}}
>
{props.children}
</PaintContext.Provider>
);
}
export function usePaintContext() {
const context = useContext(PaintContext);
if (!context) {
throw new Error("usePaintContext: used outside PaintContextProvider");
}
return context;
}

Wyświetl plik

@ -2,10 +2,11 @@ import {
createContext,
createSignal,
useContext,
type Accessor,
type JSX,
type Setter,
} from "solid-js";
import { ECL, Mode, Mask } from "fuqr";
import { ECL, Mode, Mask, QrError } from "fuqr";
import { createStore, type SetStoreFunction } from "solid-js/store";
type InputQr = {
@ -14,21 +15,35 @@ type InputQr = {
minEcl: ECL;
mode: Mode | null;
mask: Mask | null;
margin: {
top: number;
right: number;
bottom: number;
left: number;
};
};
type OutputQr = {
export type OutputQr = {
text: string;
/** Stored as value b/c Version is a ptr which becomes null after use */
version: number;
ecl: ECL;
mode: Mode;
mask: Mask;
/** Stored as value b/c Margin is a ptr which becomes null after use */
margin: {
top: number;
right: number;
bottom: number;
left: number;
};
};
export const QrContext = createContext<{
inputQr: InputQr;
setInputQr: SetStoreFunction<InputQr>;
outputQr: OutputQr | null;
setOutputQr: Setter<OutputQr | null>;
outputQr: Accessor<OutputQr | QrError>;
setOutputQr: Setter<OutputQr | QrError>;
}>();
export function QrContextProvider(props: { children: JSX.Element }) {
@ -38,14 +53,20 @@ export function QrContextProvider(props: { children: JSX.Element }) {
minEcl: ECL.Low,
mode: null,
mask: null,
margin: {
top: 2,
right: 2,
bottom: 2,
left: 2,
},
});
const [outputQr, setOutputQr] = createSignal<OutputQr | null>(null);
const [outputQr, setOutputQr] = createSignal<OutputQr | QrError>(
QrError.InvalidEncoding
);
return (
<QrContext.Provider
value={{ inputQr, setInputQr, outputQr: outputQr(), setOutputQr }}
>
<QrContext.Provider value={{ inputQr, setInputQr, outputQr, setOutputQr }}>
{props.children}
</QrContext.Provider>
);

Wyświetl plik

@ -2,7 +2,6 @@ import { createContext, useContext, type JSX } from "solid-js";
import { createStore, type SetStoreFunction } from "solid-js/store";
type SvgOptions = {
margin: number;
bgColor: string;
fgColor: string;
bgImgFile: File | null;
@ -18,7 +17,6 @@ export const SvgContext = createContext<{
export function SvgContextProvider(props: { children: JSX.Element }) {
const [svgOptions, setSvgOptions] = createStore<SvgOptions>({
margin: 2,
bgColor: "#ffffff",
fgColor: "#000000",
bgImgFile: null,
@ -28,12 +26,7 @@ export function SvgContextProvider(props: { children: JSX.Element }) {
});
return (
<SvgContext.Provider
value={{
svgOptions: svgOptions,
setSvgOptions: setSvgOptions,
}}
>
<SvgContext.Provider value={{ svgOptions, setSvgOptions }}>
{props.children}
</SvgContext.Provider>
);

Wyświetl plik

@ -2,10 +2,15 @@ import { Switch, Match, createSignal } from "solid-js";
import { clientOnly } from "@solidjs/start";
import init from "fuqr";
import { Editor } from "~/components/Editor";
import SvgPreview from "~/components/qr/SvgPreview";
import { PaintContextProvider } from "~/lib/PaintContext";
import { SvgContextProvider } from "~/lib/SvgContext";
const QRCode = clientOnly(async () => {
const QrContextProvider = clientOnly(async () => {
await init();
return import("../components/qr/SvgPreview");
return {
default: (await import("../lib/QrContext")).QrContextProvider,
};
});
// const MODULE_NAMES = [
@ -24,18 +29,24 @@ enum Stage {
export default function Home() {
const [stage, setStage] = createSignal(Stage.Create);
return (
<main class="max-w-screen-lg mx-auto">
<Switch>
<Match when={stage() == Stage.Create}>
<div class="flex gap-4 flex-wrap">
<Editor />
<div class="flex-1 min-w-200px sticky top-0 self-start p-4">
<QRCode />
</div>
</div>
</Match>
{/* <Match when={stage() == Stage.Customize}></Match> */}
</Switch>
</main>
<QrContextProvider>
<SvgContextProvider>
<PaintContextProvider>
<main class="max-w-screen-lg mx-auto">
<Switch>
<Match when={stage() == Stage.Create}>
<div class="flex gap-4 flex-wrap">
<Editor />
<div class="flex-1 min-w-200px sticky top-0 self-start p-4">
<SvgPreview />
</div>
</div>
</Match>
{/* <Match when={stage() == Stage.Customize}></Match> */}
</Switch>
</main>
</PaintContextProvider>
</SvgContextProvider>
</QrContextProvider>
);
}