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

相关推荐
光影少年1 分钟前
vue2与vue3的全局通信插件,如何实现自定义的插件
前端·javascript·vue.js
As977_2 分钟前
前端学习Day12 CSS盒子的定位(相对定位篇“附练习”)
前端·css·学习
susu10830189114 分钟前
vue3 css的样式如果background没有,如何覆盖有background的样式
前端·css
Ocean☾6 分钟前
前端基础-html-注册界面
前端·算法·html
Rattenking6 分钟前
React 源码学习01 ---- React.Children.map 的实现与应用
javascript·学习·react.js
Dragon Wu8 分钟前
前端 Canvas 绘画 总结
前端
CodeToGym13 分钟前
Webpack性能优化指南:从构建到部署的全方位策略
前端·webpack·性能优化
~甲壳虫14 分钟前
说说webpack中常见的Loader?解决了什么问题?
前端·webpack·node.js
~甲壳虫18 分钟前
说说webpack proxy工作原理?为什么能解决跨域
前端·webpack·node.js
Cwhat19 分钟前
前端性能优化2
前端