9

Question was answered. For more information, check out EDIT #4 at the end of this text.

We are currently working on a gamemaking engine which is going pretty well. I am working on the Animation Creator and was wondering if it was possible to draw an image with additive blending.

Let me explain.

We use System.Drawing library of C# and we work with Windows Forms. For now, the user is able to create his animation by importing a framed animation image (an image containing every frame of the animation) and the user is able to drag and drop these frames wherever he wants.

The actual problem is that we can't figure out how to draw a frame with the additive blending.

Here's an exemple of what Additive Blending is if you don't quite get it. I won't blame you, I have a hard time writing in english. Additive blending exemple

We are using the following method to draw on a Panel or directly on the form. For exemple here's the code to draw a tiled map for the map editor. Since the AnimationManager code is a mess, it'll be clearer with this exemple.

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

Is there a possible way of drawing an image with an additive drawing and if so, I'd be forever grateful if someone could point me out how. Thank you.

EDIT #1 :

For drawing images, we are using a color matrix to set hue and alph (opacity) like this:

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

Maybe the color matrix can be used for additive blending?

EDIT #2 :

Just found this article by Mahesh Chand.

After further browsing, it may not be possible with a color matrix even though it can accomplish a lot regarding color transformations. I will answer my own question if solution found.

Thank you for you help.

EDIT #3 :

XNA has a lot of documentation here about blending. I found the formula used to accomplish additive blending on each pixels of an image.

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

Maybe there's a way of using this formula in the current context? I will start a 50 bounty on next edit, we really need this to work.

Thank you again for your time.

EDIT #4

Thanks to axon, now the problem is solved. Using XNA and its Spritebatch, you can accomplish Additive blending doing so :

First of all you create a GraphicsDevice and a 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);

Then, when it's time to draw on the control or on the form (with the OnPaint event for example), you can use the following code block

// 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();
Érik Desjardins
  • 993
  • 1
  • 12
  • 25
  • 1
    I'm not sure if this is possible in straight up winforms without resorting to manipulating the pixel values themselves. Would using a WPF hosted component for the drawing be an option? WPF has the ability to set blend modes much easier. – Dervall Aug 29 '12 at 07:47
  • @Dervall The more we search the more we start to think that using a WPF hosted component is a very good idea. Maybe there are no solutions without using a by-pixel algo directly with Winforms. Or maybe someone will come up with some kind of miracle? – Érik Desjardins Aug 29 '12 at 09:34
  • possible duplicate of [.DrawImage with opacity?](http://stackoverflow.com/questions/5519956/drawimage-with-opacity) – Hans Passant Aug 30 '12 at 21:59
  • @HansPassant Thank you for the info. The question is similar but not the same. The link you provided is about changing opacity which is easily done altering the [3][3] or [4][3] value of a color matrix. What I am trying to do is blend the image with the one underneath but using an additive blending, see image exemple. – Érik Desjardins Aug 30 '12 at 23:49
  • It is not about opacity, it is about blending. Crappy title, that happens. The `cm.Matrix33 = 1F - mBlend;` statement in the snippet is what you missed. – Hans Passant Aug 30 '12 at 23:52
  • cm.Matrix33 is the position in the color matrix that contains a floating point which is going to be multiplied by the current alpha channel of an image. If you look closely at the code, mBlend is a Single (float). cm.Matrix33 is a shortcut property for position [3][3] which represents alpha multiplication. – Érik Desjardins Aug 31 '12 at 00:53
  • If you look at it this way, blending and opacity are almost the same since if you draw an image above another image with half opacity, the two of them will blend together as one. Problem is blending and additive blending are two different subjects. See image exemple, you'll see the difference between normal blending and additive. – Érik Desjardins Aug 31 '12 at 01:04
  • XNA's documentation is focussed on using raster operations (ROPs) to combine textures. You can achieve the same thing much more simply with XNA (or any other graphics API that uses the graphics pipeline and shader programs) using a simple pixel-shader (PS); The PS looks up a pixel from each of the two textures that you want to combine. The image is drawn in 2D to screen using a full-screen quad (pair of triangles with texture-coords from (0,0) to (1,1)). Then your pixel shader in HLSL can simply add the pixel values together (or do any other combining operation). This is GPU-accelerated too – axon Aug 31 '12 at 06:26
  • Seems to me that [XNA is dead](http://gamasutra.com/view/news/185894/Its_official_XNA_is_dead.php#.UREwnmResyF) now. – Uwe Keim Aug 27 '13 at 14:20

1 Answers1

8

Either use 1) XNA (recommended for speed), or 2) use pixel-operations in C#. There may be other methods, but either of these work (I'm using each of them for 3D effects and image analysis apps (respectively) that I maintain).

Pixel Operations in C#: Using 3 bitmaps; bmpA, bmpB, bmpC, where you want to store bmpA+bmpB in 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);
    }
}

The above code is very slow. A faster solution in C# could use pointers like this:

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

The above method requires pointers and hence must be compiled with the "unsafe" directive. Also assumes 1-byte for each of R,G, and B. Change the code to suit your pixel format.

Using XNA is a lot faster (performance) since it is hardware accelerated (by the GPU). It basically consists of the following: 1. Create the geometry needed to draw the image (a rectangle, most likely a full-screen quad). 2. Write a vertex-shader and pixel-shader. The vertex-shader can simply pass-through the geometry unmodified. Or you can apply an orthogonal projection (depending on what coordinates you want to work with for the quad). The pixel shader will have the following lines (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);
}

There are different methods available for accessing textures. The following will also work (depending on how you want the XNA code to bind to the shader parameters):

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

Which of the above shaders you use will depend on whether you want to use the "sampler2D" or "texture" HLSL interfaces to access the textures.

You should also be careful to use an appropriate sampler setting to ensure that no sampling (e.g. linear interpolation) is used when looking up colour values unless that's something you want (in which case use something higher-quality/higher-order).

XNA also has built-in BlendStates you can use to specify how overlapped textures will be combined. I.e. BlendState.Additive (see updated original post).

axon
  • 1,190
  • 6
  • 16
  • Let me try this tomorrow as soon as I get with the team. It makes lot of sense; you need the source image in order to blend pixels with the one you want to draw. Eventhough we decided not to use XNA, I might be able to convince them with this extremely wellformed answer you provided. – Érik Desjardins Aug 31 '12 at 09:56
  • Okay, so the team finally decided to use XNA with WinForms. Sir, you just saved the day. Thank you! Got to wait 14 hours until I can give you the 50pts though. – Érik Desjardins Aug 31 '12 at 16:02
  • Thank you, I added a fourth edit showing how to do all of the drawing if you want to check it out. – Érik Desjardins Sep 05 '12 at 20:11
  • I notice you found the inbuilt BlendState.Additive (which is a bit easier than doing it in the shader). Hopefully texture sampling isn't a problem (if your sprite draws at a size other than its original size then the additive blending will also change the image due to re-sampling of pixels). – axon Sep 05 '12 at 23:44
  • Seems that [XNA is dead](http://gamasutra.com/view/news/185894/Its_official_XNA_is_dead.php#.UREwnmResyF) now. – Uwe Keim Aug 27 '13 at 14:20