aboutsummaryrefslogtreecommitdiffstats
path: root/src/lib/test/canvas/CanvasCopy.svelte
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib/test/canvas/CanvasCopy.svelte')
-rw-r--r--src/lib/test/canvas/CanvasCopy.svelte148
1 files changed, 148 insertions, 0 deletions
diff --git a/src/lib/test/canvas/CanvasCopy.svelte b/src/lib/test/canvas/CanvasCopy.svelte
new file mode 100644
index 0000000..7795116
--- /dev/null
+++ b/src/lib/test/canvas/CanvasCopy.svelte
@@ -0,0 +1,148 @@
+<!-- Small test for making copy-pasting images harder (without putting any major technical restrictions in there, and keeping the site itself working if it errors) -->
+
+<script lang="ts">
+ import { onDestroy, tick } from 'svelte';
+ import { canvascopy, shuffleInPlace } from './CanvasCopyLib';
+
+ let {
+ src,
+ alt,
+ loading = 'lazy',
+ width,
+ height,
+ artifacts = 'fix',
+ useBg = false,
+ }: {
+ src: string;
+ alt: string;
+ width?: number;
+ height?: number;
+ loading?: 'eager' | 'lazy' | undefined | null;
+ artifacts?: 'fix' | 'keep';
+ useBg?: boolean;
+ } = $props();
+ let blobUrl = $state(undefined as undefined | string);
+ let additionalOverlayedBlobs = $state([] as string[]);
+ let additionalOverlayedBlobsDone = $state(false);
+ // svelte-ignore state_referenced_locally
+ let lastLoadedSrc = $state(src);
+
+ const blobify = (
+ image: HTMLImageElement,
+ artifacts: 'fix' | 'keep' = 'fix',
+ ) => {
+ firstLoad = false;
+ if (image.src.startsWith('blob:') || image.src === blobUrl) {
+ // The image is likely a partial of the image. Tell the browser to reload the original
+ lastLoadedSrc = '';
+ } else {
+ console.debug('[blobify] Entering blobify');
+ if (blobUrl) URL.revokeObjectURL(blobUrl);
+ if (additionalOverlayedBlobs.length) {
+ for (const blobUrl of additionalOverlayedBlobs)
+ URL.revokeObjectURL(blobUrl);
+ additionalOverlayedBlobs.length = 0;
+ }
+ additionalOverlayedBlobsDone = false;
+
+ // Need to update lastLoaded to prevent unneeded refreshes
+ console.debug('[blobify] Set lastload to', src);
+ const lastLoad = lastLoadedSrc;
+ lastLoadedSrc = src;
+
+ let nextPerf = 0;
+ canvascopy(
+ image,
+ artifacts,
+ () => blobUrl,
+ (blob) => (blobUrl = blob),
+ tick,
+ (blob) => additionalOverlayedBlobs.push(blob),
+ () => shuffleInPlace(additionalOverlayedBlobs).shift()!,
+ () => {
+ const p = performance.now();
+ if (p >= nextPerf) {
+ nextPerf = p + 10;
+ return new Promise((rs) => requestAnimationFrame(() => rs(void 0)));
+ }
+ },
+ )
+ .then(() => {
+ additionalOverlayedBlobsDone = true;
+ })
+ .catch((e) => {
+ lastLoadedSrc = lastLoad;
+ throw e;
+ });
+ }
+ };
+
+ let imgloaded = $state(false);
+ let image = $state(null as null | HTMLImageElement);
+
+ $effect(() => {
+ if (lastLoadedSrc !== src && blobUrl) {
+ URL.revokeObjectURL(blobUrl);
+ blobUrl = undefined;
+ imgloaded = false;
+ console.debug('[effect] Revoking blobURL due to src change');
+ }
+ });
+ let firstLoad = true;
+ $effect(() => {
+ if (imgloaded && image) {
+ console.debug('[effect][imgload] Calling blobify. State:', {
+ imgloaded,
+ image,
+ artifacts,
+ });
+ if (firstLoad) {
+ requestIdleCallback(() => blobify(image!, artifacts));
+ firstLoad = false;
+ } else requestAnimationFrame(() => blobify(image!, artifacts));
+ }
+ });
+
+ onDestroy(() => {
+ if (blobUrl) URL.revokeObjectURL(blobUrl);
+ });
+</script>
+
+<div
+ class="relative max-w-max max-h-max block"
+ aria-label={additionalOverlayedBlobsDone ? alt : undefined}
+ role={additionalOverlayedBlobsDone ? 'img' : undefined}
+>
+ <img
+ class="select-none"
+ {loading}
+ src={blobUrl ?? src}
+ alt={additionalOverlayedBlobsDone ? undefined : alt}
+ {width}
+ {height}
+ onload={() => (blobUrl !== undefined ? void 0 : (imgloaded = true))}
+ onloadstart={() => (blobUrl !== undefined ? void 0 : (imgloaded = false))}
+ bind:this={image}
+ />
+ {#if additionalOverlayedBlobsDone}
+ {#each additionalOverlayedBlobs
+ .map( (v, i, a) => (useBg ? (i % 2 === 0 ? ([v, a[i + 1]] as [string, string | undefined]) : undefined!) : ([v] as const)), )
+ .filter((v) => v !== undefined) as [blob1, blob2]}
+ <img
+ src={blob1}
+ {loading}
+ alt=""
+ class="blob-img"
+ style={blob2 ? `background-image: url(${JSON.stringify(blob2)});}` : ''}
+ />
+ {/each}
+ {/if}
+</div>
+
+<style lang="postcss">
+ @reference "tailwindcss";
+
+ .blob-img {
+ @apply absolute top-0 left-0 h-full w-full bg-contain select-none;
+ }
+</style>