Html 如何使用 HTML5 Canvas 为图像着色?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/2688961/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How do i tint an image with HTML5 Canvas?
提问by djdolber
My question is, what is the best way to tint an image that is drawn using the drawImage method. The target useage for this is advanced 2d particle-effects (game development) where particles change colors over time etc. I am not asking how to tint the whole canvas, only the current image i am about to draw.
我的问题是,为使用 drawImage 方法绘制的图像着色的最佳方法是什么。其目标用途是高级 2d 粒子效果(游戏开发),其中粒子会随着时间的推移改变颜色等。我不是在问如何给整个画布着色,只问我将要绘制的当前图像。
I have concluded that the globalAlpha parameter affects the current image that is drawn.
我得出的结论是 globalAlpha 参数会影响绘制的当前图像。
//works with drawImage()
canvas2d.globalAlpha = 0.5;
But how do i tint each image with an arbitrary color value ? It would be awesome if there was some kind of globalFillStyle or globalColor or that kind of thing...
但是我如何用任意颜色值为每个图像着色?如果有某种 globalFillStyle 或 globalColor 或那种东西,那就太棒了......
EDIT:
编辑:
Here is a screenshot of the application i am working with: http://twitpic.com/1j2aeg/fullalt text http://web20.twitpic.com/img/92485672-1d59e2f85d099210d4dafb5211bf770f.4bd804ef-scaled.png
这是我正在使用的应用程序的屏幕截图:http: //twitpic.com/1j2aeg/full alt text http://web20.twitpic.com/img/92485672-1d59e2f85d099210d4dafb5211bf770f.4bd804ef-scaled
回答by Nathan
You have compositing operations, and one of them is destination-atop. If you composite an image onto a solid color with the 'context.globalCompositeOperation = "destination-atop"', it will have the alpha of the foreground image, and the color of the background image. I used this to make a fully tinted copy of an image, and then drew that fully tinted copy on top of the original at an opacity equal to the amount that I want to tint.
您有合成操作,其中之一是目标顶部。如果您使用 'context.globalCompositeOperation = "destination-atop"' 将图像合成为纯色,它将具有前景图像的 alpha 和背景图像的颜色。我用它来制作图像的完全着色副本,然后以等于我想要着色的量的不透明度在原件的顶部绘制该完全着色的副本。
Here is the full code:
这是完整的代码:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
<title>HTML5 Canvas Test</title>
<script type="text/javascript">
var x; //drawing context
var width;
var height;
var fg;
var buffer
window.onload = function() {
var drawingCanvas = document.getElementById('myDrawing');
// Check the element is in the DOM and the browser supports canvas
if(drawingCanvas && drawingCanvas.getContext) {
// Initaliase a 2-dimensional drawing context
x = drawingCanvas.getContext('2d');
width = x.canvas.width;
height = x.canvas.height;
// grey box grid for transparency testing
x.fillStyle = '#666666';
x.fillRect(0,0,width,height);
x.fillStyle = '#AAAAAA';
var i,j;
for (i=0; i<100; i++){
for (j=0; j<100; j++){
if ((i+j)%2==0){
x.fillRect(20*i,20*j,20,20);
}
}
}
fg = new Image();
fg.src = 'http://uncc.ath.cx/LayerCake/images/16/3.png';
// create offscreen buffer,
buffer = document.createElement('canvas');
buffer.width = fg.width;
buffer.height = fg.height;
bx = buffer.getContext('2d');
// fill offscreen buffer with the tint color
bx.fillStyle = '#FF0000'
bx.fillRect(0,0,buffer.width,buffer.height);
// destination atop makes a result with an alpha channel identical to fg, but with all pixels retaining their original color *as far as I can tell*
bx.globalCompositeOperation = "destination-atop";
bx.drawImage(fg,0,0);
// to tint the image, draw it first
x.drawImage(fg,0,0);
//then set the global alpha to the amound that you want to tint it, and draw the buffer directly on top of it.
x.globalAlpha = 0.5;
x.drawImage(buffer,0,0);
}
}
</script>
</head>
</body>
<canvas id="myDrawing" width="770" height="400">
<p>Your browser doesn't support canvas.</p>
</canvas>
</body>
</html>
回答by JL235
There is a method hereyou can use to tint images, and it's more accurate then drawing coloured rectangles and faster then working on a pixel-by-pixel basis. A full explanation is in that blog post, including the JS code, but here is a summary of how it works.
有一种方法,在这里,你可以用它来着色的图像,这是更准确的再画彩色矩形和更快然后在逐像素的基础工作。该博客文章中有完整的解释,包括 JS 代码,但这里是其工作原理的摘要。
First you go through the image you are tinting pixel by pixel, reading out the data and splitting each pixel up into 4 separate components: red, green, blue and black. You write each component to a separate canvas. So now you have 4 (red, green, blue and black) versions of the original image.
首先,您浏览要逐个像素着色的图像,读出数据并将每个像素分成 4 个独立的部分:红色、绿色、蓝色和黑色。您将每个组件写入单独的画布。所以现在您有原始图像的 4 个(红色、绿色、蓝色和黑色)版本。
When you want to draw a tinted image, you create (or find) an off-screen canvas and draw these components to it. The black is drawn first, and then you need set the globalCompositeOperation of the canvas to 'lighter' so the next components are added to the canvas. The black is also non-transparent.
当您想要绘制着色图像时,您可以创建(或找到)一个离屏画布并将这些组件绘制到它上面。首先绘制黑色,然后您需要将画布的 globalCompositeOperation 设置为“更亮”,以便将下一个组件添加到画布中。黑色也是不透明的。
The next three components are drawn (the red, blue and green images), but their alpha value is based on how much their component makes up the drawing colour. So if the colour is white, then all three are drawn with 1 alpha. If the colour is green, then only the green image is drawn and the other two are skipped. If the colour is orange then you have full alpha on the red, draw green partially transparent and skip the blue.
绘制了接下来的三个组件(红色、蓝色和绿色图像),但它们的 alpha 值取决于它们的组件构成绘图颜色的程度。因此,如果颜色为白色,则所有三个都使用 1 alpha 绘制。如果颜色为绿色,则只绘制绿色图像,跳过其他两个。如果颜色是橙色,那么你在红色上有完整的 alpha,绘制绿色部分透明并跳过蓝色。
Now you have a tinted version of your image rendered onto the spare canvas, and you just draw it to where ever you need it on your canvas.
现在,您已将图像的着色版本渲染到备用画布上,您只需将其绘制到画布上需要的任何位置即可。
Again the code to do this is in the blog post.
再次执行此操作的代码在博客文章中。
回答by Sirisian
When I created a particle test I just cached images based on rotation (like 35 rotations), color tint, and alpha and created a wrapper so that they were created automatically. Worked well. Yes there should be some kind of tint operation, but when dealing with software rendering your best bet much like in flash is to cache everything. Particle Example I made for fun
当我创建粒子测试时,我只是根据旋转(如 35 次旋转)、色调和 alpha 缓存图像,并创建了一个包装器,以便自动创建它们。工作得很好。是的,应该有某种色调操作,但是在处理软件渲染时,最好的选择就像在闪存中一样缓存所有内容。 我为了好玩而制作的粒子示例
<!DOCTYPE HTML>
<html lang="en">
<head>
<title>Particle Test</title>
<script language="javascript" src="../Vector.js"></script>
<script type="text/javascript">
function Particle(x, y)
{
this.position = new Vector(x, y);
this.velocity = new Vector(0.0, 0.0);
this.force = new Vector(0.0, 0.0);
this.mass = 1;
this.alpha = 0;
}
// Canvas
var canvas = null;
var context2D = null;
// Blue Particle Texture
var blueParticleTexture = new Image();
var blueParticleTextureLoaded = false;
var blueParticleTextureAlpha = new Array();
var mousePosition = new Vector();
var mouseDownPosition = new Vector();
// Particles
var particles = new Array();
var center = new Vector(250, 250);
var imageData;
function Initialize()
{
canvas = document.getElementById('canvas');
context2D = canvas.getContext('2d');
for (var createEntity = 0; createEntity < 150; ++createEntity)
{
var randomAngle = Math.random() * Math.PI * 2;
var particle = new Particle(Math.cos(randomAngle) * 250 + 250, Math.sin(randomAngle) * 250 + 250);
particle.velocity = center.Subtract(particle.position).Normal().Normalize().Multiply(Math.random() * 5 + 2);
particle.mass = Math.random() * 3 + 0.5;
particles.push(particle);
}
blueParticleTexture.onload = function()
{
context2D.drawImage(blueParticleTexture, 0, 0);
imageData = context2D.getImageData(0, 0, 5, 5);
var imageDataPixels = imageData.data;
for (var i = 0; i <= 255; ++i)
{
var newImageData = context2D.createImageData(5, 5);
var pixels = newImageData.data;
for (var j = 0, n = pixels.length; j < n; j += 4)
{
pixels[j] = imageDataPixels[j];
pixels[j + 1] = imageDataPixels[j + 1];
pixels[j + 2] = imageDataPixels[j + 2];
pixels[j + 3] = Math.floor(imageDataPixels[j + 3] * i / 255);
}
blueParticleTextureAlpha.push(newImageData);
}
blueParticleTextureLoaded = true;
}
blueParticleTexture.src = 'blueparticle.png';
setInterval(Update, 50);
}
function Update()
{
// Clear the screen
context2D.clearRect(0, 0, canvas.width, canvas.height);
for (var i = 0; i < particles.length; ++i)
{
var particle = particles[i];
var v = center.Subtract(particle.position).Normalize().Multiply(0.5);
particle.force = v;
particle.velocity.ThisAdd(particle.force.Divide(particle.mass));
particle.velocity.ThisMultiply(0.98);
particle.position.ThisAdd(particle.velocity);
particle.force = new Vector();
//if (particle.alpha + 5 < 255) particle.alpha += 5;
if (particle.position.Subtract(center).LengthSquared() < 20 * 20)
{
var randomAngle = Math.random() * Math.PI * 2;
particle.position = new Vector(Math.cos(randomAngle) * 250 + 250, Math.sin(randomAngle) * 250 + 250);
particle.velocity = center.Subtract(particle.position).Normal().Normalize().Multiply(Math.random() * 5 + 2);
//particle.alpha = 0;
}
}
if (blueParticleTextureLoaded)
{
for (var i = 0; i < particles.length; ++i)
{
var particle = particles[i];
var intensity = Math.min(1, Math.max(0, 1 - Math.abs(particle.position.Subtract(center).Length() - 125) / 125));
context2D.putImageData(blueParticleTextureAlpha[Math.floor(intensity * 255)], particle.position.X - 2.5, particle.position.Y - 2.5, 0, 0, blueParticleTexture.width, blueParticleTexture.height);
//context2D.drawImage(blueParticleTexture, particle.position.X - 2.5, particle.position.Y - 2.5);
}
}
}
</script>
<body onload="Initialize()" style="background-color:black">
<canvas id="canvas" width="500" height="500" style="border:2px solid gray;"/>
<h1>Canvas is not supported in this browser.</h1>
</canvas>
<p>No directions</p>
</body>
</html>
where vector.js is just a naive vector object:
vector.js 只是一个简单的向量对象:
// Vector class
// TODO: EXamples
// v0 = v1 * 100 + v3 * 200;
// v0 = v1.MultiplY(100).Add(v2.MultiplY(200));
// TODO: In the future maYbe implement:
// VectorEval("%1 = %2 * %3 + %4 * %5", v0, v1, 100, v2, 200);
function Vector(X, Y)
{
/*
this.__defineGetter__("X", function() { return this.X; });
this.__defineSetter__("X", function(value) { this.X = value });
this.__defineGetter__("Y", function() { return this.Y; });
this.__defineSetter__("Y", function(value) { this.Y = value });
*/
this.Add = function(v)
{
return new Vector(this.X + v.X, this.Y + v.Y);
}
this.Subtract = function(v)
{
return new Vector(this.X - v.X, this.Y - v.Y);
}
this.Multiply = function(s)
{
return new Vector(this.X * s, this.Y * s);
}
this.Divide = function(s)
{
return new Vector(this.X / s, this.Y / s);
}
this.ThisAdd = function(v)
{
this.X += v.X;
this.Y += v.Y;
return this;
}
this.ThisSubtract = function(v)
{
this.X -= v.X;
this.Y -= v.Y;
return this;
}
this.ThisMultiply = function(s)
{
this.X *= s;
this.Y *= s;
return this;
}
this.ThisDivide = function(s)
{
this.X /= s;
this.Y /= s;
return this;
}
this.Length = function()
{
return Math.sqrt(this.X * this.X + this.Y * this.Y);
}
this.LengthSquared = function()
{
return this.X * this.X + this.Y * this.Y;
}
this.Normal = function()
{
return new Vector(-this.Y, this.X);
}
this.ThisNormal = function()
{
var X = this.X;
this.X = -this.Y
this.Y = X;
return this;
}
this.Normalize = function()
{
var length = this.Length();
if(length != 0)
{
return new Vector(this.X / length, this.Y / length);
}
}
this.ThisNormalize = function()
{
var length = this.Length();
if (length != 0)
{
this.X /= length;
this.Y /= length;
}
return this;
}
this.Negate = function()
{
return new Vector(-this.X, -this.Y);
}
this.ThisNegate = function()
{
this.X = -this.X;
this.Y = -this.Y;
return this;
}
this.Compare = function(v)
{
return Math.abs(this.X - v.X) < 0.0001 && Math.abs(this.Y - v.Y) < 0.0001;
}
this.Dot = function(v)
{
return this.X * v.X + this.Y * v.Y;
}
this.Cross = function(v)
{
return this.X * v.Y - this.Y * v.X;
}
this.Projection = function(v)
{
return this.MultiplY(v, (this.X * v.X + this.Y * v.Y) / (v.X * v.X + v.Y * v.Y));
}
this.ThisProjection = function(v)
{
var temp = (this.X * v.X + this.Y * v.Y) / (v.X * v.X + v.Y * v.Y);
this.X = v.X * temp;
this.Y = v.Y * temp;
return this;
}
// If X and Y aren't supplied, default them to zero
if (X == undefined) this.X = 0; else this.X = X;
if (Y == undefined) this.Y = 0; else this.Y = Y;
}
/*
Object.definePropertY(Vector, "X", {get : function(){ return X; },
set : function(value){ X = value; },
enumerable : true,
configurable : true});
Object.definePropertY(Vector, "Y", {get : function(){ return X; },
set : function(value){ X = value; },
enumerable : true,
configurable : true});
*/
回答by djdolber
This question still stands. The solution some seem to be suggesting is drawing the image to be tinted onto another canvas and from there grabbing the ImageData object to be able to modify it pixel by pixel, the problem with this is that it is not really acceptable in a game development context because i basically will have to draw each particle 2 times instead of 1. A solution i am about to try is to draw each particle once on a canvas and grabbing the ImageData object, before the actual application starts, and then work with the ImageData object instead of the actual Image object but it might prove kind of costly to create new copies since i will have to keep an unmodified original ImageData object for each graphic.
这个问题仍然成立。一些人似乎建议的解决方案是将要着色的图像绘制到另一个画布上,然后从那里抓取 ImageData 对象以便能够逐个像素地修改它,问题在于它在游戏开发环境中是不可接受的因为我基本上必须将每个粒子绘制 2 次而不是 1 次。我要尝试的解决方案是在实际应用程序启动之前在画布上绘制每个粒子一次并抓取 ImageData 对象,然后使用 ImageData 对象而不是实际的 Image 对象,但创建新副本可能会证明有点昂贵,因为我必须为每个图形保留一个未修改的原始 ImageData 对象。
回答by SmujMaiku
Unfortunately, there is not simply one value to change similar to openGL or DirectX libraries I've used in the past. However, it's not too much work to create a new buffer canvas and use the available globalCompositeOperationwhen drawing an image.
不幸的是,与我过去使用过的 openGL 或 DirectX 库类似,要更改的值不仅仅是一个。但是,在绘制图像时创建新的缓冲区画布并使用可用的globalCompositeOperation并没有太多工作。
// Create a buffer element to draw based on the Image img
let el = Object.assign(document.createElement('canvas'), {
width: img.width,
height: img.height
});
let btx = el.getContext('2d');
// First draw your image to the buffer
btx.drawImage(img, 0, 0);
// Now we'll multiply a rectangle of your chosen color
btx.fillStyle = '#FF7700';
btx.globalCompositeOperation = 'multiply';
btx.fillRect(0, 0, el.width, el.height);
// Finally, fix masking issues you'll probably incur and optional globalAlpha
btx.globalAlpha = 0.5;
btx.globalCompositeOperation = 'destination-in';
btx.drawImage(img, 0, 0);
You can now use el as your first parameter canvas2d.drawImage. Using multiplyyou'll get literal tint but hueand colormay also be to your liking. Also, this is fast enough to wrap in a function for reuse.
您现在可以使用 el 作为您的第一个参数canvas2d.drawImage。使用乘法你会得到字面上的色调,但色调和颜色也可能是你喜欢的。此外,这足以包装在一个函数中以供重用。
回答by cjgammon
I would take a look at this: http://www.arahaya.com/canvasscript3/examples/he seems to have a ColorTransform method, I believe he is drawing a shape to do the transform but perhaps based on this you can find a way to adjust a specific image.
我会看看这个:http: //www.arahaya.com/canvasscript3/examples/他似乎有一个 ColorTransform 方法,我相信他正在绘制一个形状来进行变换,但也许基于此你可以找到一个调整特定图像的方法。