Нанесение изображения с аддитивным смешиванием

На вопрос был дан ответ.Для получения дополнительной информации, проверьте РЕДАКТИРОВАТЬ #4 в конце этого текста.

В настоящее время мы работаем над созданием игрового движка, который работает довольно хорошо. Я работаю над создателем анимации и мне было интересно, можно ли нарисовать изображение с помощью аддитивного смешивания.

Позволь мне объяснить.

Мы используем библиотеку System.Drawing C# и работаем с Windows Forms. На данный момент пользователь может создать свою анимацию, импортировав изображение анимации в рамке (изображение, содержащее каждый кадр анимации), и пользователь может перетаскивать эти кадры куда угодно.

Проблема в том, что мы не можем понять, как нарисовать рамку с помощью аддитивного смешивания.

Вот пример того, что такое аддитивное смешивание, если вы не совсем поняли. Я не буду винить тебя, мне трудно писать по-английски.Пример аддитивного смешивания

Мы используем следующий метод для рисования на панели или непосредственно в форме. Например, вот код для рисования мозаичной карты для редактора карт. Поскольку код AnimationManager - беспорядок, с этим примером это будет понятнее.

        using (Graphics g = Graphics.FromImage(MapBuffer as Image))
        using (Brush brush = new SolidBrush(Color.White))
        using (Pen pen = new Pen(Color.FromArgb(255, 0, 0, 0), 1))
        {
            g.FillRectangle(brush, new Rectangle(new Point(0, 0), new Size(CurrentMap.MapSize.Width * TileSize, CurrentMap.MapSize.Height * TileSize)));

            Tile tile = CurrentMap.Tiles[l, x, y];
            if (tile.Background != null) g.DrawImage(tile.Background, new Point(tile.X * TileSize, tile.Y * TileSize));

            g.DrawRectangle(pen, x * TileSize, y * TileSize, TileSize, TileSize);
        }

Есть ли возможный способ нарисовать изображение с помощью аддитивного рисунка, и если так, я был бы всегда благодарен, если бы кто-то мог указать мне, как. Спасибо.

РЕДАКТИРОВАНИЕ № 1:

Для рисования изображений мы используем цветовую матрицу для установки оттенка и альфа-канала (непрозрачность) следующим образом:

ColorMatrix matrix = new ColorMatrix
(
    new Single[][]  
    { 
        new Single[] {r, 0, 0, 0, 0},
        new Single[] {0, g, 0, 0, 0},
        new Single[] {0, 0, b, 0, 0},
        new Single[] {0, 0, 0, a, 0},
        new Single[] {0, 0, 0, 0, 1}
    }
);

Может быть, цветовая матрица может быть использована для смешивания добавок?

РЕДАКТИРОВАТЬ № 2:

Только что нашел эту статью Махеш Чанд.

После дальнейшего просмотра, это может быть невозможно с цветовой матрицей, даже несмотря на то, что она может многое сделать в отношении цветовых преобразований. Я отвечу на свой вопрос, если найдется решение.

Спасибо за помощь.

РЕДАКТИРОВАТЬ № 3:

У XNA есть много документации о смешивании. Я нашел формулу, используемую для выполнения аддитивного смешивания на каждом пикселе изображения.

PixelColor = (источник * [1, 1, 1, 1]) + (пункт назначения * [1, 1, 1, 1])

Может быть, есть способ использовать эту формулу в текущем контексте? Я начну вознаграждение за 50 при следующем редактировании, нам действительно нужно, чтобы это работало.

Еще раз спасибо за ваше время.

РЕДАКТИРОВАТЬ № 4

Благодаря аксону, теперь проблема решена. Используя XNA и его Spritebatch, вы можете выполнить аддитивное смешивание следующим образом:

Прежде всего вы создаете GraphicsDevice и SpriteBatch

// In the following example, we want to draw inside a Panel called PN_Canvas. 
// If you want to draw directly on the form, simply use "this" if you
// write the following code in your form class

PresentationParameters pp = new PresentationParameters();

// Replace PN_Canvas with the control to be drawn on
pp.BackBufferHeight = PN_Canvas.Height;
pp.BackBufferWidth = PN_Canvas.Width;
pp.DeviceWindowHandle = PN_Canvas.Handle;
pp.IsFullScreen = false;

device = new GraphicsDevice(GraphicsAdapter.DefaultAdapter, GraphicsProfile.Reach, pp);
batch = new SpriteBatch(device);

Затем, когда пришло время рисовать на элементе управления или в форме (например, с событием OnPaint), вы можете использовать следующий блок кода

// You should always clear the GraphicsDevice first
device.Clear(Microsoft.Xna.Framework.Color.Black);

// Note the last parameter of Begin method
batch.Begin(SpriteSortMode.BackToFront, BlendState.Additive);
batch.draw( /* Things you want to draw, positions and other infos */ );
batch.End();

// The Present method will draw buffer onto control or form
device.Present();

1 ответ

Решение

Либо используйте 1) XNA (рекомендуется для скорости), либо 2) используйте операции с пикселями в C#. Могут быть и другие методы, но любой из них работает (я использую каждый из них для 3D-эффектов и приложений для анализа изображений (соответственно), которые я поддерживаю).

Пиксельные операции в C#: использование 3 растровых изображений; bmpA, bmpB, bmpC, где вы хотите сохранить bmpA+bmpB в bmpC.

for (int y = 0; y < bmp.Height; y++)
{
    for (int x = 0; x < bmp.Width; x++)
    {
        Color cA = bmpA.GetPixel(x,y);
        Color cB = bmpB.GetPixel(x,y);
        Color cC = Color.FromArgb(cA.A, cA.R + cB.R, cA.G + cB.G, cA.B + cB.B);
        bmpC.SetPixel(x, y, cC);
    }
}

Код выше очень медленный. Более быстрое решение в C# может использовать такие указатели:

    // Assumes all bitmaps are the same size and same pixel format
    BitmapData bmpDataA = bmpA.LockBits(new Rectangle(0, 0, bmpA.Width, bmpA.Height), ImageLockMode.ReadOnly, bmpA.PixelFormat);
    BitmapData bmpDataB = bmpB.LockBits(new Rectangle(0, 0, bmpA.Width, bmpA.Height), ImageLockMode.ReadOnly, bmpA.PixelFormat);
    BitmapData bmpDataC = bmpC.LockBits(new Rectangle(0, 0, bmpA.Width, bmpA.Height), ImageLockMode.WriteOnly, bmpA.PixelFormat);
    void* pBmpA = bmpDataA.Scan0.ToPointer();
    void* pBmpB = bmpDataB.Scan0.ToPointer();
    void* pBmpC = bmpDataC.Scan0.ToPointer();
    int bytesPerPix = bmpDataA.Stride / bmpA.Width;
    for (int y = 0; y < bmp.Height; y++)
    {
        for (int x = 0; x < bmp.Width; x++, pBmpA += bytesPerPix, pBmpB += bytesPerPix, pBmpC += bytesPerPix)
        {
            *(byte*)(pBmpC) = *(byte*)(pBmpA) + *(byte*)(pBmpB); // R
            *(byte*)(pBmpC + 1) = *(byte*)(pBmpA + 1) + *(byte*)(pBmpB + 1); // G
            *(byte*)(pBmpC + 2) = *(byte*)(pBmpA + 2) + *(byte*)(pBmpB + 2); // B
        }
    }
    bmpA.UnlockBits(bmpDataA);
    bmpB.UnlockBits(bmpDataB);
    bmpC.UnlockBits(bmpDataC);

Вышеуказанный метод требует указателей и, следовательно, должен быть скомпилирован с директивой "unsafe". Также предполагается, что по 1 байту для каждого из R,G и B. Измените код в соответствии с вашим форматом пикселей.

Использование XNA намного быстрее (производительность), поскольку оно аппаратно ускоряется (графическим процессором). Он в основном состоит из следующего: 1. Создайте геометрию, необходимую для рисования изображения (прямоугольник, скорее всего, полноэкранный квад). 2. Напишите вершинный шейдер и пиксельный шейдер. Вершинный шейдер может просто проходить сквозь геометрию без изменений. Или вы можете применить ортогональную проекцию (в зависимости от того, с какими координатами вы хотите работать для квадрата). Пиксельный шейдер будет иметь следующие строки (HLSL):

float4 ps(vertexOutput IN) : COLOR 
{
    float3 a = tex2D(ColorSampler,IN.UV).rgb;
    float3 b = tex2D(ColorSampler2,IN.UV).rgb;
    return float4(a + b,1.0f);
}

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

float4 ps(vertexOutput IN) : COLOR 
{
    float3 a = texA.Sample(samplerState, IN.UV).xyz;
    float3 b = texB.Sample(samplerState, IN.UV).xyz;
    return float4(a + b,1.0f);
}

Какой из вышеперечисленных шейдеров вы используете, будет зависеть от того, хотите ли вы использовать HLSL-интерфейсы "sampler2D" или "texture" для доступа к текстурам.

Вы также должны быть осторожны, используя соответствующую настройку сэмплера, чтобы гарантировать, что при поиске значений цвета не используется сэмплирование (например, линейная интерполяция), если это не то, что вам нужно (в этом случае используйте что-то более высокого качества / более высокого порядка).

В XNA также есть встроенные BlendStates, которые вы можете использовать, чтобы указать, как будут комбинироваться перекрывающиеся текстуры. Т.е. BlendState.Additive (см. Обновленный оригинальный пост).

Другие вопросы по тегам