diff --git a/locales/index.d.ts b/locales/index.d.ts index 9fca2b28bc..71a341bda8 100644 --- a/locales/index.d.ts +++ b/locales/index.d.ts @@ -12083,6 +12083,10 @@ export interface Locale extends ILocale { * エフェクトを追加 */ "addEffect": string; + /** + * 変更を破棄して終了しますか? + */ + "discardChangesConfirm": string; "_fxs": { /** * 色収差 diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index 9ac19aae5c..6c8414eefa 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -3237,6 +3237,7 @@ _watermarkEditor: _imageEffector: title: "エフェクト" addEffect: "エフェクトを追加" + discardChangesConfirm: "変更を破棄して終了しますか?" _fxs: chromaticAberration: "色収差" diff --git a/packages/frontend/src/components/MkImageEffectorDialog.vue b/packages/frontend/src/components/MkImageEffectorDialog.vue index 9c05e8237e..6009f82066 100644 --- a/packages/frontend/src/components/MkImageEffectorDialog.vue +++ b/packages/frontend/src/components/MkImageEffectorDialog.vue @@ -61,7 +61,7 @@ import { deepClone } from '@/utility/clone.js'; import { FXS } from '@/utility/image-effector/fxs.js'; const props = defineProps<{ - image: ImageBitmap; + image: File; }>(); const emit = defineEmits<{ @@ -72,7 +72,14 @@ const emit = defineEmits<{ const dialog = useTemplateRef('dialog'); -function cancel() { +async function cancel() { + if (layers.length > 0) { + const { canceled } = await os.confirm({ + text: i18n.ts._imageEffector.discardChangesConfirm, + }); + if (canceled) return; + } + emit('cancel'); dialog.value?.close(); } @@ -108,14 +115,19 @@ function onLayerDelete(layer: ImageEffectorLayer) { const canvasEl = useTemplateRef('canvasEl'); let renderer: ImageEffector | null = null; +let imageBitmap: ImageBitmap | null = null; onMounted(async () => { if (canvasEl.value == null) return; + const closeWaiting = os.waiting(); + + imageBitmap = await window.createImageBitmap(props.image); + const MAX_W = 500; const MAX_H = 500; - let w = props.image.width; - let h = props.image.height; + 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); @@ -127,13 +139,15 @@ onMounted(async () => { canvas: canvasEl.value, renderWidth: w, renderHeight: h, - image: props.image, + image: imageBitmap, fxs: FXS, }); await renderer.setLayers(layers); renderer.render(); + + closeWaiting(); }); onUnmounted(() => { @@ -141,19 +155,26 @@ onUnmounted(() => { renderer.destroy(); renderer = null; } + if (imageBitmap != null) { + imageBitmap.close(); + imageBitmap = null; + } }); function save() { - if (layers.length === 0) { + if (layers.length === 0 || renderer == null || imageBitmap == null || canvasEl.value == null) { cancel(); return; } - renderer!.changeResolution(props.image.width, props.image.height); // 本番レンダリングのためオリジナル画質に戻す - renderer!.render(); // toBlobの直前にレンダリングしないと何故か壊れる - canvasEl.value!.toBlob((blob) => { + const closeWaiting = os.waiting(); + + renderer.changeResolution(imageBitmap.width, imageBitmap.height); // 本番レンダリングのためオリジナル画質に戻す + renderer.render(); // toBlobの直前にレンダリングしないと何故か壊れる + canvasEl.value.toBlob((blob) => { emit('ok', new File([blob!], `image-${Date.now()}.png`, { type: 'image/png' })); dialog.value?.close(); + closeWaiting(); }, 'image/png'); } diff --git a/packages/frontend/src/components/MkUploaderDialog.vue b/packages/frontend/src/components/MkUploaderDialog.vue index 0f25e255d5..15b106863c 100644 --- a/packages/frontend/src/components/MkUploaderDialog.vue +++ b/packages/frontend/src/components/MkUploaderDialog.vue @@ -297,9 +297,8 @@ function showMenu(ev: MouseEvent, item: typeof items.value[0]) { icon: 'ti ti-sparkles', text: i18n.ts._imageEffector.title, action: async () => { - const imageBitmap = await window.createImageBitmap(item.file); const { dispose } = os.popup(defineAsyncComponent(() => import('@/components/MkImageEffectorDialog.vue')), { - image: imageBitmap, + image: item.file, }, { ok: (file) => { URL.revokeObjectURL(item.thumbnail); @@ -313,10 +312,7 @@ function showMenu(ev: MouseEvent, item: typeof items.value[0]) { triggerRef(items); }); }, - closed: () => { - imageBitmap.close(); - dispose(); - }, + closed: () => dispose(), }); }, }); @@ -353,18 +349,14 @@ function showMenu(ev: MouseEvent, item: typeof items.value[0]) { icon: 'ti ti-plus', text: i18n.ts.add, action: async () => { - const imageBitmap = await window.createImageBitmap(item.file); const { dispose } = os.popup(defineAsyncComponent(() => import('@/components/MkWatermarkEditorDialog.vue')), { - image: imageBitmap, + image: item.file, }, { ok: (preset) => { prefer.commit('watermarkPresets', [...prefer.s.watermarkPresets, preset]); changeWatermarkPreset(preset.id); }, - closed: () => { - imageBitmap.close(); - dispose(); - }, + closed: () => dispose(), }); }, }], diff --git a/packages/frontend/src/components/MkWatermarkEditorDialog.vue b/packages/frontend/src/components/MkWatermarkEditorDialog.vue index 81c0c72199..f8c11e9867 100644 --- a/packages/frontend/src/components/MkWatermarkEditorDialog.vue +++ b/packages/frontend/src/components/MkWatermarkEditorDialog.vue @@ -63,7 +63,7 @@ const $i = ensureSignin(); const props = defineProps<{ preset?: WatermarkPreset | null; - image?: ImageBitmap | null; + image?: File | null; }>(); const preset = reactive(deepClone(props.preset) ?? { @@ -149,6 +149,7 @@ watch(sampleImageType, async () => { }); let renderer: WatermarkRenderer | null = null; +let imageBitmap: ImageBitmap | null = null; async function initRenderer() { if (canvasEl.value == null) return; @@ -168,10 +169,12 @@ async function initRenderer() { image: sampleImage_2_3, }); } else if (props.image != null) { + imageBitmap = await window.createImageBitmap(props.image); + const MAX_W = 1000; const MAX_H = 1000; - let w = props.image.width; - let h = props.image.height; + 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); @@ -183,7 +186,7 @@ async function initRenderer() { canvas: canvasEl.value, renderWidth: w, renderHeight: h, - image: props.image, + image: imageBitmap, }); } @@ -193,10 +196,14 @@ async function initRenderer() { } onMounted(async () => { + const closeWaiting = os.waiting(); + await sampleImage_3_2_loading; await sampleImage_2_3_loading; - initRenderer(); + await initRenderer(); + + closeWaiting(); }); onUnmounted(() => { @@ -204,6 +211,10 @@ onUnmounted(() => { renderer.destroy(); renderer = null; } + if (imageBitmap != null) { + imageBitmap.close(); + imageBitmap = null; + } }); async function save() {