【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~

相关推荐
GIS之路12 分钟前
ArcPy,一个基于 Python 的 GIS 开发库简介
前端
可夫小子2 小时前
OpenClaw基础-为什么会有两个端口
前端
喝拿铁写前端2 小时前
Dify 构建 FE 工作流:前端团队可复用 AI 工作流实战
前端·人工智能
喝咖啡的女孩2 小时前
React 合成事件系统
前端
从文处安3 小时前
「九九八十一难」组合式函数到底有什么用?
前端·vue.js
前端Hardy3 小时前
面试官:JS数组的常用方法有哪些?这篇总结让你面试稳了!
javascript·面试
用户5962585736063 小时前
戴上AI眼镜逛花市——感受不一样的体验
前端
yuki_uix3 小时前
Props、Context、EventBus、状态管理:组件通信方案选择指南
前端·javascript·react.js
老板我改不动了3 小时前
前端面试复习指南【代码演示多多版】之——HTML
前端
panshihao3 小时前
Mac 环境下通过 SSH 操作服务器,完成前端静态资源备份与更新(全程实操无坑)
前端