数字故障美学:用 Canvas 实现 RGB 偏移、像素排序与扫描线

数字故障美学:用 Canvas 实现 RGB 偏移、像素排序与扫描线

故障艺术 (Glitch Art) 不只是一种视觉风格------它是数据被错误解读时产生的意外美。本文将用 Canvas 实现 6 种失真算法,让你彻底理解「破坏」的美学。


什么是 Glitch Art?

在模拟信号时代,雪花屏、视频撕裂、磁带卡顿是技术缺陷。今天,这些「错误」成了一种视觉语言------赛博朋克的标志性元素、音乐视频的转场特效、独立游戏的滤镜风格。

本文将实现:

  • RGB 通道偏移 (Channel Shift)
  • 像素排序 (Pixel Sort)
  • 扫描线 (Scanlines)
  • 色差 (Chromatic Aberration)
  • 水平撕裂 (Screen Tear)
  • 数据损坏模拟 (Data Corruption)

1. RGB 通道偏移

将 R、G、B 三个通道分别偏移不同距离,模拟色差和信号错位。

javascript 复制代码
function rgbShift(ctx, width, height, shiftX = 8, shiftY = 0) {
  const imageData = ctx.getImageData(0, 0, width, height);
  const src = imageData.data;
  const dst = new Uint8ClampedArray(src.length);

  for (let y = 0; y < height; y++) {
    for (let x = 0; x < width; x++) {
      const idx = (y * width + x) * 4;

      // R 通道向左偏移
      const rx = Math.max(0, x - shiftX);
      const rIdx = (y * width + rx) * 4;

      // B 通道向右偏移
      const bx = Math.min(width - 1, x + shiftX);
      const bIdx = (y * width + bx) * 4;

      // G 通道保持不变
      dst[idx]     = src[rIdx];     // R
      dst[idx + 1] = src[idx + 1];  // G
      dst[idx + 2] = src[bIdx + 2]; // B
      dst[idx + 3] = 255;           // A
    }
  }

  ctx.putImageData(new ImageData(dst, width, height), 0, 0);
}

效果:画面出现红蓝双重影,仿佛 3D 电影摘下眼镜后的视觉。


2. 像素排序------Glitch 艺术的核心

像素排序是 glitch 艺术最具辨识度的技术:选取一行(或列)像素,按亮度排序,创造出「融化」的视觉效果。

javascript 复制代码
function pixelSort(ctx, width, height, threshold = 128, vertical = false) {
  const imageData = ctx.getImageData(0, 0, width, height);
  const data = imageData.data;

  for (let y = 0; y < height; y++) {
    let start = -1;

    for (let x = 0; x < width; x++) {
      const idx = (y * width + x) * 4;
      const brightness = (data[idx] + data[idx + 1] + data[idx + 2]) / 3;

      if (brightness < threshold && start === -1) {
        start = x; // 暗部开始,标记排序区间
      } else if (brightness >= threshold && start !== -1) {
        sortSegment(data, y, start, x - 1, width);
        start = -1;
      }
    }
    if (start !== -1) {
      sortSegment(data, y, start, width - 1, width);
    }
  }

  ctx.putImageData(imageData, 0, 0);
}

function sortSegment(data, y, x1, x2, width) {
  const pixels = [];
  for (let x = x1; x <= x2; x++) {
    const idx = (y * width + x) * 4;
    pixels.push({
      r: data[idx], g: data[idx + 1],
      b: data[idx + 2], a: data[idx + 3],
      brightness: (data[idx] + data[idx + 1] + data[idx + 2]) / 3,
    });
  }

  pixels.sort((a, b) => a.brightness - b.brightness);

  for (let i = 0; i < pixels.length; i++) {
    const idx = (y * width + (x1 + i)) * 4;
    data[idx]     = pixels[i].r;
    data[idx + 1] = pixels[i].g;
    data[idx + 2] = pixels[i].b;
    data[idx + 3] = pixels[i].a;
  }
}

3. 扫描线

模拟 CRT 显示器的水平扫描线------每隔 N 行降低亮度。

javascript 复制代码
function scanlines(ctx, width, height, gap = 3, opacity = 0.3) {
  const imageData = ctx.getImageData(0, 0, width, height);
  const data = imageData.data;

  for (let y = 0; y < height; y++) {
    if (y % gap === 0) continue; // 保留扫描线间隙

    for (let x = 0; x < width; x++) {
      const idx = (y * width + x) * 4;
      data[idx]     = data[idx] * (1 - opacity);
      data[idx + 1] = data[idx + 1] * (1 - opacity);
      data[idx + 2] = data[idx + 2] * (1 - opacity);
    }
  }

  ctx.putImageData(imageData, 0, 0);
}

4. 色差 (Chromatic Aberration)

与 RGB 偏移不同,色差模拟的是镜头缺陷------边缘比中心偏移更大。

javascript 复制代码
function chromaticAberration(ctx, width, height, maxShift = 12) {
  const imageData = ctx.getImageData(0, 0, width, height);
  const src = imageData.data;
  const dst = new Uint8ClampedArray(src.length);
  const cx = width / 2, cy = height / 2;
  const maxDist = Math.sqrt(cx * cx + cy * cy);

  for (let y = 0; y < height; y++) {
    for (let x = 0; x < width; x++) {
      const idx = (y * width + x) * 4;
      const dist = Math.sqrt((x - cx) ** 2 + (y - cy) ** 2);
      const shift = (dist / maxDist) * maxShift; // 边缘偏移最大

      const rx = clamp(x + shift, 0, width - 1);
      const bx = clamp(x - shift, 0, width - 1);

      dst[idx]     = src[(y * width + Math.floor(rx)) * 4];
      dst[idx + 1] = src[idx + 1];
      dst[idx + 2] = src[(y * width + Math.floor(bx)) * 4 + 2];
      dst[idx + 3] = 255;
    }
  }

  ctx.putImageData(new ImageData(dst, width, height), 0, 0);
}

5. 水平撕裂

模拟 VHS 磁带损坏------随机行被替换为相邻行的内容。

javascript 复制代码
function screenTear(ctx, width, height, intensity = 0.1) {
  const imageData = ctx.getImageData(0, 0, width, height);
  const data = imageData.data;

  for (let y = 0; y < height - 1; y++) {
    if (Math.random() > intensity) continue;

    // 将当前行的部分像素替换为下一行的
    const tearStart = Math.floor(Math.random() * width * 0.8);
    const tearWidth = Math.floor(Math.random() * width * 0.3) + 10;

    for (let x = tearStart; x < Math.min(tearStart + tearWidth, width); x++) {
      const srcIdx = (y * width + x) * 4;
      const dstIdx = ((y + 1) * width + (x + Math.floor(Math.random() * 20 - 10))) * 4;
      const clampedIdx = Math.max(0, Math.min(dstIdx, data.length - 4));

      data[srcIdx]     = data[clampedIdx];
      data[srcIdx + 1] = data[clampedIdx + 1];
      data[srcIdx + 2] = data[clampedIdx + 2];
    }
  }

  ctx.putImageData(imageData, 0, 0);
}

6. 数据损坏模拟 (Data Corruption)

随机替换像素块为纯色或噪点,模拟 JPEG 文件头损坏的效果。

javascript 复制代码
function dataCorruption(ctx, width, height, blockCount = 5) {
  for (let i = 0; i < blockCount; i++) {
    const bx = Math.floor(Math.random() * (width - 40));
    const by = Math.floor(Math.random() * (height - 40));
    const bw = Math.floor(Math.random() * 30) + 20;
    const bh = Math.floor(Math.random() * 30) + 20;

    // 随机纯色块或噪点块
    if (Math.random() > 0.5) {
      ctx.fillStyle = `hsl(${Math.random() * 360}, 80%, ${Math.random() * 50 + 30}%)`;
      ctx.fillRect(bx, by, bw, bh);
    } else {
      const imageData = ctx.getImageData(bx, by, bw, bh);
      for (let j = 0; j < imageData.data.length; j += 4) {
        imageData.data[j]     = Math.random() * 255;
        imageData.data[j + 1] = Math.random() * 255;
        imageData.data[j + 2] = Math.random() * 255;
      }
      ctx.putImageData(imageData, bx, by);
    }
  }
}

组合使用

真正的 glitch 效果是多种算法叠加:

javascript 复制代码
function fullGlitchPipeline(ctx, width, height) {
  rgbShift(ctx, width, height, 6);
  pixelSort(ctx, width, height, 140);
  chromaticAberration(ctx, width, height, 4);
  scanlines(ctx, width, height, 2, 0.2);
  screenTear(ctx, width, height, 0.05);
  dataCorruption(ctx, width, height, 3);
}

每种算法的参数都可以做成滑块,让用户实时调节------这就是 Glitch Studio 的核心设计。


在线体验

完整源码及实时 demo,支持 6 种失真算法叠加、参数调节和实时预览:

👉 Glitch Studio


下一篇预告:《把摄像头变成终端艺术:实时 ASCII 字符画原理与实现》

相关推荐
小森林之主1 小时前
深入正则表达式:核心语法与实战剖析
javascript·python·正则表达式·编程技巧·字符串处理
alexander0682 小时前
JavaScript 中,对象内部函数的几种等价写法,对象外部的 几种等价写法
javascript
云水一下2 小时前
Vue.js从零到精通系列(八):项目实战——构建一个完整的电商后台管理系统
前端·javascript·vue.js
LAM LAB2 小时前
【Web】网页如何模拟移动端获取定位\定位模拟测试
开发语言·前端·javascript
小森林之主2 小时前
JavaScript 正则表达式:从零开始的实战对比
javascript·正则表达式·前端开发·性能对比·文本处理
weixin_471383032 小时前
Taro-04-网络请求
前端·javascript·taro
快乐的哈士奇2 小时前
【Next.js实战②】Excel 派送表动态解析:表头识别与 FIELD_ALIASES 映射
前端·javascript·excel
研☆香3 小时前
jQuery特殊属性操作方法
前端·javascript·jquery
努力的lpp3 小时前
渗透主流工具完整参数手册(sqlmap、Nmap、Hydra、Dirsearch、Xray)
javascript·网络协议·测试工具·安全·http·工具