颜色矩阵

FrameBuffer 通过两个方法支持原生 4x4 RGBA 矩阵变换:colorMatrix(...)colorMatrixUniform(...)

当你想以逐单元格的强度变换特定单元格时,使用 colorMatrix(...)。当你想将一个变换应用到整个缓冲区时,使用 colorMatrixUniform(...)

两个方法都基于归一化的 RGBA 值(0.01.0)工作,可以针对前景、背景或两者通道。

API

import { TargetChannel } from "@opentui/core"

frameBuffer.colorMatrix(
  matrix: Float32Array,
  cellMask: Float32Array,
  strength = 1.0,
  target = TargetChannel.Both,
)

frameBuffer.colorMatrixUniform(
  matrix: Float32Array,
  strength = 1.0,
  target = TargetChannel.Both,
)

矩阵格式

矩阵必须是恰好 16 个浮点数,按行优先顺序排列。

每一行定义一个输出通道:

Row 0: [r->r, g->r, b->r, a->r]  // output red
Row 1: [r->g, g->g, b->g, a->g]  // output green
Row 2: [r->b, g->b, b->b, a->b]  // output blue
Row 3: [r->a, g->a, b->a, a->a]  // output alpha

每个单元格分两步进行变换:

newColor = M * color
output = original + (newColor - original) * strength

这意味着 strength = 0.0 保持原始颜色,strength = 1.0 应用完整的矩阵结果。

目标通道

使用 TargetChannel 枚举来选择受影响的颜色缓冲区:

  • TargetChannel.FG (1):仅前景
  • TargetChannel.BG (2):仅背景
  • TargetChannel.Both (3):前景 + 背景

colorMatrix 单元格遮罩格式

cellMask 使用打包的三元组:

[x, y, perCellStrength, x, y, perCellStrength, ...]
  • xy 是单元格坐标
  • perCellStrength 会与方法的 strength 相乘
  • 末尾不完整的值(不是 3 的倍数)会被忽略
  • 越界或非有限值的坐标会被跳过
  • 非有限值的有效强度会被跳过

此方法适用于仅影响缓冲区一部分的效果,例如扫描线、暗角或局部调色。

colorMatrixUniform 行为

colorMatrixUniform 将相同的矩阵应用到所选通道的每个像素。

  • 优化路径使用 SIMD 每次处理 4 个像素
  • 标量回退处理剩余的任何像素
  • strength === 0 或非有限值的 strength 会提前返回

使用此方法进行全帧调色和全局后处理。

无钳制

结果值不会被钳制到 [0, 1]。矩阵系数和强度可以将通道值推高到 1.0 以上或降低到 0.0 以下。

示例:反转整个缓冲区

import { INVERT_MATRIX, TargetChannel } from "@opentui/core"

frameBuffer.colorMatrixUniform(INVERT_MATRIX, 1.0, TargetChannel.Both)

示例:对选定单元格应用棕褐色效果

import { SEPIA_MATRIX, TargetChannel } from "@opentui/core"

// Affect only three cells at custom strengths
const cellMask = new Float32Array([
  5,
  2,
  1.0, // full sepia at (5,2)
  6,
  2,
  0.5, // half sepia at (6,2)
  7,
  2,
  0.25, // subtle sepia at (7,2)
])

frameBuffer.colorMatrix(SEPIA_MATRIX, cellMask, 1.0, TargetChannel.FG)

示例:自定义饱和度矩阵

import { TargetChannel } from "@opentui/core"

function createSaturationMatrix(saturation: number): Float32Array {
  const rw = 0.299
  const gw = 0.587
  const bw = 0.114
  const inv = 1 - saturation

  return new Float32Array([
    rw * inv + saturation,
    gw * inv,
    bw * inv,
    0,
    rw * inv,
    gw * inv + saturation,
    bw * inv,
    0,
    rw * inv,
    gw * inv,
    bw * inv + saturation,
    0,
    0,
    0,
    0,
    1,
  ])
}

const saturation = createSaturationMatrix(1.25)
frameBuffer.colorMatrixUniform(saturation, 0.8, TargetChannel.Both)