用js给黑白图片添加彩色滤镜?

问题描述 投票:0回答:4

是否可以在黑白图像中添加彩色滤镜?

举例来说,假设我想在游戏中使用此图像作为粒子:particle image

我可以用某种滤镜来绘制这个图像,这样它就可以变成我想要的任何颜色吗?

类似于如何在矩形中使用您想要的任何颜色进行着色:

ctx.fillstyle = "rgb(255, 0, 0)";
ctx.fillrect(0, 0, 100, 100);

有什么方法可以让我以这种方式在图像上动态添加滤色器?

(我只想将颜色应用于图像而不是整个画布)

javascript html5-canvas
4个回答
2
投票

更新了

我错过了阅读问题并给出的答案可能不适合您的需求。因此,此更新演示了如何为粒子 FX 的黑白图像着色。

不幸的是,2D API 的设计并未考虑到游戏渲染,为了获得最佳结果,您可以通过 WebGL 实现渲染,从而为您提供多种多样的 FX 和巨大的性能增益。然而,WebGL 需要大量代码和对着色器的良好理解。


注意您提供的图像是黑色的。对黑白图像进行着色时,黑色仍然是黑色。因此,我假设您想要的彩色图像是白色的。我将使用您的图像副本,该图像的 Alpha 范围设置为 0 - 255,RGB 具有从黑到白的圆形渐变。


精灵

在游戏世界中,渲染到显示器上的图像称为精灵。精灵是一个矩形图像,它有 4 个 RGBA 通道,可以在任意位置、旋转、缩放和淡入淡出(Alpha)下绘制

使用 2D API 的基本精灵渲染解决方案的示例。

要为单个精灵着色,您有两种选择。

选项 1
ctx.filter

您可以使用 ctx.filter 属性并创建自定义过滤器来为精灵着色。

然而,这并不是最佳选择,因为与替代解决方案相比,过滤器相当慢。

选项 2 分割通道

要绘制任何颜色的精灵,您需要将图像分割成红、绿、蓝和 Alpha 部分。然后根据您想要显示的颜色渲染每个通道。

为了避免安全限制,您可以使用以下函数分割图像

复制图片

创建图像的副本作为画布

function copyImage(img) { // img can be any image type
    const can = Object.assign(document.createElement("canvas"), {
        width: img.width,
        height: img.height,
    });
    can.ctx = can.getContext("2d");
    can.ctx.drawImage(img, 0, 0, img.width, img.height);
    return can;

}

过滤频道

这将复制图像并删除两个或三个 RGBA 颜色通道。它需要函数

copyImage


function imageFilterChannel(img, channel = "red") {
    const imgf = copyImage(img);
    // remove unwanted channel data
    imgf.ctx.globalCompositeOperation = "multiply";
    imgf.ctx.fillStyle = imageFilterChannel.filters[channel] ?? "#FFF";
    imgf.ctx.fillRect(0,0, img.width, img.height);

    // add alpha mask
    imgf.ctx.globalCompositeOperation = "destination-in";
    imgf.ctx.drawImage(img, 0, 0, img.width, img.height);
    imgf.ctx.globalCompositeOperation = "source-over";
    return imgf;
}

imageFilterChannel.filters = {
    red:   "#F00",
    green: "#0F0",
    blue:  "#00F",
    alpha: "#000",
};

彩色精灵

此函数将加载图像,创建 4 个独立的通道图像并将其封装在 sprite 对象中。

需要功能

imageFilterChannel

注意没有错误检查

function createColorSprite(srcUrl) {
    var W, H;
    const img = new Image;
    img.src = srcUrl;
    const channels = [];
    img.addEventListener("load",() => {
        channels[0] = imageFilterChannel(img, "red");
        channels[1] = imageFilterChannel(img, "green");
        channels[2] = imageFilterChannel(img, "blue");
        channels[3] = imageFilterChannel(img, "alpha");
        API.width = W = img.width;
        API.height = H = img.height;
        API.ready = true;
    }, {once: true});

    const API = {
        ready: false,
        drawColored(ctx, x, y, scale, rot, color) { // color is CSS hex color #RRGGBBAA
                                                    // eg #FFFFFFFF
            // get RGBA from color string
            const r = parseInt(color[1] + color[2], 16);
            const g = parseInt(color[3] + color[4], 16);
            const b = parseInt(color[5] + color[6], 16);
            const a = parseInt(color[7] + color[8], 16);

            // Setup location and transformation
            const ax = Math.cos(rot) * scale;
            const ay = Math.sin(rot) * scale;
            ctx.setTransform(ax, ay, -ay, ax, x, y);
            const offX = -W / 2;
            const offY = -H / 2;

          
            // draw alpha first then RGB
            ctx.globalCompositeOperation = "source-over";
            ctx.globalAlpha = a / 255;
            ctx.drawImage(channels[3], offX, offY, W, H);

            ctx.globalCompositeOperation = "lighter";

            ctx.globalAlpha = r / 255;
            ctx.drawImage(channels[0], offX, offY, W, H);
            ctx.globalAlpha = g / 255;
            ctx.drawImage(channels[1], offX, offY, W, H);
            ctx.globalAlpha = b / 255;
            ctx.drawImage(channels[2], offX, offY, W, H);
                  
            ctx.globalCompositeOperation = "source-over";
        }
    };
    return API;
}

使用方法如下

// to load
const image = createColorSprite("https://i.stack.imgur.com/RXAVJ.png");

// to render with red
if (image.ready) { image.drawColored(ctx, 100, 100, 1, 0, "#FF0000FF") }

演示

使用演示上面的功能展示了如何使用它们,并将为您提供一些有关性能的反馈。它使用上面链接的精灵示例中的一些代码。

演示渲染 200 张图像。

请注意,性能还有一些提升空间。

  • 如果通道为零,则不渲染通道图像
  • 不要使用CSS颜色字符串,使用单位颜色数组,省去了解析CSS颜色字符串的需要。例如 RGBA 红色为 [1,0,0,1]
  • 使用与显示尺寸紧密匹配的图像,演示中的图像比显示尺寸大很多倍。

function copyImage(img) { // img can be any image type
    const can = Object.assign(document.createElement("canvas"), {
        width: img.width,
        height: img.height,
    });
    can.ctx = can.getContext("2d");
    can.ctx.drawImage(img, 0, 0, img.width, img.height);
    return can;

}

function imageFilterChannel(img, channel = "red") {
    const imgf = copyImage(img);
    // remove unwanted channel data
    imgf.ctx.globalCompositeOperation = "multiply";
    imgf.ctx.fillStyle = imageFilterChannel.filters[channel] ?? "#FFF";
    imgf.ctx.fillRect(0,0, img.width, img.height);

    // add alpha mask
    imgf.ctx.globalCompositeOperation = "destination-in";
    imgf.ctx.drawImage(img, 0, 0, img.width, img.height);
    imgf.ctx.globalCompositeOperation = "source-over";
    return imgf;
}
imageFilterChannel.filters = {
    red:   "#F00",
    green: "#0F0",
    blue:  "#00F",
    alpha: "#000",
};

function createColorSprite(srcUrl) {
    var W, H;
    const img = new Image;
    img.src = srcUrl;
    const channels = [];
    img.addEventListener("load",() => {
        channels[0] = imageFilterChannel(img, "red");
        channels[1] = imageFilterChannel(img, "green");
        channels[2] = imageFilterChannel(img, "blue");
        channels[3] = imageFilterChannel(img, "alpha");
        API.width = W = img.width;
        API.height = H = img.height;
        API.ready = true;
    }, {once: true});

    const API = {
        ready: false,
        drawColored(ctx, x, y, scale, rot, color) { // color is CSS hex color #RRGGBBAA
                                                    // eg #FFFFFFFF
            // get RGBA from color string
            const r = parseInt(color[1] + color[2], 16);
            const g = parseInt(color[3] + color[4], 16);
            const b = parseInt(color[5] + color[6], 16);
            const a = parseInt(color[7] + color[8], 16);

            // Setup location and transformation
            const ax = Math.cos(rot) * scale;
            const ay = Math.sin(rot) * scale;
            ctx.setTransform(ax, ay, -ay, ax, x, y);
            const offX = -W / 2;
            const offY = -H / 2;

          
            // draw alpha first then RGB
            ctx.globalCompositeOperation = "source-over";
            ctx.globalAlpha = a / 255;
            ctx.drawImage(channels[3], offX, offY, W, H);

            ctx.globalCompositeOperation = "lighter";

            ctx.globalAlpha = r / 255;
            ctx.drawImage(channels[0], offX, offY, W, H);
            ctx.globalAlpha = g / 255;
            ctx.drawImage(channels[1], offX, offY, W, H);
            ctx.globalAlpha = b / 255;
            ctx.drawImage(channels[2], offX, offY, W, H);
                  
            ctx.globalCompositeOperation = "source-over";
        }
    };
    return API;
}

var image = createColorSprite("https://i.stack.imgur.com/C7qq2.png?s=328&g=1");
var image1 = createColorSprite("https://i.stack.imgur.com/RXAVJ.png");
var canvas = document.createElement("canvas");
var ctx = canvas.getContext("2d");
canvas.style.position = "absolute";
canvas.style.top = "0px";
canvas.style.left = "0px";
document.body.appendChild(canvas);
var w,h;
function resize(){ w = canvas.width = innerWidth; h = canvas.height = innerHeight;}
resize();
addEventListener("resize",resize);
function rand(min,max){return Math.random() * (max ?(max-min) : min) + (max ? min : 0) }
const randHex = () => (Math.random() * 255 | 0).toString(16).padStart(2,"0");
const randColRGB = () => "#" + randHex() + randHex() + randHex() + "FF";
function DO(count,callback){ while (count--) { callback(count) } }
const sprites = [];
DO(200,(i)=>{
    sprites.push({
       x : rand(w), y : rand(h),
       xr : 0, yr : 0, // actual position of sprite
       r : rand(Math.PI * 2),
       scale: rand(0.1,0.25),
       dx: rand(-2,2), dy : rand(-2,2),
       dr: rand(-0.2,0.2),
       color: randColRGB(),
       img: i%2 ? image : image1,
    });
});

function update(){
    var ihM,iwM;
    ctx.setTransform(1,0,0,1,0,0);
    ctx.clearRect(0,0,w,h);
    if(image.ready && image1.ready){
      for(var i = 0; i < sprites.length; i ++){
          var spr = sprites[i];
          iw = spr.img.width;
          ih = spr.img.height;
          spr.x += spr.dx;
          spr.y += spr.dy;
          spr.r += spr.dr;
          iwM = iw * spr.scale * 2 + w;
          ihM = ih * spr.scale * 2 + h;
          spr.xr = ((spr.x % iwM) + iwM) % iwM - iw * spr.scale;
          spr.yr = ((spr.y % ihM) + ihM) % ihM - ih * spr.scale;
          spr.img.drawColored(ctx, spr.xr, spr.yr, spr.scale, spr.r, spr.color);
      }
    }    
    requestAnimationFrame(update);
}
requestAnimationFrame(update);



旧答案

添加颜色特效

绘制图像后,设置

ctx.globalCompositeOperation = "color"
ctx.globalCompositeOperation = "hue"
并以您想要的颜色在图像上绘制。

示例

ctx.drawImage(img, 0, 0, 50, 50);            // draw image. Ensure that the current
                                             // composite op is 
                                             // "source-over" see last line

ctx.globalCompositeOperation = "color";      // set the comp op (can also use "hue")
ctx.fillStyle = "red";                       // set fillstyle to color you want
ctx.fillRect(0,0,50,50);                     // draw red over image
ctx.globalCompositeOperation = "source-over";// restore default comp op

有关更多信息,请参阅 MDN 全球综合运营


1
投票

您可以使用您的图像作为蒙版。当您绘制背景以在蒙版后“剪切”时,请在使用

ctx.globalCompositeOperation
绘制蒙版后使用
"source-in"
。如果您的蒙版是在背景之后绘制的,则
"destination-in"

let color = 'rgb(255, 0, 0)';

let image = new Image();
image.src = 'https://i.stack.imgur.com/XFpL3.png';
image.onload = () => {
    let canvas = document.createElement('canvas');
    let w = canvas.width = image.width;
    let h = canvas.height = image.height;
    let ctx = canvas.getContext('2d');
    ctx.fillStyle = color;
    
    ctx.drawImage(image,0,0);

    ctx.globalCompositeOperation = 'source-in';
    
    ctx.fillRect(0,0,w,h);
    
    document.body.append(canvas);    
}


0
投票

您可以尝试在具有部分透明背景的图像上放置一个图层:

.container{
  position:relative;
  width:fit-content;
}
.layer{
  position:absolute;
  height:100%;
  width:100%;
  background-color:rgb(0, 98, 255, 0.5);
  z-index:1;
}
<div class="container">
<div class="layer"></div>
  <img src="https://i.stack.imgur.com/XFpL3.png">
</div>


0
投票

这个问题已经有几年了,但我试图做同样的事情,但这里发布的答案都没有按预期工作,所以我想出了一个解决方案。借鉴 Blindman67 的答案,这很好,但比 4 个滤镜通道所需的更复杂,并且如果您使用 alpha 值小于 1 的颜色,则生成的颜色/不透明度不太正确。

对于等效但更简单的解决方案:

const srcUrl = 'https://i.stack.imgur.com/XFpL3.png';
const color = '#FF0000';
  
const drawColoredImage = (ctx, img, rgb, width, height) => {
    // Draw original image
    ctx.globalCompositeOperation = 'source-over';
    ctx.drawImage(img, 0, 0, width, height);

    // Create colored mask
    ctx.globalCompositeOperation = 'source-in';
    ctx.fillStyle = rgb;
    ctx.fillRect(0, 0, width, height);

    // Multiply mask by original image to get final colored image
    ctx.globalCompositeOperation = 'multiply';
    ctx.drawImage(img, 0, 0, width, height);
};

const canvas = document.createElement('canvas');
document.body.appendChild(canvas); // Add canvas to DOM somewhere
const ctx = canvas.getContext('2d');

// Using a CSS hex color #RRGGBB or #RRGGBBAA
// Remove alpha if present and store in separate variable
const rgb = color.substring(0, 7);
const a = color.length === 9
    ? parseInt(color[7] + color[8], 16)
    : 255;
const alpha = a / 255; // alpha value between 0 and 1

const img = new Image;
img.src = srcUrl;
img.addEventListener('load', () => {
    const width = img.width;
    const height = img.height;
    Object.assign(canvas, {
        width,
        height,
    });

    ctx.globalAlpha = alpha;
    drawColoredImage(ctx, img, rgb, width, height);
});

现在,如果您使用 alpha 值小于 1 的颜色,并且希望颜色/不透明度准确,则需要进行小的修改并再添加一个步骤。首先将彩色图像绘制到 alpha 为 1 的新临时画布上,然后使用计算出的 alpha 将该图像复制到目标画布:

img.addEventListener('load', () => {
    const width = img.width;
    const height = img.height;
    Object.assign(canvas, {
        width,
        height,
    });

    // Draw colored image to a temp canvas with default alpha of 1
    const canvas2 = document.createElement('canvas');
    Object.assign(canvas2, {
      width,
      height,
    });
    const ctx2 = canvas2.getContext('2d');
    drawColoredImage(ctx2, img, rgb, width, height);

    // Copy colored image to target canvas with calculated alpha
    ctx.globalAlpha = alpha;
    ctx.drawImage(canvas2, 0, 0, width, height);
});

根据您的用例,您可能不会注意到或关心这两种方法之间的差异,但如果您有一个白色图标 png,并且您想更改颜色以完全匹配您的#RRGGBBAA,请使用修改后的方法.

© www.soinside.com 2019 - 2024. All rights reserved.