2010-04-22 9 views
24

Meine Frage ist, was ist die beste Möglichkeit, ein Bild zu tönen, das mit der Methode drawImage gezeichnet wird. Die Zielanwendung hierfür sind fortgeschrittene 2d-Partikeleffekte (Spieleentwicklung), bei denen Partikel im Laufe der Zeit ihre Farbe ändern usw. Ich frage nicht, wie man die gesamte Leinwand tönt, sondern nur das aktuelle Bild, das ich zeichnen möchte.Wie töne ich ein Bild mit HTML5 Canvas?

Ich bin zu dem Schluss gekommen, dass der Parameter globalAlpha das aktuelle Bild beeinflusst, das gezeichnet wird.

//works with drawImage() 
canvas2d.globalAlpha = 0.5; 

Aber wie tönen i jedes Bild mit einer beliebigen Farbwert? Es wäre fantastisch, wenn es irgendeine Art von globalFillStyle oder globalColor oder so etwas war ...

EDIT:

Hier ist ein Screenshot der Anwendung mit denen ich arbeite: http://twitpic.com/1j2aeg/full alt text http://web20.twitpic.com/img/92485672-1d59e2f85d099210d4dafb5211bf770f.4bd804ef-scaled.png

Antwort

4

Beim Erstellen eines Partikeltests habe ich nur Bilder basierend auf Rotation (wie 35 Rotationen), Farbtönung und Alpha zwischengespeichert und einen Wrapper erstellt, so dass sie automatisch erstellt wurden. Gut gearbeitet. Ja, es sollte eine Art von Tint-Operation geben, aber wenn Sie mit Software-Rendering zu tun haben, ist Ihre beste Wette ähnlich wie im Flash, alles zu cachen. Particle Example I made for 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> 

wo vector.js ist nur ein naives Vektorobjekt:

// 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}); 
*/ 
+0

Vielen Dank für die Antwort, es scheint wie eine faire Lösung, wenn Sie jetzt eine gute Leistung wollen, aber ich bin auf der Suche nach der derzeit besten Möglichkeit, die Partikel im laufenden Betrieb zu tönen, ohne irgendwelche Grafikdaten zwischenzuspeichern. – djdolber

+0

Ich werde einen Blick darauf werfen, wie Sie Ihre Grafikdaten tönen und diese Lösung für den Moment ausprobieren, aber ich hoffe auf eine bessere Lösung, die sozusagen "eingebaut" ist. – djdolber

+0

Trotzdem verwende ich Ihre Tönungsmethode, aber für die Echtzeit Tönung. – djdolber

2

Diese Frage steht noch. Die Lösung, die einige vorzuschlagen scheinen, besteht darin, das zu färbende Bild auf eine andere Leinwand zu zeichnen und von dort das ImageData-Objekt zu packen, um es Pixel für Pixel modifizieren zu können. Das Problem dabei ist, dass es in einem Spieleentwicklungskontext nicht wirklich akzeptabel ist weil ich im Grunde jedes Partikel 2 Mal anstelle von 1 zeichnen muss. Eine Lösung, die ich versuche, ist, jedes Partikel einmal auf einer Leinwand zu zeichnen und das ImageData-Objekt zu greifen, bevor die eigentliche Anwendung startet, und dann mit dem ImageData-Objekt zu arbeiten anstelle des eigentlichen Image-Objekts, aber es könnte sich als sehr kostspielig erweisen, neue Kopien zu erstellen, da ich ein unmodifiziertes Original-ImageData-Objekt für jede Grafik behalten muss.

28

Sie haben Compositing-Operationen und einer von ihnen ist Ziel-atop. Wenn Sie ein Bild mit 'context.globalCompositeOperation = "Ziel-auf"' auf eine Volltonfarbe zusammensetzen, wird es das Alpha des Vordergrundbilds und die Farbe des Hintergrundbilds haben. Ich benutzte dies, um eine vollständig getönte Kopie eines Bildes zu erstellen, und zeichnete dann diese vollständig getönte Kopie auf das Original mit einer Opazität, die der Menge entspricht, die ich einfärben möchte. Hier

ist der vollständige 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> 
+0

Das hat super für mich funktioniert! Vielen Dank. – rhigdon

+0

Weiß jemand, ob dies "spielähnliche" Leistung hat? Ich denke, es ist besser, den Puffer einmal zu erstellen und dann wieder zu verwenden? –

1

ich einen Blick auf diese nehmen würde: http://www.arahaya.com/canvasscript3/examples/ er ein Colortransform-Methode zu haben scheint, ich glaube, dass er eine Form zieht die Transformation zu tun, aber vielleicht auf dieser Basis Sie können einen Weg finden, um ein bestimmtes Bild anzupassen.

4

Es gibt eine Methode here, die Sie verwenden können, um Bilder zu tönen, und es ist genauer als das Zeichnen von farbigen Rechtecken und schneller, dann auf einer Pixel-für-Pixel-Basis arbeiten. Eine vollständige Erklärung ist in diesem Blogpost, einschließlich des JS-Codes, aber hier ist eine Zusammenfassung, wie es funktioniert.

Zuerst gehen Sie durch das Bild, das Sie pixelweise abtasten, lesen Sie die Daten aus und teilen Sie jedes Pixel in 4 separate Komponenten auf: rot, grün, blau und schwarz.Sie schreiben jede Komponente in eine separate Zeichenfläche. So, jetzt haben Sie 4 (rot, grün, blau und schwarz) Versionen des Originalbildes.

Wenn Sie ein getöntes Bild zeichnen möchten, erstellen Sie (oder finden Sie) eine Leinwand außerhalb des Bildschirms und zeichnen Sie diese Komponenten darauf. Das Schwarz wird zuerst gezeichnet, und dann müssen Sie die globalCompositeOperation der Arbeitsfläche auf "heller" einstellen, damit die nächsten Komponenten zur Arbeitsfläche hinzugefügt werden. Das Schwarz ist auch nicht transparent.

Die nächsten drei Komponenten werden gezeichnet (die roten, blauen und grünen Bilder), aber ihr Alpha-Wert basiert darauf, wie sehr ihre Komponente die Zeichnungsfarbe ausmacht. Wenn also die Farbe weiß ist, werden alle drei mit 1 Alpha gezeichnet. Wenn die Farbe grün ist, wird nur das grüne Bild gezeichnet und die anderen beiden werden übersprungen. Wenn die Farbe orange ist, haben Sie volle Alpha auf dem roten, zeichnen Sie grün teilweise transparent und überspringen Sie das blaue.

Jetzt haben Sie eine getönte Version Ihres Bildes auf dem Ersatz-Canvas gerendert, und Sie zeichnen es einfach dorthin, wo Sie es auf Ihrer Leinwand benötigen.

Wieder ist der Code dafür im Blogpost.

0

Leider gibt es nicht einfach einen zu ändernden Wert, der den OpenGL- oder DirectX-Bibliotheken ähnelt, die ich in der Vergangenheit verwendet habe. Es ist jedoch nicht zu viel Arbeit, einen neuen Pufferbereich zu erstellen und die verfügbare globalCompositeOperation beim Zeichnen eines Bildes zu verwenden.

// 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); 

Sie können nun el als ersten Parameter canvas2d.drawImage verwenden. Mit multiplizieren Sie erhalten Sie wörtliche Tönung aber Farbton und Farbe kann auch nach Ihren Wünschen sein. Dies ist auch schnell genug, um in eine Funktion zur Wiederverwendung einzubinden.