bak
This commit is contained in:
1 parent
43933f93d1
commit
03b1147a93
10 files changed
+804
-80
No files matched your search
@@ -1,10 +1,11 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, nextTick, onBeforeUnmount, onMounted, ref, watch } from 'vue'
|
||||
import type Konva from 'konva'
|
||||
import { PhCornersIn, PhCrop } from '@phosphor-icons/vue'
|
||||
import { PhCornersIn, PhCrop, PhMaskHappy, PhPencilSimple } from '@phosphor-icons/vue'
|
||||
import { useSceneStore } from '@/stores/scene'
|
||||
import { sampleLayer } from '@/lib/interpolate'
|
||||
import { loadImage } from '@/lib/fileLoader'
|
||||
import { drawSceneFrame, loadSceneImages } from '@/lib/render'
|
||||
import type { AnimatableProps, Asset, Layer } from '@/types/scene'
|
||||
import { t } from '@/i18n'
|
||||
import { useKfSelection } from '@/composables/useKfSelection'
|
||||
@@ -22,6 +23,9 @@ const zoom = ref(1)
|
||||
const viewX = ref(0)
|
||||
const viewY = ref(0)
|
||||
const cropObjects = ref(false)
|
||||
const maskPreview = ref(false)
|
||||
const previewCanvasEl = ref<HTMLCanvasElement | null>(null)
|
||||
const previewImages = ref<Record<string, HTMLImageElement>>({})
|
||||
let didInitialFit = false
|
||||
|
||||
const ZOOM_MIN = 0.05
|
||||
@@ -251,6 +255,15 @@ function shadowCfg(props: AnimatableProps): Record<string, unknown> {
|
||||
}
|
||||
|
||||
function baseNodeConfig(layer: Layer, props: AnimatableProps) {
|
||||
const maskGhost = layer.isMask
|
||||
? {
|
||||
opacity: props.opacity * 0.45,
|
||||
stroke: layer.maskMode === 'negative' ? '#ff6b6b' : '#6ea8ff',
|
||||
strokeWidth: 1.5,
|
||||
dash: [6, 4],
|
||||
strokeScaleEnabled: false,
|
||||
}
|
||||
: {}
|
||||
return {
|
||||
id: layer.id,
|
||||
x: props.x,
|
||||
@@ -262,6 +275,7 @@ function baseNodeConfig(layer: Layer, props: AnimatableProps) {
|
||||
visible: layer.visible,
|
||||
draggable: true,
|
||||
...shadowCfg(props),
|
||||
...maskGhost,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -825,6 +839,62 @@ function onContainerDrop(e: DragEvent) {
|
||||
}
|
||||
}
|
||||
|
||||
function resizePreviewCanvas() {
|
||||
const canvas = previewCanvasEl.value
|
||||
if (!canvas) return
|
||||
const dpr = window.devicePixelRatio || 1
|
||||
const cw = containerSize.value.width
|
||||
const ch = containerSize.value.height
|
||||
canvas.width = Math.max(1, Math.round(cw * dpr))
|
||||
canvas.height = Math.max(1, Math.round(ch * dpr))
|
||||
canvas.style.width = `${cw}px`
|
||||
canvas.style.height = `${ch}px`
|
||||
}
|
||||
|
||||
function drawPreview() {
|
||||
const canvas = previewCanvasEl.value
|
||||
if (!canvas) return
|
||||
const ctx = canvas.getContext('2d')
|
||||
if (!ctx) return
|
||||
const dpr = window.devicePixelRatio || 1
|
||||
const cw = containerSize.value.width
|
||||
const ch = containerSize.value.height
|
||||
if (!cw || !ch) return
|
||||
ctx.setTransform(dpr, 0, 0, dpr, 0, 0)
|
||||
ctx.clearRect(0, 0, cw, ch)
|
||||
ctx.save()
|
||||
ctx.translate(viewX.value, viewY.value)
|
||||
ctx.scale(zoom.value, zoom.value)
|
||||
drawSceneFrame(ctx, store.scene, store.playhead, previewImages.value)
|
||||
ctx.restore()
|
||||
}
|
||||
|
||||
watch(
|
||||
[
|
||||
() => store.playhead,
|
||||
() => store.scene,
|
||||
() => containerSize.value,
|
||||
zoom,
|
||||
viewX,
|
||||
viewY,
|
||||
],
|
||||
async () => {
|
||||
if (!maskPreview.value) return
|
||||
await nextTick()
|
||||
resizePreviewCanvas()
|
||||
drawPreview()
|
||||
},
|
||||
{ deep: true },
|
||||
)
|
||||
|
||||
watch(maskPreview, async (on) => {
|
||||
if (!on) return
|
||||
previewImages.value = await loadSceneImages(store.scene)
|
||||
await nextTick()
|
||||
resizePreviewCanvas()
|
||||
drawPreview()
|
||||
})
|
||||
|
||||
function zoomBy(factor: number) {
|
||||
const cw = containerSize.value.width
|
||||
const ch = containerSize.value.height
|
||||
@@ -966,6 +1036,11 @@ onBeforeUnmount(() => {
|
||||
<v-transformer ref="transformerRef" :config="transformerConfig" />
|
||||
</v-layer>
|
||||
</v-stage>
|
||||
<canvas
|
||||
v-show="maskPreview"
|
||||
ref="previewCanvasEl"
|
||||
class="mask-preview-canvas"
|
||||
></canvas>
|
||||
|
||||
<div class="zoom-controls" @pointerdown.stop>
|
||||
<button class="zc-btn" :title="t('canvas.zoomOut')" @click="zoomBy(1 / 1.25)">−</button>
|
||||
@@ -987,6 +1062,15 @@ onBeforeUnmount(() => {
|
||||
>
|
||||
<PhCrop :size="13" />
|
||||
</button>
|
||||
<button
|
||||
class="zc-btn"
|
||||
:class="{ active: maskPreview }"
|
||||
:title="maskPreview ? t('canvas.maskPreviewOff') : t('canvas.maskPreviewOn')"
|
||||
@click="maskPreview = !maskPreview"
|
||||
>
|
||||
<PhMaskHappy v-if="!maskPreview" :size="13" />
|
||||
<PhPencilSimple v-else :size="13" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="dims">{{ store.scene.canvas.width }} × {{ store.scene.canvas.height }}</div>
|
||||
@@ -1001,6 +1085,13 @@ onBeforeUnmount(() => {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.mask-preview-canvas {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
pointer-events: none;
|
||||
background: var(--bg);
|
||||
}
|
||||
|
||||
.dims {
|
||||
position: absolute;
|
||||
bottom: 10px;
|
||||
|
||||
Reference in new issue
Block a user