textmode.js / loadables / TextmodeTexture
Class: TextmodeTexture
Represents an external texture source for textmode rendering via Textmodifier.createTexture.
This class enables integration with other WebGL-based libraries like three.js, p5.js, Babylon.js, hydra-synth, or any library that renders to a canvas element.
It can be drawn to the canvas via Textmodifier.image.
The texture automatically updates each frame to capture the latest content from the source canvas or video.
Extends
Extended by
Accessors
height
Get Signature
get height(): number;Ideal height in grid cells.
Returns
number
Inherited from
originalHeight
Get Signature
get originalHeight(): number;Original pixel height.
Returns
number
Inherited from
originalWidth
Get Signature
get originalWidth(): number;Original pixel width.
Returns
number
Inherited from
source
Get Signature
get source(): HTMLCanvasElement | HTMLVideoElement;The source element this texture captures from.
Returns
HTMLCanvasElement | HTMLVideoElement
texture
Get Signature
get texture(): WebGLTexture;Return the WebGL texture currently backing this source.
Returns
WebGLTexture
Inherited from
width
Get Signature
get width(): number;Ideal width in grid cells.
Returns
number
Inherited from
Methods
_hasFrameOverrides()
_hasFrameOverrides(): boolean;Returns
boolean
Inherited from
TextmodeSource._hasFrameOverrides
background()
background(
colorOrGray,
g?,
b?,
a?): this;Defines the background color used for transparent pixels.
Parameters
| Parameter | Type | Description |
|---|---|---|
colorOrGray | string | number | TextmodeColor | A grayscale value (0-255), hex string ('#RGB', '#RRGGBB', '#RRGGBBAA'), or TextmodeColor instance |
g? | number | Optional green component (0-255) if using RGB format, or alpha (0-255) when using grayscale form |
b? | number | Optional blue component (0-255) if using RGB format |
a? | number | Optional alpha component (0-255) if using RGBA format |
Returns
this
This instance for chaining.
Example
const t = textmode.create({ width: window.innerWidth, height: window.innerHeight });
let plainSource;
let backgroundSource;
function createTransparentCanvas() {
const canvas = document.createElement('canvas');
canvas.width = 160;
canvas.height = 160;
const ctx = canvas.getContext('2d');
if (!ctx) return canvas;
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = '#ffffff';
ctx.beginPath();
ctx.arc(canvas.width / 2, canvas.height / 2, 48, 0, Math.PI * 2);
ctx.fill();
ctx.fillStyle = '#000000';
ctx.fillRect(canvas.width / 2 - 10, canvas.height / 2 - 10, 20, 20);
return canvas;
}
function drawLabel(text, x, y) {
t.push();
t.translate(x - Math.floor(text.length / 2), y);
t.charColor(255);
for (let i = 0; i < text.length; i++) {
t.push();
t.translate(i, 0);
t.char(text[i]);
t.point();
t.pop();
}
t.pop();
}
t.setup(() => {
const canvas = createTransparentCanvas();
plainSource = t.createTexture(canvas);
plainSource.characters(' .:-=+*#%@');
backgroundSource = t.createTexture(canvas);
backgroundSource.characters(' .:-=+*#%@');
backgroundSource.background(255, 0, 0);
});
t.draw(() => {
t.background(40);
if (!plainSource || !backgroundSource) return;
const size = Math.min(plainSource.width, plainSource.height) * 0.7;
const offset = Math.floor(size * 0.7);
t.push();
t.translate(-offset, 0);
t.image(plainSource, size, size);
t.pop();
t.push();
t.translate(offset, 0);
t.image(backgroundSource, size, size);
t.pop();
drawLabel('default transparent fallback', -offset, Math.floor(t.grid.rows / 2) - 2);
drawLabel('background(255, 0, 0)', offset, Math.floor(t.grid.rows / 2) - 2);
});
t.windowResized(() => {
t.resizeCanvas(window.innerWidth, window.innerHeight);
});
Inherited from
cellColor()
cellColor(
colorOrGray,
g?,
b?,
a?): this;Defines the cell color when cellColorMode is 'fixed'.
Parameters
| Parameter | Type | Description |
|---|---|---|
colorOrGray | string | number | TextmodeColor | A grayscale value (0-255), hex string ('#RGB', '#RRGGBB', '#RRGGBBAA'), or TextmodeColor instance |
g? | number | Optional green component (0-255) if using RGB format, or alpha (0-255) when using grayscale form |
b? | number | Optional blue component (0-255) if using RGB format |
a? | number | Optional alpha component (0-255) if using RGBA format |
Returns
this
This instance for chaining.
Example
const t = textmode.create({ width: window.innerWidth, height: window.innerHeight });
let source;
function createGradientCanvas() {
const canvas = document.createElement('canvas');
canvas.width = 160;
canvas.height = 160;
const ctx = canvas.getContext('2d');
if (!ctx) return canvas;
const gradient = ctx.createLinearGradient(0, 0, canvas.width, canvas.height);
gradient.addColorStop(0, '#050505');
gradient.addColorStop(1, '#f5f5f5');
ctx.fillStyle = gradient;
ctx.fillRect(0, 0, canvas.width, canvas.height);
return canvas;
}
function drawLabel(text, y) {
t.push();
t.translate(-Math.floor(text.length / 2), y);
t.charColor(255);
for (let i = 0; i < text.length; i++) {
t.push();
t.translate(i, 0);
t.char(text[i]);
t.point();
t.pop();
}
t.pop();
}
t.setup(() => {
source = t.createTexture(createGradientCanvas());
source.characters(' .:-=+*#%@');
source.charColorMode('fixed');
source.charColor(255);
source.cellColorMode('fixed');
});
t.draw(() => {
t.background(0);
if (!source) return;
const blue = 32 + Math.round(64 * (1 + Math.sin(t.frameCount * 0.05)));
source.cellColor('#000033');
source.cellColor(0, 0, blue);
t.image(source, source.width, source.height);
drawLabel('cellColor(0, 0, blue)', Math.floor(t.grid.rows / 2) - 2);
});
t.windowResized(() => {
t.resizeCanvas(window.innerWidth, window.innerHeight);
});
Inherited from
cellColorMode()
cellColorMode(mode): this;Set cell color mode: 'sampled' (from source) or 'fixed'.
Parameters
| Parameter | Type | Description |
|---|---|---|
mode | "sampled" | "fixed" | The cell color mode |
Returns
this
This instance for chaining.
Example
const t = textmode.create({ width: window.innerWidth, height: window.innerHeight });
let sampledSource;
let fixedSource;
function createColorCanvas() {
const canvas = document.createElement('canvas');
canvas.width = 160;
canvas.height = 160;
const ctx = canvas.getContext('2d');
if (!ctx) return canvas;
const gradient = ctx.createLinearGradient(0, 0, canvas.width, canvas.height);
gradient.addColorStop(0, '#00c2ff');
gradient.addColorStop(0.5, '#00ff88');
gradient.addColorStop(1, '#ffee00');
ctx.fillStyle = gradient;
ctx.fillRect(0, 0, canvas.width, canvas.height);
return canvas;
}
function drawLabel(text, x, y) {
t.push();
t.translate(x - Math.floor(text.length / 2), y);
t.charColor(255);
for (let i = 0; i < text.length; i++) {
t.push();
t.translate(i, 0);
t.char(text[i]);
t.point();
t.pop();
}
t.pop();
}
t.setup(() => {
const canvas = createColorCanvas();
sampledSource = t.createTexture(canvas);
sampledSource.characters(' .:-=+*#%@');
sampledSource.charColorMode('sampled');
sampledSource.cellColorMode('sampled');
fixedSource = t.createTexture(canvas);
fixedSource.characters(' .:-=+*#%@');
fixedSource.charColorMode('sampled');
fixedSource.cellColorMode('fixed');
fixedSource.cellColor('#120022');
});
t.draw(() => {
t.background(0);
if (!sampledSource || !fixedSource) return;
const pulse = 64 + Math.round(64 * (1 + Math.sin(t.frameCount * 0.05)));
fixedSource.cellColor(pulse, 0, 40);
const size = Math.min(sampledSource.width, sampledSource.height) * 0.7;
const offset = Math.floor(size * 0.7);
t.push();
t.translate(-offset, 0);
t.image(sampledSource, size, size);
t.pop();
t.push();
t.translate(offset, 0);
t.image(fixedSource, size, size);
t.pop();
drawLabel("cellColorMode('sampled')", -offset, Math.floor(t.grid.rows / 2) - 2);
drawLabel("cellColorMode('fixed')", offset, Math.floor(t.grid.rows / 2) - 2);
});
t.windowResized(() => {
t.resizeCanvas(window.innerWidth, window.innerHeight);
});
Inherited from
characters()
characters(chars): this;Define the characters to use for brightness mapping as a string. Maximum length is 255; excess characters are ignored.
Parameters
| Parameter | Type | Description |
|---|---|---|
chars | string | String of characters to map |
Returns
this
This instance for chaining.
Example
const t = textmode.create({ width: window.innerWidth, height: window.innerHeight });
let sparseSource;
let denseSource;
function createGradientCanvas() {
const canvas = document.createElement('canvas');
canvas.width = 160;
canvas.height = 160;
const ctx = canvas.getContext('2d');
if (!ctx) return canvas;
const gradient = ctx.createLinearGradient(0, 0, canvas.width, canvas.height);
gradient.addColorStop(0, '#000000');
gradient.addColorStop(1, '#ffffff');
ctx.fillStyle = gradient;
ctx.fillRect(0, 0, canvas.width, canvas.height);
return canvas;
}
function drawLabel(text, x, y) {
t.push();
t.translate(x - Math.floor(text.length / 2), y);
t.charColor(255);
for (let i = 0; i < text.length; i++) {
t.push();
t.translate(i, 0);
t.char(text[i]);
t.point();
t.pop();
}
t.pop();
}
t.setup(() => {
const canvas = createGradientCanvas();
sparseSource = t.createTexture(canvas);
sparseSource.characters(' .oO@');
denseSource = t.createTexture(canvas);
denseSource.characters(' .:-=+*#%@');
});
t.draw(() => {
t.background(0);
if (!sparseSource || !denseSource) return;
const size = Math.min(sparseSource.width, sparseSource.height) * 0.7;
const offset = Math.floor(size * 0.7);
t.push();
t.translate(-offset, 0);
t.image(sparseSource, size, size);
t.pop();
t.push();
t.translate(offset, 0);
t.image(denseSource, size, size);
t.pop();
drawLabel("characters(' .oO@')", -offset, Math.floor(t.grid.rows / 2) - 2);
drawLabel("characters(' .:-=+*#%@')", offset, Math.floor(t.grid.rows / 2) - 2);
});
t.windowResized(() => {
t.resizeCanvas(window.innerWidth, window.innerHeight);
});
Inherited from
charColor()
charColor(
colorOrGray,
g?,
b?,
a?): this;Defines the character color when charColorMode is 'fixed'.
Parameters
| Parameter | Type | Description |
|---|---|---|
colorOrGray | string | number | TextmodeColor | A grayscale value (0-255), hex string ('#RGB', '#RRGGBB', '#RRGGBBAA'), or TextmodeColor instance |
g? | number | Optional green component (0-255) if using RGB format, or alpha (0-255) when using grayscale form |
b? | number | Optional blue component (0-255) if using RGB format |
a? | number | Optional alpha component (0-255) if using RGBA format |
Returns
this
This instance for chaining.
Example
const t = textmode.create({ width: window.innerWidth, height: window.innerHeight });
let source;
function createGradientCanvas() {
const canvas = document.createElement('canvas');
canvas.width = 160;
canvas.height = 160;
const ctx = canvas.getContext('2d');
if (!ctx) return canvas;
const gradient = ctx.createLinearGradient(0, 0, canvas.width, canvas.height);
gradient.addColorStop(0, '#000000');
gradient.addColorStop(1, '#ffffff');
ctx.fillStyle = gradient;
ctx.fillRect(0, 0, canvas.width, canvas.height);
return canvas;
}
function drawLabel(text, y) {
t.push();
t.translate(-Math.floor(text.length / 2), y);
t.charColor(255);
for (let i = 0; i < text.length; i++) {
t.push();
t.translate(i, 0);
t.char(text[i]);
t.point();
t.pop();
}
t.pop();
}
t.setup(() => {
source = t.createTexture(createGradientCanvas());
source.characters(' .:-=+*#%@');
source.charColorMode('fixed');
source.cellColorMode('fixed');
source.cellColor(0);
});
t.draw(() => {
t.background(0);
if (!source) return;
const red = 150 + 100 * Math.sin(t.frameCount * 0.05);
const blue = 150 + 100 * Math.cos(t.frameCount * 0.05);
source.charColor(red, 100, blue);
t.image(source, source.width, source.height);
drawLabel('charColor(r, 100, b)', Math.floor(t.grid.rows / 2) - 2);
});
t.windowResized(() => {
t.resizeCanvas(window.innerWidth, window.innerHeight);
});
Inherited from
charColorMode()
charColorMode(mode): this;Set character color mode: 'sampled' (from source) or 'fixed'.
Parameters
| Parameter | Type | Description |
|---|---|---|
mode | "sampled" | "fixed" | The character color mode |
Returns
this
This instance for chaining.
Example
const t = textmode.create({ width: window.innerWidth, height: window.innerHeight });
let sampledSource;
let fixedSource;
function createColorCanvas() {
const canvas = document.createElement('canvas');
canvas.width = 160;
canvas.height = 160;
const ctx = canvas.getContext('2d');
if (!ctx) return canvas;
const gradient = ctx.createLinearGradient(0, 0, canvas.width, canvas.height);
gradient.addColorStop(0, '#ffcc00');
gradient.addColorStop(0.5, '#ff0055');
gradient.addColorStop(1, '#3300ff');
ctx.fillStyle = gradient;
ctx.fillRect(0, 0, canvas.width, canvas.height);
return canvas;
}
function drawLabel(text, x, y) {
t.push();
t.translate(x - Math.floor(text.length / 2), y);
t.charColor(255);
for (let i = 0; i < text.length; i++) {
t.push();
t.translate(i, 0);
t.char(text[i]);
t.point();
t.pop();
}
t.pop();
}
t.setup(() => {
const canvas = createColorCanvas();
sampledSource = t.createTexture(canvas);
sampledSource.characters(' .:-=+*#%@');
sampledSource.charColorMode('sampled');
sampledSource.cellColorMode('sampled');
fixedSource = t.createTexture(canvas);
fixedSource.characters(' .:-=+*#%@');
fixedSource.charColorMode('fixed');
fixedSource.charColor(255, 80, 80);
fixedSource.cellColorMode('sampled');
});
t.draw(() => {
t.background(0);
if (!sampledSource || !fixedSource) return;
const size = Math.min(sampledSource.width, sampledSource.height) * 0.7;
const offset = Math.floor(size * 0.7);
t.push();
t.translate(-offset, 0);
t.image(sampledSource, size, size);
t.pop();
t.push();
t.translate(offset, 0);
t.image(fixedSource, size, size);
t.pop();
drawLabel("charColorMode('sampled')", -offset, Math.floor(t.grid.rows / 2) - 2);
drawLabel("charColorMode('fixed')", offset, Math.floor(t.grid.rows / 2) - 2);
});
t.windowResized(() => {
t.resizeCanvas(window.innerWidth, window.innerHeight);
});
Inherited from
charRotation()
charRotation(degrees): this;Set the character rotation in degrees (0-360).
Parameters
| Parameter | Type | Description |
|---|---|---|
degrees | number | Rotation in degrees |
Returns
this
This instance for chaining.
Example
const t = textmode.create({ width: window.innerWidth, height: window.innerHeight });
let source;
function createCheckerCanvas() {
const canvas = document.createElement('canvas');
canvas.width = 160;
canvas.height = 160;
const ctx = canvas.getContext('2d');
if (!ctx) return canvas;
ctx.fillStyle = '#ffffff';
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = '#000000';
ctx.fillRect(0, 0, canvas.width / 2, canvas.height / 2);
ctx.fillRect(canvas.width / 2, canvas.height / 2, canvas.width / 2, canvas.height / 2);
return canvas;
}
function drawLabel(text, x, y) {
t.push();
t.translate(x - Math.floor(text.length / 2), y);
t.charColor(255);
for (let i = 0; i < text.length; i++) {
t.push();
t.translate(i, 0);
t.char(text[i]);
t.point();
t.pop();
}
t.pop();
}
t.setup(() => {
source = t.createTexture(createCheckerCanvas());
source.characters(' .:-=+*#%@');
});
t.draw(() => {
t.background(0);
if (!source) return;
const size = Math.min(source.width, source.height) * 0.7;
const offset = Math.floor(size * 0.7);
source.charRotation(0);
t.push();
t.translate(-offset, 0);
t.image(source, size, size);
t.pop();
source.charRotation(90);
t.push();
t.translate(offset, 0);
t.image(source, size, size);
t.pop();
drawLabel('charRotation(0)', -offset, Math.floor(t.grid.rows / 2) - 2);
drawLabel('charRotation(90)', offset, Math.floor(t.grid.rows / 2) - 2);
});
t.windowResized(() => {
t.resizeCanvas(window.innerWidth, window.innerHeight);
});
Inherited from
conversionMode()
conversionMode(mode): this;Select the conversion mode for this source.
textmode.js includes only a single built-in conversion strategy 'brightness'.
Additional conversion strategies may be provided via add-on libraries.
Parameters
| Parameter | Type | Description |
|---|---|---|
mode | string | Conversion mode to use. |
Returns
this
This instance for chaining.
Example
const t = textmode.create({ width: window.innerWidth, height: window.innerHeight });
let source;
function createGradientCanvas() {
const canvas = document.createElement('canvas');
canvas.width = 160;
canvas.height = 160;
const ctx = canvas.getContext('2d');
if (!ctx) return canvas;
const gradient = ctx.createLinearGradient(0, 0, canvas.width, canvas.height);
gradient.addColorStop(0, '#040404');
gradient.addColorStop(0.5, '#888888');
gradient.addColorStop(1, '#f8f8f8');
ctx.fillStyle = gradient;
ctx.fillRect(0, 0, canvas.width, canvas.height);
return canvas;
}
function drawLabel(text, y) {
t.push();
t.translate(-Math.floor(text.length / 2), y);
t.charColor(255);
for (let i = 0; i < text.length; i++) {
t.push();
t.translate(i, 0);
t.char(text[i]);
t.point();
t.pop();
}
t.pop();
}
t.setup(() => {
source = t.createTexture(createGradientCanvas());
source.conversionMode('brightness');
source.characters(' .:-=+*#%@');
});
t.draw(() => {
t.background(0);
if (!source) return;
t.image(source, source.width, source.height);
drawLabel("conversionMode('brightness')", Math.floor(t.grid.rows / 2) - 2);
});
t.windowResized(() => {
t.resizeCanvas(window.innerWidth, window.innerHeight);
});
Inherited from
dispose()
dispose(): void;Dispose of the resource and free associated WebGL textures.
This should be called when the resource is no longer needed to prevent memory leaks. Resources created via Textmodifier.loadImage, Textmodifier.loadVideo, and Textmodifier.createTexture are automatically disposed when the Textmodifier instance is destroyed, but you can call this manually to free memory earlier.
Returns
void
Inherited from
flipX()
flipX(v): this;Set horizontal flip indicator flag.
Parameters
| Parameter | Type | Default value | Description |
|---|---|---|---|
v | number | boolean | true | Flip flag |
Returns
this
This instance for chaining.
Example
const t = textmode.create({ width: window.innerWidth, height: window.innerHeight });
let source;
function createArrowCanvas() {
const canvas = document.createElement('canvas');
canvas.width = 160;
canvas.height = 160;
const ctx = canvas.getContext('2d');
if (!ctx) return canvas;
ctx.fillStyle = '#111111';
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = '#ffffff';
ctx.beginPath();
ctx.moveTo(24, 80);
ctx.lineTo(104, 28);
ctx.lineTo(104, 60);
ctx.lineTo(136, 60);
ctx.lineTo(136, 100);
ctx.lineTo(104, 100);
ctx.lineTo(104, 132);
ctx.closePath();
ctx.fill();
return canvas;
}
function drawLabel(text, x, y) {
t.push();
t.translate(x - Math.floor(text.length / 2), y);
t.charColor(255);
for (let i = 0; i < text.length; i++) {
t.push();
t.translate(i, 0);
t.char(text[i]);
t.point();
t.pop();
}
t.pop();
}
t.setup(() => {
source = t.createTexture(createArrowCanvas());
source.characters(' .:-=+*#%@');
});
t.draw(() => {
t.background(5, 5, 15);
if (!source) return;
const size = Math.min(source.width, source.height) * 0.7;
const offset = Math.floor(size * 0.7);
source.flipX(false);
t.push();
t.translate(-offset, 0);
t.image(source, size, size);
t.pop();
source.flipX(true);
t.push();
t.translate(offset, 0);
t.image(source, size, size);
t.pop();
drawLabel('flipX(false)', -offset, Math.floor(t.grid.rows / 2) - 2);
drawLabel('flipX(true)', offset, Math.floor(t.grid.rows / 2) - 2);
});
t.windowResized(() => {
t.resizeCanvas(window.innerWidth, window.innerHeight);
});
Inherited from
flipY()
flipY(v): this;Set vertical flip indicator flag.
Parameters
| Parameter | Type | Default value | Description |
|---|---|---|---|
v | number | boolean | true | Flip flag |
Returns
this
This instance for chaining.
Example
const t = textmode.create({ width: window.innerWidth, height: window.innerHeight });
let source;
function createArrowCanvas() {
const canvas = document.createElement('canvas');
canvas.width = 160;
canvas.height = 160;
const ctx = canvas.getContext('2d');
if (!ctx) return canvas;
ctx.fillStyle = '#101010';
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = '#ffffff';
ctx.beginPath();
ctx.moveTo(80, 24);
ctx.lineTo(132, 104);
ctx.lineTo(100, 104);
ctx.lineTo(100, 136);
ctx.lineTo(60, 136);
ctx.lineTo(60, 104);
ctx.lineTo(28, 104);
ctx.closePath();
ctx.fill();
return canvas;
}
function drawLabel(text, x, y) {
t.push();
t.translate(x - Math.floor(text.length / 2), y);
t.charColor(255);
for (let i = 0; i < text.length; i++) {
t.push();
t.translate(i, 0);
t.char(text[i]);
t.point();
t.pop();
}
t.pop();
}
t.setup(() => {
source = t.createTexture(createArrowCanvas());
source.characters(' .:-=+*#%@');
});
t.draw(() => {
t.background(0);
if (!source) return;
const size = Math.min(source.width, source.height) * 0.7;
const offset = Math.floor(size * 0.7);
source.flipY(false);
t.push();
t.translate(-offset, 0);
t.image(source, size, size);
t.pop();
source.flipY(true);
t.push();
t.translate(offset, 0);
t.image(source, size, size);
t.pop();
drawLabel('flipY(false)', -offset, Math.floor(t.grid.rows / 2) - 2);
drawLabel('flipY(true)', offset, Math.floor(t.grid.rows / 2) - 2);
});
t.windowResized(() => {
t.resizeCanvas(window.innerWidth, window.innerHeight);
});
Inherited from
invert()
invert(v): this;Set the invert flag, swapping character and cell colors when enabled.
Parameters
| Parameter | Type | Default value | Description |
|---|---|---|---|
v | number | boolean | true | Invert flag |
Returns
this
This instance for chaining.
Example
const t = textmode.create({ width: window.innerWidth, height: window.innerHeight });
let normalSource;
let invertedSource;
function createGradientCanvas() {
const canvas = document.createElement('canvas');
canvas.width = 160;
canvas.height = 160;
const ctx = canvas.getContext('2d');
if (!ctx) return canvas;
const gradient = ctx.createLinearGradient(0, 0, canvas.width, canvas.height);
gradient.addColorStop(0, '#050505');
gradient.addColorStop(1, '#f5f5f5');
ctx.fillStyle = gradient;
ctx.fillRect(0, 0, canvas.width, canvas.height);
return canvas;
}
function drawLabel(text, x, y) {
t.push();
t.translate(x - Math.floor(text.length / 2), y);
t.charColor(255);
for (let i = 0; i < text.length; i++) {
t.push();
t.translate(i, 0);
t.char(text[i]);
t.point();
t.pop();
}
t.pop();
}
t.setup(() => {
const canvas = createGradientCanvas();
normalSource = t.createTexture(canvas);
normalSource.characters(' .:-=+*#%@');
invertedSource = t.createTexture(canvas);
invertedSource.characters(' .:-=+*#%@');
invertedSource.invert(true);
});
t.draw(() => {
t.background(0);
if (!normalSource || !invertedSource) return;
const size = Math.min(normalSource.width, normalSource.height) * 0.7;
const offset = Math.floor(size * 0.7);
t.push();
t.translate(-offset, 0);
t.image(normalSource, size, size);
t.pop();
t.push();
t.translate(offset, 0);
t.image(invertedSource, size, size);
t.pop();
drawLabel('invert(false)', -offset, Math.floor(t.grid.rows / 2) - 2);
drawLabel('invert(true)', offset, Math.floor(t.grid.rows / 2) - 2);
});
t.windowResized(() => {
t.resizeCanvas(window.innerWidth, window.innerHeight);
});