Files
battle-caster-v1/src/modules/map/MapScene.vue

127 lines
3.4 KiB
Vue

<template>
<video
v-if="type === 'video'"
:src="src"
:style="dynamicStyle"
class="map-scene--video map-scene"
ref="video"
@loadedmetadata="onVideoLoaded"
autoplay
loop
muted
></video>
<canvas
v-else
:style="dynamicStyle"
ref="canvas"
class="map-scene--image map-scene"
/>
</template>
<script setup lang="ts">
import { computed, defineProps, inject, ref, watch } from 'vue';
import {ReceiverSymbol} from '../../model/injectionSymbols';
const video = ref<HTMLVideoElement>();
const canvas = ref<HTMLCanvasElement>();
const props = defineProps<{
src?: string;
width: number;
dpi: number;
position: {x: number, y: number};
flipped: boolean;
}>();
const isImage = computed(() => props.src?.endsWith('.png') || props.src?.endsWith('.jpg') || props.src?.endsWith('.jpeg') || props.src?.endsWith('.webp'));
const isVideo = computed(() => props.src?.endsWith('.mp4') || props.src?.endsWith('.webm'));
const mediaDimensions = ref({width: 0, height: 0});
watch(() => props.src, (src) => {
if (isImage.value) {
const img = new Image();
img.src = src!;
img.onload = () => {
mediaDimensions.value = {
width: img.naturalWidth,
height: img.naturalHeight,
};
canvas.value!.width = img.naturalWidth;
canvas.value!.height = img.naturalHeight;
const ctx = canvas.value!.getContext('2d')!;
ctx.drawImage(img, 0, 0);
};
} else if (isVideo.value) {
video.value!.src = src!;
}
});
const receiver = inject(ReceiverSymbol);
receiver?.addEventListener('streamImage', ({detail}) => {
if (canvas.value) {
if (canvas.value.width !== detail.canvas.width || canvas.value.height !== detail.canvas.height) {
const backup = document.createElement('canvas');
backup.width = canvas.value.width;
backup.height = canvas.value.height;
const backupCtx = backup.getContext('2d')!;
backupCtx.drawImage(canvas.value, 0, 0);
canvas.value.width = detail.canvas.width;
canvas.value.height = detail.canvas.height;
const ctx = canvas.value.getContext('2d')!;
ctx.drawImage(backup, 0, 0, detail.canvas.width, detail.canvas.height);
}
const imageData = new ImageData(
new Uint8ClampedArray(new Uint32Array(detail.imageData.data).buffer),
detail.imageData.width,
detail.imageData.height
);
const ctx = canvas.value.getContext('2d')!;
ctx.putImageData(imageData, detail.x, detail.y);
}
});
const type = computed(() => {
if (isImage.value) {
return 'image';
} else if (isVideo.value) {
return 'video';
} else {
return 'unknown';
}
});
const dynamicStyle = computed(() => {
const width = props.dpi * props.width;
return {
'--width': `${width}px`,
'--height': `${(width) * (mediaDimensions.value.height / mediaDimensions.value.width)}px`,
'--x': `${props.position.x/100}`,
'--y': `${props.position.y/100}`,
'--rotate': mediaDimensions.value.width > mediaDimensions.value.height ? (props.flipped ? '180deg' : '0deg') :
(props.flipped ? '90deg' : '-90deg'),
};
});
function onVideoLoaded() {
mediaDimensions.value = {
width: video.value!.videoWidth,
height: video.value!.videoHeight,
};
}
</script>
<style>
.map-scene {
position: absolute;
left: calc(var(--x) * 100vw - var(--width) / 2);
top: calc(var(--y) * 100vh - var(--height) / 2);
width: var(--width);
transform: rotate(var(--rotate));
transform-origin: center;
}
</style>