【问题标题】:Drawing image with additive blending使用添加剂混合绘制图像
【发布时间】:2012-08-29 04:29:30
【问题描述】:

问题已回答。有关更多信息,请查看本文末尾的 EDIT #4。

我们目前正在开发一个运行良好的游戏引擎。我正在开发动画创建器,想知道是否可以使用加法混合绘制图像。

让我解释一下。

我们使用 C# 的 System.Drawing 库并使用 Windows 窗体。目前,用户可以通过导入带框动画图像(包含动画每一帧的图像)来创建动画,并且用户可以将这些帧拖放到任何他想要的地方。

实际的问题是我们无法弄清楚如何使用加法混合绘制框架。

如果您不太了解,这里有一个添加剂混合的示例。我不会怪你,我很难用英语写作。

我们使用下面的方法在 Panel 上或直接在窗体上绘制。例如,这里是为地图编辑器绘制平铺地图的代码。由于 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:

为了绘制图像,我们使用颜色矩阵来设置色调和 alph(不透明度),如下所示:

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:

刚刚发现 this Mahesh Chand 的文章。

进一步浏览后,可能无法使用颜色矩阵,即使它可以完成很多关于颜色转换的工作。 如果找到解决方案,我将回答我自己的问题。

感谢您的帮助。

编辑#3:

XNA 有很多关于混合的文档here。我找到了用于在图像的每个像素上完成加法混合的公式。

PixelColor = (source * [1, 1, 1, 1]) + (destination * [1, 1, 1, 1])

也许有一种方法可以在当前上下文中使用这个公式? 我将在下一次编辑时开始 50 赏金,我们真的需要这个来工作。

再次感谢您的宝贵时间。

编辑#4

感谢 axon,现在问题解决了。使用 XNA 及其 Spritebatch,您可以完成 Additive 混合:

首先你创建一个 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();

【问题讨论】:

  • 我不确定这是否可以在直接的 winforms 中实现,而无需自行处理像素值。是否可以选择使用 WPF 托管组件进行绘图? WPF 能够更轻松地设置混合模式。
  • @Dervall 我们搜索得越多,我们就越开始认为使用 WPF 托管组件是一个非常好的主意。如果不直接在 Winforms 中使用逐像素算法,可能就没有解决方案。或者也许有人会想出某种奇迹?
  • .DrawImage with opacity? 的可能副本
  • @HansPassant 谢谢你的信息。问题相似但不一样。您提供的链接是关于改变不透明度的,这很容易改变颜色矩阵的 [3][3] 或 [4][3] 值。我要做的是将图像与下面的图像混合,但使用加法混合,请参见图像示例。
  • 这不是关于不透明度,而是关于混合。蹩脚的标题,发生了。 sn-p 中的cm.Matrix33 = 1F - mBlend; 语句是您遗漏的。

标签: c# winforms system.drawing color-blending


【解决方案1】:

要么使用 1) XNA(建议速度),要么 2) 在 C# 中使用像素操作。可能还有其他方法,但其中任何一种都有效(我将它们分别用于我维护的 3D 效果和图像分析应用程序)。

C# 中的像素操作: 使用 3 个位图; bmpA、bmpB、bmpC,你想在bmpC中存储bmpA+bmpB的地方。

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”指令编译。还假设 R、G 和 B 各有 1 个字节。更改代码以适合您的像素格式。

使用 XNA 更快(性能),因为它是硬件加速(由 GPU)。它基本上由以下几部分组成: 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);
}

您使用上述哪种着色器取决于您是要使用“sampler2D”还是“texture”HLSL 接口来访问纹理。

您还应该小心使用适当的采样器设置,以确保在查找颜色值时不使用采样(例如线性插值),除非这是您想要的(在这种情况下使用更高质量/更高阶的东西) .

XNA 还具有内置的 BlendState,您可以使用它来指定重叠纹理的组合方式。 IE。 BlendState.Additive(参见更新后的原始帖子)。

【讨论】:

  • 让我明天一到团队就试试这个。这很有意义;您需要源图像才能将像素与要绘制的像素混合。即使我们决定不使用 XNA,我也可以用你提供的这个非常完善的答案来说服他们。
  • 好的,所以团队最终决定将 XNA 与 WinForms 一起使用。先生,您刚刚挽救了这一天。谢谢!我得等 14 个小时才能给你 50 分。
  • 谢谢你,我添加了第四个编辑,展示了如何完成所有绘图,如果你想查看的话。
  • 我注意到您找到了内置的 BlendState.Additive(这比在着色器中执行要容易一些)。希望纹理采样不是问题(如果您的精灵以不同于其原始大小的大小绘制,那么加法混合也会由于像素的重新采样而改变图像)。
  • 现在好像XNA is dead
猜你喜欢
  • 2015-04-16
  • 2015-01-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-04-10
相关资源
最近更新 更多