Chore: improve the /dither page
This commit is contained in:
parent
03ede2c023
commit
6343bddd18
2 changed files with 43 additions and 47 deletions
|
|
@ -14,22 +14,37 @@
|
|||
let offscreenCanvas: OffscreenCanvas;
|
||||
let ctx = $derived(canvas.getContext('2d'))
|
||||
let offscreenCtx: OffscreenCanvasRenderingContext2D;
|
||||
let imgObjectUrl = $state('')
|
||||
let selectedImage: HTMLImageElement | null = $state(null)
|
||||
let errorMessage = $state('')
|
||||
let selectedAlgorithm = $state('atkinson')
|
||||
|
||||
let maxImageWidth = $state(0)
|
||||
let imageHeightRatio = $state(0)
|
||||
let imageWidthPc = $state(100)
|
||||
|
||||
onMount(() => {
|
||||
offscreenCanvas = new OffscreenCanvas(1000, 1000)
|
||||
offscreenCtx = offscreenCanvas.getContext('2d')
|
||||
})
|
||||
|
||||
const doesCtxExist = () => {
|
||||
if (!ctx) {
|
||||
errorMessage = "Problem finding context for on-screen canvas"
|
||||
return false
|
||||
$effect(() => {
|
||||
if (!selectedImage) {
|
||||
console.log(`Selected Image is null, terminating effect...`)
|
||||
return;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
maxImageWidth = selectedImage.width
|
||||
imageHeightRatio = selectedImage.height / selectedImage.width
|
||||
|
||||
ctx.clearRect(0, 0, canvas.width, canvas.height)
|
||||
|
||||
canvas.width = maxImageWidth * (imageWidthPc/100)
|
||||
canvas.height = imageHeightRatio * canvas.width
|
||||
ctx.drawImage(selectedImage, 0, 0, canvas.width, canvas.height)
|
||||
|
||||
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height)
|
||||
ctx.putImageData(DITHERING_ALGORITHMS[selectedAlgorithm](imageData), 0, 0)
|
||||
})
|
||||
|
||||
const handleImageChange = async (event: any) => {
|
||||
const file = event.target.files[0];
|
||||
|
|
@ -38,18 +53,8 @@
|
|||
return
|
||||
}
|
||||
|
||||
imgObjectUrl = URL.createObjectURL(file);
|
||||
const image = await createImage(imgObjectUrl);
|
||||
|
||||
// Change the sizes of canvases
|
||||
canvas.width = image.width
|
||||
canvas.height = image.height
|
||||
|
||||
offscreenCanvas.height = image.height
|
||||
offscreenCanvas.width = image.width
|
||||
|
||||
// Now draw the images
|
||||
drawImage(image)
|
||||
const imgObjectUrl = URL.createObjectURL(file);
|
||||
selectedImage = await createImage(imgObjectUrl);
|
||||
}
|
||||
|
||||
async function createImage(objectUrl: string): Promise<HTMLImageElement> {
|
||||
|
|
@ -63,30 +68,7 @@
|
|||
});
|
||||
}
|
||||
|
||||
const clearCanvases = () => {
|
||||
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||
offscreenCtx.clearRect(0, 0, offscreenCanvas.width, offscreenCanvas.height);
|
||||
}
|
||||
|
||||
const drawImage = (theImage: HTMLImageElement) => {
|
||||
if (!doesCtxExist()) return
|
||||
clearCanvases()
|
||||
ctx.drawImage(theImage, 0, 0, canvas.width, canvas.height);
|
||||
offscreenCtx.drawImage(theImage, offscreenCanvas.width, offscreenCanvas.height)
|
||||
}
|
||||
|
||||
function applyDitheringAlgorithm(algorithm: (imageData: ImageData) => ImageData) {
|
||||
return async () => {
|
||||
const freshImage = await createImage(imgObjectUrl);
|
||||
drawImage(freshImage)
|
||||
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
|
||||
const ditheredImageData = algorithm(imageData);
|
||||
ctx.putImageData(ditheredImageData, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
function saveImage() {
|
||||
if (!canvas) return;
|
||||
const link = document.createElement('a');
|
||||
link.download = 'dithered-image.png';
|
||||
link.href = canvas.toDataURL();
|
||||
|
|
@ -95,7 +77,7 @@
|
|||
|
||||
</script>
|
||||
|
||||
<ErrorMessage message={errorMessage} />
|
||||
<ErrorMessage errorMessage={errorMessage} />
|
||||
|
||||
<h2 class="page-dither__subtitle">1: Select a file</h2>
|
||||
|
||||
|
|
@ -108,9 +90,9 @@
|
|||
<h2 class="page-dither__subtitle">2: Select a dithering algorithm</h2>
|
||||
|
||||
<ul class="algorithms">
|
||||
{#each Object.entries(DITHERING_ALGORITHMS) as [key, value]}
|
||||
{#each Object.keys(DITHERING_ALGORITHMS) as key}
|
||||
<li class="algorithms__item">
|
||||
<button class="algorithms__button" onclick={applyDitheringAlgorithm(value)}>{key}</button>
|
||||
<button class="algorithms__button" onclick={() => selectedAlgorithm = key}>{key}</button>
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
|
|
@ -120,9 +102,20 @@
|
|||
<canvas id="photo-canvas" height="250" bind:this={canvas} onload={() => ctx = canvas.getContext('2d')}></canvas>
|
||||
</div>
|
||||
|
||||
<h2 class="page-dither__subtitle">
|
||||
3: Remix
|
||||
</h2>
|
||||
|
||||
<form>
|
||||
<label for="resolution">
|
||||
Image Ratio
|
||||
<input type="range" min="1" max="100" step="5" bind:value={imageWidthPc} width="100%"/>
|
||||
</label>
|
||||
<p>{maxImageWidth * imageWidthPc / 100}</p>
|
||||
</form>
|
||||
|
||||
<h2 class="page-dither__subtitle">
|
||||
3: Save the file
|
||||
4: Save the file
|
||||
</h2>
|
||||
<button onclick={saveImage}>Save Image</button>
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,8 @@
|
|||
<script lang="ts">
|
||||
const { errorMessage } = $props()
|
||||
interface Props {
|
||||
errorMessage: string;
|
||||
}
|
||||
const { errorMessage }: Props = $props()
|
||||
</script>
|
||||
|
||||
{#if errorMessage}
|
||||
|
|
|
|||
Loading…
Reference in a new issue