Skip to content

textmode.js / layering / LayerBlendMode

Enumeration: LayerBlendMode

Numeric blend mode constants for layer compositing.

These values are the source of truth for the wire protocol between the TypeScript layer compositor and the composite.frag GLSL shader. Each value must match the corresponding const int BLEND_* constant in composite.frag.

Use the t.BLEND_* prototype constants for ergonomic access:

ts
layer.blendMode(t.BLEND_ADDITIVE);

Enumeration Members

Enumeration MemberValueDescription
ADDITIVE1Add the layer's color channels to the layers below it. Use this for glowing marks, light trails, sparks, and other effects that should get brighter as animated layers overlap. Example /** * @title Textmodifier.BLEND_ADDITIVE */ const t = textmode.create({ width: window.innerWidth, height: window.innerHeight, fontSize: 16, }); const mode = t.BLEND_ADDITIVE; const modeName = 'BLEND_ADDITIVE'; const accent = [255, 100, 80]; const base = [28, 80, 120]; const blendLayer = t.layers.add({ blendMode: mode, opacity: 0.78 }); const labelLayer = t.layers.add(); function drawText(text, x, y, r = 220, g = 230, b = 255) { t.push(); t.printAlign('left', 'top'); t.charColor(r, g, b); t.print(text, x, y); t.pop(); } t.draw(() => { const time = t.frameCount * 0.04; t.background(4, 8, 18); const { cols, rows } = t.grid; for (let y = -Math.floor(rows / 2); y < rows / 2; y += 3) { for (let x = -Math.floor(cols / 2); x < cols / 2; x += 3) { const wave = Math.sin(x * 0.17 + y * 0.11 + time); t.push(); t.translate(x, y); t.char(wave > 0 ? '=' : ':'); t.charColor(base[0], base[1] + wave * 35, base[2]); t.cellColor(2, 6, 16); t.rect(2, 2); t.pop(); } } blendLayer.draw(() => { t.clear(); for (let ring = 0; ring < 5; ring++) { for (let i = 0; i < 16; i++) { const angle = i * 22.5 + ring * 11 + t.frameCount * 1.6; const radius = 4 + ring * 3.8 + Math.sin(time * 2 + i) * 2; t.push(); t.rotateZ(angle); t.translate(radius, Math.cos(time + i) * 2); t.char(ring % 2 ? '*' : '@'); t.charColor(...accent); t.cellColor(35 + ring * 18, 10, 12); t.rect(3, 2 + (i % 2)); 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; drawText('TEXTMODIFIER.BLEND_ADDITIVE', x, y++, 100, 255, 140); drawText('------------------------------------', x, y++, 80, 100, 150); drawText('CONCEPT: ADDITIVE LIGHT', x, y++, 100, 220, 255); drawText('Overlaps grow brighter.', x, y++, 140, 160, 190); drawText('Good for sparks and glow.', x, y++, 140, 160, 190); drawText('------------------------------------', x, y++, 80, 100, 150); drawText(MODE: ${modeName}, x, y++, 140, 255, 180); }); t.windowResized(() => { t.resizeCanvas(window.innerWidth, window.innerHeight); });
COLOR_BURN11Darken the layers below by inverting the color dodge relationship. Use this for dense shadow burns, high-pressure edges, and animated shapes that should compress color toward black. Example /** * @title Textmodifier.BLEND_COLOR_BURN */ const t = textmode.create({ width: window.innerWidth, height: window.innerHeight, fontSize: 16, }); const mode = t.BLEND_COLOR_BURN; const modeName = 'BLEND_COLOR_BURN'; const accent = [120, 50, 70]; const base = [220, 170, 110]; const blendLayer = t.layers.add({ blendMode: mode, opacity: 0.82 }); const labelLayer = t.layers.add(); function drawText(text, x, y, r = 220, g = 230, b = 255) { t.push(); t.printAlign('left', 'top'); t.charColor(r, g, b); t.print(text, x, y); t.pop(); } t.draw(() => { const time = t.frameCount * 0.034; t.background(50, 34, 22); const { cols, rows } = t.grid; for (let y = -Math.floor(rows / 2); y < rows / 2; y += 3) { for (let x = -Math.floor(cols / 2); x < cols / 2; x += 3) { const wave = Math.sin(x * 0.13 - y * 0.15 + time); t.push(); t.translate(x, y); t.char(wave > 0 ? '%' : '+'); t.charColor(base[0], base[1] + wave * 28, base[2]); t.cellColor(52, 28, 16); t.rect(2, 2); t.pop(); } } blendLayer.draw(() => { t.clear(); for (let ring = 0; ring < 5; ring++) { for (let i = 0; i < 14; i++) { const angle = i * 25.7 + ring * 18 - t.frameCount * 1.2; const radius = 5 + ring * 3.8 + Math.sin(time * 2 + i) * 2; t.push(); t.rotateZ(angle); t.translate(radius, Math.cos(time + i) * 2); t.char(ring % 2 ? '#' : 'X'); t.charColor(...accent); t.cellColor(24 + ring * 10, 5, 10 + ring * 6); t.rect(4, 3); 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; drawText('TEXTMODIFIER.BLEND_COLOR_BURN', x, y++, 100, 255, 140); drawText('------------------------------------', x, y++, 80, 100, 150); drawText('CONCEPT: BURN SHADOWS', x, y++, 100, 220, 255); drawText('Color compresses darker.', x, y++, 140, 160, 190); drawText('Motion leaves dense marks.', x, y++, 140, 160, 190); drawText('------------------------------------', x, y++, 80, 100, 150); drawText(MODE: ${modeName}, x, y++, 140, 255, 180); }); t.windowResized(() => { t.resizeCanvas(window.innerWidth, window.innerHeight); });
COLOR_DODGE10Brighten the layers below by dividing around the layer color. Use this for sharp flares, blooming accents, and small highlights that should push quickly toward white. Example /** * @title Textmodifier.BLEND_COLOR_DODGE */ const t = textmode.create({ width: window.innerWidth, height: window.innerHeight, fontSize: 16, }); const mode = t.BLEND_COLOR_DODGE; const modeName = 'BLEND_COLOR_DODGE'; const accent = [255, 245, 170]; const base = [42, 80, 130]; const blendLayer = t.layers.add({ blendMode: mode, opacity: 0.72 }); const labelLayer = t.layers.add(); function drawText(text, x, y, r = 220, g = 230, b = 255) { t.push(); t.printAlign('left', 'top'); t.charColor(r, g, b); t.print(text, x, y); t.pop(); } t.draw(() => { const time = t.frameCount * 0.038; t.background(4, 10, 24); const { cols, rows } = t.grid; for (let y = -Math.floor(rows / 2); y < rows / 2; y += 3) { for (let x = -Math.floor(cols / 2); x < cols / 2; x += 3) { const wave = Math.sin(x * 0.15 + y * 0.14 + time); t.push(); t.translate(x, y); t.char(wave > 0 ? '+' : '.'); t.charColor(base[0], base[1] + wave * 30, base[2]); t.cellColor(3, 8, 20); t.rect(2, 2); t.pop(); } } blendLayer.draw(() => { t.clear(); for (let ring = 0; ring < 4; ring++) { for (let i = 0; i < 18; i++) { const angle = i * 20 + ring * 19 + t.frameCount * 1.8; const radius = 4 + ring * 4 + Math.cos(time * 2 + i) * 2; t.push(); t.rotateZ(angle); t.translate(radius, Math.sin(time + i) * 2); t.char(ring % 2 ? '*' : '@'); t.charColor(...accent); t.cellColor(45 + ring * 22, 42 + ring * 16, 16); t.rect(3, 3); 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; drawText('TEXTMODIFIER.BLEND_COLOR_DODGE', x, y++, 100, 255, 140); drawText('------------------------------------', x, y++, 80, 100, 150); drawText('CONCEPT: DODGE FLARES', x, y++, 100, 220, 255); drawText('Small marks bloom fast.', x, y++, 140, 160, 190); drawText('Highlights push to white.', x, y++, 140, 160, 190); drawText('------------------------------------', x, y++, 80, 100, 150); drawText(MODE: ${modeName}, x, y++, 140, 255, 180); }); t.windowResized(() => { t.resizeCanvas(window.innerWidth, window.innerHeight); });
DARKEN5Keep the darker channel from either the layer or the layers below it. Use this when animated marks should only carve darker detail into the existing composition. Example /** * @title Textmodifier.BLEND_DARKEN */ const t = textmode.create({ width: window.innerWidth, height: window.innerHeight, fontSize: 16, }); const mode = t.BLEND_DARKEN; const modeName = 'BLEND_DARKEN'; const accent = [90, 110, 170]; const base = [220, 190, 120]; const blendLayer = t.layers.add({ blendMode: mode, opacity: 0.88 }); const labelLayer = t.layers.add(); function drawText(text, x, y, r = 220, g = 230, b = 255) { t.push(); t.printAlign('left', 'top'); t.charColor(r, g, b); t.print(text, x, y); t.pop(); } t.draw(() => { const time = t.frameCount * 0.03; t.background(46, 40, 25); const { cols, rows } = t.grid; for (let y = -Math.floor(rows / 2); y < rows / 2; y += 3) { for (let x = -Math.floor(cols / 2); x < cols / 2; x += 3) { const wave = Math.sin(x * 0.16 - y * 0.1 + time); t.push(); t.translate(x, y); t.char(wave > 0 ? '+' : '.'); t.charColor(base[0], base[1] + wave * 30, base[2]); t.cellColor(48, 38, 22); t.rect(2, 2); t.pop(); } } blendLayer.draw(() => { t.clear(); for (let ring = 0; ring < 5; ring++) { for (let i = 0; i < 14; i++) { const angle = i * 25.7 + ring * 19 + t.frameCount; const radius = 5 + ring * 3.8 + Math.cos(time + i) * 2; t.push(); t.rotateZ(angle); t.translate(radius, Math.sin(time * 1.5 + i) * 2); t.char(ring % 2 ? '%' : '#'); t.charColor(...accent); t.cellColor(10 + ring * 7, 12 + ring * 8, 30 + ring * 14); t.rect(4, 3); 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; drawText('TEXTMODIFIER.BLEND_DARKEN', x, y++, 100, 255, 140); drawText('------------------------------------', x, y++, 80, 100, 150); drawText('CONCEPT: DARKER CHANNELS', x, y++, 100, 220, 255); drawText('Only darker color wins.', x, y++, 140, 160, 190); drawText('Moving marks add shade.', x, y++, 140, 160, 190); drawText('------------------------------------', x, y++, 80, 100, 150); drawText(MODE: ${modeName}, x, y++, 140, 255, 180); }); t.windowResized(() => { t.resizeCanvas(window.innerWidth, window.innerHeight); });
DIFFERENCE12Use the absolute channel difference between the layer and the layers below. Use this for inversion effects, interference patterns, and animated overlaps that reveal contrast where colors diverge. Example /** * @title Textmodifier.BLEND_DIFFERENCE */ const t = textmode.create({ width: window.innerWidth, height: window.innerHeight, fontSize: 16, }); const mode = t.BLEND_DIFFERENCE; const modeName = 'BLEND_DIFFERENCE'; const accent = [255, 255, 255]; const base = [70, 180, 190]; const blendLayer = t.layers.add({ blendMode: mode, opacity: 0.9 }); const labelLayer = t.layers.add(); function drawText(text, x, y, r = 220, g = 230, b = 255) { t.push(); t.printAlign('left', 'top'); t.charColor(r, g, b); t.print(text, x, y); t.pop(); } t.draw(() => { const time = t.frameCount * 0.036; t.background(8, 16, 24); const { cols, rows } = t.grid; for (let y = -Math.floor(rows / 2); y < rows / 2; y += 3) { for (let x = -Math.floor(cols / 2); x < cols / 2; x += 3) { const wave = Math.sin(x * 0.17 + y * 0.12 + time); t.push(); t.translate(x, y); t.char(wave > 0 ? '+' : '-'); t.charColor(base[0], base[1] + wave * 35, base[2]); t.cellColor(4, 12, 20); t.rect(2, 2); t.pop(); } } blendLayer.draw(() => { t.clear(); for (let ring = 0; ring < 4; ring++) { for (let i = 0; i < 18; i++) { const angle = i * 20 + ring * 29 + t.frameCount * 1.5; const radius = 5 + ring * 4 + Math.sin(time * 2 + i) * 2.5; t.push(); t.rotateZ(angle); t.translate(radius, Math.sin(time + i) * 2); t.char(ring % 2 ? '@' : '#'); t.charColor(...accent); t.cellColor(70, 70, 70); t.rect(4, 2); 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; drawText('TEXTMODIFIER.BLEND_DIFFERENCE', x, y++, 100, 255, 140); drawText('------------------------------------', x, y++, 80, 100, 150); drawText('CONCEPT: INVERT BY DISTANCE', x, y++, 100, 220, 255); drawText('Unlike colors spark edges.', x, y++, 140, 160, 190); drawText('Overlaps flip the field.', x, y++, 140, 160, 190); drawText('------------------------------------', x, y++, 80, 100, 150); drawText(MODE: ${modeName}, x, y++, 140, 255, 180); }); t.windowResized(() => { t.resizeCanvas(window.innerWidth, window.innerHeight); });
EXCLUSION13Use a lower-contrast difference blend between the layer and the layers below. Use this for softer inversion, muted interference, and animated overlays that should shift color without the full edge of difference. Example /** * @title Textmodifier.BLEND_EXCLUSION */ const t = textmode.create({ width: window.innerWidth, height: window.innerHeight, fontSize: 16, }); const mode = t.BLEND_EXCLUSION; const modeName = 'BLEND_EXCLUSION'; const accent = [230, 230, 255]; const base = [120, 100, 190]; const blendLayer = t.layers.add({ blendMode: mode, opacity: 0.88 }); const labelLayer = t.layers.add(); function drawText(text, x, y, r = 220, g = 230, b = 255) { t.push(); t.printAlign('left', 'top'); t.charColor(r, g, b); t.print(text, x, y); t.pop(); } t.draw(() => { const time = t.frameCount * 0.033; t.background(16, 14, 30); const { cols, rows } = t.grid; for (let y = -Math.floor(rows / 2); y < rows / 2; y += 3) { for (let x = -Math.floor(cols / 2); x < cols / 2; x += 3) { const wave = Math.sin(x * 0.16 - y * 0.13 + time); t.push(); t.translate(x, y); t.char(wave > 0 ? '~' : '.'); t.charColor(base[0], base[1] + wave * 32, base[2]); t.cellColor(10, 8, 24); t.rect(2, 2); t.pop(); } } blendLayer.draw(() => { t.clear(); for (let ring = 0; ring < 4; ring++) { for (let i = 0; i < 18; i++) { const angle = i * 20 + ring * 25 - t.frameCount * 1.25; const radius = 5 + ring * 4 + Math.cos(time * 2 + i) * 2; t.push(); t.rotateZ(angle); t.translate(radius, Math.sin(time + i) * 2); t.char(ring % 2 ? '*' : '+'); t.charColor(...accent); t.cellColor(45 + ring * 12, 45 + ring * 12, 68); t.rect(3, 3); 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; drawText('TEXTMODIFIER.BLEND_EXCLUSION', x, y++, 100, 255, 140); drawText('------------------------------------', x, y++, 80, 100, 150); drawText('CONCEPT: SOFT INVERSION', x, y++, 100, 220, 255); drawText('Difference with less bite.', x, y++, 140, 160, 190); drawText('Color shifts stay muted.', x, y++, 140, 160, 190); drawText('------------------------------------', x, y++, 80, 100, 150); drawText(MODE: ${modeName}, x, y++, 140, 255, 180); }); t.windowResized(() => { t.resizeCanvas(window.innerWidth, window.innerHeight); });
HARD_LIGHT9Apply an intense contrast blend driven by the layer's brightness. Use this for crisp lighting passes, punchy animated masks, and graphic marks that should strongly reshape contrast. Example /** * @title Textmodifier.BLEND_HARD_LIGHT */ const t = textmode.create({ width: window.innerWidth, height: window.innerHeight, fontSize: 16, }); const mode = t.BLEND_HARD_LIGHT; const modeName = 'BLEND_HARD_LIGHT'; const accent = [255, 220, 70]; const base = [65, 75, 150]; const blendLayer = t.layers.add({ blendMode: mode, opacity: 0.84 }); const labelLayer = t.layers.add(); function drawText(text, x, y, r = 220, g = 230, b = 255) { t.push(); t.printAlign('left', 'top'); t.charColor(r, g, b); t.print(text, x, y); t.pop(); } t.draw(() => { const time = t.frameCount * 0.04; t.background(10, 12, 30); const { cols, rows } = t.grid; for (let y = -Math.floor(rows / 2); y < rows / 2; y += 3) { for (let x = -Math.floor(cols / 2); x < cols / 2; x += 3) { const wave = Math.sin(x * 0.18 - y * 0.12 + time); t.push(); t.translate(x, y); t.char(wave > 0 ? '#' : ':'); t.charColor(base[0], base[1] + wave * 35, base[2]); t.cellColor(8, 8, 24); t.rect(2, 2); t.pop(); } } blendLayer.draw(() => { t.clear(); for (let ring = 0; ring < 4; ring++) { for (let i = 0; i < 18; i++) { const angle = i * 20 + ring * 27 - t.frameCount * 1.6; const radius = 5 + ring * 4 + Math.sin(time * 2 + i) * 2.4; t.push(); t.rotateZ(angle); t.translate(radius, Math.sin(time + i) * 2); t.char(ring % 2 ? 'X' : '#'); t.charColor(...accent); t.cellColor(45 + ring * 20, 38 + ring * 12, 6); t.rect(4, 2); 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; drawText('TEXTMODIFIER.BLEND_HARD_LIGHT', x, y++, 100, 255, 140); drawText('------------------------------------', x, y++, 80, 100, 150); drawText('CONCEPT: HARD CONTRAST', x, y++, 100, 220, 255); drawText('The top layer drives punch.', x, y++, 140, 160, 190); drawText('Edges become more graphic.', x, y++, 140, 160, 190); drawText('------------------------------------', x, y++, 80, 100, 150); drawText(MODE: ${modeName}, x, y++, 140, 255, 180); }); t.windowResized(() => { t.resizeCanvas(window.innerWidth, window.innerHeight); });
LIGHTEN6Keep the lighter channel from either the layer or the layers below it. Use this when animated marks should only add brighter detail to the existing composition. Example /** * @title Textmodifier.BLEND_LIGHTEN */ const t = textmode.create({ width: window.innerWidth, height: window.innerHeight, fontSize: 16, }); const mode = t.BLEND_LIGHTEN; const modeName = 'BLEND_LIGHTEN'; const accent = [255, 230, 130]; const base = [40, 70, 105]; const blendLayer = t.layers.add({ blendMode: mode, opacity: 0.82 }); const labelLayer = t.layers.add(); function drawText(text, x, y, r = 220, g = 230, b = 255) { t.push(); t.printAlign('left', 'top'); t.charColor(r, g, b); t.print(text, x, y); t.pop(); } t.draw(() => { const time = t.frameCount * 0.034; t.background(8, 15, 28); const { cols, rows } = t.grid; for (let y = -Math.floor(rows / 2); y < rows / 2; y += 3) { for (let x = -Math.floor(cols / 2); x < cols / 2; x += 3) { const wave = Math.sin(x * 0.13 + y * 0.18 + time); t.push(); t.translate(x, y); t.char(wave > 0 ? ':' : '.'); t.charColor(base[0], base[1] + wave * 25, base[2]); t.cellColor(5, 10, 22); t.rect(2, 2); t.pop(); } } blendLayer.draw(() => { t.clear(); for (let ring = 0; ring < 5; ring++) { for (let i = 0; i < 14; i++) { const angle = i * 25.7 + ring * 13 - t.frameCount * 1.1; const radius = 4 + ring * 4 + Math.sin(time + i) * 2.2; t.push(); t.rotateZ(angle); t.translate(radius, Math.cos(time * 1.6 + i) * 2); t.char(ring % 2 ? '*' : '+'); t.charColor(...accent); t.cellColor(45 + ring * 15, 42 + ring * 10, 18); t.rect(3, 3); 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; drawText('TEXTMODIFIER.BLEND_LIGHTEN', x, y++, 100, 255, 140); drawText('------------------------------------', x, y++, 80, 100, 150); drawText('CONCEPT: LIGHTER CHANNELS', x, y++, 100, 220, 255); drawText('Only brighter color wins.', x, y++, 140, 160, 190); drawText('Moving marks reveal light.', x, y++, 140, 160, 190); drawText('------------------------------------', x, y++, 80, 100, 150); drawText(MODE: ${modeName}, x, y++, 140, 255, 180); }); t.windowResized(() => { t.resizeCanvas(window.innerWidth, window.innerHeight); });
MULTIPLY2Multiply the layer's colors with the layers below it. Use this to darken overlapping areas, tint a base image, or create shadow-like patterns that preserve underlying contrast. Example /** * @title Textmodifier.BLEND_MULTIPLY */ const t = textmode.create({ width: window.innerWidth, height: window.innerHeight, fontSize: 16, }); const mode = t.BLEND_MULTIPLY; const modeName = 'BLEND_MULTIPLY'; const accent = [130, 180, 255]; const base = [220, 150, 90]; const blendLayer = t.layers.add({ blendMode: mode, opacity: 0.84 }); const labelLayer = t.layers.add(); function drawText(text, x, y, r = 220, g = 230, b = 255) { t.push(); t.printAlign('left', 'top'); t.charColor(r, g, b); t.print(text, x, y); t.pop(); } t.draw(() => { const time = t.frameCount * 0.032; t.background(42, 30, 18); const { cols, rows } = t.grid; for (let y = -Math.floor(rows / 2); y < rows / 2; y += 3) { for (let x = -Math.floor(cols / 2); x < cols / 2; x += 3) { const wave = Math.sin(x * 0.12 - y * 0.16 + time); t.push(); t.translate(x, y); t.char(wave > 0 ? '%' : '+'); t.charColor(base[0], base[1] + wave * 30, base[2]); t.cellColor(50, 28, 12); t.rect(2, 2); t.pop(); } } blendLayer.draw(() => { t.clear(); for (let ring = 0; ring < 4; ring++) { for (let i = 0; i < 20; i++) { const angle = i * 18 + ring * 14 - t.frameCount * 1.1; const radius = 6 + ring * 4 + Math.cos(time * 2 + i) * 2; t.push(); t.rotateZ(angle); t.translate(radius, Math.sin(time + ring) * 2); t.char(ring % 2 ? 'X' : '#'); t.charColor(...accent); t.cellColor(12, 25 + ring * 20, 58 + ring * 12); t.rect(4, 2); 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; drawText('TEXTMODIFIER.BLEND_MULTIPLY', x, y++, 100, 255, 140); drawText('------------------------------------', x, y++, 80, 100, 150); drawText('CONCEPT: MULTIPLY SHADE', x, y++, 100, 220, 255); drawText('Overlap darkens the base.', x, y++, 140, 160, 190); drawText('Color stays tied to below.', x, y++, 140, 160, 190); drawText('------------------------------------', x, y++, 80, 100, 150); drawText(MODE: ${modeName}, x, y++, 140, 255, 180); }); t.windowResized(() => { t.resizeCanvas(window.innerWidth, window.innerHeight); });
NORMAL0Draw the layer with standard source-over alpha compositing. Use this when a layer should appear exactly as drawn, with opacity controlling how much of the layer covers the layers below it. Example /** * @title Textmodifier.BLEND_NORMAL */ const t = textmode.create({ width: window.innerWidth, height: window.innerHeight, fontSize: 16, }); const mode = t.BLEND_NORMAL; const modeName = 'BLEND_NORMAL'; const accent = [250, 180, 90]; const base = [35, 70, 120]; const blendLayer = t.layers.add({ blendMode: mode, opacity: 0.82 }); const labelLayer = t.layers.add(); function drawText(text, x, y, r = 220, g = 230, b = 255) { t.push(); t.printAlign('left', 'top'); t.charColor(r, g, b); t.print(text, x, y); t.pop(); } t.draw(() => { const time = t.frameCount * 0.035; t.background(8, 10, 22); const { cols, rows } = t.grid; for (let y = -Math.floor(rows / 2); y < rows / 2; y += 3) { for (let x = -Math.floor(cols / 2); x < cols / 2; x += 3) { const wave = Math.sin(x * 0.18 + y * 0.13 + time); t.push(); t.translate(x, y); t.char(wave > 0 ? '+' : '.'); t.charColor(base[0] + wave * 30, base[1] + 20, base[2]); t.cellColor(4, 8, 18); t.rect(2, 2); t.pop(); } } blendLayer.draw(() => { t.clear(); for (let ring = 0; ring < 4; ring++) { for (let i = 0; i < 18; i++) { const angle = i * 20 + ring * 17 + t.frameCount * (1.2 + ring * 0.2); const radius = 5 + ring * 4 + Math.sin(time * 2 + i) * 1.5; t.push(); t.rotateZ(angle); t.translate(radius, Math.sin(time + i) * 2); t.rotateZ(-angle * 0.5); t.char(ring % 2 ? '@' : '#'); t.charColor(...accent); t.cellColor(20 + ring * 18, 16, 32 + ring * 12); t.rect(3 + (i % 2), 2); 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; drawText('TEXTMODIFIER.BLEND_NORMAL', x, y++, 100, 255, 140); drawText('------------------------------------', x, y++, 80, 100, 150); drawText('CONCEPT: NORMAL BLEND', x, y++, 100, 220, 255); drawText('Animated layer covers below.', x, y++, 140, 160, 190); drawText('Opacity controls the mix.', x, y++, 140, 160, 190); drawText('------------------------------------', x, y++, 80, 100, 150); drawText(MODE: ${modeName}, x, y++, 140, 255, 180); }); t.windowResized(() => { t.resizeCanvas(window.innerWidth, window.innerHeight); });
OVERLAY7Combine multiply and screen based on the brightness below the layer. Use this for high-contrast overlays that deepen shadows and brighten highlights in one pass. Example /** * @title Textmodifier.BLEND_OVERLAY */ const t = textmode.create({ width: window.innerWidth, height: window.innerHeight, fontSize: 16, }); const mode = t.BLEND_OVERLAY; const modeName = 'BLEND_OVERLAY'; const accent = [255, 90, 190]; const base = [80, 125, 190]; const blendLayer = t.layers.add({ blendMode: mode, opacity: 0.83 }); const labelLayer = t.layers.add(); function drawText(text, x, y, r = 220, g = 230, b = 255) { t.push(); t.printAlign('left', 'top'); t.charColor(r, g, b); t.print(text, x, y); t.pop(); } t.draw(() => { const time = t.frameCount * 0.037; t.background(10, 18, 32); const { cols, rows } = t.grid; for (let y = -Math.floor(rows / 2); y < rows / 2; y += 3) { for (let x = -Math.floor(cols / 2); x < cols / 2; x += 3) { const wave = Math.sin(x * 0.14 + y * 0.12 + time); t.push(); t.translate(x, y); t.char(wave > 0 ? '=' : '-'); t.charColor(base[0], base[1] + wave * 35, base[2]); t.cellColor(8, 14, 26); t.rect(2, 2); t.pop(); } } blendLayer.draw(() => { t.clear(); for (let ring = 0; ring < 4; ring++) { for (let i = 0; i < 18; i++) { const angle = i * 20 + ring * 21 + t.frameCount * 1.35; const radius = 5 + ring * 4 + Math.sin(time * 2 + i) * 2; t.push(); t.rotateZ(angle); t.translate(radius, Math.sin(time + i) * 2); t.char(ring % 2 ? '@' : '%'); t.charColor(...accent); t.cellColor(35 + ring * 18, 10, 42 + ring * 12); t.rect(4, 2); 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; drawText('TEXTMODIFIER.BLEND_OVERLAY', x, y++, 100, 255, 140); drawText('------------------------------------', x, y++, 80, 100, 150); drawText('CONCEPT: CONTRAST BOOST', x, y++, 100, 220, 255); drawText('Dark stays deep, light lifts.', x, y++, 140, 160, 190); drawText('Good for graphic masks.', x, y++, 140, 160, 190); drawText('------------------------------------', x, y++, 80, 100, 150); drawText(MODE: ${modeName}, x, y++, 140, 255, 180); }); t.windowResized(() => { t.resizeCanvas(window.innerWidth, window.innerHeight); });
SCREEN3Screen the layer against the layers below it. Use this for soft light washes and bright overlays that lift darker areas while preserving highlight detail. Example /** * @title Textmodifier.BLEND_SCREEN */ const t = textmode.create({ width: window.innerWidth, height: window.innerHeight, fontSize: 16, }); const mode = t.BLEND_SCREEN; const modeName = 'BLEND_SCREEN'; const accent = [100, 220, 255]; const base = [85, 45, 120]; const blendLayer = t.layers.add({ blendMode: mode, opacity: 0.8 }); const labelLayer = t.layers.add(); function drawText(text, x, y, r = 220, g = 230, b = 255) { t.push(); t.printAlign('left', 'top'); t.charColor(r, g, b); t.print(text, x, y); t.pop(); } t.draw(() => { const time = t.frameCount * 0.036; t.background(12, 8, 30); const { cols, rows } = t.grid; for (let y = -Math.floor(rows / 2); y < rows / 2; y += 3) { for (let x = -Math.floor(cols / 2); x < cols / 2; x += 3) { const wave = Math.sin(x * 0.2 + y * 0.08 + time); t.push(); t.translate(x, y); t.char(wave > 0 ? '~' : '.'); t.charColor(base[0], base[1] + wave * 28, base[2]); t.cellColor(8, 4, 22); t.rect(2, 2); t.pop(); } } blendLayer.draw(() => { t.clear(); for (let ring = 0; ring < 4; ring++) { for (let i = 0; i < 18; i++) { const angle = i * 20 + ring * 23 + t.frameCount * 1.3; const radius = 6 + ring * 3.5 + Math.sin(time + i) * 2; t.push(); t.rotateZ(angle); t.translate(radius, Math.cos(time * 1.4 + i) * 2); t.char(ring % 2 ? '*' : '+'); t.charColor(...accent); t.cellColor(5, 30 + ring * 10, 45 + ring * 20); t.rect(3, 3); 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; drawText('TEXTMODIFIER.BLEND_SCREEN', x, y++, 100, 255, 140); drawText('------------------------------------', x, y++, 80, 100, 150); drawText('CONCEPT: SCREEN LIGHT', x, y++, 100, 220, 255); drawText('Dark areas lift upward.', x, y++, 140, 160, 190); drawText('Highlights stay open.', x, y++, 140, 160, 190); drawText('------------------------------------', x, y++, 80, 100, 150); drawText(MODE: ${modeName}, x, y++, 140, 255, 180); }); t.windowResized(() => { t.resizeCanvas(window.innerWidth, window.innerHeight); });
SOFT_LIGHT8Apply a softer contrast blend based on the layer's brightness. Use this for gentle illumination, atmospheric color movement, and animated texture that should stay subtle. Example /** * @title Textmodifier.BLEND_SOFT_LIGHT */ const t = textmode.create({ width: window.innerWidth, height: window.innerHeight, fontSize: 16, }); const mode = t.BLEND_SOFT_LIGHT; const modeName = 'BLEND_SOFT_LIGHT'; const accent = [255, 170, 150]; const base = [70, 120, 160]; const blendLayer = t.layers.add({ blendMode: mode, opacity: 0.86 }); const labelLayer = t.layers.add(); function drawText(text, x, y, r = 220, g = 230, b = 255) { t.push(); t.printAlign('left', 'top'); t.charColor(r, g, b); t.print(text, x, y); t.pop(); } t.draw(() => { const time = t.frameCount * 0.029; t.background(12, 20, 30); const { cols, rows } = t.grid; for (let y = -Math.floor(rows / 2); y < rows / 2; y += 3) { for (let x = -Math.floor(cols / 2); x < cols / 2; x += 3) { const wave = Math.sin(x * 0.12 + y * 0.15 + time); t.push(); t.translate(x, y); t.char(wave > 0 ? '~' : '.'); t.charColor(base[0], base[1] + wave * 28, base[2]); t.cellColor(8, 14, 22); t.rect(2, 2); t.pop(); } } blendLayer.draw(() => { t.clear(); for (let ring = 0; ring < 5; ring++) { for (let i = 0; i < 14; i++) { const angle = i * 25.7 + ring * 16 + t.frameCount * 0.9; const radius = 5 + ring * 3.5 + Math.sin(time + i) * 1.5; t.push(); t.rotateZ(angle); t.translate(radius, Math.cos(time + i) * 2); t.char(ring % 2 ? '+' : '*'); t.charColor(...accent); t.cellColor(28 + ring * 14, 20 + ring * 10, 30); t.rect(3, 3); 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; drawText('TEXTMODIFIER.BLEND_SOFT_LIGHT', x, y++, 100, 255, 140); drawText('------------------------------------', x, y++, 80, 100, 150); drawText('CONCEPT: SOFT CONTRAST', x, y++, 100, 220, 255); drawText('Texture changes gently.', x, y++, 140, 160, 190); drawText('Useful for atmosphere.', x, y++, 140, 160, 190); drawText('------------------------------------', x, y++, 80, 100, 150); drawText(MODE: ${modeName}, x, y++, 140, 255, 180); }); t.windowResized(() => { t.resizeCanvas(window.innerWidth, window.innerHeight); });
SUBTRACT4Subtract the layer's colors from the layers below it. Use this for cutout-like animation, eroded trails, and dark pulses that remove brightness from the composite. Example /** * @title Textmodifier.BLEND_SUBTRACT */ const t = textmode.create({ width: window.innerWidth, height: window.innerHeight, fontSize: 16, }); const mode = t.BLEND_SUBTRACT; const modeName = 'BLEND_SUBTRACT'; const accent = [180, 230, 255]; const base = [210, 120, 70]; const blendLayer = t.layers.add({ blendMode: mode, opacity: 0.86 }); const labelLayer = t.layers.add(); function drawText(text, x, y, r = 220, g = 230, b = 255) { t.push(); t.printAlign('left', 'top'); t.charColor(r, g, b); t.print(text, x, y); t.pop(); } t.draw(() => { const time = t.frameCount * 0.033; t.background(48, 28, 18); const { cols, rows } = t.grid; for (let y = -Math.floor(rows / 2); y < rows / 2; y += 3) { for (let x = -Math.floor(cols / 2); x < cols / 2; x += 3) { const wave = Math.sin(x * 0.11 + y * 0.19 + time); t.push(); t.translate(x, y); t.char(wave > 0 ? '=' : '+'); t.charColor(base[0], base[1] + wave * 30, base[2]); t.cellColor(55, 24, 10); t.rect(2, 2); t.pop(); } } blendLayer.draw(() => { t.clear(); for (let ring = 0; ring < 4; ring++) { for (let i = 0; i < 18; i++) { const angle = i * 20 + ring * 15 - t.frameCount * 1.4; const radius = 5 + ring * 4 + Math.sin(time * 2 + i) * 1.8; t.push(); t.rotateZ(angle); t.translate(radius, Math.cos(time + i) * 2); t.char(ring % 2 ? '/' : '\\'); t.charColor(...accent); t.cellColor(35 + ring * 18, 45 + ring * 12, 58); t.rect(4, 2); 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; drawText('TEXTMODIFIER.BLEND_SUBTRACT', x, y++, 100, 255, 140); drawText('------------------------------------', x, y++, 80, 100, 150); drawText('CONCEPT: SUBTRACT CUTS', x, y++, 100, 220, 255); drawText('The top layer removes light.', x, y++, 140, 160, 190); drawText('Bright marks carve paths.', x, y++, 140, 160, 190); drawText('------------------------------------', x, y++, 80, 100, 150); drawText(MODE: ${modeName}, x, y++, 140, 255, 180); }); t.windowResized(() => { t.resizeCanvas(window.innerWidth, window.innerHeight); });