Как изменить изображение с помощью холста HTML5?
Мой вопрос в том, что лучший способ подкрасить изображение, которое нарисовано с использованием метода drawImage. Целевое использование для этого - продвинутые 2-мерные частицы-эффекты (разработка игр), где частицы меняют цвета во времени и т.д. Я не спрашиваю, как оттенять весь холст, только текущее изображение, которое я собираюсь рисовать.
Я пришел к выводу, что параметр globalAlpha влияет на текущий рисунок.
//works with drawImage()
canvas2d.globalAlpha = 0.5;
Но как я могу отображать каждое изображение с произвольным значением цвета? Было бы замечательно, если бы существовал какой-то тип globalFillStyle или globalColor или что-то вроде этого...
EDIT:
Вот скриншот приложения, с которым я работаю:
http://twitpic.com/1j2aeg/full
alt text http://web20.twitpic.com/img/92485672-1d59e2f85d099210d4dafb5211bf770f.4bd804ef-scaled.png
Ответы
Ответ 1
У вас есть операции компоновки, и один из них - цель-поверх. Если вы составьте изображение на сплошной цвет с помощью параметра context.globalCompositeOperation = "destination-atop", он будет иметь альфа изображения переднего плана и цвет фона. Я использовал это, чтобы сделать полностью тонированную копию изображения, а затем нарисовал полностью тонированную копию поверх оригинала с непрозрачностью, равной количеству, которое я хочу оттенять.
Вот полный код:
<!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>
Ответ 2
Когда я создал тест на частицу, я просто кэшировал изображения на основе вращения (например, 35 вращений), оттенка цвета и альфы и создал обертку, чтобы они были созданы автоматически. Хорошо работает. Да, должна быть какая-то операция оттенка, но при работе с программным обеспечением ваш лучший выбор, как во флеше, - это кешировать все.
Пример частицы, который я сделал для удовольствия
<!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});
*/
Ответ 3
Существует метод здесь, который вы можете использовать для оттенков изображений, и он более точный, чем рисование цветных прямоугольников и быстрее, чем работа над пикселем- по-пиксельной основе. Полное объяснение в том блоге, в том числе JS-код, но вот резюме того, как он работает.
Сначала вы просматриваете изображение, которое вы подсвечиваете по пикселям, считывая данные и разбивая каждый пиксель на 4 отдельных компонента: красный, зеленый, синий и черный. Вы записываете каждый компонент на отдельный холст. Итак, теперь у вас есть 4 (красный, зеленый, синий и черный) версии исходного изображения.
Если вы хотите нарисовать тонированное изображение, вы создаете (или находите) заставку и рисуете на ней эти компоненты. Сначала черным рисуется, а затем вам нужно установить globalCompositeOperation холста на "легче", чтобы добавить следующие компоненты в холст. Черный также непрозрачен.
Следующие три компонента рисуются (красные, синие и зеленые изображения), но их альфа-значение зависит от того, насколько их компонент составляет цвет чертежа. Поэтому, если цвет белый, то все три рисуются с 1 альфа. Если цвет зеленый, тогда отображается только зеленое изображение, а остальные два пропускаются. Если цвет оранжевый, то у вас есть полная альфа на красном, частично нанесите зеленый цвет и пропустите синий цвет.
Теперь у вас есть тонированная версия вашего изображения, представленная на запасном холсте, и вы просто рисуете ее там, где вам нужно, на вашем холсте.
Снова код для этого в блоге.
Ответ 4
Этот вопрос все еще стоит. Некоторое решение, похоже, предполагает, что изображение должно быть окрашено на другой холст, а оттуда захватить объект ImageData, чтобы он мог изменять его пиксель за пикселем, проблема заключается в том, что он не очень приемлем в контексте развития игры потому что мне в основном придется рисовать каждую частицу 2 раза, а не 1. Решение, которое я собираюсь попробовать, состоит в том, чтобы нарисовать каждую частицу на холсте и захватить объект ImageData до начала реального приложения, а затем работать с объектом ImageData вместо фактического объекта Image, но может оказаться довольно дорогостоящим для создания новых копий, так как мне нужно будет сохранить неизменный исходный объект ImageData для каждого изображения.
Ответ 5
Я бы посмотрел на это: http://www.arahaya.com/canvasscript3/examples/ у него, кажется, есть метод ColorTransform, я считаю, что он рисует форму, чтобы сделать преобразование, но, возможно, на основе этого вы можете найти способ настройки определенного изображения.
Ответ 6
К сожалению, есть не просто одно значение для изменения, похожее на библиотеки 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);
Теперь вы можете использовать el в качестве первого параметра canvas2d.drawImage. Используя multiply, вы получите литеральный оттенок, но оттенок и цвет также по вашему вкусу. Кроме того, это достаточно быстро, чтобы обернуть функцию повторного использования.