GameOverlay.Net icon indicating copy to clipboard operation
GameOverlay.Net copied to clipboard

Random "Unsupported Image Format" Exception at graphics.CreateImage(string)

Open Scavanger opened this issue 2 years ago • 5 comments

I have to load a lot of images (250+) for my overlay, and graphics.CreateImage is very slow, so i want to show a kind of load screen to the user, images are loaded in an extra Task, started in the SetupGraphics event handler:

public async Task LoadImagesAsync(Graphics graphic)
{
    await Task.Run(() =>
    {
        foreach (var image in imageFileList)
        {
            string name = Path.GetFileNameWithoutExtension(image);
            lock (lockObject)
            {
                try
                {
                    images.Add(name, graphic.CreateImage(image));
                }
                catch (Exception ex)
                {
                    break;
                }
            }
        }
    });
}

I get random "Unsupported Image Format" exceptions, on completely different images, sometimes it goes through without a problem, sometimes after 10 images or after 220 I get these random exceptions.

Images are transparent PNG Files, relatively small: 12x18 px,

-		ex	{"Unsupported Image Format!"}	System.Exception
+		Data	{System.Collections.ListDictionaryInternal}	System.Collections.IDictionary {System.Collections.ListDictionaryInternal}
		HResult	-2146233088	int
		HasBeenThrown	true	bool
		HelpLink	null	string
+		InnerException	null	System.Exception
		Message	"Unsupported Image Format!"	string
		SerializationStackTraceString	"   at GameOverlay.Drawing.Imaging.ImageDecoder.Decode(RenderTarget device, BitmapDecoder decoder)\r\n   at GameOverlay.Drawing.Image.LoadBitmapFromMemory(RenderTarget device, Byte[] bytes)\r\n   at GameOverlay.Drawing.Graphics.CreateImage(String path)\r\n   at iNavSim.OSD.<>c__DisplayClass14_0.<LoadImagesAsync>b__0() in C:\\Users\\andi__000\\Source\\Repos\\iNavSim\\src\\OSD.cs:line 151"	string
		SerializationWatsonBuckets	null	object
		Source	"GameOverlay"	string
		StackTrace	"   bei GameOverlay.Drawing.Imaging.ImageDecoder.Decode(RenderTarget device, BitmapDecoder decoder)\r\n   bei GameOverlay.Drawing.Image.LoadBitmapFromMemory(RenderTarget device, Byte[] bytes)\r\n   bei GameOverlay.Drawing.Graphics.CreateImage(String path)\r\n   bei iNavSim.OSD.<>c__DisplayClass14_0.<LoadImagesAsync>b__0() in C:\\Users\\andi__000\\Source\\Repos\\iNavSim\\src\\OSD.cs: Zeile151"	string
+		TargetSite	{SharpDX.Direct2D1.Bitmap Decode(SharpDX.Direct2D1.RenderTarget, SharpDX.WIC.BitmapDecoder)}	System.Reflection.MethodBase {System.Reflection.RuntimeMethodInfo}
		_HResult	-2146233088	int
+		_data	{System.Collections.ListDictionaryInternal}	System.Collections.IDictionary {System.Collections.ListDictionaryInternal}
		_dynamicMethods	null	object[]
		_exceptionMethod	null	System.Reflection.MethodBase
		_helpURL	null	string
+		_innerException	null	System.Exception
		_ipForWatsonBuckets	0x00007ffa72106af0	System.UIntPtr
		_message	"Unsupported Image Format!"	string
		_remoteStackTraceString	null	string
		_source	null	string
+		_stackTrace	{byte[192]}	byte[]
		_stackTraceString	null	string
		_watsonBuckets	null	byte[]
		_xcode	-532462766	int
		_xptrs	0x0000000000000000	System.IntPtr
+		Statische Member		

Scavanger avatar May 10 '22 19:05 Scavanger

Ok i found the issue:

public static Bitmap Decode(RenderTarget device, BitmapDecoder decoder)
{
	var frame = decoder.GetFrame(0);
	var converter = new FormatConverter(Image.ImageFactory);

	foreach (var format in _pixelFormatEnumerator)
	{
		try
		{
			converter.Initialize(frame, format);

			var bmp = Bitmap.FromWicBitmap(device, converter);

			TryCatch(() => converter.Dispose());
			TryCatch(() => frame.Dispose());

			return bmp;
		}
		catch
		{
			TryCatch(() => converter.Dispose());
			converter = new FormatConverter(Image.ImageFactory);
		}
	}

	TryCatch(() => converter.Dispose());
	TryCatch(() => frame.Dispose());

	throw new Exception("Unsupported Image Format!");
}

This wild mix of try ... catch and a strange use of IDisposable does not work reasonably with tasks and is extremely slow. Try ... catch is intended for exceptions, and should never be used for program flow control. Yes I know, there is no reasonable way to determine the pixel format beforehand.

I have reduced the whole ImageDecoder.cs to the following code and adapted the constructors of Image and the Grapics.createImage(...) methods accordingly.

using System;

using SharpDX.Direct2D1;
using SharpDX.WIC;

using Bitmap = SharpDX.Direct2D1.Bitmap;

namespace GameOverlay.Drawing.Imaging
{
    internal static class ImageDecoder
    {   
        public static Bitmap Decode(RenderTarget device, BitmapDecoder decoder, Guid format)
        {
            using (var frame = decoder.GetFrame(0))
            {
                using (var converter = new FormatConverter(Image.ImageFactory))
                {
                        converter.Initialize(frame, format);
                        return Bitmap.FromWicBitmap(device, converter);
                }
            }
        }
    }
}

This runs so fast that I don't need an extra task anymore, here are my results of the fast performance test with 273 transparent PNGs with 13x18 pixels:

Testcode:

private void LoadImages(Graphics graphics)
{
    DateTime start = DateTime.Now;
    foreach (var file in Directory.GetFiles("img_folder"))
    {
        string name = Path.GetFileNameWithoutExtension(file);
        //Original
        _images.Add(name, graphics.CreateImage(file));
        // New
        _images.Add(name, graphic.CreateImage(file, PixelFormat.Format64bppPRGBA));
    }
    TimeSpan duration = DateTime.Now - start;
}

Result (No kidding!): Original: 37.76 seconds New Code: 143 Millisesonds

That's a speedup of 264 times!

Scavanger avatar May 11 '22 14:05 Scavanger

Would you create a PR for this? The existing trial and error method shouldn't be removed. Accepting an extra parameter is fine. You could also fastpath for the required format in the existing code

michel-pi avatar May 12 '22 07:05 michel-pi

you can determine the format beforehand. i don't know how. that's why i ended up with this

michel-pi avatar May 12 '22 07:05 michel-pi

Did this ever get merged?

h5kk avatar Jul 22 '22 18:07 h5kk

There was no PR so nothing got merged

michel-pi avatar Aug 16 '22 12:08 michel-pi