[英]How do i tint an image with HTML5 Canvas?
我的問題是,為使用 drawImage 方法繪制的圖像着色的最佳方法是什么。 其目標用途是高級 2d 粒子效果(游戲開發),其中粒子會隨着時間改變顏色等。我不是在問如何給整個畫布着色,只問我將要繪制的當前圖像。
我得出的結論是 globalAlpha 參數會影響繪制的當前圖像。
//works with drawImage()
canvas2d.globalAlpha = 0.5;
但是我如何用任意顏色值為每個圖像着色? 如果有某種 globalFillStyle 或 globalColor 或那種東西,那就太棒了......
編輯:
這是我正在使用的應用程序的屏幕截圖: http : //twitpic.com/1j2aeg/full alt text http://web20.twitpic.com/img/92485672-1d59e2f85d099210d4dafb5211bf770f.4bd804ef-scaled
您有合成操作,其中之一是目標頂部。 如果您使用 'context.globalCompositeOperation = "destination-atop"' 將圖像合成為純色,它將具有前景圖像的 alpha 和背景圖像的顏色。 我用它來制作圖像的完全着色副本,然后在原件的頂部繪制完全着色的副本,不透明度等於我想要着色的數量。
這是完整的代碼:
<!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>
有一種方法, 在這里,你可以用它來着色的圖像,這是更准確的再畫彩色矩形和更快然后在逐像素的基礎工作。 該博客文章中有完整的解釋,包括 JS 代碼,但這里是其工作原理的摘要。
首先,您瀏覽要逐個像素着色的圖像,讀出數據並將每個像素分成 4 個獨立的部分:紅色、綠色、藍色和黑色。 您將每個組件寫入單獨的畫布。 所以現在您有原始圖像的 4 個(紅色、綠色、藍色和黑色)版本。
當您想要繪制着色圖像時,您可以創建(或找到)一個離屏畫布並將這些組件繪制到它上面。 首先繪制黑色,然后您需要將畫布的 globalCompositeOperation 設置為“更亮”,以便將下一個組件添加到畫布中。 黑色也是不透明的。
繪制了接下來的三個組件(紅色、藍色和綠色圖像),但它們的 alpha 值取決於它們的組件構成繪圖顏色的程度。 因此,如果顏色為白色,則所有三個都使用 1 alpha 繪制。 如果顏色為綠色,則只繪制綠色圖像,跳過其他兩個。 如果顏色是橙色,那么你在紅色上有完整的 alpha,繪制綠色部分透明並跳過藍色。
現在,您已將圖像的着色版本渲染到備用畫布上,您只需將其繪制到畫布上需要的任何位置即可。
再次執行此操作的代碼在博客文章中。
當我創建粒子測試時,我只是根據旋轉(如 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>
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});
*/
不幸的是,與我過去使用過的 openGL 或 DirectX 庫類似,要更改的值不僅僅是一個。 但是,在繪制圖像時創建新的緩沖區畫布並使用可用的globalCompositeOperation並沒有太多工作。
// Create a buffer element to draw based on the Image img
const buffer = document.createElement('canvas');
buffer.width = img.width;
buffer.height = img.height;
const btx = buffer.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, buffer.width, buffer.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);
您現在可以使用buffer
作為您的第一個參數canvas2d.drawImage 。 使用乘法你會得到字面上的色調,但色調和顏色也可能是你喜歡的。 此外,這足以包裝在一個函數中以供重用。
這個問題仍然成立。 一些人似乎建議的解決方案是將要着色的圖像繪制到另一個畫布上,然后從那里抓取 ImageData 對象以便能夠逐個像素地修改它,問題在於它在游戲開發環境中是不可接受的因為我基本上必須將每個粒子繪制 2 次而不是 1 次。我要嘗試的解決方案是在實際應用程序啟動之前在畫布上繪制每個粒子一次並抓取 ImageData 對象,然后使用 ImageData 對象而不是實際的 Image 對象,但創建新副本可能會證明成本很高,因為我必須為每個圖形保留一個未修改的原始 ImageData 對象。
我會看看這個: http : //www.arahaya.com/canvasscript3/examples/他似乎有一個 ColorTransform 方法,我相信他正在繪制一個形狀來進行變換,但也許基於此你可以找到一個調整特定圖像的方法。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.