【问题标题】:Add color filter to dark part of image and another filter to light part of the image?将滤色器添加到图像的暗部分,将另一个滤色器添加到图像的亮部分?
【发布时间】:2021-11-10 16:23:57
【问题描述】:

我的挑战是在图像的暗部添加滤色器,在图像的亮部添加另一个滤色器。要达到这样的效果https://imgur.com/a/cGmJbs9

我正在使用具有 globalCompositeOperation 效果的画布,但我只能应用一个过滤器而不影响另一个过滤器。

ctx.drawImage(image, 0, 0, 380, 540);
ctx.globalCompositeOperation = 'darken';
ctx.fillStyle = overlayFillColor;
ctx.fillRect(0, 0, 380, 540);

这非常适合根据 globalCompositeOperation 将颜色过滤器应用于黑暗或明亮区域,但如果我添加另一个过滤器,它也会改变前一个过滤器的颜色。

有什么想法吗?

谢谢艾尔斯

【问题讨论】:

    标签: javascript canvas colors css-filters globalcompositeoperation


    【解决方案1】:

    有一个不错的 SVG 滤镜组件可以将亮度映射到 alpha:<feColorMatrix type="luminanceToAlpha"/>
    由于我们可以在画布中使用 SVG 滤镜,因此我们可以将暗区与亮区分开,并使用合成而不是混合。

    这样,您的输入颜色就会被保留。

    (async () => {
      const canvas = document.querySelector("canvas");
      const ctx = canvas.getContext("2d");
      const img = new Image();
      img.src = "https://picsum.photos/500/500";
      await img.decode();
      canvas.width = img.width;
      canvas.height = img.height;
      // first we create our alpha layer
      ctx.filter = "url(#lumToAlpha)";
      ctx.drawImage(img, 0, 0);
      ctx.filter = "none";
      const alpha = await createImageBitmap(canvas);
      
      // draw on "light" zone
      ctx.globalCompositeOperation = "source-in";
      ctx.fillStyle = "red";
      ctx.fillRect(0, 0, canvas.width, canvas.height);
      // save into an ImageBitmap
      // (note that we could also use a second canvas to do this all synchronously)
      const light = await createImageBitmap(canvas);
      
      // clean canvas
      ctx.globalCompositeOperation = "source-over";
      ctx.clearRect(0, 0, canvas.width, canvas.height);
      // draw on "dark" zone
      ctx.drawImage(alpha, 0, 0);
      ctx.globalCompositeOperation = "source-out";
      ctx.fillStyle = "blue";
      ctx.fillRect(0, 0, canvas.width, canvas.height);
      // reintroduce "light" zone
      ctx.globalCompositeOperation = "source-over";
      ctx.drawImage(light, 0, 0);
    })().catch(console.error);
    <svg width="0" height="0" style="visibility:hidden;position:absolute">
      <filter id="lumToAlpha">
        <feColorMatrix type="luminanceToAlpha" />
      </filter>
    </svg>
    <canvas></canvas>
    <!--
      If you don't like having an element in the DOM just for that
      you could also directly set the context's filter to a data:// URI
      url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cfilter%20id%3D%22f%22%3E%3CfeColorMatrix%20type%3D%22luminanceToAlpha%22%2F%3E%3C%2Ffilter%3E%3C%2Fsvg%3E#f");
      but you'd have to wait a least a task (setTimeout(fn, 0))
      because setting filters this way is async...
      Hopefully CanvasFilters will solve this soon enough
    -->

    请注意,希望我们能在不久的将来拥有 CanvasFilters 对象,这将使 SVG 过滤器更易于使用,并且可以在 Workers 中访问(它们目前还不是......)。 所以对于未来的(或者在 Canary 上打开网络功能标志的现在),这看起来像:

    // typeof CanvasFilter === "function"
    // should be enough for detecting colorMatrix
    // but see below for how to "correctly" feature-detect
    // a particular CanvasFilter
    if (supportsColorMatrixCanvasFilter()) {
    (async () => {
      const canvas = document.querySelector("canvas");
      const ctx = canvas.getContext("2d");
      const img = new Image();
      img.src = "https://picsum.photos/500/500";
      await img.decode();
      canvas.width = img.width;
      canvas.height = img.height;
      // first we create our alpha layer
      ctx.filter = new CanvasFilter({
        filter: "colorMatrix",
        type: "luminanceToAlpha"
      });
      ctx.drawImage(img, 0, 0);
      ctx.filter = "none";
      const alpha = await createImageBitmap(canvas);
      
      // draw on "light" zone
      ctx.globalCompositeOperation = "source-in";
      ctx.fillStyle = "red";
      ctx.fillRect(0, 0, canvas.width, canvas.height);
      // save into an ImageBitmap
      // (note that we could also use a second canvas to do this all synchronously)
      const light = await createImageBitmap(canvas);
      
      // clean canvas
      ctx.globalCompositeOperation = "source-over";
      ctx.clearRect(0, 0, canvas.width, canvas.height);
      // draw on "dark" zone
      ctx.drawImage(alpha, 0, 0);
      ctx.globalCompositeOperation = "source-out";
      ctx.fillStyle = "blue";
      ctx.fillRect(0, 0, canvas.width, canvas.height);
      // reintroduce "light" zone
      ctx.globalCompositeOperation = "source-over";
      ctx.drawImage(light, 0, 0);
    })().catch(console.error);
    }
    else {
      console.error("your browser doesn't support CanvasFilters yet");
    }
    // Feature detection is hard...
    // see https://gist.github.com/Kaiido/45d189c110d29ac2eda25a7762c470f2
    // to get the list of all supported CanvasFilters
    // below only checks for colorMatrix
    function supportsColorMatrixCanvasFilter() {
      if(typeof CanvasFilter !== "function") {
        return false;
      }
      let supported = false;
      try {
        new CanvasFilter({
          filter: "colorMatrix",
          // "type" will be visited for colorMatrix
          // we throw in to avoid actually creating the filter
          get type() { supported = true; throw ""; }
        });
      } catch(err) {}
      return supported;
    }
    &lt;canvas&gt;&lt;/canvas&gt;

    【讨论】:

    • 你是英雄,工作绝对完美,感谢您的快速帮助!
    • 实际上,有一个小问题,如果“光”部分不是 100% 白色,它会应用透明的光覆盖,因此我最终会得到非常不同的颜色。但我想我们对此无能为力。 imgur.com/a/KhFJxMN
    • 你可能会玩一点亮度和饱和度(两者都可以作为 CSS 过滤器,你可以在url(#... 之前设置)但没有通用的。
    • 明白,再次感谢!
    猜你喜欢
    • 1970-01-01
    • 2016-08-12
    • 2012-01-15
    • 1970-01-01
    • 1970-01-01
    • 2020-03-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多