This commit is contained in:
syuilo 2025-05-28 09:44:09 +09:00
parent e3aae009b4
commit cd296d60d8
3 changed files with 78 additions and 22 deletions

View File

@ -5,10 +5,6 @@ SPDX-License-Identifier: AGPL-3.0-only
<template> <template>
<div :class="$style.root" class="_gaps"> <div :class="$style.root" class="_gaps">
<div>
<MkButton inline rounded primary @click="chooseFile">{{ i18n.ts.selectFile }}</MkButton>
</div>
<template v-if="layer.type === 'text'"> <template v-if="layer.type === 'text'">
<MkInput v-model="layer.text"> <MkInput v-model="layer.text">
<template #label>{{ i18n.ts._watermarkEditor.text }}</template> <template #label>{{ i18n.ts._watermarkEditor.text }}</template>
@ -42,6 +38,41 @@ SPDX-License-Identifier: AGPL-3.0-only
<template #label>{{ i18n.ts._watermarkEditor.opacity }}</template> <template #label>{{ i18n.ts._watermarkEditor.opacity }}</template>
</MkRange> </MkRange>
<MkSwitch v-model="layer.repeat">
<template #label>{{ i18n.ts._watermarkEditor.repeat }}</template>
</MkSwitch>
</template>
<template v-else-if="layer.type === 'image'">
<MkButton inline rounded primary @click="chooseFile">{{ i18n.ts.selectFile }}</MkButton>
<FormSlot>
<template #label>{{ i18n.ts._watermarkEditor.position }}</template>
<MkPositionSelector
v-model:x="layer.alignX"
v-model:y="layer.alignY"
></MkPositionSelector>
</FormSlot>
<MkRange
v-model="layer.scale"
:min="0"
:max="1"
:step="0.01"
continuousUpdate
>
<template #label>{{ i18n.ts._watermarkEditor.scale }}</template>
</MkRange>
<MkRange
v-model="layer.opacity"
:min="0"
:max="1"
:step="0.01"
continuousUpdate
>
<template #label>{{ i18n.ts._watermarkEditor.opacity }}</template>
</MkRange>
<MkSwitch v-model="layer.repeat"> <MkSwitch v-model="layer.repeat">
<template #label>{{ i18n.ts._watermarkEditor.repeat }}</template> <template #label>{{ i18n.ts._watermarkEditor.repeat }}</template>
</MkSwitch> </MkSwitch>
@ -84,14 +115,7 @@ onMounted(async () => {
}); });
function chooseFile(ev: MouseEvent) { function chooseFile(ev: MouseEvent) {
selectFile({ selectFile(ev.currentTarget ?? ev.target, i18n.ts.selectFile).then((file) => {
anchorElement: ev.currentTarget ?? ev.target,
multiple: false,
label: i18n.ts.selectFile,
features: {
watermark: false,
},
}).then((file) => {
if (!file.type.startsWith('image')) { if (!file.type.startsWith('image')) {
os.alert({ os.alert({
type: 'warning', type: 'warning',
@ -101,9 +125,8 @@ function chooseFile(ev: MouseEvent) {
return; return;
} }
fileId.value = file.id; layer.value.imageId = file.id;
fileUrl.value = file.url; layer.value.imageUrl = file.url;
fileName.value = file.name;
driveFileError.value = false; driveFileError.value = false;
}); });
} }

View File

@ -97,6 +97,32 @@ function cancel() {
} }
const type = ref(preset.layers[0].type); const type = ref(preset.layers[0].type);
watch(type, () => {
if (type.value === 'text') {
preset.layers = [{
id: uuid(),
type: type.value,
text: `(c) @${$i.username}`,
alignX: 'right',
alignY: 'bottom',
scale: 0.3,
opacity: 0.75,
repeat: false,
}];
} else if (type.value === 'image') {
preset.layers = [{
id: uuid(),
type: type.value,
imageId: null,
imageUrl: null,
alignX: 'right',
alignY: 'bottom',
scale: 0.3,
opacity: 0.75,
repeat: false,
}];
}
});
watch(preset, async (newValue, oldValue) => { watch(preset, async (newValue, oldValue) => {
if (renderer != null) { if (renderer != null) {

View File

@ -68,11 +68,11 @@ type WatermarkerTextLayer = {
opacity: number; opacity: number;
}; };
export type WatermarkerImageLayer = { type WatermarkerImageLayer = {
id: string; id: string;
type: 'image'; type: 'image';
imageUrl: string; imageUrl: string | null;
imageId: string; imageId: string | null;
repeat: boolean; repeat: boolean;
scale: number; scale: number;
alignX: 'left' | 'center' | 'right'; alignX: 'left' | 'center' | 'right';
@ -234,10 +234,17 @@ export class Watermarker {
img.src = layer.imageUrl; img.src = layer.imageUrl;
}); });
const texture = this.createTexture();
gl.activeTexture(gl.TEXTURE0); gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_2D, this.originalImageTexture); gl.bindTexture(gl.TEXTURE_2D, texture);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, image.width, image.height, 0, gl.RGBA, gl.UNSIGNED_BYTE, image); gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, image.width, image.height, 0, gl.RGBA, gl.UNSIGNED_BYTE, image);
gl.bindTexture(gl.TEXTURE_2D, null); gl.bindTexture(gl.TEXTURE_2D, null);
this.bakedTextures.set(layer.id, {
texture: texture,
width: image.width,
height: image.height,
});
} else if (layer.type === 'text') { } else if (layer.type === 'text') {
const measureCtx = window.document.createElement('canvas').getContext('2d')!; const measureCtx = window.document.createElement('canvas').getContext('2d')!;
measureCtx.canvas.width = this.renderWidth; measureCtx.canvas.width = this.renderWidth;
@ -323,7 +330,7 @@ export class Watermarker {
return shaderProgram; return shaderProgram;
} }
private renderTextLayer(layer: WatermarkerTextLayer) { private renderTextOrImageLayer(layer: WatermarkerTextLayer | WatermarkerImageLayer) {
const gl = this.gl; const gl = this.gl;
if (gl == null) { if (gl == null) {
throw new Error('gl is not initialized'); throw new Error('gl is not initialized');
@ -385,9 +392,9 @@ export class Watermarker {
private renderLayer(layer: WatermarkerLayer) { private renderLayer(layer: WatermarkerLayer) {
if (layer.type === 'image') { if (layer.type === 'image') {
this.renderImageLayer(layer); this.renderTextOrImageLayer(layer);
} else if (layer.type === 'text') { } else if (layer.type === 'text') {
this.renderTextLayer(layer); this.renderTextOrImageLayer(layer);
} }
} }