1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
|
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { FXS } from './fxs.js';
import type { ImageCompositorFunction } from '@/lib/ImageCompositor.js';
import { ImageCompositor } from '@/lib/ImageCompositor.js';
export type ImageEffectorRGB = [r: number, g: number, b: number];
interface CommonParamDef {
type: string;
label?: string;
caption?: string;
default: unknown;
}
interface NumberParamDef extends CommonParamDef {
type: 'number';
default: number;
min: number;
max: number;
step?: number;
toViewValue?: (v: number) => string;
};
interface NumberEnumParamDef extends CommonParamDef {
type: 'number:enum';
enum: {
value: number;
label?: string;
icon?: string;
}[];
default: number;
};
interface BooleanParamDef extends CommonParamDef {
type: 'boolean';
default: boolean;
};
interface AlignParamDef extends CommonParamDef {
type: 'align';
default: {
x: 'left' | 'center' | 'right';
y: 'top' | 'center' | 'bottom';
margin?: number;
};
};
interface SeedParamDef extends CommonParamDef {
type: 'seed';
default: number;
};
interface ColorParamDef extends CommonParamDef {
type: 'color';
default: ImageEffectorRGB;
};
type ImageEffectorFxParamDef = NumberParamDef | NumberEnumParamDef | BooleanParamDef | AlignParamDef | SeedParamDef | ColorParamDef;
export type ImageEffectorFxParamDefs = Record<string, ImageEffectorFxParamDef>;
export type ImageEffectorLayer = {
[K in keyof typeof FXS]: {
id: string;
fxId: K;
params: Parameters<(typeof FXS)[K]['fn']['main']>[0]['params'];
};
}[keyof typeof FXS];
export type ImageEffectorUiDefinition<Fn extends ImageCompositorFunction<any> = ImageCompositorFunction> = {
name: string;
params: Fn extends ImageCompositorFunction<infer P> ? {
[K in keyof P]: ImageEffectorFxParamDef;
} : never;
};
type ImageEffectorImageCompositor = ImageCompositor<{
[K in keyof typeof FXS]: typeof FXS[K]['fn'];
}>;
export class ImageEffector {
private canvas: HTMLCanvasElement | null = null;
private compositor: ImageEffectorImageCompositor;
constructor(options: {
canvas: HTMLCanvasElement;
renderWidth: number;
renderHeight: number;
image: ImageData | ImageBitmap | HTMLImageElement | HTMLCanvasElement | null;
}) {
this.canvas = options.canvas;
this.compositor = new ImageCompositor({
canvas: this.canvas,
renderWidth: options.renderWidth,
renderHeight: options.renderHeight,
image: options.image,
functions: Object.fromEntries(Object.entries(FXS).map(([fxId, fx]) => [fxId, fx.fn])),
});
}
public async render(layers: ImageEffectorLayer[]) {
const compositorLayers: Parameters<ImageCompositor<any>['render']>[0] = [];
for (const layer of layers) {
compositorLayers.push({
id: layer.id,
functionId: layer.fxId,
params: layer.params,
});
}
this.compositor.render(compositorLayers as Parameters<ImageEffectorImageCompositor['render']>[0]);
}
public changeResolution(width: number, height: number) {
this.compositor.changeResolution(width, height);
}
/*
* disposeCanvas = true だとloseContextを呼ぶため、コンストラクタで渡されたcanvasも再利用不可になるので注意
*/
public destroy(disposeCanvas = true) {
this.compositor.destroy(disposeCanvas);
}
}
|