滤镜与前端应用

滤镜与前端应用

Tags
WebMedia
Published
Sep 3, 2024
💡
这篇文章教大家写一个醒图(XD

一、前言

大家常常会开玩笑说不要看到摄影师的原图,因为往往会做一些后期处理照片。毕竟,原图可能存在着各种各样的瑕疵,比如曝光不准确、色彩不鲜艳、构图不够完美等等。摄影师们通过后期处理,可以对这些问题进行修正和优化,让照片更加出色。比如调整亮度和对比度,让画面更加清晰和有层次感;增强色彩饱和度,使照片更加鲜艳和生动;甚至还可以进行裁剪和旋转,以达到更好的构图效果。
notion image
notion image
PS: 原图因为太大没有贴在这里(📍徐汇滨江)
最简单的操作大家可能都比较熟悉:
notion image
  • 打开一个修图软件,例如醒图
  • 选中图片
  • 熟练的点开一个滤镜处理
  • 导出
即可拥有一个风格化处理的图片
notion image
 

二、滤镜

其实下载的是一个完整的叫做 LUT (Look-Up-Table) 的东西。
notion image
notion image
💡
为什么不是 32 *32 *32,要是 33 *33 *33 呢?
3D LUTs 将红色、绿色和蓝色映射到一个三维立方体的三个轴上。颜色值可以相对调整,这允许任何颜色映射到任何其他颜色。
例如: 一个33*33*33 规格的一个 LUT 文件
暂时无法在飞书文档外展示此内容
notion image
notion image
notion image
33*33*33=35937 个点
eg: https://lut.tgratzer.com/

计算方式

一个图片往往是一个二维数组的数据组成的,处理图片,就是对于每一个像素点的值做一次运算。
// 对图片的 二维数组 进行处理
function applyLUT(buffer,width,height,lutData) {
    const data = new Uint8Array(buffer);
    const output = new Uint8Array(width * height * 4);
    const lutSize = 33;// 假设使用 33x33x33 的 LUT

    for (let i = 0; i < data.length; i += 4) {
// 获取原始 RGB 值
        const r = data[i];
        const g = data[i + 1];
        const b = data[i + 2];
        const a = data[i + 3];

// 计算 LUT 索引
        const lutR = (r / 255) * (lutSize - 1);
        const lutG = (g / 255) * (lutSize - 1);
        const lutB = (b / 255) * (lutSize - 1);

// 获取新的颜色值(通过 LUT)const newColor = lookupColor(lutR, lutG, lutB, lutData, lutSize);

// 写入新的颜色值
        output[i] = newColor.r;
        output[i + 1] = newColor.g;
        output[i + 2] = newColor.b;
        output[i + 3] = a;
    }

    return output.buffer;
}
notion image
  • 某个点是深天空蓝,RGB 色值是(0, 191, 255)
  • .cube 滤镜中的数值范围都是 0-1,因此,我们的 RGB 色值也要转换到这个范围(0, 0.7490196, 1):
  • 我们的 Cube文件是 33*33*33
  • 分别乘以32,得到:(0, 23.9686272, 32)
  • B 是32,也就是蓝色是32,正好是整数,因为33个色块,每一块四块蓝色都是固定的,很显然,蓝色的这个色块是最后一个。
  • G比较麻烦,因为23.9686272是小数,我们不妨先简单点来算取近似整数24,则表示最后一格纵向第24个点是我们的目标颜色。
  • R 是0,因此水平第1个点。
  • 算一下索引:(32 * 32) * 32 + 24 * 32 + 1 = 33537 行
    • notion image
  • (0.03102911, 0.78814191, 0.90205824) 乘以 255 之后进行四舍五入之后变成 (8, 201, 230)
notion image
  • 更仔细计算的话:一般在小数的情况下会进行差值计算
    • 三线性插值(Trilinear Interpolation)原理:
    • notion image
      // 假设我们有一个颜色点 (r,g,b)
      const r = 128; // 0-255
      const g = 128;
      const b = 128;
      
      // 在 33x33x33 的 LUT 中,需要映射到 0-32 的范围
      const lutSize = 33;
      const lutR = (r / 255) * (lutSize - 1); // 例如: 16.5
      const lutG = (g / 255) * (lutSize - 1);
      const lutB = (b / 255) * (lutSize - 1);
      
      // 这个点会落在 LUT 的 8 个格点之间
      
      // 获取周围 8 个点的坐标
      const r0 = Math.floor(lutR);  // 16
      const g0 = Math.floor(lutG);
      const b0 = Math.floor(lutB);
      
      const r1 = Math.min(r0 + 1, lutSize - 1);  // 17
      const g1 = Math.min(g0 + 1, lutSize - 1);
      const b1 = Math.min(b0 + 1, lutSize - 1);
      
      // 计算权重(小数部分)
      const rw = lutR - r0;  // 0.5
      const gw = lutG - g0;
      const bw = lutB - b0;
      
      function trilinearInterpolation(c000, c001, c010, c011, c100, c101, c110, c111, x, y, z) {
          // 第一步:在 x 方向插值(R 方向)
          const c00 = c000 * (1 - x) + c100 * x;  // 前下左
          const c01 = c001 * (1 - x) + c101 * x;  // 前下右
          const c10 = c010 * (1 - x) + c110 * x;  // 前上左
          const c11 = c011 * (1 - x) + c111 * x;  // 前上右
      
          // 第二步:在 y 方向插值(G 方向)
          const c0 = c00 * (1 - y) + c10 * y;  // 前面
          const c1 = c01 * (1 - y) + c11 * y;  // 后面
      
          // 第三步:在 z 方向插值(B 方向)
          return c0 * (1 - z) + c1 * z;
      }
      
      
          c110 -------- c111
           /|           /|
          / |          / |
      c010 -------- c011 |
        |   |         |  |
        |  c100 ----- |-- c101
        | /           |  /
        |/            | /
      c000 -------- c001
      
      • c000: 前下左点 (r0,g0,b0)
      • c001: 前下右点 (r0,g0,b1)
      • c010: 前上左点 (r0,g1,b0)
      • c011: 前上右点 (r0,g1,b1)
      • c100: 后下左点 (r1,g0,b0)
      • c101: 后下右点 (r1,g0,b1)
      • c110: 后上左点 (r1,g1,b0)
      • c111: 后上右点 (r1,g1,b1)
      
  • 这种插值计算确保了:
    • 颜色过渡平滑
    • 没有明显的阶梯效果
    • 保持了 LUT 的精确性
    • 适用于任何大小的 LUT
我们之前常用的修图软件里面会有,百分比的选项,请问这个一般是怎么算出来的?

滤镜强度

0-100%
notion image
当计算出某个颜色对应的 finalColor 后:
// 在应用最终颜色时,使用强度参数进行混合
pixels[i] = Math.round(pixels[i] * (1 - intensity) + finalColor[0] * intensity);
pixels[i + 1] = Math.round(pixels[i + 1] * (1 - intensity) + finalColor[1] * intensity);
pixels[i + 2] = Math.round(pixels[i + 2] * (1 - intensity) + finalColor[2] * intensity);

三、前端渲染

Mac M2 Pro 32G 环境下 480p 视频

主线程计算

帧率稳定在 11 fps
notion image

Worker 计算

帧率稳定在 40 fps
notion image
https://codesandbox.io/p/sandbox/5vjk78

再往上优化性能

之前介绍过 WebGL 和 WebGPU 的一些优化手段我就不重复介绍了。

一些有意思的问题

  • 之前 Photoshop 和 LightRoom 和醒图区别?
  • 为什么30M 的图片,使用同样的滤镜,LR 导出同样格式还是 30M,醒图导出只有 2M