Как мне подкрасить изображение с помощью HTML5 Canvas? - PullRequest
26 голосов
/ 22 апреля 2010

Мой вопрос: как лучше всего подкрасить изображение, нарисованное с помощью метода drawImage. Целевое использование для этого - продвинутые 2d-эффекты частиц (разработка игр), где частицы меняют цвет с течением времени и т. Д. Я не спрашиваю, как подкрасить весь холст, только текущее изображение, которое я собираюсь нарисовать.

Я пришел к выводу, что параметр globalAlpha влияет на текущее изображение, которое рисуется.

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

Но как мне подкрасить каждое изображение произвольным значением цвета? Было бы замечательно, если бы был какой-то globalFillStyle или globalColor или что-то подобное ...

EDIT:

Вот скриншот приложения, с которым я работаю: http://twitpic.com/1j2aeg/full альтернативный текст http://web20.twitpic.com/img/92485672-1d59e2f85d099210d4dafb5211bf770f.4bd804ef-scaled.png

Ответы [ 6 ]

30 голосов
/ 20 ноября 2010

У вас есть операции компоновки, и одна из них - назначение-поверх.Если вы скомбинируете изображение в сплошной цвет с помощью 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>
5 голосов
/ 13 июля 2011

Существует метод здесь , который можно использовать для тонирования изображений, и он более точен, чем рисование цветных прямоугольников, и быстрее, чем работа по попиксельной основе. Полное объяснение приведено в этом посте, включая код JS, но здесь приведено краткое описание его работы.

Сначала вы просматриваете изображение, которое вы тонируете попиксельно, считываете данные и разбиваете каждый пиксель на 4 отдельных компонента: красный, зеленый, синий и черный. Вы пишете каждый компонент на отдельный холст. Итак, теперь у вас есть 4 (красная, зеленая, синяя и черная) версии исходного изображения.

Когда вы хотите нарисовать тонированное изображение, вы создаете (или находите) закадровый холст и рисуете на нем эти компоненты. Сначала рисуется черный цвет, а затем вам нужно установить globalCompositeOperation холста на «светлее», чтобы следующие компоненты были добавлены на холст. Черный также непрозрачен.

Нарисованы следующие три компонента (красные, синие и зеленые изображения), но их альфа-значение основано на том, насколько их компонент составляет цвет рисунка. Так что, если цвет белый, то все три нарисованы с 1 альфа. Если цвет зеленый, то рисуется только зеленое изображение, а два других пропускаются. Если цвет оранжевый, то у вас полная альфа на красном, нарисуйте зеленый частично прозрачным и пропустите синий.

Теперь у вас есть тонированная версия вашего изображения, представленная на запасном холсте, и вы просто рисуете ее там, где вам нужно, на вашем холсте.

Снова код для этого в сообщении в блоге.

4 голосов
/ 22 апреля 2010

Когда я создавал тест частиц, я просто кэшировал изображения на основе поворота (например, 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});
*/
2 голосов
/ 15 июня 2017

К сожалению, нет просто одного значения для изменения, подобного библиотекам 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 .Используя умножение , вы получите буквальный оттенок, но оттенок и цвет также могут вам понравиться.Кроме того, это достаточно быстро, чтобы включить функцию для повторного использования.

2 голосов
/ 28 апреля 2010

Этот вопрос все еще стоит.Решение, которое некоторые, похоже, предлагают, состоит в том, чтобы нарисовать изображение для тонирования на другом холсте и оттуда захватить объект ImageData, чтобы иметь возможность изменять его попиксельно, проблема в том, что это не совсем приемлемо в контексте разработки игры.потому что мне в основном придется рисовать каждую частицу 2 раза вместо 1. Решение, которое я собираюсь попробовать, состоит в том, чтобы нарисовать каждую частицу один раз на холсте и захватить объект ImageData, прежде чем собственно приложение запустится, а затем работать с объектом ImageData.вместо фактического объекта Image, но создание новых копий может оказаться довольно дорогостоящим, поскольку мне придется хранить неизмененный исходный объект ImageData для каждой графики.

1 голос
/ 02 мая 2011

Я хотел бы взглянуть на это: http://www.arahaya.com/canvasscript3/examples/ у него, кажется, есть метод ColorTransform, я думаю, он рисует фигуру для преобразования, но, возможно, основываясь на этом, вы можете найти способ настроить конкретный изображение.

...