Skip to content

textmode.js / filters / TextmodeFilterManager

Class: TextmodeFilterManager

Registers filter shaders and applies layer/global filter chains.

Example

ts
// Register a custom filter
await t.filters.register('brightness', brightnessShader, {
    u_amount: ['amount', 1.0]
});

// Use the filter globally
t.filter('brightness', 1.5);

// Or on a layer
t.layers.base.filter('brightness', { amount: 0.8 });

Methods

has()

ts
has(id): boolean;

Check if a filter with the given ID is registered.

Parameters

ParameterTypeDescription
idstringThe filter ID to check

Returns

boolean

true if the filter exists

Example

javascript
const t = textmode.create({ width: window.innerWidth, height: window.innerHeight });

const labelLayer = t.layers.add();
let hasCustom = false;

t.setup(async () => {
	const fragment = `#version 300 es
		precision highp float;
		in vec2 v_uv;
		uniform sampler2D u_src;
		out vec4 outColor;
		void main() {
			outColor = texture(u_src, v_uv);
		}
	`;

	await t.filters.register('custom-noop', fragment, {});
});

t.draw(() => {
	t.background(6, 9, 20);

	hasCustom = t.filters.has('custom-noop');

	t.push();
	t.char('#');
	t.rotateZ(t.frameCount * 1.5);
	t.charColor(255, 220, 120);
	t.rect(12, 12);
	t.pop();
});

t.mouseClicked(async () => {
	if (hasCustom) {
		t.filters.unregister('custom-noop');
	} else {
		const fragment = `#version 300 es
			precision highp float;
			in vec2 v_uv;
			uniform sampler2D u_src;
			out vec4 outColor;
			void main() {
				outColor = texture(u_src, v_uv);
			}
		`;
		await t.filters.register('custom-noop', fragment, {});
	}
});

function drawText(text, x, y, r = 220, g = 230, b = 255) {
	t.push();
	t.translate(x, y);
	t.charColor(r, g, b);
	for (let i = 0; i < text.length; i++) {
		t.char(text[i]);
		t.point();
		t.translate(1, 0);
	}
	t.pop();
}

labelLayer.draw(() => {
	t.clear();
	const left = -Math.floor(t.grid.cols / 2);
	const top = -Math.floor(t.grid.rows / 2);
	let y = top + 3;
	const x = left + 3;

	const isInvert = t.filters.has('invert');

	drawText('FILTERS.HAS', x, y++, 100, 255, 140);
	drawText('------------------------------------', x, y++, 80, 100, 150);
	drawText('CONCEPT: CHECK REGISTERED FILTER', x, y++, 100, 220, 255);
	drawText('Performs lookup in filter registry.', x, y++, 140, 160, 190);
	drawText('------------------------------------', x, y++, 80, 100, 150);
	drawText(`has('invert')     : ${isInvert}`, x, y++, 180, 255, 180);
	drawText(
		`has('custom-noop'): ${hasCustom}`,
		x,
		y++,
		hasCustom ? 180 : 255,
		hasCustom ? 255 : 120,
		hasCustom ? 180 : 120
	);
	drawText('------------------------------------', x, y++, 80, 100, 150);
	drawText(hasCustom ? 'Click to unregister.' : 'Click to register custom-noop.', x, y++, 120, 205, 255);
});

t.windowResized(() => {
	t.resizeCanvas(window.innerWidth, window.innerHeight);
});

register()

ts
register(
   id, 
   shader, 
uniformDefs?): Promise<void>;

Register a custom filter with the given ID, shader, and uniform definitions.

Parameters

ParameterTypeDescription
idstringUnique filter identifier
shaderstring | TextmodeShaderPre-compiled GLShader, fragment shader source string, or path to a .frag/.glsl file
uniformDefsFilterUniformDefinitionsMaps uniform names to [paramName, defaultValue] tuples

Returns

Promise<void>

Example

ts
// Register with inline shader source
await t.filters.register('blur', blurFragSource, {
    u_radius: ['radius', 5.0],
    u_direction: ['direction', [1.0, 0.0]]
});

// Register with file path
await t.filters.register('vignette', './vignette.frag', {
    u_intensity: ['intensity', 0.5]
});

unregister()

ts
unregister(id): boolean;

Unregister a filter by its ID.

Parameters

ParameterTypeDescription
idstringThe filter ID to unregister

Returns

boolean

true if the filter was unregistered, false if it wasn't found

Example

javascript
const t = textmode.create({ width: window.innerWidth, height: window.innerHeight });

const labelLayer = t.layers.add();
let filterActive = false;

t.setup(async () => {
	const fragment = `#version 300 es
		precision highp float;
		in vec2 v_uv;
		uniform sampler2D u_src;
		out vec4 outColor;
		void main() {
			vec4 col = texture(u_src, v_uv);
			outColor = vec4(col.r * 0.1, col.g * 1.5, col.b * 0.2, col.a);
		}
	`;

	await t.filters.register('green-wash', fragment, {});
	filterActive = true;
});

t.draw(() => {
	t.background(6, 9, 20);

	t.push();
	t.char('#');
	t.rotateZ(t.frameCount * 1.2);
	t.charColor(255, 220, 120);
	t.rect(14, 14);
	t.pop();

	if (filterActive && t.filters.has('green-wash')) {
		t.filter('green-wash');
	}
});

t.mouseClicked(() => {
	if (!filterActive) return;
	t.filters.unregister('green-wash');
	filterActive = false;
});

function drawText(text, x, y, r = 220, g = 230, b = 255) {
	t.push();
	t.translate(x, y);
	t.charColor(r, g, b);
	for (let i = 0; i < text.length; i++) {
		t.char(text[i]);
		t.point();
		t.translate(1, 0);
	}
	t.pop();
}

labelLayer.draw(() => {
	t.clear();
	const left = -Math.floor(t.grid.cols / 2);
	const top = -Math.floor(t.grid.rows / 2);
	let y = top + 3;
	const x = left + 3;

	const stateStr = filterActive ? 'ACTIVE' : 'INACTIVE';

	drawText('FILTERS.UNREGISTER', x, y++, 100, 255, 140);
	drawText('------------------------------------', x, y++, 80, 100, 150);
	drawText('CONCEPT: DISPOSE CUSTOM FILTER', x, y++, 100, 220, 255);
	drawText('Removes registered custom shader.', x, y++, 140, 160, 190);
	drawText('------------------------------------', x, y++, 80, 100, 150);
	drawText(`FILTER STATE: ${stateStr}`, x, y++, 140, 190, 255);
	drawText(
		filterActive ? 'Click to unregister green-wash.' : 'Filter unregistered successfully.',
		x,
		y++,
		180,
		255,
		180
	);
});

t.windowResized(() => {
	t.resizeCanvas(window.innerWidth, window.innerHeight);
});