summaryrefslogtreecommitdiff
path: root/packages/frontend/src/components/MkImageEffectorDialog.vue
diff options
context:
space:
mode:
Diffstat (limited to 'packages/frontend/src/components/MkImageEffectorDialog.vue')
-rw-r--r--packages/frontend/src/components/MkImageEffectorDialog.vue87
1 files changed, 52 insertions, 35 deletions
diff --git a/packages/frontend/src/components/MkImageEffectorDialog.vue b/packages/frontend/src/components/MkImageEffectorDialog.vue
index 19ddb81919..3d7801f925 100644
--- a/packages/frontend/src/components/MkImageEffectorDialog.vue
+++ b/packages/frontend/src/components/MkImageEffectorDialog.vue
@@ -18,7 +18,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<div :class="$style.root">
<div :class="$style.container">
- <div :class="$style.preview">
+ <div :class="[$style.preview, prefer.s.animation ? $style.animatedBg : null]">
<canvas ref="canvasEl" :class="$style.previewCanvas" @pointerdown.prevent.stop="onImagePointerdown"></canvas>
<div :class="$style.previewContainer">
<div class="_acrylic" :class="$style.previewTitle">{{ i18n.ts.preview }}</div>
@@ -64,6 +64,7 @@ import * as os from '@/os.js';
import { deepClone } from '@/utility/clone.js';
import { FXS } from '@/utility/image-effector/fxs.js';
import { genId } from '@/utility/id.js';
+import { prefer } from '@/preferences.js';
const props = defineProps<{
image: File;
@@ -94,19 +95,19 @@ const layers = reactive<ImageEffectorLayer[]>([]);
watch(layers, async () => {
if (renderer != null) {
- renderer.setLayers(layers);
+ renderer.render(layers);
}
}, { deep: true });
function addEffect(ev: MouseEvent) {
- os.popupMenu(FXS.map((fx) => ({
- text: fx.name,
+ os.popupMenu(Object.entries(FXS).map(([id, fx]) => ({
+ text: fx.uiDefinition.name,
action: () => {
layers.push({
id: genId(),
- fxId: fx.id,
- params: Object.fromEntries(Object.entries(fx.params).map(([k, v]) => [k, v.default])),
- });
+ fxId: id as keyof typeof FXS,
+ params: Object.fromEntries(Object.entries(fx.uiDefinition.params).map(([k, v]) => [k, v.default])),
+ } as ImageEffectorLayer);
},
})), ev.currentTarget ?? ev.target);
}
@@ -136,7 +137,7 @@ function onLayerDelete(layer: ImageEffectorLayer) {
const canvasEl = useTemplateRef('canvasEl');
-let renderer: ImageEffector<typeof FXS> | null = null;
+let renderer: ImageEffector | null = null;
let imageBitmap: ImageBitmap | null = null;
onMounted(async () => {
@@ -146,30 +147,35 @@ onMounted(async () => {
await nextTick(); // waitingがレンダリングされるまで待つ
- imageBitmap = await window.createImageBitmap(props.image);
+ try {
+ imageBitmap = await window.createImageBitmap(props.image);
- const MAX_W = 1000;
- const MAX_H = 1000;
- let w = imageBitmap.width;
- let h = imageBitmap.height;
+ const MAX_W = 1000;
+ const MAX_H = 1000;
+ let w = imageBitmap.width;
+ let h = imageBitmap.height;
- if (w > MAX_W || h > MAX_H) {
- const scale = Math.min(MAX_W / w, MAX_H / h);
- w *= scale;
- h *= scale;
- }
-
- renderer = new ImageEffector({
- canvas: canvasEl.value,
- renderWidth: w,
- renderHeight: h,
- image: imageBitmap,
- fxs: FXS,
- });
+ if (w > MAX_W || h > MAX_H) {
+ const scale = Math.min(MAX_W / w, MAX_H / h);
+ w = Math.floor(w * scale);
+ h = Math.floor(h * scale);
+ }
- await renderer.setLayers(layers);
+ renderer = new ImageEffector({
+ canvas: canvasEl.value,
+ renderWidth: w,
+ renderHeight: h,
+ image: imageBitmap,
+ });
- renderer.render();
+ await renderer.render(layers);
+ } catch (err) {
+ console.error(err);
+ os.alert({
+ type: 'error',
+ text: i18n.ts._imageEffector.failedToLoadImage,
+ });
+ }
closeWaiting();
});
@@ -196,7 +202,7 @@ async function save() {
await nextTick(); // waitingがレンダリングされるまで待つ
renderer.changeResolution(imageBitmap.width, imageBitmap.height); // 本番レンダリングのためオリジナル画質に戻す
- renderer.render(); // toBlobの直前にレンダリングしないと何故か壊れる
+ await renderer.render(layers); // toBlobの直前にレンダリングしないと何故か壊れる
canvasEl.value.toBlob((blob) => {
emit('ok', new File([blob!], `image-${Date.now()}.png`, { type: 'image/png' }));
dialog.value?.close();
@@ -208,11 +214,10 @@ const enabled = ref(true);
watch(enabled, () => {
if (renderer != null) {
if (enabled.value) {
- renderer.setLayers(layers);
+ renderer.render(layers);
} else {
- renderer.setLayers([]);
+ renderer.render([]);
}
- renderer.render();
}
});
@@ -281,6 +286,7 @@ function onImagePointerdown(ev: PointerEvent) {
angle: 0,
opacity: 1,
color: [1, 1, 1],
+ ellipse: false,
},
});
} else if (penMode.value === 'blur') {
@@ -294,6 +300,7 @@ function onImagePointerdown(ev: PointerEvent) {
scaleY: 0.1,
angle: 0,
radius: 3,
+ ellipse: false,
},
});
} else if (penMode.value === 'pixelate') {
@@ -307,6 +314,7 @@ function onImagePointerdown(ev: PointerEvent) {
scaleY: 0.1,
angle: 0,
strength: 0.2,
+ ellipse: false,
},
});
}
@@ -329,7 +337,7 @@ function onImagePointerdown(ev: PointerEvent) {
const scaleY = Math.abs(y - startY);
const layerIndex = layers.findIndex((l) => l.id === id);
- const layer = layerIndex !== -1 ? layers[layerIndex] : null;
+ const layer = layerIndex !== -1 ? (layers[layerIndex] as Extract<ImageEffectorLayer, { fxId: 'fill' } | { fxId: 'blur' } | { fxId: 'pixelate' }>) : null;
if (layer != null) {
layer.params.offsetX = (x + startX) - 1;
layer.params.offsetY = (y + startY) - 1;
@@ -373,8 +381,17 @@ function onImagePointerdown(ev: PointerEvent) {
.preview {
position: relative;
background-color: var(--MI_THEME-bg);
- background-size: auto auto;
- background-image: repeating-linear-gradient(135deg, transparent, transparent 6px, var(--MI_THEME-panel) 6px, var(--MI_THEME-panel) 12px);
+ background-image: linear-gradient(135deg, transparent 30%, var(--MI_THEME-panel) 30%, var(--MI_THEME-panel) 50%, transparent 50%, transparent 80%, var(--MI_THEME-panel) 80%, var(--MI_THEME-panel) 100%);
+ background-size: 20px 20px;
+}
+
+.animatedBg {
+ animation: bg 1.2s linear infinite;
+}
+
+@keyframes bg {
+ 0% { background-position: 0 0; }
+ 100% { background-position: -20px -20px; }
}
.previewContainer {