diff options
Diffstat (limited to 'src/lib/test/canvas/CanvasCopy.svelte')
| -rw-r--r-- | src/lib/test/canvas/CanvasCopy.svelte | 148 |
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> |