wip
This commit is contained in:
parent
2d2b9e7a3f
commit
236b8913d2
|
@ -48,7 +48,6 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, useTemplateRef, watch, onMounted, onUnmounted, reactive } from 'vue';
|
import { ref, useTemplateRef, watch, onMounted, onUnmounted, reactive } from 'vue';
|
||||||
import { v4 as uuid } from 'uuid';
|
import { v4 as uuid } from 'uuid';
|
||||||
import type { WatermarkPreset } from '@/utility/watermark.js';
|
|
||||||
import type { ImageEffectorLayer } from '@/utility/image-effector/ImageEffector.js';
|
import type { ImageEffectorLayer } from '@/utility/image-effector/ImageEffector.js';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import { ImageEffector } from '@/utility/image-effector/ImageEffector.js';
|
import { ImageEffector } from '@/utility/image-effector/ImageEffector.js';
|
||||||
|
@ -82,7 +81,7 @@ const layers = reactive<ImageEffectorLayer[]>([]);
|
||||||
|
|
||||||
watch(layers, async () => {
|
watch(layers, async () => {
|
||||||
if (renderer != null) {
|
if (renderer != null) {
|
||||||
renderer.updateLayers(layers);
|
renderer.setLayers(layers);
|
||||||
}
|
}
|
||||||
}, { deep: true });
|
}, { deep: true });
|
||||||
|
|
||||||
|
@ -111,18 +110,19 @@ const canvasEl = useTemplateRef('canvasEl');
|
||||||
let renderer: ImageEffector | null = null;
|
let renderer: ImageEffector | null = null;
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
|
if (canvasEl.value == null) return;
|
||||||
|
|
||||||
renderer = new ImageEffector({
|
renderer = new ImageEffector({
|
||||||
canvas: canvasEl.value,
|
canvas: canvasEl.value,
|
||||||
width: props.image.width,
|
renderWidth: props.image.width,
|
||||||
height: props.image.height,
|
renderHeight: props.image.height,
|
||||||
layers: layers,
|
image: props.image,
|
||||||
originalImage: props.image,
|
|
||||||
fxs: FXS,
|
fxs: FXS,
|
||||||
});
|
});
|
||||||
|
|
||||||
await renderer!.bakeTextures();
|
await renderer.setLayers(layers);
|
||||||
|
|
||||||
renderer!.render();
|
renderer.render();
|
||||||
});
|
});
|
||||||
|
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
|
@ -149,9 +149,9 @@ const enabled = ref(true);
|
||||||
watch(enabled, () => {
|
watch(enabled, () => {
|
||||||
if (renderer != null) {
|
if (renderer != null) {
|
||||||
if (enabled.value) {
|
if (enabled.value) {
|
||||||
renderer.updateLayers(layers);
|
renderer.setLayers(layers);
|
||||||
} else {
|
} else {
|
||||||
renderer.updateLayers([]);
|
renderer.setLayers([]);
|
||||||
}
|
}
|
||||||
renderer.render();
|
renderer.render();
|
||||||
}
|
}
|
||||||
|
|
|
@ -96,9 +96,7 @@ import { isWebpSupported } from '@/utility/isWebpSupported.js';
|
||||||
import { uploadFile, UploadAbortedError } from '@/utility/drive.js';
|
import { uploadFile, UploadAbortedError } from '@/utility/drive.js';
|
||||||
import * as os from '@/os.js';
|
import * as os from '@/os.js';
|
||||||
import { ensureSignin } from '@/i.js';
|
import { ensureSignin } from '@/i.js';
|
||||||
import { ImageEffector } from '@/utility/image-effector/ImageEffector.js';
|
import { WatermarkRenderer } from '@/utility/watermark.js';
|
||||||
import { makeImageEffectorLayers } from '@/utility/watermark.js';
|
|
||||||
import { FX_watermarkPlacement } from '@/utility/image-effector/fxs/watermarkPlacement.js';
|
|
||||||
|
|
||||||
const $i = ensureSignin();
|
const $i = ensureSignin();
|
||||||
|
|
||||||
|
@ -522,16 +520,14 @@ async function preprocess(item: (typeof items)['value'][number]): Promise<void>
|
||||||
const preset = prefer.s.watermarkPresets.find(p => p.id === item.watermarkPresetId);
|
const preset = prefer.s.watermarkPresets.find(p => p.id === item.watermarkPresetId);
|
||||||
if (needsWatermark && preset != null) {
|
if (needsWatermark && preset != null) {
|
||||||
const canvas = window.document.createElement('canvas');
|
const canvas = window.document.createElement('canvas');
|
||||||
const renderer = new ImageEffector({
|
const renderer = new WatermarkRenderer({
|
||||||
canvas: canvas,
|
canvas: canvas,
|
||||||
width: img.width,
|
renderWidth: img.width,
|
||||||
height: img.height,
|
renderHeight: img.height,
|
||||||
layers: makeImageEffectorLayers(preset.layers),
|
image: img,
|
||||||
originalImage: img,
|
|
||||||
fxs: [FX_watermarkPlacement],
|
|
||||||
});
|
});
|
||||||
|
|
||||||
await renderer.bakeTextures();
|
await renderer.setLayers(preset.layers);
|
||||||
|
|
||||||
renderer.render();
|
renderer.render();
|
||||||
|
|
||||||
|
@ -541,6 +537,7 @@ async function preprocess(item: (typeof items)['value'][number]): Promise<void>
|
||||||
throw new Error('Failed to convert canvas to blob');
|
throw new Error('Failed to convert canvas to blob');
|
||||||
}
|
}
|
||||||
resolve(blob);
|
resolve(blob);
|
||||||
|
renderer.destroy();
|
||||||
}, 'image/png');
|
}, 'image/png');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -47,10 +47,8 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, useTemplateRef, watch, onMounted, onUnmounted, reactive } from 'vue';
|
import { ref, useTemplateRef, watch, onMounted, onUnmounted, reactive } from 'vue';
|
||||||
import { v4 as uuid } from 'uuid';
|
import { v4 as uuid } from 'uuid';
|
||||||
import type { WatermarkPreset } from '@/utility/watermark.js';
|
import { WatermarkRenderer } from '@/utility/watermark.js';
|
||||||
import { makeImageEffectorLayers } from '@/utility/watermark.js';
|
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import { ImageEffector } from '@/utility/image-effector/ImageEffector.js';
|
|
||||||
import MkModalWindow from '@/components/MkModalWindow.vue';
|
import MkModalWindow from '@/components/MkModalWindow.vue';
|
||||||
import MkSelect from '@/components/MkSelect.vue';
|
import MkSelect from '@/components/MkSelect.vue';
|
||||||
import MkButton from '@/components/MkButton.vue';
|
import MkButton from '@/components/MkButton.vue';
|
||||||
|
@ -59,7 +57,6 @@ import XLayer from '@/components/MkWatermarkEditorDialog.Layer.vue';
|
||||||
import * as os from '@/os.js';
|
import * as os from '@/os.js';
|
||||||
import { deepClone } from '@/utility/clone.js';
|
import { deepClone } from '@/utility/clone.js';
|
||||||
import { ensureSignin } from '@/i.js';
|
import { ensureSignin } from '@/i.js';
|
||||||
import { FX_watermarkPlacement } from '@/utility/image-effector/fxs/watermarkPlacement.js';
|
|
||||||
|
|
||||||
const $i = ensureSignin();
|
const $i = ensureSignin();
|
||||||
|
|
||||||
|
@ -122,7 +119,7 @@ watch(type, () => {
|
||||||
|
|
||||||
watch(preset, async (newValue, oldValue) => {
|
watch(preset, async (newValue, oldValue) => {
|
||||||
if (renderer != null) {
|
if (renderer != null) {
|
||||||
renderer.updateLayers(makeImageEffectorLayers(preset.layers));
|
renderer.setLayers(preset.layers);
|
||||||
}
|
}
|
||||||
}, { deep: true });
|
}, { deep: true });
|
||||||
|
|
||||||
|
@ -149,32 +146,28 @@ watch(sampleImageType, async () => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
let renderer: ImageEffector | null = null;
|
let renderer: WatermarkRenderer | null = null;
|
||||||
|
|
||||||
async function initRenderer() {
|
async function initRenderer() {
|
||||||
if (canvasEl.value == null) return;
|
if (canvasEl.value == null) return;
|
||||||
|
|
||||||
if (sampleImageType.value === '3_2') {
|
if (sampleImageType.value === '3_2') {
|
||||||
renderer = new ImageEffector({
|
renderer = new WatermarkRenderer({
|
||||||
canvas: canvasEl.value,
|
canvas: canvasEl.value,
|
||||||
width: 1500,
|
renderWidth: 1500,
|
||||||
height: 1000,
|
renderHeight: 1000,
|
||||||
layers: makeImageEffectorLayers(preset.layers),
|
image: sampleImage_3_2,
|
||||||
originalImage: sampleImage_3_2,
|
|
||||||
fxs: [FX_watermarkPlacement],
|
|
||||||
});
|
});
|
||||||
} else if (sampleImageType.value === '2_3') {
|
} else if (sampleImageType.value === '2_3') {
|
||||||
renderer = new ImageEffector({
|
renderer = new WatermarkRenderer({
|
||||||
canvas: canvasEl.value,
|
canvas: canvasEl.value,
|
||||||
width: 1000,
|
renderWidth: 1000,
|
||||||
height: 1500,
|
renderHeight: 1500,
|
||||||
layers: makeImageEffectorLayers(preset.layers),
|
image: sampleImage_2_3,
|
||||||
originalImage: sampleImage_2_3,
|
|
||||||
fxs: [FX_watermarkPlacement],
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
await renderer!.bakeTextures();
|
await renderer!.setLayers(preset.layers);
|
||||||
|
|
||||||
renderer!.render();
|
renderer!.render();
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,14 +23,12 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { defineAsyncComponent, onMounted, onUnmounted, ref, useTemplateRef, watch } from 'vue';
|
import { defineAsyncComponent, onMounted, onUnmounted, ref, useTemplateRef, watch } from 'vue';
|
||||||
import type { WatermarkPreset } from '@/utility/watermark.js';
|
import type { WatermarkPreset } from '@/utility/watermark.js';
|
||||||
import { makeImageEffectorLayers } from '@/utility/watermark.js';
|
import { WatermarkRenderer } from '@/utility/watermark.js';
|
||||||
import MkButton from '@/components/MkButton.vue';
|
import MkButton from '@/components/MkButton.vue';
|
||||||
import * as os from '@/os.js';
|
import * as os from '@/os.js';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import { deepClone } from '@/utility/clone.js';
|
import { deepClone } from '@/utility/clone.js';
|
||||||
import MkFolder from '@/components/MkFolder.vue';
|
import MkFolder from '@/components/MkFolder.vue';
|
||||||
import { ImageEffector } from '@/utility/image-effector/ImageEffector.js';
|
|
||||||
import { FX_watermarkPlacement } from '@/utility/image-effector/fxs/watermarkPlacement.js';
|
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
preset: WatermarkPreset;
|
preset: WatermarkPreset;
|
||||||
|
@ -66,23 +64,21 @@ const canvasEl = useTemplateRef('canvasEl');
|
||||||
const sampleImage = new Image();
|
const sampleImage = new Image();
|
||||||
sampleImage.src = '/client-assets/sample/3-2.jpg';
|
sampleImage.src = '/client-assets/sample/3-2.jpg';
|
||||||
|
|
||||||
let renderer: ImageEffector | null = null;
|
let renderer: WatermarkRenderer | null = null;
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
sampleImage.onload = async () => {
|
sampleImage.onload = async () => {
|
||||||
watch(canvasEl, async () => {
|
watch(canvasEl, async () => {
|
||||||
if (canvasEl.value == null) return;
|
if (canvasEl.value == null) return;
|
||||||
|
|
||||||
renderer = new ImageEffector({
|
renderer = new WatermarkRenderer({
|
||||||
canvas: canvasEl.value,
|
canvas: canvasEl.value,
|
||||||
width: 1500,
|
renderWidth: 1500,
|
||||||
height: 1000,
|
renderHeight: 1000,
|
||||||
layers: makeImageEffectorLayers(props.preset.layers),
|
image: sampleImage,
|
||||||
originalImage: sampleImage,
|
|
||||||
fxs: [FX_watermarkPlacement],
|
|
||||||
});
|
});
|
||||||
|
|
||||||
await renderer.bakeTextures();
|
await renderer.setLayers(props.preset.layers);
|
||||||
|
|
||||||
renderer.render();
|
renderer.render();
|
||||||
}, { immediate: true });
|
}, { immediate: true });
|
||||||
|
@ -98,8 +94,7 @@ onUnmounted(() => {
|
||||||
|
|
||||||
watch(() => props.preset, async () => {
|
watch(() => props.preset, async () => {
|
||||||
if (renderer != null) {
|
if (renderer != null) {
|
||||||
renderer.updateLayers(makeImageEffectorLayers(props.preset.layers));
|
await renderer.setLayers(props.preset.layers);
|
||||||
await renderer.bakeTextures();
|
|
||||||
renderer.render();
|
renderer.render();
|
||||||
}
|
}
|
||||||
}, { deep: true });
|
}, { deep: true });
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { getProxiedImageUrl } from '../media-proxy.js';
|
import { createTexture } from './utilts.js';
|
||||||
|
|
||||||
type ParamTypeToPrimitive = {
|
type ParamTypeToPrimitive = {
|
||||||
'number': number;
|
'number': number;
|
||||||
|
@ -11,6 +11,7 @@ type ParamTypeToPrimitive = {
|
||||||
'boolean': boolean;
|
'boolean': boolean;
|
||||||
'align': { x: 'left' | 'center' | 'right'; y: 'top' | 'center' | 'bottom'; };
|
'align': { x: 'left' | 'center' | 'right'; y: 'top' | 'center' | 'bottom'; };
|
||||||
'seed': number;
|
'seed': number;
|
||||||
|
'texture': string | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
type ImageEffectorFxParamDefs = Record<string, {
|
type ImageEffectorFxParamDefs = Record<string, {
|
||||||
|
@ -22,26 +23,27 @@ export function defineImageEffectorFx<ID extends string, P extends ImageEffector
|
||||||
return fx;
|
return fx;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ImageEffectorFx<ID extends string = string, P extends ImageEffectorFxParamDefs = any, U extends string[] = string[]> = {
|
export type ImageEffectorFx<ID extends string = string, PS extends ImageEffectorFxParamDefs = any, US extends string[] = string[], TS extends string[] = string[]> = {
|
||||||
id: ID;
|
id: ID;
|
||||||
name: string;
|
name: string;
|
||||||
shader: string;
|
shader: string;
|
||||||
uniforms: U;
|
uniforms: US;
|
||||||
params: P,
|
params: PS,
|
||||||
|
textures?: TS;
|
||||||
main: (ctx: {
|
main: (ctx: {
|
||||||
gl: WebGL2RenderingContext;
|
gl: WebGL2RenderingContext;
|
||||||
program: WebGLProgram;
|
program: WebGLProgram;
|
||||||
params: {
|
params: {
|
||||||
[key in keyof P]: ParamTypeToPrimitive[P[key]['type']];
|
[key in keyof PS]: ParamTypeToPrimitive[PS[key]['type']];
|
||||||
};
|
};
|
||||||
u: Record<U[number], WebGLUniformLocation>;
|
u: Record<US[number], WebGLUniformLocation>;
|
||||||
width: number;
|
width: number;
|
||||||
height: number;
|
height: number;
|
||||||
watermark?: {
|
textures: Record<TS[number], {
|
||||||
texture: WebGLTexture;
|
texture: WebGLTexture;
|
||||||
width: number;
|
width: number;
|
||||||
height: number;
|
height: number;
|
||||||
};
|
} | null>;
|
||||||
}) => void;
|
}) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -49,54 +51,54 @@ export type ImageEffectorLayer = {
|
||||||
id: string;
|
id: string;
|
||||||
fxId: string;
|
fxId: string;
|
||||||
params: Record<string, any>;
|
params: Record<string, any>;
|
||||||
|
textures?: Record<string, ExternalTextureId | null>;
|
||||||
// for watermarkPlacement fx
|
|
||||||
imageUrl?: string | null;
|
|
||||||
text?: string | null;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type ExternalTextureId = string;
|
||||||
|
|
||||||
export class ImageEffector {
|
export class ImageEffector {
|
||||||
|
public gl: WebGL2RenderingContext;
|
||||||
private canvas: HTMLCanvasElement | null = null;
|
private canvas: HTMLCanvasElement | null = null;
|
||||||
private gl: WebGL2RenderingContext | null = null;
|
|
||||||
private renderTextureProgram!: WebGLProgram;
|
private renderTextureProgram!: WebGLProgram;
|
||||||
private renderInvertedTextureProgram!: WebGLProgram;
|
private renderInvertedTextureProgram!: WebGLProgram;
|
||||||
private renderWidth!: number;
|
private renderWidth!: number;
|
||||||
private renderHeight!: number;
|
private renderHeight!: number;
|
||||||
private originalImage: ImageData | ImageBitmap | HTMLImageElement | HTMLCanvasElement;
|
private originalImage: ImageData | ImageBitmap | HTMLImageElement | HTMLCanvasElement;
|
||||||
private layers: ImageEffectorLayer[];
|
private layers: ImageEffectorLayer[] = [];
|
||||||
private originalImageTexture: WebGLTexture;
|
private originalImageTexture: WebGLTexture;
|
||||||
private bakedTexturesForWatermarkFx: Map<string, { texture: WebGLTexture; width: number; height: number; }> = new Map();
|
|
||||||
private texturesKey: string;
|
|
||||||
private shaderCache: Map<string, WebGLProgram> = new Map();
|
private shaderCache: Map<string, WebGLProgram> = new Map();
|
||||||
private perLayerResultTextures: Map<string, WebGLTexture> = new Map();
|
private perLayerResultTextures: Map<string, WebGLTexture> = new Map();
|
||||||
private perLayerResultFrameBuffers: Map<string, WebGLFramebuffer> = new Map();
|
private perLayerResultFrameBuffers: Map<string, WebGLFramebuffer> = new Map();
|
||||||
private fxs: ImageEffectorFx[];
|
private fxs: ImageEffectorFx[];
|
||||||
|
private externalTextures: Map<ExternalTextureId, { texture: WebGLTexture; width: number; height: number; }> = new Map();
|
||||||
|
|
||||||
constructor(options: {
|
constructor(options: {
|
||||||
canvas: HTMLCanvasElement;
|
canvas: HTMLCanvasElement;
|
||||||
width: number;
|
renderWidth: number;
|
||||||
height: number;
|
renderHeight: number;
|
||||||
originalImage: ImageData | ImageBitmap | HTMLImageElement | HTMLCanvasElement;
|
image: ImageData | ImageBitmap | HTMLImageElement | HTMLCanvasElement;
|
||||||
layers: ImageEffectorLayer[];
|
|
||||||
fxs: ImageEffectorFx[];
|
fxs: ImageEffectorFx[];
|
||||||
}) {
|
}) {
|
||||||
this.canvas = options.canvas;
|
this.canvas = options.canvas;
|
||||||
this.canvas.width = options.width;
|
this.renderWidth = options.renderWidth;
|
||||||
this.canvas.height = options.height;
|
this.renderHeight = options.renderHeight;
|
||||||
this.renderWidth = options.width;
|
this.originalImage = options.image;
|
||||||
this.renderHeight = options.height;
|
|
||||||
this.originalImage = options.originalImage;
|
|
||||||
this.layers = options.layers;
|
|
||||||
this.fxs = options.fxs;
|
this.fxs = options.fxs;
|
||||||
this.texturesKey = this.calcTexturesKey();
|
|
||||||
|
|
||||||
this.gl = this.canvas.getContext('webgl2', {
|
this.canvas.width = this.renderWidth;
|
||||||
|
this.canvas.height = this.renderHeight;
|
||||||
|
|
||||||
|
const gl = this.canvas.getContext('webgl2', {
|
||||||
preserveDrawingBuffer: false,
|
preserveDrawingBuffer: false,
|
||||||
alpha: true,
|
alpha: true,
|
||||||
premultipliedAlpha: false,
|
premultipliedAlpha: false,
|
||||||
})!;
|
});
|
||||||
|
|
||||||
const gl = this.gl;
|
if (gl == null) {
|
||||||
|
throw new Error('Failed to initialize WebGL2 context');
|
||||||
|
}
|
||||||
|
|
||||||
|
this.gl = gl;
|
||||||
|
|
||||||
gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight);
|
gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight);
|
||||||
|
|
||||||
|
@ -105,10 +107,10 @@ export class ImageEffector {
|
||||||
gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
|
gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
|
||||||
gl.bufferData(gl.ARRAY_BUFFER, VERTICES, gl.STATIC_DRAW);
|
gl.bufferData(gl.ARRAY_BUFFER, VERTICES, gl.STATIC_DRAW);
|
||||||
|
|
||||||
this.originalImageTexture = this.createTexture();
|
this.originalImageTexture = createTexture(gl);
|
||||||
gl.activeTexture(gl.TEXTURE0);
|
gl.activeTexture(gl.TEXTURE0);
|
||||||
gl.bindTexture(gl.TEXTURE_2D, this.originalImageTexture);
|
gl.bindTexture(gl.TEXTURE_2D, this.originalImageTexture);
|
||||||
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, options.width, options.height, 0, gl.RGBA, gl.UNSIGNED_BYTE, this.originalImage);
|
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, this.originalImage.width, this.originalImage.height, 0, gl.RGBA, gl.UNSIGNED_BYTE, this.originalImage);
|
||||||
gl.bindTexture(gl.TEXTURE_2D, null);
|
gl.bindTexture(gl.TEXTURE_2D, null);
|
||||||
|
|
||||||
this.renderTextureProgram = this.initShaderProgram(`#version 300 es
|
this.renderTextureProgram = this.initShaderProgram(`#version 300 es
|
||||||
|
@ -153,119 +155,8 @@ export class ImageEffector {
|
||||||
`)!;
|
`)!;
|
||||||
}
|
}
|
||||||
|
|
||||||
private calcTexturesKey() {
|
|
||||||
return this.layers.map(layer => {
|
|
||||||
if (layer.fxId === 'watermarkPlacement' && layer.imageUrl != null) {
|
|
||||||
return layer.imageUrl;
|
|
||||||
} else if (layer.fxId === 'watermarkPlacement' && layer.text != null) {
|
|
||||||
return layer.text;
|
|
||||||
}
|
|
||||||
return '';
|
|
||||||
}).join(';');
|
|
||||||
}
|
|
||||||
|
|
||||||
private createTexture(): WebGLTexture {
|
|
||||||
const gl = this.gl!;
|
|
||||||
const texture = gl.createTexture();
|
|
||||||
gl.bindTexture(gl.TEXTURE_2D, texture);
|
|
||||||
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.REPEAT);
|
|
||||||
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.REPEAT);
|
|
||||||
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
|
|
||||||
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
|
|
||||||
gl.bindTexture(gl.TEXTURE_2D, null);
|
|
||||||
return texture!;
|
|
||||||
}
|
|
||||||
|
|
||||||
public disposeBakedTextures() {
|
|
||||||
const gl = this.gl;
|
|
||||||
if (gl == null) {
|
|
||||||
throw new Error('gl is not initialized');
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const bakedTexture of this.bakedTexturesForWatermarkFx.values()) {
|
|
||||||
gl.deleteTexture(bakedTexture.texture);
|
|
||||||
}
|
|
||||||
this.bakedTexturesForWatermarkFx.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
public async bakeTextures() {
|
|
||||||
const gl = this.gl;
|
|
||||||
if (gl == null) {
|
|
||||||
throw new Error('gl is not initialized');
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log('baking textures', this.texturesKey);
|
|
||||||
|
|
||||||
this.disposeBakedTextures();
|
|
||||||
|
|
||||||
for (const layer of this.layers) {
|
|
||||||
if (layer.fxId === 'watermarkPlacement' && layer.imageUrl != null) {
|
|
||||||
const image = await new Promise<HTMLImageElement>((resolve, reject) => {
|
|
||||||
const img = new Image();
|
|
||||||
img.onload = () => resolve(img);
|
|
||||||
img.onerror = reject;
|
|
||||||
img.src = getProxiedImageUrl(layer.imageUrl); // CORS対策
|
|
||||||
});
|
|
||||||
|
|
||||||
const texture = this.createTexture();
|
|
||||||
gl.activeTexture(gl.TEXTURE0);
|
|
||||||
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.bindTexture(gl.TEXTURE_2D, null);
|
|
||||||
|
|
||||||
this.bakedTexturesForWatermarkFx.set(layer.id, {
|
|
||||||
texture: texture,
|
|
||||||
width: image.width,
|
|
||||||
height: image.height,
|
|
||||||
});
|
|
||||||
} else if (layer.fxId === 'watermarkPlacement' && layer.text != null) {
|
|
||||||
const measureCtx = window.document.createElement('canvas').getContext('2d')!;
|
|
||||||
measureCtx.canvas.width = this.renderWidth;
|
|
||||||
measureCtx.canvas.height = this.renderHeight;
|
|
||||||
const fontSize = Math.min(this.renderWidth, this.renderHeight) / 20;
|
|
||||||
const margin = Math.min(this.renderWidth, this.renderHeight) / 50;
|
|
||||||
measureCtx.font = `bold ${fontSize}px sans-serif`;
|
|
||||||
const textMetrics = measureCtx.measureText(layer.text);
|
|
||||||
measureCtx.canvas.remove();
|
|
||||||
|
|
||||||
const RESOLUTION_FACTOR = 4;
|
|
||||||
|
|
||||||
const textCtx = window.document.createElement('canvas').getContext('2d')!;
|
|
||||||
textCtx.canvas.width = (Math.ceil(textMetrics.actualBoundingBoxRight + textMetrics.actualBoundingBoxLeft) + margin + margin) * RESOLUTION_FACTOR;
|
|
||||||
textCtx.canvas.height = (Math.ceil(textMetrics.actualBoundingBoxAscent + textMetrics.actualBoundingBoxDescent) + margin + margin) * RESOLUTION_FACTOR;
|
|
||||||
|
|
||||||
//textCtx.fillStyle = '#00ff00';
|
|
||||||
//textCtx.fillRect(0, 0, textCtx.canvas.width, textCtx.canvas.height);
|
|
||||||
|
|
||||||
textCtx.shadowColor = '#000000';
|
|
||||||
textCtx.shadowBlur = 10 * RESOLUTION_FACTOR;
|
|
||||||
|
|
||||||
textCtx.fillStyle = '#ffffff';
|
|
||||||
textCtx.font = `bold ${fontSize * RESOLUTION_FACTOR}px sans-serif`;
|
|
||||||
textCtx.textBaseline = 'middle';
|
|
||||||
textCtx.textAlign = 'center';
|
|
||||||
|
|
||||||
textCtx.fillText(layer.text, textCtx.canvas.width / 2, textCtx.canvas.height / 2);
|
|
||||||
|
|
||||||
const texture = this.createTexture();
|
|
||||||
gl.activeTexture(gl.TEXTURE0);
|
|
||||||
gl.bindTexture(gl.TEXTURE_2D, texture);
|
|
||||||
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, textCtx.canvas.width, textCtx.canvas.height, 0, gl.RGBA, gl.UNSIGNED_BYTE, textCtx.canvas);
|
|
||||||
gl.bindTexture(gl.TEXTURE_2D, null);
|
|
||||||
|
|
||||||
this.bakedTexturesForWatermarkFx.set(layer.id, {
|
|
||||||
texture: texture,
|
|
||||||
width: textCtx.canvas.width,
|
|
||||||
height: textCtx.canvas.height,
|
|
||||||
});
|
|
||||||
|
|
||||||
textCtx.canvas.remove();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public loadShader(type, source) {
|
public loadShader(type, source) {
|
||||||
const gl = this.gl!;
|
const gl = this.gl;
|
||||||
|
|
||||||
const shader = gl.createShader(type)!;
|
const shader = gl.createShader(type)!;
|
||||||
|
|
||||||
|
@ -284,7 +175,7 @@ export class ImageEffector {
|
||||||
}
|
}
|
||||||
|
|
||||||
public initShaderProgram(vsSource, fsSource): WebGLProgram {
|
public initShaderProgram(vsSource, fsSource): WebGLProgram {
|
||||||
const gl = this.gl!;
|
const gl = this.gl;
|
||||||
|
|
||||||
const vertexShader = this.loadShader(gl.VERTEX_SHADER, vsSource)!;
|
const vertexShader = this.loadShader(gl.VERTEX_SHADER, vsSource)!;
|
||||||
const fragmentShader = this.loadShader(gl.FRAGMENT_SHADER, fsSource)!;
|
const fragmentShader = this.loadShader(gl.FRAGMENT_SHADER, fsSource)!;
|
||||||
|
@ -308,15 +199,10 @@ export class ImageEffector {
|
||||||
|
|
||||||
private renderLayer(layer: ImageEffectorLayer, preTexture: WebGLTexture) {
|
private renderLayer(layer: ImageEffectorLayer, preTexture: WebGLTexture) {
|
||||||
const gl = this.gl;
|
const gl = this.gl;
|
||||||
if (gl == null) {
|
|
||||||
throw new Error('gl is not initialized');
|
|
||||||
}
|
|
||||||
|
|
||||||
const fx = this.fxs.find(fx => fx.id === layer.fxId);
|
const fx = this.fxs.find(fx => fx.id === layer.fxId);
|
||||||
if (fx == null) return;
|
if (fx == null) return;
|
||||||
|
|
||||||
const watermark = layer.fxId === 'watermarkPlacement' ? this.bakedTexturesForWatermarkFx.get(layer.id) : undefined;
|
|
||||||
|
|
||||||
const cachedShader = this.shaderCache.get(fx.id);
|
const cachedShader = this.shaderCache.get(fx.id);
|
||||||
const shaderProgram = cachedShader ?? this.initShaderProgram(`#version 300 es
|
const shaderProgram = cachedShader ?? this.initShaderProgram(`#version 300 es
|
||||||
in vec2 position;
|
in vec2 position;
|
||||||
|
@ -352,7 +238,13 @@ export class ImageEffector {
|
||||||
u: Object.fromEntries(fx.uniforms.map(u => [u, gl.getUniformLocation(shaderProgram, 'u_' + u)!])),
|
u: Object.fromEntries(fx.uniforms.map(u => [u, gl.getUniformLocation(shaderProgram, 'u_' + u)!])),
|
||||||
width: this.renderWidth,
|
width: this.renderWidth,
|
||||||
height: this.renderHeight,
|
height: this.renderHeight,
|
||||||
watermark: watermark,
|
textures: Object.fromEntries(
|
||||||
|
Object.entries(layer.textures ?? {}).map(([key, textureId]) => {
|
||||||
|
if (textureId == null) return [key, null];
|
||||||
|
const externalTexture = this.externalTextures.get(textureId);
|
||||||
|
if (externalTexture == null) return [key, null];
|
||||||
|
return [key, externalTexture];
|
||||||
|
})),
|
||||||
});
|
});
|
||||||
|
|
||||||
gl.drawArrays(gl.TRIANGLES, 0, 6);
|
gl.drawArrays(gl.TRIANGLES, 0, 6);
|
||||||
|
@ -360,9 +252,6 @@ export class ImageEffector {
|
||||||
|
|
||||||
public render() {
|
public render() {
|
||||||
const gl = this.gl;
|
const gl = this.gl;
|
||||||
if (gl == null) {
|
|
||||||
throw new Error('gl is not initialized');
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
{
|
||||||
gl.activeTexture(gl.TEXTURE0);
|
gl.activeTexture(gl.TEXTURE0);
|
||||||
|
@ -386,7 +275,7 @@ export class ImageEffector {
|
||||||
|
|
||||||
for (const layer of this.layers) {
|
for (const layer of this.layers) {
|
||||||
const cachedResultTexture = this.perLayerResultTextures.get(layer.id);
|
const cachedResultTexture = this.perLayerResultTextures.get(layer.id);
|
||||||
const resultTexture = cachedResultTexture ?? this.createTexture();
|
const resultTexture = cachedResultTexture ?? createTexture(gl);
|
||||||
if (cachedResultTexture == null) {
|
if (cachedResultTexture == null) {
|
||||||
this.perLayerResultTextures.set(layer.id, resultTexture);
|
this.perLayerResultTextures.set(layer.id, resultTexture);
|
||||||
}
|
}
|
||||||
|
@ -420,42 +309,41 @@ export class ImageEffector {
|
||||||
gl.drawArrays(gl.TRIANGLES, 0, 6);
|
gl.drawArrays(gl.TRIANGLES, 0, 6);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async updateLayers(layers: ImageEffectorLayer[]) {
|
public async setLayers(layers: ImageEffectorLayer[]) {
|
||||||
this.layers = layers;
|
this.layers = layers;
|
||||||
|
|
||||||
const newTexturesKey = this.calcTexturesKey();
|
|
||||||
if (newTexturesKey !== this.texturesKey) {
|
|
||||||
this.texturesKey = newTexturesKey;
|
|
||||||
await this.bakeTextures();
|
|
||||||
}
|
|
||||||
|
|
||||||
this.render();
|
this.render();
|
||||||
}
|
}
|
||||||
|
|
||||||
public destroy() {
|
public registerExternalTexture(id: string, texture: WebGLTexture, width: number, height: number) {
|
||||||
const gl = this.gl;
|
this.externalTextures.set(id, { texture, width, height });
|
||||||
if (gl == null) {
|
}
|
||||||
throw new Error('gl is not initialized');
|
|
||||||
}
|
|
||||||
|
|
||||||
|
public disposeExternalTextures() {
|
||||||
|
for (const bakedTexture of this.externalTextures.values()) {
|
||||||
|
this.gl.deleteTexture(bakedTexture.texture);
|
||||||
|
}
|
||||||
|
this.externalTextures.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
public destroy() {
|
||||||
for (const shader of this.shaderCache.values()) {
|
for (const shader of this.shaderCache.values()) {
|
||||||
gl.deleteProgram(shader);
|
this.gl.deleteProgram(shader);
|
||||||
}
|
}
|
||||||
this.shaderCache.clear();
|
this.shaderCache.clear();
|
||||||
|
|
||||||
for (const texture of this.perLayerResultTextures.values()) {
|
for (const texture of this.perLayerResultTextures.values()) {
|
||||||
gl.deleteTexture(texture);
|
this.gl.deleteTexture(texture);
|
||||||
}
|
}
|
||||||
this.perLayerResultTextures.clear();
|
this.perLayerResultTextures.clear();
|
||||||
|
|
||||||
for (const framebuffer of this.perLayerResultFrameBuffers.values()) {
|
for (const framebuffer of this.perLayerResultFrameBuffers.values()) {
|
||||||
gl.deleteFramebuffer(framebuffer);
|
this.gl.deleteFramebuffer(framebuffer);
|
||||||
}
|
}
|
||||||
this.perLayerResultFrameBuffers.clear();
|
this.perLayerResultFrameBuffers.clear();
|
||||||
|
|
||||||
this.disposeBakedTextures();
|
this.disposeExternalTextures();
|
||||||
gl.deleteProgram(this.renderTextureProgram);
|
this.gl.deleteProgram(this.renderTextureProgram);
|
||||||
gl.deleteProgram(this.renderInvertedTextureProgram);
|
this.gl.deleteProgram(this.renderInvertedTextureProgram);
|
||||||
gl.deleteTexture(this.originalImageTexture);
|
this.gl.deleteTexture(this.originalImageTexture);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -99,16 +99,17 @@ export const FX_watermarkPlacement = defineImageEffectorFx({
|
||||||
step: 0.01,
|
step: 0.01,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
main: ({ gl, u, params, watermark }) => {
|
textures: ['watermark'] as const,
|
||||||
if (watermark == null) {
|
main: ({ gl, u, params, textures }) => {
|
||||||
|
if (textures.watermark == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
gl.activeTexture(gl.TEXTURE1);
|
gl.activeTexture(gl.TEXTURE1);
|
||||||
gl.bindTexture(gl.TEXTURE_2D, watermark.texture);
|
gl.bindTexture(gl.TEXTURE_2D, textures.watermark.texture);
|
||||||
gl.uniform1i(u.texture_watermark, 1);
|
gl.uniform1i(u.texture_watermark, 1);
|
||||||
|
|
||||||
gl.uniform2fv(u.resolution_watermark, [watermark.width, watermark.height]);
|
gl.uniform2fv(u.resolution_watermark, [textures.watermark.width, textures.watermark.height]);
|
||||||
gl.uniform1f(u.scale, params.scale);
|
gl.uniform1f(u.scale, params.scale);
|
||||||
gl.uniform1f(u.opacity, params.opacity);
|
gl.uniform1f(u.opacity, params.opacity);
|
||||||
gl.uniform1f(u.angle, 0.0);
|
gl.uniform1f(u.angle, 0.0);
|
||||||
|
|
|
@ -69,7 +69,7 @@ export const FX_zoomLines = defineImageEffectorFx({
|
||||||
},
|
},
|
||||||
threshold: {
|
threshold: {
|
||||||
type: 'number' as const,
|
type: 'number' as const,
|
||||||
default: 0.5,
|
default: 0.8,
|
||||||
min: 0.0,
|
min: 0.0,
|
||||||
max: 1.0,
|
max: 1.0,
|
||||||
step: 0.01,
|
step: 0.01,
|
||||||
|
|
|
@ -0,0 +1,78 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { getProxiedImageUrl } from '../media-proxy.js';
|
||||||
|
|
||||||
|
export function createTexture(gl: WebGL2RenderingContext): WebGLTexture {
|
||||||
|
const texture = gl.createTexture();
|
||||||
|
gl.bindTexture(gl.TEXTURE_2D, texture);
|
||||||
|
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.REPEAT);
|
||||||
|
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.REPEAT);
|
||||||
|
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
|
||||||
|
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
|
||||||
|
gl.bindTexture(gl.TEXTURE_2D, null);
|
||||||
|
return texture;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function createTextureFromUrl(gl: WebGL2RenderingContext, imageUrl: string): Promise<{ texture: WebGLTexture, width: number, height: number }> {
|
||||||
|
const image = await new Promise<HTMLImageElement>((resolve, reject) => {
|
||||||
|
const img = new Image();
|
||||||
|
img.onload = () => resolve(img);
|
||||||
|
img.onerror = reject;
|
||||||
|
img.src = getProxiedImageUrl(imageUrl); // CORS対策
|
||||||
|
});
|
||||||
|
|
||||||
|
const texture = createTexture(gl);
|
||||||
|
gl.activeTexture(gl.TEXTURE0);
|
||||||
|
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.bindTexture(gl.TEXTURE_2D, null);
|
||||||
|
|
||||||
|
return {
|
||||||
|
texture,
|
||||||
|
width: image.width,
|
||||||
|
height: image.height,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function createTextureFromText(gl: WebGL2RenderingContext, text: string, resolution = 2048): Promise<{ texture: WebGLTexture, width: number, height: number }> {
|
||||||
|
const ctx = window.document.createElement('canvas').getContext('2d')!;
|
||||||
|
ctx.canvas.width = resolution;
|
||||||
|
ctx.canvas.height = resolution / 4;
|
||||||
|
const fontSize = resolution / 32;
|
||||||
|
const margin = fontSize / 2;
|
||||||
|
ctx.shadowColor = '#000000';
|
||||||
|
ctx.shadowBlur = fontSize / 4;
|
||||||
|
|
||||||
|
//ctx.fillStyle = '#00ff00';
|
||||||
|
//ctx.fillRect(0, 0, ctx.canvas.width, ctx.canvas.height);
|
||||||
|
|
||||||
|
ctx.fillStyle = '#ffffff';
|
||||||
|
ctx.font = `bold ${fontSize}px sans-serif`;
|
||||||
|
ctx.textBaseline = 'middle';
|
||||||
|
|
||||||
|
ctx.fillText(text, margin, ctx.canvas.height / 2);
|
||||||
|
|
||||||
|
const textMetrics = ctx.measureText(text);
|
||||||
|
const cropWidth = (Math.ceil(textMetrics.actualBoundingBoxRight + textMetrics.actualBoundingBoxLeft) + margin + margin);
|
||||||
|
const cropHeight = (Math.ceil(textMetrics.actualBoundingBoxAscent + textMetrics.actualBoundingBoxDescent) + margin + margin);
|
||||||
|
const data = ctx.getImageData(0, (ctx.canvas.height / 2) - (cropHeight / 2), ctx.canvas.width, ctx.canvas.height);
|
||||||
|
|
||||||
|
const texture = createTexture(gl);
|
||||||
|
gl.activeTexture(gl.TEXTURE0);
|
||||||
|
gl.bindTexture(gl.TEXTURE_2D, texture);
|
||||||
|
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, cropWidth, cropHeight, 0, gl.RGBA, gl.UNSIGNED_BYTE, data);
|
||||||
|
gl.bindTexture(gl.TEXTURE_2D, null);
|
||||||
|
|
||||||
|
const info = {
|
||||||
|
texture: texture,
|
||||||
|
width: cropWidth,
|
||||||
|
height: cropHeight,
|
||||||
|
};
|
||||||
|
|
||||||
|
ctx.canvas.remove();
|
||||||
|
|
||||||
|
return info;
|
||||||
|
}
|
|
@ -3,7 +3,10 @@
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { FX_watermarkPlacement } from './image-effector/fxs/watermarkPlacement.js';
|
||||||
|
import { createTextureFromText, createTextureFromUrl } from './image-effector/utilts.js';
|
||||||
import type { ImageEffectorLayer } from '@/utility/image-effector/ImageEffector.js';
|
import type { ImageEffectorLayer } from '@/utility/image-effector/ImageEffector.js';
|
||||||
|
import { ImageEffector } from '@/utility/image-effector/ImageEffector.js';
|
||||||
|
|
||||||
export type WatermarkPreset = {
|
export type WatermarkPreset = {
|
||||||
id: string;
|
id: string;
|
||||||
|
@ -29,36 +32,101 @@ export type WatermarkPreset = {
|
||||||
})[];
|
})[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export function makeImageEffectorLayers(layers: WatermarkPreset['layers']): ImageEffectorLayer[] {
|
export class WatermarkRenderer {
|
||||||
return layers.map(layer => {
|
private effector: ImageEffector;
|
||||||
if (layer.type === 'text') {
|
private layers: WatermarkPreset['layers'] = [];
|
||||||
return {
|
private texturesKey = '';
|
||||||
fxId: 'watermarkPlacement',
|
|
||||||
id: layer.id,
|
constructor(options: {
|
||||||
params: {
|
canvas: HTMLCanvasElement,
|
||||||
repeat: layer.repeat,
|
renderWidth: number,
|
||||||
scale: layer.scale,
|
renderHeight: number,
|
||||||
align: layer.align,
|
image: HTMLImageElement,
|
||||||
opacity: layer.opacity,
|
}) {
|
||||||
cover: false,
|
this.effector = new ImageEffector({
|
||||||
},
|
canvas: options.canvas,
|
||||||
text: layer.text,
|
renderWidth: options.renderWidth,
|
||||||
imageUrl: null,
|
renderHeight: options.renderHeight,
|
||||||
};
|
image: options.image,
|
||||||
} else {
|
fxs: [FX_watermarkPlacement],
|
||||||
return {
|
});
|
||||||
fxId: 'watermarkPlacement',
|
}
|
||||||
id: layer.id,
|
|
||||||
params: {
|
private calcTexturesKey() {
|
||||||
repeat: layer.repeat,
|
return this.layers.map(layer => {
|
||||||
scale: layer.scale,
|
if (layer.type === 'image' && layer.imageUrl != null) {
|
||||||
align: layer.align,
|
return layer.imageUrl;
|
||||||
opacity: layer.opacity,
|
} else if (layer.type === 'text' && layer.text != null) {
|
||||||
cover: layer.cover,
|
return layer.text;
|
||||||
},
|
}
|
||||||
text: null,
|
return '';
|
||||||
imageUrl: layer.imageUrl ?? null,
|
}).join(';');
|
||||||
};
|
}
|
||||||
|
|
||||||
|
private async bakeTextures(): Promise<void> {
|
||||||
|
this.effector.disposeExternalTextures();
|
||||||
|
for (const layer of this.layers) {
|
||||||
|
if (layer.type === 'text' && layer.text != null) {
|
||||||
|
const { texture, width, height } = await createTextureFromText(this.effector.gl, layer.text);
|
||||||
|
this.effector.registerExternalTexture(layer.id, texture, width, height);
|
||||||
|
} else if (layer.type === 'image' && layer.imageUrl != null) {
|
||||||
|
const { texture, width, height } = await createTextureFromUrl(this.effector.gl, layer.imageUrl);
|
||||||
|
this.effector.registerExternalTexture(layer.id, texture, width, height);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
|
private makeImageEffectorLayers(): ImageEffectorLayer[] {
|
||||||
|
return this.layers.map(layer => {
|
||||||
|
if (layer.type === 'text') {
|
||||||
|
return {
|
||||||
|
fxId: 'watermarkPlacement',
|
||||||
|
id: layer.id,
|
||||||
|
params: {
|
||||||
|
repeat: layer.repeat,
|
||||||
|
scale: layer.scale,
|
||||||
|
align: layer.align,
|
||||||
|
opacity: layer.opacity,
|
||||||
|
cover: false,
|
||||||
|
},
|
||||||
|
textures: { watermark: layer.id },
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
fxId: 'watermarkPlacement',
|
||||||
|
id: layer.id,
|
||||||
|
params: {
|
||||||
|
repeat: layer.repeat,
|
||||||
|
scale: layer.scale,
|
||||||
|
align: layer.align,
|
||||||
|
opacity: layer.opacity,
|
||||||
|
cover: layer.cover,
|
||||||
|
},
|
||||||
|
textures: { watermark: layer.id },
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public async setLayers(layers: WatermarkPreset['layers']) {
|
||||||
|
this.layers = layers;
|
||||||
|
|
||||||
|
const newTexturesKey = this.calcTexturesKey();
|
||||||
|
if (newTexturesKey !== this.texturesKey) {
|
||||||
|
this.texturesKey = newTexturesKey;
|
||||||
|
await this.bakeTextures();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.effector.setLayers(this.makeImageEffectorLayers());
|
||||||
|
|
||||||
|
this.render();
|
||||||
|
}
|
||||||
|
|
||||||
|
public render(): void {
|
||||||
|
this.effector.render();
|
||||||
|
}
|
||||||
|
|
||||||
|
public destroy(): void {
|
||||||
|
this.effector.destroy();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue