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

相关推荐
zhougl9961 小时前
html处理Base文件流
linux·前端·html
花花鱼2 小时前
node-modules-inspector 可视化node_modules
前端·javascript·vue.js
HBR666_2 小时前
marked库(高效将 Markdown 转换为 HTML 的利器)
前端·markdown
careybobo3 小时前
海康摄像头通过Web插件进行预览播放和控制
前端
TDengine (老段)4 小时前
TDengine 中的关联查询
大数据·javascript·网络·物联网·时序数据库·tdengine·iotdb
杉之5 小时前
常见前端GET请求以及对应的Spring后端接收接口写法
java·前端·后端·spring·vue
喝拿铁写前端5 小时前
字段聚类,到底有什么用?——从系统混乱到结构认知的第一步
前端
再学一点就睡5 小时前
大文件上传之切片上传以及开发全流程之前端篇
前端·javascript
木木黄木木6 小时前
html5炫酷图片悬停效果实现详解
前端·html·html5
请来次降维打击!!!6 小时前
优选算法系列(5.位运算)
java·前端·c++·算法