181 lines
4.3 KiB
TypeScript
181 lines
4.3 KiB
TypeScript
/*
|
|
* SPDX-FileCopyrightText: syuilo and misskey-project
|
|
* SPDX-License-Identifier: AGPL-3.0-only
|
|
*/
|
|
|
|
import { FX_watermarkPlacement } from './image-effector/fxs/watermarkPlacement.js';
|
|
import { FX_stripe } from './image-effector/fxs/stripe.js';
|
|
import { FX_polkadot } from './image-effector/fxs/polkadot.js';
|
|
import { FX_checker } from './image-effector/fxs/checker.js';
|
|
import type { ImageEffectorLayer } from '@/utility/image-effector/ImageEffector.js';
|
|
import { ImageEffector } from '@/utility/image-effector/ImageEffector.js';
|
|
|
|
export type WatermarkPreset = {
|
|
id: string;
|
|
name: string;
|
|
layers: ({
|
|
id: string;
|
|
type: 'text';
|
|
text: string;
|
|
repeat: boolean;
|
|
scale: number;
|
|
angle: number;
|
|
align: { x: 'left' | 'center' | 'right'; y: 'top' | 'center' | 'bottom' };
|
|
opacity: number;
|
|
} | {
|
|
id: string;
|
|
type: 'image';
|
|
imageUrl: string | null;
|
|
imageId: string | null;
|
|
cover: boolean;
|
|
repeat: boolean;
|
|
scale: number;
|
|
angle: number;
|
|
align: { x: 'left' | 'center' | 'right'; y: 'top' | 'center' | 'bottom' };
|
|
opacity: number;
|
|
} | {
|
|
id: string;
|
|
type: 'stripe';
|
|
angle: number;
|
|
frequency: number;
|
|
threshold: number;
|
|
color: [r: number, g: number, b: number];
|
|
opacity: number;
|
|
} | {
|
|
id: string;
|
|
type: 'polkadot';
|
|
angle: number;
|
|
scale: number;
|
|
majorRadius: number;
|
|
majorOpacity: number;
|
|
minorDivisions: number;
|
|
minorRadius: number;
|
|
minorOpacity: number;
|
|
color: [r: number, g: number, b: number];
|
|
opacity: number;
|
|
} | {
|
|
id: string;
|
|
type: 'checker';
|
|
angle: number;
|
|
scale: number;
|
|
color: [r: number, g: number, b: number];
|
|
opacity: number;
|
|
})[];
|
|
};
|
|
|
|
export class WatermarkRenderer {
|
|
private effector: ImageEffector;
|
|
private layers: WatermarkPreset['layers'] = [];
|
|
|
|
constructor(options: {
|
|
canvas: HTMLCanvasElement,
|
|
renderWidth: number,
|
|
renderHeight: number,
|
|
image: HTMLImageElement | ImageBitmap,
|
|
}) {
|
|
this.effector = new ImageEffector({
|
|
canvas: options.canvas,
|
|
renderWidth: options.renderWidth,
|
|
renderHeight: options.renderHeight,
|
|
image: options.image,
|
|
fxs: [FX_watermarkPlacement, FX_stripe, FX_polkadot, FX_checker],
|
|
});
|
|
}
|
|
|
|
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,
|
|
angle: layer.angle,
|
|
opacity: layer.opacity,
|
|
cover: false,
|
|
watermark: {
|
|
type: 'text',
|
|
text: layer.text,
|
|
},
|
|
},
|
|
};
|
|
} else if (layer.type === 'image') {
|
|
return {
|
|
fxId: 'watermarkPlacement',
|
|
id: layer.id,
|
|
params: {
|
|
repeat: layer.repeat,
|
|
scale: layer.scale,
|
|
align: layer.align,
|
|
angle: layer.angle,
|
|
opacity: layer.opacity,
|
|
cover: layer.cover,
|
|
watermark: {
|
|
type: 'url',
|
|
url: layer.imageUrl,
|
|
},
|
|
},
|
|
};
|
|
} else if (layer.type === 'stripe') {
|
|
return {
|
|
fxId: 'stripe',
|
|
id: layer.id,
|
|
params: {
|
|
angle: layer.angle,
|
|
frequency: layer.frequency,
|
|
threshold: layer.threshold,
|
|
color: layer.color,
|
|
opacity: layer.opacity,
|
|
},
|
|
};
|
|
} else if (layer.type === 'polkadot') {
|
|
return {
|
|
fxId: 'polkadot',
|
|
id: layer.id,
|
|
params: {
|
|
angle: layer.angle,
|
|
scale: layer.scale,
|
|
majorRadius: layer.majorRadius,
|
|
majorOpacity: layer.majorOpacity,
|
|
minorDivisions: layer.minorDivisions,
|
|
minorRadius: layer.minorRadius,
|
|
minorOpacity: layer.minorOpacity,
|
|
color: layer.color,
|
|
opacity: layer.opacity,
|
|
},
|
|
};
|
|
} else if (layer.type === 'checker') {
|
|
return {
|
|
fxId: 'checker',
|
|
id: layer.id,
|
|
params: {
|
|
angle: layer.angle,
|
|
scale: layer.scale,
|
|
color: layer.color,
|
|
opacity: layer.opacity,
|
|
},
|
|
};
|
|
}
|
|
});
|
|
}
|
|
|
|
public async setLayers(layers: WatermarkPreset['layers']) {
|
|
this.layers = layers;
|
|
await this.effector.setLayers(this.makeImageEffectorLayers());
|
|
this.render();
|
|
}
|
|
|
|
public render(): void {
|
|
this.effector.render();
|
|
}
|
|
|
|
/*
|
|
* disposeCanvas = true だとloseContextを呼ぶため、コンストラクタで渡されたcanvasも再利用不可になるので注意
|
|
*/
|
|
public destroy(disposeCanvas = true): void {
|
|
this.effector.destroy(disposeCanvas);
|
|
}
|
|
}
|