【WEB】记录如何实现PS 中的色相/饱和度/明度 调节功能

提要

在 PS中色相、饱和度、明度调节的一个功能模块。这两天业务需要一个一样的功能,用于批量自动渲染。 那在 前端如何实现呢,特此记录下。

功能效果如下:

思路

  • 色相的调节,可以经过 CSS或 Canvas 中的 filter 属性 hue滤镜来实现.
  • 饱和度的调节,可以经过 CSS或 Canvas 中 filter 属性的 saturate 滤镜来实现。
  • 明度的调节,哈哈哈,可能大家想当然的觉得是brightness滤镜,其实不是的!!这里是个坑, 在 PS中的明度和亮度是有区别的,PS 中明度向上调满是变成了纯白,向下调满是纯黑。两者的算法和效果是有区别的。那应当如何??应该自己写一个 filter 去应用画布数据。

实现

  1. 可以看到我以下代码主要是通过filter 来应用效果的:

ctx.filter = hue-rotate(${hueValue}deg) saturate(${saturateValue}%) brightness(${brightnessValue}%) contrast(${contrastValue}%) opacity(${opacityValue}%);

其中:

  • hue-rotate :对应的是色相。
  • saturate : 对应的是饱和度,取值 0%~100%(或以上),PS 是从-100 到+100,所以我们需要做数据转换。
  • brightness : 是亮度,不是 PS 的明度。
  • contrast : 是对比度
  • opacity : 是透明度
ts 复制代码
 /**
   * 绘制图片
   */
  function drawImage() {
    const canvas = refCanvas.current;
    const ctx = canvas.getContext("2d");

    const hueValue = hue;
    const saturateValue = saturation + 100;
    const brightnessValue = brightness + 100;
    const contrastValue = contrast + 100;
    const opacityValue = opacity;

    ctx.filter = `hue-rotate(${hueValue}deg) saturate(${saturateValue}%) brightness(${brightnessValue}%)  contrast(${contrastValue}%) opacity(${opacityValue}%)`;
    ctx.clearRect(0, 0, canvas.width, canvas.height);
    if (refImage.current != null) {
      ctx.drawImage(refImage.current, 0, 0, canvas.width, canvas.height);
      //应用明度滤镜
      applyLightFilter(ctx, lightness / 100);
    } else {
      refImage.current = new Image();
      refImage.current.style.objectFit = "contain";
      refImage.current.style.background = "transparent";
      refImage.current.setAttribute("crossOrigin", "anonymous");
      refImage.current.crossOrigin = "anonymous";
      refImage.current.style.width = refCanvas.current.style.width;
      refImage.current.style.height = refCanvas.current.style.height;
      refImage.current.src = `${src}?${Date.now()}`;
      refImage.current.onload = () => {
        ctx.drawImage(refImage.current, 0, 0, canvas.width, canvas.height);
        //明度滤镜
        applyLightFilter(ctx, lightness / 100);
      };
    }
  }

自定义明度的滤镜实现

实现原理: 明度为正数的情况下,相当于混合了一张纯白的图片,负数的情况混合了一张纯黑的图片。然后设置这个纯色图片的透明度,就可以达到 PS 中的明度滤镜效果了。

  • 这里有个小知识点,Canvas 的 ImageData是一个数组,其中每四位是一个像素点的颜色,分别对应[a,r,g,b],也就是透明度,红色,绿色,蓝色。
ts 复制代码
/**
 * 明亮效果的滤镜
 * @param ctx 
 * @param light 0 ~ 1 ,百分比
 */
function applyLightFilter(ctx: CanvasRenderingContext2D, light: number) {
  const imageData = ctx.getImageData(0, 0, ctx.canvas.width, ctx.canvas.height);

  var nPixels = imageData.data.length;

  //判断亮度是正数还是负数,来设置混合的图片是白色还是黑色。其中亮度值是对应的透明度
  const lightValue = light < 0 ? 0 : 255;
  const r = lightValue;
  const g = lightValue;
  const b = lightValue;
  const a = Math.abs(light) * 255;

  for (var i = 3; i < nPixels; i += 4) {
    if (imageData.data[i] == 0) {
      //透明部分跳过
    } else {
      const a1 = imageData.data[i];
      const r1 = imageData.data[i + 1];
      const g1 = imageData.data[i + 2];
      const b1 = imageData.data[i + 3];
      // 颜色混合
      imageData.data[i] = a1 + a - (a1 * a) / 255;
      imageData.data[i + 1] =
        (r1 * a1 + r * a - (r1 * a1 * a) / 255) / (a1 + a - (a1 * a) / 255);
      imageData.data[i + 2] =
        (g1 * a1 + g * a - (g1 * a1 * a) / 255) / (a1 + a - (a1 * a) / 255);
      imageData.data[i + 3] =
        (b1 * a1 + b * a - (b1 * a1 * a) / 255) / (a1 + a - (a1 * a) / 255);
    }
  }
  ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
  ctx.putImageData(imageData, 0, 0);
}

最终实现效果

Bye~

相关推荐
李q华几秒前
react与vue的渲染原理
javascript·vue.js·react.js
爱看书的小沐2 分钟前
【小沐杂货铺】基于Three.JS绘制太阳系Solar System(GIS 、WebGL、vue、react,提供全部源代码)第2期
javascript·vue.js·gis·webgl·three.js·地球·earth
孟陬4 分钟前
组件库自动化脚本:监听构建成功并打开浏览器
前端·node.js
前端设计诗10 分钟前
Cursor Figma MCP 完整使用教程
前端·javascript·cursor
Mike_jia11 分钟前
ImHex:开源十六进制编辑器的终极指南——逆向工程师的“夜视仪
前端
Mike_jia15 分钟前
用 Linux Wifi Hotspot 开启无线共享革命:从极简部署到企业级实战
前端
北京_宏哥21 分钟前
🔥Python零基础从入门到精通详细教程2-变量与常量
前端·python·面试
CAD老兵23 分钟前
TypeScript 中如何表示当前类的类型
前端
架构个驾驾23 分钟前
Mixin 深度解析与实战指南
前端·javascript·vue.js