是否可以在黑白图像中添加彩色滤镜?
我可以用某种滤镜来绘制这个图像,这样它就可以变成我想要的任何颜色吗?
类似于如何在矩形中使用您想要的任何颜色进行着色:
ctx.fillstyle = "rgb(255, 0, 0)";
ctx.fillrect(0, 0, 100, 100);
有什么方法可以让我以这种方式在图像上动态添加滤色器?
(我只想将颜色应用于图像而不是整个画布)
我错过了阅读问题并给出的答案可能不适合您的需求。因此,此更新演示了如何为粒子 FX 的黑白图像着色。
不幸的是,2D API 的设计并未考虑到游戏渲染,为了获得最佳结果,您可以通过 WebGL 实现渲染,从而为您提供多种多样的 FX 和巨大的性能增益。然而,WebGL 需要大量代码和对着色器的良好理解。
注意您提供的图像是黑色的。对黑白图像进行着色时,黑色仍然是黑色。因此,我假设您想要的彩色图像是白色的。我将使用您的图像副本,该图像的 Alpha 范围设置为 0 - 255,RGB 具有从黑到白的圆形渐变。
在游戏世界中,渲染到显示器上的图像称为精灵。精灵是一个矩形图像,它有 4 个 RGBA 通道,可以在任意位置、旋转、缩放和淡入淡出(Alpha)下绘制
使用 2D API 的基本精灵渲染解决方案的示例。
要为单个精灵着色,您有两种选择。
ctx.filter
您可以使用 ctx.filter 属性并创建自定义过滤器来为精灵着色。
然而,这并不是最佳选择,因为与替代解决方案相比,过滤器相当慢。
要绘制任何颜色的精灵,您需要将图像分割成红、绿、蓝和 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 张图像。
请注意,性能还有一些提升空间。
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 全球综合运营
您可以使用您的图像作为蒙版。当您绘制背景以在蒙版后“剪切”时,请在使用
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);
}
您可以尝试在具有部分透明背景的图像上放置一个图层:
.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>
这个问题已经有几年了,但我试图做同样的事情,但这里发布的答案都没有按预期工作,所以我想出了一个解决方案。借鉴 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,请使用修改后的方法.