Comment teinter une image avec de la toile HTML5?
ma question Est, Quelle est la meilleure façon de teinte une image qui est dessinée en utilisant la méthode drawImage. L'usage cible pour cela est avancé 2D particle-effects (développement de jeu) où les particules changent de couleurs au fil du temps, etc. Je ne demande pas comment teinter toute la toile, seulement l'image actuelle que je suis sur le point de dessiner.
j'ai conclu que le paramètre globalAlpha affecte l'image actuelle qui est dessinée.
//works with drawImage()
canvas2d.globalAlpha = 0.5;
Mais comment puis-je teinter chaque image avec un arbitraire valeur de couleur ? Ce serait génial s'il y avait une sorte de globalFillStyle ou globalColor ou ce genre de chose...
EDIT:
Voici une capture d'écran de l'application avec laquelle je travaille: http://twitpic.com/1j2aeg/full texte alternatif http://web20.twitpic.com/img/92485672-1d59e2f85d099210d4dafb5211bf770f.4bd804ef-scaled.png
6 réponses
Vous avez des opérations de compositing, et l'une d'elles est destination-atop. Si vous composez une image sur une couleur unie avec le contexte.globalCompositeOperation = "destination-atop"', il aura l'alpha de l'image de premier plan, et la couleur de l'image de fond. J'ai utilisé ce faire entièrement teinté copie d'une image, puis tira entièrement teinté copie sur l'original à une opacité égale à la quantité que je veux teinte.
Ici, c'est la pleine 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>
Il y a une méthode ici vous pouvez utiliser pour tindre des images, et c'est plus précis que de dessiner des rectangles colorés et plus rapide que de travailler sur une base pixel par pixel. Une explication complète est dans ce post de blog, y compris le code JS, mais voici un résumé de la façon dont cela fonctionne.
tout d'abord, vous parcourez l'image que vous teintez pixel par pixel, la lecture des données et la division de chaque pixel en 4 composants distincts: rouge, vert, bleu et noir. Vous écrivez chaque composant une autre toile. Donc maintenant vous avez 4 (rouge, vert, bleu et noir) versions de l'image originale.
quand vous voulez dessiner une image teintée, vous créez (ou trouvez) une toile hors-écran et y dessinez ces composants. Le noir est dessiné d'abord, et ensuite vous avez besoin de définir la compositeopération globale de la toile à "plus léger" de sorte que les composants suivants sont ajoutés à la toile. Le noir n'est pas non plus transparent.
Les trois composants sont issus (le rouge, le bleu et le vert images), mais leur valeur alpha est basée sur la quantité de leur composant constitue la couleur du dessin. Donc si la couleur est blanche, alors tous les trois sont dessinés avec 1 alpha. Si la couleur est verte, seule l'image verte est dessinée et les deux autres sont sautées. Si la couleur est orange, vous avez l'alpha complet sur le rouge, dessinez Vert partiellement transparent et sautez le bleu.
Maintenant vous avez une version teintée de votre image rendue sur la toile de rechange, et vous venez de le dessiner où que vous est-il besoin de votre toile.
encore une fois le code pour faire ceci est dans le post de blog.
quand j'ai créé un test de particules, j'ai juste mis en cache des images basées sur la rotation (comme 35 rotations), la teinte de couleur, et alpha et j'ai créé un wrapper pour qu'elles soient créées automatiquement. A bien fonctionné. Oui, il devrait y avoir une sorte d'opération de teinte, mais quand il s'agit de rendre votre meilleur pari un peu comme dans flash est de tout mettre en cache. Particule Exemple j'ai fait pour le fun
<!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>
où le vecteur.js est juste un naïf objet vectoriel:
// 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});
*/
cette question demeure. La solution que certains semblent suggérer est de dessiner l'image à teinter sur une autre toile et de là à saisir L'objet ImageData pour pouvoir la modifier pixel par pixel, le problème avec cela est qu'il n'est pas vraiment acceptable dans un contexte de développement de jeu parce que je vais essentiellement avoir à dessiner chaque particule 2 fois au lieu de 1. Une solution que je suis sur le point d'essayer est de dessiner chaque particule une fois sur une toile et de saisir L'objet ImageData, avant la l'application proprement dite commence, puis travaille avec L'objet ImageData au lieu de l'Objet Image proprement dit, mais il pourrait s'avérer coûteux de créer de nouvelles copies, car je devrai conserver un objet ImageData original non modifié pour chaque graphique.
I would take a look at this:http://www.arahaya.com/canvasscript3/examples/ il semble avoir une méthode ColorTransform, je crois qu'il dessine une forme pour faire la transformation, mais peut-être basé sur cela, vous pouvez trouver un moyen d'ajuster une image spécifique.
malheureusement, il n'y a pas qu'une seule valeur à changer comme les bibliothèques openGL ou DirectX que j'ai utilisées dans le passé. Cependant, ce n'est pas trop de travail pour créer une nouvelle toile tampon et utiliser la Disponible globalCompositeOperation lors du dessin d'une image.
// 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);
Vous pouvez maintenant utiliser el comme premier paramètre canvas2d.drawImage. En utilisant multiplier vous aurez littérale teinte mais teinte et couleur peut également être à votre convenance. Aussi, c'est assez rapide à encapsuler dans une fonction pour la réutilisation.