This commit is contained in:
syuilo 2025-05-28 12:54:48 +09:00
parent 33486ebdf2
commit 09eb631fdc
7 changed files with 46 additions and 45 deletions

View File

@ -96,7 +96,7 @@ import { isWebpSupported } from '@/utility/isWebpSupported.js';
import { uploadFile, UploadAbortedError } from '@/utility/drive.js';
import * as os from '@/os.js';
import { ensureSignin } from '@/i.js';
import { Watermarker } from '@/utility/watermarker.js';
import { ImageEffector } from '@/utility/ImageEffector.js';
const $i = ensureSignin();
@ -152,7 +152,7 @@ const items = ref<{
uploaded: Misskey.entities.DriveFile | null;
uploadFailed: boolean;
aborted: boolean;
compressionLevel: 0 | 1 | 2 | 3;
compressionLevel: number;
compressedSize?: number | null;
preprocessedFile?: Blob | null;
file: File;
@ -486,11 +486,11 @@ async function preprocess(item: (typeof items)['value'][number]): Promise<void>
const preset = prefer.s.watermarkPresets.find(p => p.id === item.watermarkPresetId);
if (needsWatermark && preset != null) {
const canvas = window.document.createElement('canvas');
const renderer = new Watermarker({
const renderer = new ImageEffector({
canvas: canvas,
width: img.width,
height: img.height,
preset: preset,
layers: preset.layers,
originalImage: img,
});

View File

@ -87,9 +87,9 @@ SPDX-License-Identifier: AGPL-3.0-only
<script setup lang="ts">
import { ref, useTemplateRef, watch, onMounted, onUnmounted } from 'vue';
import { v4 as uuid } from 'uuid';
import type { WatermarkerLayer, WatermarkPreset } from '@/utility/watermarker.js';
import type { ImageEffectorLayer } from '@/utility/ImageEffector.js';
import { i18n } from '@/i18n.js';
import { Watermarker } from '@/utility/watermarker.js';
import { ImageEffector } from '@/utility/ImageEffector.js';
import MkSelect from '@/components/MkSelect.vue';
import MkButton from '@/components/MkButton.vue';
import MkInput from '@/components/MkInput.vue';
@ -102,7 +102,7 @@ import { selectFile } from '@/utility/drive.js';
import { misskeyApi } from '@/utility/misskey-api.js';
import { prefer } from '@/preferences.js';
const layer = defineModel<WatermarkerLayer>('layer', { required: true });
const layer = defineModel<ImageEffectorLayer>('layer', { required: true });
const driveFile = ref();
const driveFileError = ref(false);

View File

@ -45,9 +45,9 @@ SPDX-License-Identifier: AGPL-3.0-only
<script setup lang="ts">
import { ref, useTemplateRef, watch, onMounted, onUnmounted, reactive } from 'vue';
import { v4 as uuid } from 'uuid';
import type { WatermarkPreset } from '@/utility/watermarker.js';
import type { WatermarkPreset } from '@/preferences/def.js';
import { i18n } from '@/i18n.js';
import { Watermarker } from '@/utility/watermarker.js';
import { ImageEffector } from '@/utility/ImageEffector.js';
import MkModalWindow from '@/components/MkModalWindow.vue';
import MkSelect from '@/components/MkSelect.vue';
import MkButton from '@/components/MkButton.vue';
@ -121,7 +121,7 @@ watch(type, () => {
watch(preset, async (newValue, oldValue) => {
if (renderer != null) {
renderer.updatePreset(preset);
renderer.updateLayers(preset.layers);
}
}, { deep: true });
@ -148,25 +148,25 @@ watch(sampleImageType, async () => {
}
});
let renderer: Watermarker | null = null;
let renderer: ImageEffector | null = null;
async function initRenderer() {
if (canvasEl.value == null) return;
if (sampleImageType.value === '3_2') {
renderer = new Watermarker({
renderer = new ImageEffector({
canvas: canvasEl.value,
width: 1500,
height: 1000,
preset: preset,
layers: preset.layers,
originalImage: sampleImage_3_2,
});
} else if (sampleImageType.value === '2_3') {
renderer = new Watermarker({
renderer = new ImageEffector({
canvas: canvasEl.value,
width: 1000,
height: 1500,
preset: preset,
layers: preset.layers,
originalImage: sampleImage_2_3,
});
}

View File

@ -22,13 +22,13 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import { defineAsyncComponent, onMounted, onUnmounted, ref, useTemplateRef, watch } from 'vue';
import type { WatermarkPreset } from '@/utility/watermarker.js';
import type { WatermarkPreset } from '@/preferences/def.js';
import MkButton from '@/components/MkButton.vue';
import * as os from '@/os.js';
import { i18n } from '@/i18n.js';
import { deepClone } from '@/utility/clone.js';
import MkFolder from '@/components/MkFolder.vue';
import { Watermarker } from '@/utility/watermarker.js';
import { ImageEffector } from '@/utility/ImageEffector.js';
const props = defineProps<{
preset: WatermarkPreset;
@ -64,18 +64,18 @@ const canvasEl = useTemplateRef('canvasEl');
const sampleImage = new Image();
sampleImage.src = '/client-assets/sample/3-2.jpg';
let renderer: Watermarker | null = null;
let renderer: ImageEffector | null = null;
onMounted(() => {
sampleImage.onload = async () => {
watch(canvasEl, async () => {
if (canvasEl.value == null) return;
renderer = new Watermarker({
renderer = new ImageEffector({
canvas: canvasEl.value,
width: 1500,
height: 1000,
preset: props.preset,
layers: props.preset.layers,
originalImage: sampleImage,
});
@ -95,7 +95,7 @@ onUnmounted(() => {
watch(() => props.preset, async () => {
if (renderer != null) {
renderer.updatePreset(props.preset);
renderer.updateLayers(props.preset.layers);
await renderer.bakeTextures();
renderer.render();
}

View File

@ -149,7 +149,7 @@ import { computed, defineAsyncComponent, ref } from 'vue';
import * as Misskey from 'misskey-js';
import tinycolor from 'tinycolor2';
import XWatermarkItem from './drive.WatermarkItem.vue';
import type { WatermarkPreset } from '@/utility/watermarker.js';
import type { WatermarkPreset } from '@/preferences/def.js';
import FormLink from '@/components/form/link.vue';
import MkSwitch from '@/components/MkSwitch.vue';
import MkSelect from '@/components/MkSelect.vue';

View File

@ -11,7 +11,7 @@ import type { Plugin } from '@/plugin.js';
import type { DeviceKind } from '@/utility/device-kind.js';
import type { DeckProfile } from '@/deck.js';
import type { PreferencesDefinition } from './manager.js';
import type { WatermarkPreset } from '@/utility/watermarker.js';
import type { ImageEffectorLayer } from '@/utility/ImageEffector.js';
import { DEFAULT_DEVICE_KIND } from '@/utility/device-kind.js';
/** サウンド設定 */
@ -30,6 +30,12 @@ export type SoundStore = {
volume: number;
};
export type WatermarkPreset = {
id: string;
name: string;
layers: ImageEffectorLayer[];
};
// NOTE: デフォルト値は他の設定の状態に依存してはならない(依存していた場合、ユーザーがその設定項目単体で「初期値にリセット」した場合不具合の原因になる)
export const PREF_DEF = {

View File

@ -3,7 +3,7 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
const IMAGE_ADD_SHADER = `#version 300 es
const IMAGE_PLACEMENT_SHADER = `#version 300 es
precision highp float;
in vec2 in_uv;
@ -51,13 +51,7 @@ void main() {
}
`;
export type WatermarkPreset = {
id: string;
name: string;
layers: WatermarkerLayer[];
};
type WatermarkerTextLayer = {
type ImageEffectorTextLayer = {
id: string;
type: 'text';
text: string;
@ -68,7 +62,7 @@ type WatermarkerTextLayer = {
opacity: number;
};
type WatermarkerImageLayer = {
type ImageEffectorImageLayer = {
id: string;
type: 'image';
imageUrl: string | null;
@ -80,9 +74,9 @@ type WatermarkerImageLayer = {
opacity: number;
};
export type WatermarkerLayer = WatermarkerTextLayer | WatermarkerImageLayer;
export type ImageEffectorLayer = ImageEffectorTextLayer | ImageEffectorImageLayer;
export class Watermarker {
export class ImageEffector {
private canvas: HTMLCanvasElement | null = null;
private gl: WebGL2RenderingContext | null = null;
private renderTextureProgram!: WebGLProgram;
@ -90,7 +84,7 @@ export class Watermarker {
private renderWidth!: number;
private renderHeight!: number;
private originalImage: ImageData | ImageBitmap | HTMLImageElement | HTMLCanvasElement;
private preset: WatermarkPreset;
private layers: ImageEffectorLayer[];
private originalImageTexture: WebGLTexture;
private resultTexture: WebGLTexture;
private resultFrameBuffer: WebGLFramebuffer;
@ -102,7 +96,7 @@ export class Watermarker {
width: number;
height: number;
originalImage: ImageData | ImageBitmap | HTMLImageElement | HTMLCanvasElement;
preset: WatermarkPreset;
layers: ImageEffectorLayer[];
}) {
this.canvas = options.canvas;
this.canvas.width = options.width;
@ -110,7 +104,7 @@ export class Watermarker {
this.renderWidth = options.width;
this.renderHeight = options.height;
this.originalImage = options.originalImage;
this.preset = options.preset;
this.layers = options.layers;
this.texturesKey = this.calcTexturesKey();
this.gl = this.canvas.getContext('webgl2', {
@ -180,7 +174,7 @@ export class Watermarker {
}
private calcTexturesKey() {
return this.preset.layers.map(layer => {
return this.layers.map(layer => {
if (layer.type === 'image') {
return layer.imageId;
} else if (layer.type === 'text') {
@ -224,10 +218,11 @@ export class Watermarker {
this.disposeBakedTextures();
for (const layer of this.preset.layers) {
for (const layer of this.layers) {
if (layer.type === 'image') {
const image = await new Promise<HTMLImageElement>((resolve, reject) => {
const img = new Image();
img.crossOrigin = 'use-credentials';
img.onload = () => resolve(img);
img.onerror = reject;
img.src = layer.imageUrl;
@ -332,7 +327,7 @@ export class Watermarker {
return shaderProgram;
}
private renderTextOrImageLayer(layer: WatermarkerTextLayer | WatermarkerImageLayer) {
private renderTextOrImageLayer(layer: ImageEffectorTextLayer | ImageEffectorImageLayer) {
const gl = this.gl;
if (gl == null) {
throw new Error('gl is not initialized');
@ -351,7 +346,7 @@ export class Watermarker {
in_uv = (position + 1.0) / 2.0;
gl_Position = vec4(position, 0.0, 1.0);
}
`, IMAGE_ADD_SHADER);
`, IMAGE_PLACEMENT_SHADER);
gl.useProgram(shaderProgram);
@ -392,7 +387,7 @@ export class Watermarker {
gl.drawArrays(gl.TRIANGLES, 0, 6);
}
private renderLayer(layer: WatermarkerLayer) {
private renderLayer(layer: ImageEffectorLayer) {
if (layer.type === 'image') {
this.renderTextOrImageLayer(layer);
} else if (layer.type === 'text') {
@ -435,7 +430,7 @@ export class Watermarker {
// --------------------
for (const layer of this.preset.layers) {
for (const layer of this.layers) {
this.renderLayer(layer);
}
@ -450,8 +445,8 @@ export class Watermarker {
gl.drawArrays(gl.TRIANGLES, 0, 6);
}
public async updatePreset(preset: WatermarkPreset) {
this.preset = preset;
public async updateLayers(layers: ImageEffectorLayer[]) {
this.layers = layers;
const newTexturesKey = this.calcTexturesKey();
if (newTexturesKey !== this.texturesKey) {