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

相关推荐
everyStudy18 分钟前
JS中判断字符串中是否包含指定字符
开发语言·前端·javascript
城南云小白18 分钟前
web基础+http协议+httpd详细配置
前端·网络协议·http
前端小趴菜、19 分钟前
Web Worker 简单使用
前端
web_learning_32121 分钟前
信息收集常用指令
前端·搜索引擎
Ylucius27 分钟前
动态语言? 静态语言? ------区别何在?java,js,c,c++,python分给是静态or动态语言?
java·c语言·javascript·c++·python·学习
tabzzz29 分钟前
Webpack 概念速通:从入门到掌握构建工具的精髓
前端·webpack
200不是二百42 分钟前
Vuex详解
前端·javascript·vue.js
滔滔不绝tao1 小时前
自动化测试常用函数
前端·css·html5
LvManBa1 小时前
Vue学习记录之三(ref全家桶)
javascript·vue.js·学习
码爸1 小时前
flink doris批量sink
java·前端·flink