【问题标题】:Is it possible to make a gradient-transparent/layer masking image using canvas?是否可以使用画布制作渐变透明/图层蒙版图像?
【发布时间】:2011-03-20 05:22:15
【问题描述】:

我一直在学习 Mozilla 网站上有关透明度和渐变的课程:https://developer.mozilla.org/en-US/docs/Web/Guide/HTML/Canvas_tutorial/Applying_styles_and_colors,但我无法弄清楚这一点。

我知道我可以用 png 图像实现这些效果;但是,在我正在处理的程序中,渐变会根据图像的移动位置不断变化。

这是我正在寻找的效果示例。 http://home.insightbb.com/~epyonxl1/gradientex.jpg

【问题讨论】:

    标签: javascript html canvas transparency gradient


    【解决方案1】:

    我通过合并图像的异步加载对Alnitak's response 进行了现代化改造。

    我也摆脱了幻数。

    const imageUrls = [
      // Base image
      'http://www.netstate.com/states/symb/flowers/images/apple_blossoms.jpg',
      // Gradient image
      'http://www.netstate.com/states/symb/flowers/images/oklahoma_rose.jpg'
    ];
    
    const main = async () => {
      const ctx = document.getElementById('cv').getContext('2d'),
        baseImg = await loadImage(imageUrls[0]),
        gradImg = await loadImage(imageUrls[1]);
    
      draw(ctx, baseImg, gradImg);
    };
    
    /**
     * Loads an Image object via a Promise.
     * @param {String} url - Location of an image file
     * @return {Promise<Image>} Returns a promise that resolves to an Image.
     */
    const loadImage = (url) => new Promise((resolve, reject) => {
      const img = new Image();
      img.addEventListener('load', () => resolve(img));
      img.addEventListener('error', reject);
      img.src = url;
    });
    
    /**
     * Draws an image, as a gradient applied to it, above another image.
     * @param {Image} baseImg - Image that is applied under the gradient
     * @param {Image} gradImg - Image to be applied as a gradient
     */
    const draw = (ctx, baseImg, gradImg) => {
      const {width, height} = baseImg,
          originX = Math.floor(width / 2),
          originY = Math.floor(height / 2),
          radius = Math.min(originX, originY);
    
      const offScreen = document.createElement('canvas');
      offScreen.width = offScreen.height = width;
      const ctx2 = offScreen.getContext('2d');
    
      ctx2.drawImage(gradImg, 0, 0, width, height);
      const gradient = ctx2.createRadialGradient(originX, originY, 0, originX, originY, radius);
    
      gradient.addColorStop(0, 'rgba(255, 255, 255, 0)');
      gradient.addColorStop(1, 'rgba(255, 255, 255, 1)');
    
      ctx2.fillStyle = gradient;
      ctx2.globalCompositeOperation = 'destination-out';
      ctx2.fillRect(0, 0, width, height);
    
      ctx.drawImage(baseImg, 0, 0, width, height);
      ctx.drawImage(offScreen, 0, 0, width, height);
    };
    
    main();
    &lt;canvas id="cv" width="300" height="300"&gt;&lt;/canvas&gt;

    【讨论】:

    • 注意:err =&gt; reject(err) 可以只替换为 reject
    • img =&gt; loadImage(img)loadImage
    • FWIW,(鉴于只有两张图片,所以 Array#mapPromise#all 太过分了)这些天我会使用 await - const src = await loadImage(image[0]); const dst = await loadImage(image[1]); draw(ctx, src, dst)
    • @Alnitak 我只是想加载一组图像。 :) 但是,是的,如果我总是加载两个图像,我会 async 我的函数和 await 为他们。我继续更新它以使用您的建议。我也有srcdest 翻转的含义... dest(基本图像)是应用src(渐变)的那个。
    • 他们并没有真正单独获得应用到他们的渐变 - 他们实际上两者都这样做,只是一张图像将显示透明度渐变为 1 的位置,并且另一个为 0 时,两者之间按比例混合。
    【解决方案2】:

    我在这里添加了一些代码:https://code.google.com/archive/p/canvasimagegradient/drawImageGradient 函数添加到 CanvasRenderingContext2D。您可以使用线性或径向渐变绘制图像。由于缺少 getImageData/putImageData 支持,它在 IE 中不起作用,即使使用 excanvas 也是如此。

    例如,以下代码将绘制具有径向渐变的图像(未显示上下文检索和图像加载):

    var radGrad = ctx.createRadialGradient(
        img.width / 2, img.height / 2, 10, 
        img.width / 2, img.height / 2, img.width/2);
    radGrad.addColorStop(0, "transparent");
    radGrad.addColorStop(1, "#000");
    
    ctx.drawImageGradient(img, 112.5, 130, radGrad);
    

    代码如下:

    1. 动态创建画布元素并在其上绘制图像。
    2. 检索此新画布的 imageData。
    3. 检索您要在画布上绘制图像的位置的 imageData。
    4. 遍历目标 imageData 并更新每个像素,并将图像和目标像素值的百分比(源自渐变透明度值)相加。
    5. 最后将更新后的图像数据放回目标画布上。​​

    随着图像变大,性能显然是一个问题。 https://code.google.com/archive/p/canvasimagegradient/ 上的图像绘制大约需要 6-10 毫秒。一个 1024x768 的图像大约需要 100ms-250ms 来绘制。只要您不制作动画,仍然可以使用。

    【讨论】:

    • 啊,我知道在我忘记指定之后我已经知道如何创建像 developer.mozilla.org/samples/canvas-tutorial/… 这样的渐变,我会得到这个答案。不幸的是,这些仅适用于特定颜色。另一方面,如果它是图像(非纯色),则此方法根本行不通。
    • 我回去看了看图片,看看你现在想要什么。好问题。我在想,如果您在单独的画布中取出图像并检索图像数据,您可以对其应用渐变效果,然后将其覆盖在原始画布上。我会玩一玩,看看我能想出什么。
    • 听起来不错,周末自己不会有太多时间来处理它,但我也会看看我想出什么。让我发布你发现的任何事情。我绝对想解决这个问题。
    • 要在没有逐像素操作的情况下完成相同的操作,请参阅下面的@Alnitak 答案。
    • @Nathan 我下载了这个有 10 年历史(存档)的存储库,并将其转换为使用 npm 和 webpack 的a GitHub project。只要有时间,我都会尝试合并 Alnitak 的 response
    【解决方案3】:

    要使用透明蒙版正确合并两张图像,首先需要将两张图像中的一张放入屏幕外画布,然后使用context.globalCompositeOperation = destination-out per @ 添加所需的透明蒙版汤米卡的回答

    var offscreen = document.createElement('canvas'); // detached from DOM
    var context = offscreen.getContext('2d');
    context.drawImage(image1, 0, 0, image1.width, image1.height);
    
    var gradient = context.createLinearGradient(0, 0, 0, img.height);
    gradient.addColorStop(0, "rgba(255, 255, 255, 0.5)");
    gradient.addColorStop(1, "rgba(255, 255, 255, 1.0)");
    context.globalCompositeOperation = "destination-out";
    context.fillStyle = gradient;
    context.fillRect(0, 0, image1.width, image1.height);
    

    然后,要实际合并两个图像,您需要将另一个图像绘制到另一个画布中,然后简单地在其上绘制 alpha 合成的离屏画布:

    var onscreen = document.getElementById('mycanvas');
    var context2 = onscreen.getContext('2d');
    context2.drawImage(image2, 0, 0, image2.width, image2.height);
    context2.drawImage(offscreen, 0, 0, onscreen.width, onscreen.height);
    

    http://jsfiddle.net/alnitak/rfdjoh31/4/的演示

    【讨论】:

    • 这应该是公认的答案。它与@Castrohenge 的解决方案一样有效,但不需要(缓慢)逐像素操作。谢谢!
    • 我在上面看到的代码中添加了my own implementation
    【解决方案4】:

    如果您需要使图像透明,请将ctx.globalAlpha 设置为您需要的任何值(1,默认为不透明)。然后在绘制图像后将其重置。这个 URL 可能也会有帮助https://developer.mozilla.org/en-US/docs/Web/Guide/HTML/Canvas_tutorial/Compositing

    【讨论】:

      【解决方案5】:

      可以使用 context.globalCompositeOperation 来制作掩码

      context.drawImage(img, 0, 0, img.width, img.height, 0,0, img.width, img.height);
      context.globalCompositeOperation = "destination-out";
      gradient = context.createLinearGradient(0, 0, 0, img.height);
      gradient.addColorStop(0, "rgba(255, 255, 255, 0.5)");
      gradient.addColorStop(1, "rgba(255, 255, 255, 1.0)");
      context.fillStyle = gradient;
      context.fillRect(0, 0, img.width, img.height);
      

      这不是按像素操作,应该更快

      【讨论】:

      • 我同意这应该更快,并且可能是正确的答案。
      • 据我所知,这不是正确答案的原因是您仅使用渐变来影响纯色矩形。 OP 询问如何使用渐变来影响图像。
      • @Alnitak 您的解决方案可以将纯色放在图像上,但不能将一个图像放在另一个图像上。 OP 在问题本身中链接到的示例显示了一个图像在另一个图像上,而不仅仅是图像上的纯色。我认为这不能实现 OP 的目标。
      • 查看@Alnitak 的 awnsor 以获得此方法的更好示例。
      猜你喜欢
      • 2014-06-23
      • 2012-01-12
      • 1970-01-01
      • 1970-01-01
      • 2017-03-15
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-03-20
      相关资源
      最近更新 更多