@eva/plugin-renderer-filter 2.1.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,89 @@
1
+ import { Component } from "@eva/eva.js";
2
+
3
+ export type FilterType = "blur" | "colorMatrix" | "displacement" | "noise" | "alpha";
4
+
5
+ export type ColorMatrixPreset =
6
+ | "sepia"
7
+ | "grayscale"
8
+ | "negative"
9
+ | "polaroid"
10
+ | "vintage"
11
+ | "lsd"
12
+ | "predator"
13
+ | "kodachrome"
14
+ | "browni"
15
+ | "technicolor"
16
+ | "blackAndWhite"
17
+ | "tint"
18
+ | "saturate"
19
+ | "brightness"
20
+ | "contrast"
21
+ | "hue"
22
+ | "night";
23
+
24
+ /**
25
+ * 单条 filter spec — 各类型共用的字段池,只读自己关心的字段。
26
+ */
27
+ export interface FilterSpec {
28
+ type: FilterType;
29
+ enabled?: boolean;
30
+
31
+ // BlurFilter
32
+ strength?: number;
33
+ quality?: number;
34
+ blurX?: number;
35
+ blurY?: number;
36
+
37
+ // ColorMatrixFilter
38
+ preset?: ColorMatrixPreset;
39
+ presetArg?: number;
40
+ matrix?: number[];
41
+
42
+ // DisplacementFilter
43
+ resource?: string;
44
+ scaleX?: number;
45
+ scaleY?: number;
46
+
47
+ // NoiseFilter
48
+ noise?: number;
49
+ seed?: number;
50
+ noiseAnimSpeed?: number;
51
+
52
+ // AlphaFilter
53
+ alpha?: number;
54
+ }
55
+
56
+ export interface FilterParams {
57
+ filters?: FilterSpec[];
58
+ filterArea?: { x: number; y: number; width: number; height: number };
59
+ }
60
+
61
+ /**
62
+ * Filter 组件
63
+ *
64
+ * 给挂载它的 GameObject 应用一组 PixiJS 内置 2D 滤镜:Blur / ColorMatrix / Displacement / Noise / Alpha。
65
+ * 实现层是 PixiJS DisplayObject.filters 数组,FilterSystem 把 spec 实例化为 PixiJS Filter 实例并绑到容器上。
66
+ *
67
+ * @example
68
+ * ```typescript
69
+ * sprite.addComponent(new Filter({
70
+ * filters: [
71
+ * { type: "blur", strength: 8 },
72
+ * { type: "colorMatrix", preset: "sepia" },
73
+ * ],
74
+ * }));
75
+ * ```
76
+ */
77
+ export default class Filter extends Component<FilterParams> {
78
+ static componentName: string = "Filter";
79
+
80
+ filters: FilterSpec[] = [];
81
+ filterArea: FilterParams["filterArea"] | null = null;
82
+
83
+ init(params?: FilterParams) {
84
+ if (params) {
85
+ this.filters = params.filters ?? [];
86
+ this.filterArea = params.filterArea ?? null;
87
+ }
88
+ }
89
+ }
@@ -0,0 +1,6 @@
1
+ import { AlphaFilter } from "pixi.js";
2
+ import type { FilterSpec } from "../component";
3
+
4
+ export function createAlphaFilter(spec: FilterSpec): AlphaFilter {
5
+ return new AlphaFilter({ alpha: spec.alpha ?? 1 } as any);
6
+ }
@@ -0,0 +1,13 @@
1
+ import { BlurFilter } from "pixi.js";
2
+ import type { FilterSpec } from "../component";
3
+
4
+ export function createBlurFilter(spec: FilterSpec): BlurFilter {
5
+ const strength = spec.strength ?? 8;
6
+ const opts: any = {
7
+ strength,
8
+ quality: spec.quality ?? 4,
9
+ };
10
+ if (spec.blurX != null) opts.strengthX = spec.blurX;
11
+ if (spec.blurY != null) opts.strengthY = spec.blurY;
12
+ return new BlurFilter(opts);
13
+ }
@@ -0,0 +1,31 @@
1
+ import { ColorMatrixFilter } from "pixi.js";
2
+ import type { FilterSpec } from "../component";
3
+
4
+ export function createColorMatrixFilter(spec: FilterSpec): ColorMatrixFilter {
5
+ const cm = new ColorMatrixFilter();
6
+ if (spec.matrix && spec.matrix.length === 20) {
7
+ (cm as any).matrix = spec.matrix as any;
8
+ return cm;
9
+ }
10
+ const arg = spec.presetArg ?? 1;
11
+ switch (spec.preset) {
12
+ case "sepia": (cm as any).sepia(true); break;
13
+ case "grayscale": (cm as any).greyscale(arg, true); break;
14
+ case "negative": (cm as any).negative(true); break;
15
+ case "polaroid": (cm as any).polaroid(true); break;
16
+ case "vintage": (cm as any).vintage(true); break;
17
+ case "lsd": (cm as any).lsd(true); break;
18
+ case "predator": (cm as any).predator(arg, true); break;
19
+ case "kodachrome": (cm as any).kodachrome(true); break;
20
+ case "browni": (cm as any).browni(true); break;
21
+ case "technicolor": (cm as any).technicolor(true); break;
22
+ case "blackAndWhite": (cm as any).blackAndWhite(true); break;
23
+ case "tint": (cm as any).tint(arg, true); break;
24
+ case "saturate": (cm as any).saturate(arg, true); break;
25
+ case "brightness": (cm as any).brightness(arg, true); break;
26
+ case "contrast": (cm as any).contrast(arg, true); break;
27
+ case "hue": (cm as any).hue(arg * 360, true); break;
28
+ case "night": (cm as any).night(arg, true); break;
29
+ }
30
+ return cm;
31
+ }
@@ -0,0 +1,27 @@
1
+ import { DisplacementFilter, Sprite, Texture } from "pixi.js";
2
+ import { resource } from "@eva/eva.js";
3
+ import type { FilterSpec } from "../component";
4
+
5
+ /**
6
+ * Build a DisplacementFilter from FilterSpec.
7
+ * Pulls the displacement texture from the Eva.js resource registry.
8
+ * If the resource isn't ready yet, returns a no-op filter (caller can later swap).
9
+ */
10
+ export function createDisplacementFilter(spec: FilterSpec): DisplacementFilter {
11
+ const scale = { x: spec.scaleX ?? 20, y: spec.scaleY ?? 20 };
12
+ let sprite: Sprite;
13
+ if (spec.resource) {
14
+ const res = (resource as any).getResource?.(spec.resource);
15
+ const data = res?.data ?? res?.instance ?? null;
16
+ const tex: Texture | null =
17
+ data?.texture instanceof Texture
18
+ ? data.texture
19
+ : data?.image
20
+ ? Texture.from(data.image)
21
+ : null;
22
+ sprite = new Sprite(tex ?? Texture.EMPTY);
23
+ } else {
24
+ sprite = new Sprite(Texture.EMPTY);
25
+ }
26
+ return new DisplacementFilter({ sprite, scale });
27
+ }
@@ -0,0 +1,9 @@
1
+ import { NoiseFilter } from "pixi.js";
2
+ import type { FilterSpec } from "../component";
3
+
4
+ export function createNoiseFilter(spec: FilterSpec): NoiseFilter {
5
+ return new NoiseFilter({
6
+ noise: spec.noise ?? 0.5,
7
+ seed: spec.seed ?? Math.random(),
8
+ } as any);
9
+ }
package/lib/index.ts ADDED
@@ -0,0 +1,3 @@
1
+ export { default as Filter } from "./component";
2
+ export { default as FilterSystem } from "./system";
3
+ export type { FilterParams, FilterSpec, FilterType, ColorMatrixPreset } from "./component";
package/lib/system.ts ADDED
@@ -0,0 +1,137 @@
1
+ import {
2
+ GameObject,
3
+ decorators,
4
+ ComponentChanged,
5
+ OBSERVER_TYPE,
6
+ } from "@eva/eva.js";
7
+ import { ContainerManager, RendererSystem, Renderer } from "@eva/plugin-renderer";
8
+ import type { Filter as PIXIFilter, Rectangle } from "pixi.js";
9
+ import FilterComponent, { FilterSpec } from "./component";
10
+ import { createBlurFilter } from "./filters/blur";
11
+ import { createColorMatrixFilter } from "./filters/colorMatrix";
12
+ import { createDisplacementFilter } from "./filters/displacement";
13
+ import { createNoiseFilter } from "./filters/noise";
14
+ import { createAlphaFilter } from "./filters/alpha";
15
+
16
+ interface BoundFilter {
17
+ pixiFilters: PIXIFilter[];
18
+ specs: FilterSpec[];
19
+ }
20
+
21
+ @decorators.componentObserver({
22
+ Filter: [
23
+ { prop: ["filters"], deep: true },
24
+ { prop: ["filterArea"], deep: true },
25
+ ],
26
+ })
27
+ export default class FilterSystem extends Renderer {
28
+ name: string = "FilterSystem";
29
+ containerManager: ContainerManager;
30
+ renderSystem: RendererSystem;
31
+
32
+ private bound: Map<number, BoundFilter> = new Map();
33
+ private elapsed = 0;
34
+
35
+ init() {
36
+ this.renderSystem = this.game.getSystem(RendererSystem) as RendererSystem;
37
+ this.renderSystem.rendererManager.register(this);
38
+ }
39
+
40
+ rendererUpdate(_gameObject: GameObject) {
41
+ // no-op (filters are static; filterArea applied on change only)
42
+ }
43
+
44
+ componentChanged(changed: ComponentChanged) {
45
+ if (changed.componentName !== "Filter") return;
46
+ const id = changed.gameObject.id;
47
+
48
+ if (changed.type === OBSERVER_TYPE.REMOVE) {
49
+ this.removeFilters(id);
50
+ return;
51
+ }
52
+
53
+ // ADD or CHANGE
54
+ this.applyFilters(changed.gameObject, changed.component as FilterComponent);
55
+ }
56
+
57
+ private applyFilters(go: GameObject, component: FilterComponent) {
58
+ const container = this.containerManager.getContainer(go.id);
59
+ if (!container) return;
60
+
61
+ // Dispose existing
62
+ this.disposeBound(go.id);
63
+
64
+ const specs: FilterSpec[] = (component.filters || []).filter(
65
+ (s) => s.enabled !== false,
66
+ );
67
+ const pixiFilters = specs.map((s) => this.createOne(s)).filter(Boolean) as PIXIFilter[];
68
+
69
+ (container as any).filters = pixiFilters;
70
+
71
+ if (component.filterArea) {
72
+ const fa = component.filterArea;
73
+ (container as any).filterArea = { x: fa.x, y: fa.y, width: fa.width, height: fa.height } as Rectangle;
74
+ } else {
75
+ (container as any).filterArea = null;
76
+ }
77
+
78
+ this.bound.set(go.id, { pixiFilters, specs });
79
+ }
80
+
81
+ private removeFilters(id: number) {
82
+ const container = this.containerManager.getContainer(id);
83
+ if (container) {
84
+ (container as any).filters = null;
85
+ (container as any).filterArea = null;
86
+ }
87
+ this.disposeBound(id);
88
+ }
89
+
90
+ private disposeBound(id: number) {
91
+ const b = this.bound.get(id);
92
+ if (!b) return;
93
+ for (const f of b.pixiFilters) {
94
+ try {
95
+ (f as any).destroy?.();
96
+ } catch {
97
+ /* ignore */
98
+ }
99
+ }
100
+ this.bound.delete(id);
101
+ }
102
+
103
+ private createOne(spec: FilterSpec): PIXIFilter | null {
104
+ switch (spec.type) {
105
+ case "blur":
106
+ return createBlurFilter(spec) as unknown as PIXIFilter;
107
+ case "colorMatrix":
108
+ return createColorMatrixFilter(spec) as unknown as PIXIFilter;
109
+ case "displacement":
110
+ return createDisplacementFilter(spec) as unknown as PIXIFilter;
111
+ case "noise":
112
+ return createNoiseFilter(spec) as unknown as PIXIFilter;
113
+ case "alpha":
114
+ return createAlphaFilter(spec) as unknown as PIXIFilter;
115
+ default:
116
+ return null;
117
+ }
118
+ }
119
+
120
+ update(time?: { deltaTime: number }) {
121
+ // Drain componentObserver so componentChanged fires for ADD/CHANGE/REMOVE.
122
+ super.update(time as any);
123
+
124
+ // Animate noise seed for filters with noiseAnimSpeed
125
+ const dt = (time?.deltaTime ?? 16) / 1000;
126
+ this.elapsed += dt;
127
+ for (const { pixiFilters, specs } of this.bound.values()) {
128
+ for (let i = 0; i < specs.length; i++) {
129
+ const s = specs[i];
130
+ if (s.type === "noise" && s.noiseAnimSpeed) {
131
+ const f = pixiFilters[i] as any;
132
+ if (f) f.seed = (this.elapsed * s.noiseAnimSpeed) % 1;
133
+ }
134
+ }
135
+ }
136
+ }
137
+ }
package/package.json ADDED
@@ -0,0 +1,21 @@
1
+ {
2
+ "name": "@eva/plugin-renderer-filter",
3
+ "version": "2.1.0-beta.1",
4
+ "description": "@eva/plugin-renderer-filter — wraps PixiJS built-in 2D filters (Blur/ColorMatrix/Displacement/Noise/Alpha) as Eva.js components.",
5
+ "main": "lib/index.ts",
6
+ "module": "lib/index.ts",
7
+ "types": "lib/index.ts",
8
+ "files": [
9
+ "lib"
10
+ ],
11
+ "keywords": [
12
+ "eva.js"
13
+ ],
14
+ "license": "MIT",
15
+ "dependencies": {
16
+ "@eva/eva.js": "2.1.0-beta.1",
17
+ "@eva/inspector-decorator": "^2.0.0-beta.0",
18
+ "@eva/plugin-renderer": "2.1.0-beta.1",
19
+ "pixi.js": "^8.17.0"
20
+ }
21
+ }