2
votes

I'm trying to accomplish something that I figure should be quite simple in MonoGame GL on Windows.. That is to load a texture from a PNG file and render it to the screen as a sprite. So far I'm having a lot of trouble with this. I'm loading the PNG to a Texture2D with the following code (F#):

    use file = System.IO.File.OpenRead("testTexture.png")
    this.texture <- Texture2D.FromStream(this.GraphicsDevice, file)

and then rendering with:

    this.spriteBatch.Begin(SpriteSortMode.Immediate, BlendState.NonPremultiplied);
    this.spriteBatch.Draw(this.texture, Vector2.Zero, Color.White)
    this.spriteBatch.End()

The problem is some kind of weird effect with the alpha channels, as if there's something up with channels not being alpha pre-multiplied or something, but I can't place for sure exactly what's happening. What's notable though is that the exact same code renders perfectly using the official XNA libraries. The issue is only in MonoGame, which I tested with version 3.0 and 3.2, both of which have the same issue.

Here's the rendering a test PNG in MonoGame to illustrate the problem:

http://i.imgur.com/Dny26gI.png

The background in each image is cornflower blue, then pure red, green and blue respectively. Notice in the image with the red background, you can see a dark outline around the red lines in the texture. This outline shouldn't be there as the lines and the background are both pure red. Note the same thing occurs around the blue lines in the image with the blue background, but not in the image with the green background. In that image the green lines blend in with the green background as they should.

Below is how the exact same file renders using the official XNA library.

http://i.imgur.com/2Rtqhuk.png

Notice how the blue, green and red lines blend in to the background when the background is the same colour. This is the correct behaviour.

Given that the same code behaves differently in both XNA and MonoGame, I believe there must be a bug in the framework. Would anyone have any good guess as to where the bug might be and of what nature? If it's an easy fix then the best solution might be to just fix that bug myself.

Besides that though I really just want to learn a way I can simply load a PNG and render it correctly to the screen in MonoGame. I'm sure I can't be the first one who's wanted to do this. I would like to avoid the content pipeline if at all possible, simply for the sake of simplicity.

Update

I've done some more digging trying to figure out the nature of this problem. I've changed the testTexture.png file to be more revealing about alpha blending problems. After using this new texture file, I took screenshots of the incorrect MonoGame rendering and viewed it in it's separate colour channels. What's happening is pretty perplexing to me. I initially though it might be a simple case of BlendState.NonPremultiplied being ignored, but what I'm seeing looks more complicated that that. Among other things, the green colour channel appears to be blending differently to the blue and red channels. Here's a rather large PNG image that's a compilation of screenshots and explanations as to what I'm talking about:

i.imgur.com/CEUQqm0.png

Clearly there's some kind of bug in MonoGame for Windows GL, and possibly other editions I haven't tried this with (though I'd be interested to see verified). If anyone thinks they know what might be happening here please let me know.

lastly, here's the project files to reproduce the problem I'm having:

mega.co.nz/#!llFxBbrb!OrpPIn4Tu2UaHHuoSPL03nqOtAJFK59cfxI5TDzLyYI

1
My first guess would have been to use BlendState.NonPremultiplied in the SpriteBatch.Begin call, but I see you are already doing that. Try changing to SpriteSortMode.Deferred and see what happens. In any case, I agree this is probably a bug in MonoGame.craftworkgames
@craftworkgames Tried it, but makes no difference :(. Thanks though.battlebottle

1 Answers

6
votes

This issue has been resolved. The bug in the framework was found at: MonoGame.Framework/Graphics/ImageEx.cs in the following method:

internal static void RGBToBGR(this Image bmp)
{
    System.Drawing.Imaging.ImageAttributes ia = new System.Drawing.Imaging.ImageAttributes();
    System.Drawing.Imaging.ColorMatrix cm = new System.Drawing.Imaging.ColorMatrix(rgbtobgr);

    ia.SetColorMatrix(cm);
    using (System.Drawing.Graphics g = System.Drawing.Graphics.FromImage(bmp))
    {
        g.DrawImage(bmp, new System.Drawing.Rectangle(0, 0, bmp.Width, bmp.Height), 0, 0, bmp.Width, bmp.Height, System.Drawing.GraphicsUnit.Pixel, ia);
    }
}

changing it to this fixes the issue:

internal static void RGBToBGR(this Image bmp)
{
    using (Bitmap bmpCopy = (Bitmap)bmp.Clone())
    {
        System.Drawing.Imaging.ImageAttributes ia = new System.Drawing.Imaging.ImageAttributes();
        System.Drawing.Imaging.ColorMatrix cm = new System.Drawing.Imaging.ColorMatrix(rgbtobgr);

        ia.SetColorMatrix(cm);
        using (System.Drawing.Graphics g = System.Drawing.Graphics.FromImage(bmp))
        {
            g.Clear(Color.Transparent);
            g.DrawImage(bmpCopy, new System.Drawing.Rectangle(0, 0, bmp.Width, bmp.Height), 0, 0, bmp.Width, bmp.Height, System.Drawing.GraphicsUnit.Pixel, ia);
        }
    }
}

What's happening is when the RGBToBGR conversion performed, it draws the converted version of the image on top of the image object it's converting from. This is what's causing the strange effects. As far as I can tell the only thing to do is to clear the image object before drawing over it again, which of course means the bitmap being converting from must be copied so you can read from the copied version while writing over the original version that was just cleared.

Thanks to dellis1972 for pointing me in the right direction on this: https://github.com/mono/MonoGame/issues/1946