image loading for web

This commit is contained in:
Emerald 2023-10-19 19:07:17 -04:00
parent dbc883924a
commit 6b728c98c0
Signed by: emerald
GPG Key ID: 13F7EFB915A0F623
5 changed files with 158 additions and 92 deletions

View File

@ -6,22 +6,21 @@
'Eyes closed | Mouth open'
];
let loading = [false, false, false, false];
</script>
<script lang="ts">
import { frames, mode } from '../store';
import { fs, invoke } from '@tauri-apps/api';
import { fade } from 'svelte/transition';
import Context from './context.svelte';
import Image from 'image-js';
import { Gif } from '../gifler';
import { ProgressRadial } from '@skeletonlabs/skeleton';
import { info } from '$lib/logging';
import { loadImage } from '$lib/io';
export let index: number;
let menuTimeout: NodeJS.Timeout | null = null;
let showMenu = false;
let src: string | null;
let fileInput: HTMLInputElement | undefined;
let files: FileList;
$: loading = false;
$: {
const frame = $frames[index];
@ -36,36 +35,73 @@
}
const openImage = async () => {
loading[index] = true;
const path = (await invoke('open_image')) as {
kind: 'png' | 'gif';
data: string;
};
if (path) {
const data = await fs.readBinaryFile(path.data);
if (path.kind == 'png')
$frames[index] = {
kind: 'still',
value: await Image.load(data)
};
else {
$frames[index] = {
kind: 'GIF',
value: new Gif(new Promise((res) => res(data))),
data: data
};
}
} else {
console.error('Error loading image');
if (mode === 'web') {
fileInput?.click();
loading = true;
}
loading[index] = false;
// loading[index] = true;
// const path = (await invoke('open_image')) as {
// kind: 'png' | 'gif';
// data: string;
// };
// if (path) {
// const data = await fs.readBinaryFile(path.data);
// if (path.kind == 'png')
// $frames[index] = {
// kind: 'still',
// value: await Image.load(data)
// };
// else {
// $frames[index] = {
// kind: 'GIF',
// value: new Gif(new Promise((res) => res(data))),
// data: data
// };
// }
// } else {
// console.error('Error loading image');
// }
// loading[index] = false;
};
const finishLoad = async () => {
info("Loading image in web mode");
const file = files.item(0);
info("Got file:", file);
if (file) {
switch(file.type) {
case "image/gif":
info("TODO");
break;
default:
const img = await loadImage(file);
$frames[index] = img ?
{
kind:"still",
value: img
} : null
}
}
loading = false;
};
const clearImage = () => {
$frames[index] = null;
};
</script>
{#if mode === 'web'}
<input
type="file"
accept="image/*"
bind:this={fileInput}
class="hidden w-0 h-0"
bind:files
on:change={finishLoad}
/>
{/if}
<button
class="btn btn-xl w-24 md:w-32 variant-ghost-primary aspect-square cursor-pointer z-50"
@ -76,15 +112,8 @@
openImage();
}
}}
on:mouseenter={() => (menuTimeout = setTimeout(() => (showMenu = true), 200))}
on:mouseleave={() => {
if (menuTimeout) {
clearTimeout(menuTimeout);
}
showMenu = false;
}}
>
{#if loading[index]}
{#if loading}
<ProgressRadial />
{:else if src}
<img {src} alt="Frame {{ index }}" />

View File

@ -1,13 +1,12 @@
<script lang="ts">
import { frames, type FrameData } from "../store";
import { onMount } from "svelte";
import anime from "animejs";
import type { Animator } from "../gifler";
import { active, blink } from "$lib/state";
import { frames, type FrameData } from '../store';
import { onMount } from 'svelte';
import anime from 'animejs';
import type { Animator } from '$lib/gifler';
import { active, blink } from '$lib/state';
import { debug, info } from '$lib/logging';
type MaybeSrc = | { mode: "png"; data: ImageBitmap }
| { mode: "gif"; data: Animator }
| null;
type MaybeSrc = { mode: 'png'; data: ImageBitmap } | { mode: 'gif'; data: Animator } | null;
$: bitMaps = [null, null, null, null] as MaybeSrc[];
$: currFrame = 0;
@ -15,15 +14,16 @@
$: src = null as MaybeSrc;
$: {
if (src?.mode === "gif" && src.data.running()) src.data.stop();
if (src?.mode === 'gif' && src.data.running()) src.data.stop();
src = bitMaps[currFrame] as MaybeSrc;
if (src?.mode === "gif") src.data.reset().start();
debug('canvas source', src);
if (src?.mode === 'gif') src.data.reset().start();
}
let pos = { x: 0, y: 0 };
$: {
if ($active) {
if (!$active) {
currFrame = 0;
} else {
currFrame = 1;
@ -42,10 +42,10 @@
targets: pos,
y: [
{ value: 50, duration: 200 },
{ value: 0, duration: 200 },
{ value: 0, duration: 200 }
],
autoplay: true,
easing: "easeOutCubic",
easing: 'easeOutCubic'
});
}
}
@ -66,55 +66,61 @@
const createBitmaps = async (f: Array<FrameData | null>): Promise<MaybeSrc[]> => {
return await Promise.all(
f.map(async (v) => {
debug('creating bitmap for', v);
if (!v) return null;
return v.kind === "still"
? {
mode: "png",
data: await createImageBitmap(await v.value.toBlob()),
}
: {
mode: "gif",
data: (
await v.value.frames(
"#png",
(
ctx: CanvasRenderingContext2D,
frame: {
buffer: CanvasImageSource;
width: number;
height: number;
}
) => {
ctx.drawImage(
frame.buffer,
canvas.width / 2 - pos.x - frame.width / 2,
canvas.height / 2 - pos.y - frame.height / 2
);
},
false
)
).stop(),
};
if (v?.kind === 'still') {
info('Creating bitmap for still image');
return {
mode: 'png',
data: await createImageBitmap(await v?.value.toBlob())
};
} else {
return {
mode: 'gif',
data: (
await v?.value.frames(
'#png',
(
ctx: CanvasRenderingContext2D,
frame: {
buffer: CanvasImageSource;
width: number;
height: number;
}
) => {
ctx.drawImage(
frame.buffer,
canvas.width / 2 - pos.x - frame.width / 2,
canvas.height / 2 - pos.y - frame.height / 2
);
},
false
)
).stop()
};
}
})
);
};
onMount(async () => {
bitMaps = await createBitmaps($frames);
console.log("bitMaps: ", bitMaps);
console.log('bitMaps: ', bitMaps);
frames.subscribe(async (f) => {
bitMaps.forEach((v) => (v && v.mode === "gif" ? v.data.stop() : null));
debug('Updating image bitmaps');
// make sure any loaded gifs are stopped or they'll keep drawing themselves
bitMaps.forEach((v) => (v && v.mode === 'gif' ? v.data.stop() : null));
bitMaps = await createBitmaps(f);
});
updateCanvasSize();
const ctx = canvas.getContext("2d");
const ctx = canvas.getContext('2d');
const update = async () => {
try {
if (src?.mode === "png") {
if (src?.mode === 'png') {
const img = src?.data;
ctx?.clearRect(0, 0, canvas.width, canvas.height);
ctx?.drawImage(
@ -122,10 +128,12 @@
canvas.width / 2 - pos.x - img.width / 2,
canvas.height / 2 - pos.y - img.height / 2
);
} else if (src?.mode === "gif" && !src.data.running()) {
} else if (src?.mode === 'gif' && !src.data.running()) {
src.data.start();
}
} catch {}
} catch(e) {
debug('Rendering error', e);
}
requestAnimationFrame(update);
};
@ -135,12 +143,13 @@
const updateCanvasSize = () => {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
info(`Set canvas size to ${canvas.width}, ${canvas.height}`);
};
let canvas: HTMLCanvasElement;
</script>
<canvas bind:this={canvas} id="png" />
<canvas bind:this={canvas} id="png" />
<style lang="scss">
canvas {

View File

@ -1,3 +1,26 @@
export const openImage = async () => {
import { Image } from 'image-js';
import { mode } from '$lib/store';
}
export const loadImage = async (data: File | Blob): Promise<Image | null> => {
if (mode === 'web') {
return new Promise(async (resolve) => {
const reader = new FileReader();
reader.onload = async () => {
const raw = reader.result as ArrayBuffer | null;
if (!raw) return null;
let img = await Image.load(raw);
if (img.width > 600 || img.height > 400){
img = img.resize({
width: 600
})
}
resolve(
img
);
};
reader.readAsArrayBuffer(data);
});
}
return null;
};

View File

@ -1,6 +1,11 @@
import { mode } from "$lib/store"
export const info = <T extends String>(...args: T[]) => {
export const info = (...args: any[]) => {
if(mode === "web")
console.log("%c[INFO]", "color: lightgreen", ":", ...args);
}
export const debug = (...args: any[]) => {
if(mode === "web")
console.log("%c[DEBUG]", "color: yellow", ":", ...args);
}

View File

@ -23,7 +23,7 @@
if (deinitAudio) clearInterval(deinitAudio);
});
$: bgColor = $transparent ? 'bg-pink-' : 'bg-surface-900';
$: bgColor = $transparent ? 'bg-green-800' : 'bg-surface-900';
</script>
<div class="{bgColor} w-screen h-screen" id="container">