aboutsummaryrefslogtreecommitdiffstats
path: root/src/routes/+page.svelte
diff options
context:
space:
mode:
Diffstat (limited to 'src/routes/+page.svelte')
-rw-r--r--src/routes/+page.svelte202
1 files changed, 202 insertions, 0 deletions
diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte
new file mode 100644
index 0000000..7cc3eb7
--- /dev/null
+++ b/src/routes/+page.svelte
@@ -0,0 +1,202 @@
+<!--
+MIT License
+
+Copyright (c) 2025 memdmp
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+ -->
+
+<script lang="ts">
+ import Alerts, { alert, confirm } from '$lib/Alerts.svelte';
+ import { onMount } from 'svelte';
+
+ let fileInput = $state(null as unknown as HTMLInputElement);
+ let fileName = $state('');
+ let fileRaw = $state(null as null | File);
+ let canvas = $state(null as unknown as HTMLCanvasElement);
+ let imgUrl = $state('');
+ let output = $state('');
+ $effect(() => {
+ if (fileInput?.files?.length) {
+ const file = fileInput.files![0];
+ fileName = file.name;
+ fileRaw = file ?? null;
+ }
+ });
+ onMount(() => {
+ const el = document.createElement('canvas');
+ const ctx = el.getContext('2d');
+ if (!ctx) {
+ alert(
+ "Could not create 2d Canvas Context. This application will not work. Make sure any privacy-resistance features are disabled and your browser isn't ancient.",
+ {
+ title: 'Fatal Error',
+ }
+ );
+ } else {
+ if (
+ JSON.stringify(ctx.getImageData(0, 0, 32, 32).data) !==
+ JSON.stringify(ctx.getImageData(0, 0, 32, 32).data)
+ ) {
+ alert(
+ "Canvas readbacks not identical. This application may not work correctly. Make sure any privacy-resistance features are disabled and your browser isn't ancient.",
+ {
+ title: 'Error',
+ }
+ );
+ }
+ }
+ });
+ const update = async (fileRaw: File) => {
+ const blob = new Blob([await fileRaw.bytes()]);
+ const blobUrl = URL.createObjectURL(blob);
+ imgUrl = blobUrl;
+ };
+ $effect(() => {
+ if (fileRaw) update(fileRaw);
+ else imgUrl = '';
+ });
+ const toHex = (v: number) => Math.floor(v).toString(16).padStart(2, '0');
+ const save = (
+ data: [r: number, g: number, b: number, a: number][],
+ width: number,
+ height: number
+ ) => {
+ let content = `<svg xmlns="http://www.w3.org/2000/svg" shape-rendering="crispEdges" viewBox=${JSON.stringify(`0 0 ${width} ${height}`)} width=${JSON.stringify('' + width)} height=${JSON.stringify('' + height)}>`;
+ let item: (typeof data)[0];
+ let x = 0;
+ let y = 0;
+ while ((item = data.shift()!)) {
+ if (++x >= width) (x = 0), y++;
+ if (item[3] !== 0)
+ content += `<rect fill="#${toHex(item[0])}${toHex(item[1])}${toHex(item[2])}${item[3] !== 255 ? toHex(item[3]) : ''}" x=${JSON.stringify('' + x)} y=${JSON.stringify('' + y)} width="1" height="1"/>`;
+ }
+ content += '</svg>';
+ output = URL.createObjectURL(
+ new Blob([content], {
+ type: 'image/svg+xml',
+ })
+ );
+ };
+</script>
+
+<svelte:head>
+ <title>Bitmap to Pixelated SVG</title>
+ <link rel="stylesheet" href="/cs16.css" />
+</svelte:head>
+
+<Alerts />
+<div>
+ <h1>PixelSVG</h1>
+ <div>
+ <label style="display: flex; gap:4px;">
+ <span class="cs-btn"
+ >{fileName ? `File: ${fileName}` : 'Upload a File'}</span
+ >
+ <img
+ src={imgUrl}
+ alt=""
+ style="height: 24px"
+ onload={async (e) => {
+ const imgEl = e.currentTarget as HTMLImageElement;
+ const rect = {
+ width: imgEl.naturalWidth,
+ height: imgEl.naturalHeight,
+ };
+ if (
+ rect.width * rect.height > 65536 &&
+ !(await confirm(
+ `This image isn't small; it's made up of ${rect.width * rect.height} pixels. The outputted file could be up to ${((`<svg xmlns="http://www.w3.org/2000/svg" shape-rendering="crispEdges" viewBox=${JSON.stringify(`0 0 ${rect.width} ${rect.height}`)} width=${JSON.stringify('' + rect.width)} height=${JSON.stringify('' + rect.height)}></svg>`.length + `<rect fill="#ffffffff" x="${rect.width}" y="${rect.height}" width="1" height="1"/>`.length * rect.width * rect.height) / 1024 / 1024).toFixed(1)} MB large. It may crash your browser. Are you sure you wish to continue?`,
+ {
+ title: 'Confirmation',
+ }
+ ))
+ )
+ return (imgUrl = ''), (fileName = '');
+ canvas.width = rect.width;
+ canvas.height = rect.height;
+ const ctx = canvas.getContext('2d', {
+ alpha: true,
+ colorSpace: 'display-p3',
+ });
+ if (!ctx) throw new Error('no 2d context obtainable');
+ try {
+ ctx.drawImage(e.currentTarget as HTMLImageElement, 0, 0);
+ } catch (error) {
+ await alert(
+ "Failed to draw image to canvas. The image's data may be invalid or the image may be too big. Check the Console.",
+ {
+ title: 'Failed to draw image',
+ }
+ );
+ throw error;
+ }
+ const data = ctx.getImageData(0, 0, rect.width, rect.height, {
+ colorSpace: 'display-p3',
+ });
+ let out = [] as [r: number, g: number, b: number, a: number][];
+ for (let i = 0; i < data.data.length; i += 4) {
+ const r = data.data[i + 0];
+ const g = data.data[i + 1];
+ const b = data.data[i + 2];
+ const a = data.data[i + 3];
+ out.push([r, g, b, a]);
+ }
+ save(out, rect.width, rect.height);
+ }}
+ onerror={(e) => {
+ if (imgUrl !== '')
+ alert(
+ 'Failed to load the image into an HTML Image Element. Please make sure the image you provided is valid and in a format your browser understands.',
+ {
+ title: 'Failed to load image',
+ }
+ );
+ }}
+ />
+ <input
+ type="file"
+ bind:this={fileInput}
+ onchange={() => {
+ if (fileInput?.files?.length) {
+ const file = fileInput.files![0];
+ fileName = file.name;
+ fileRaw = file ?? null;
+ }
+ }}
+ style="opacity:0;position:fixed;top:0;left:0;width:0;height:0;pointer-events:none;"
+ />
+ </label>
+ </div>
+ {#if output}
+ <div style="margin-top:12px;margin-bottom:12px;">
+ Output:<br />
+ <a href={output} target="_blank"
+ ><img src={output} alt="could not render" /> (open)</a
+ >
+ </div>
+ {/if}
+ <div style="opacity:0.5">
+ {imgUrl ? 'Canvas:' : ''}<br /><canvas
+ bind:this={canvas}
+ width="0"
+ height="0"
+ ></canvas>
+ </div>
+</div>