Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

System.Drawing - Parameter is not valid

After some more testing I've found that this problem may be due to the images somehow not being loaded in time to be cloned into bitmaps and displayed. Is this possible or no?

NOTE: Yes, there are other questions with this error in the title, but from a bit of a research it seems to be an ambiguous error with many possible causes. I haven't found any questions with the same scenario as mine.

I'm getting the following error.

System.ArgumentException was unhandled
HResult=-2147024809
Message=Parameter is not valid.
Source=System.Drawing

It arises from this code. seemingly at random (i.e., sometimes it works and sometimes it doesn't. The more times it's run in a row without restarting VS and rebuilding the project, the more likely it is to fail):

private Bitmap GetSprite(bool anim, int tsIndex, int tileIdx) {
    System.Drawing.Rectangle cloneRect;
    string prefix = (anim) ? "A" : "S";
    using (Bitmap b = new Bitmap(prefix + tsIndex.ToString() + ".png")) {
        if (anim) {
            cloneRect = new System.Drawing.Rectangle(BaseObjects.A_AnimSpriteSets[tsIndex].StaticRecs[tileIdx].X, BaseObjects.A_AnimSpriteSets[tsIndex].StaticRecs[tileIdx].Y, BaseObjects.A_AnimSpriteSets[tsIndex].RecWidth, BaseObjects.A_AnimSpriteSets[tsIndex].RecHeight);
        } else {
            cloneRect = new System.Drawing.Rectangle(BaseObjects.A_StaticSpriteSets[tsIndex].StaticRecs[tileIdx].X, BaseObjects.A_StaticSpriteSets[tsIndex].StaticRecs[tileIdx].Y, BaseObjects.A_StaticSpriteSets[tsIndex].RecWidth, BaseObjects.A_StaticSpriteSets[tsIndex].RecHeight);
        }
        return b.Clone(cloneRect, b.PixelFormat);
    }
}

Specifically, the fourth line:

using (Bitmap b = new Bitmap(prefix + tsIndex.ToString() + ".png"))

The simplified objective of the code is to return a bitmap containing a sprite from a spriteset based on a spriteset index and a sprite index. This bitmap is displayed in a PictureBox until it is changed to a different image. I know for a fact that the logic works; that's not the issue here. The .png I'm using to test is 384*256.

All the parameters are set properly, all the referenced files are there, everything seems to be in order. Strangest thing of all is that sometimes it works, sometimes it doesn't. This has led me to believe that it may be a memory leak within System.Drawing itself but I can't seem to track it down.

EDIT: Updated the code and added the StackTrace. Still having the same issue despite disposing the Bitmaps when they are no longer used (see code below for example of how Bitmap is disposed).

if (Sprite.Image != null) { Sprite.Image.Dispose(); }
    Sprite.Image = GetSprite(true, tsIdx, tileIdx);

StackTrace:

System.ArgumentException was unhandled
  HResult=-2147024809
  Message=Parameter is not valid.
  Source=System.Drawing
  StackTrace:
       at System.Drawing.Bitmap..ctor(String filename)
       at CreationTool.Main.GetSprite(Boolean anim, Int32 tsIndex, Int32 tileIdx) in F:\~\~\CreationTool\Main.cs:line 420
       at CreationTool.Main.Input_EnemySprite_SelectedIndexChanged(Object sender, EventArgs e) in F:\~\~\CreationTool\Main.cs:line 107
       at System.Windows.Forms.ComboBox.OnSelectedIndexChanged(EventArgs e)
       at System.Windows.Forms.ComboBox.set_SelectedIndex(Int32 value)
       at CreationTool.States.State_Enemy.populateForm() in F:\~\~\CreationTool\States\State_Enemy.cs:line 28
       at CreationTool.States.State_Enemy.Load(String name) in F:\~\~\CreationTool\States\State_Enemy.cs:line 22
       at CreationTool.Main.btnLoad_Click(Object sender, EventArgs e) in F:\~\~\CreationTool\Main.cs:line 174
       at System.Windows.Forms.Control.OnClick(EventArgs e)
       at System.Windows.Forms.Button.OnClick(EventArgs e)
       at System.Windows.Forms.Button.OnMouseUp(MouseEventArgs mevent)
       at System.Windows.Forms.Control.WmMouseUp(Message& m, MouseButtons button, Int32 clicks)
       at System.Windows.Forms.Control.WndProc(Message& m)
       at System.Windows.Forms.ButtonBase.WndProc(Message& m)
       at System.Windows.Forms.Button.WndProc(Message& m)
       at System.Windows.Forms.Control.ControlNativeWindow.OnMessage(Message& m)
       at System.Windows.Forms.Control.ControlNativeWindow.WndProc(Message& m)
       at System.Windows.Forms.NativeWindow.DebuggableCallback(IntPtr hWnd, Int32 msg, IntPtr wparam, IntPtr lparam)
       at System.Windows.Forms.UnsafeNativeMethods.DispatchMessageW(MSG& msg)
       at System.Windows.Forms.Application.ComponentManager.System.Windows.Forms.UnsafeNativeMethods.IMsoComponentManager.FPushMessageLoop(IntPtr dwComponentID, Int32 reason, Int32 pvLoopData)
       at System.Windows.Forms.Application.ThreadContext.RunMessageLoopInner(Int32 reason, ApplicationContext context)
       at System.Windows.Forms.Application.ThreadContext.RunMessageLoop(Int32 reason, ApplicationContext context)
       at System.Windows.Forms.Application.Run(Form mainForm)
       at CreationTool.Program.Main() in F:\~\~\CreationTool\Program.cs:line 15
       at System.AppDomain._nExecuteAssembly(RuntimeAssembly assembly, String[] args)
       at System.AppDomain.ExecuteAssembly(String assemblyFile, Evidence assemblySecurity, String[] args)
       at Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly()
       at System.Threading.ThreadHelper.ThreadStart_Context(Object state)
       at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
       at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
       at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
       at System.Threading.ThreadHelper.ThreadStart()
like image 548
Djentleman Avatar asked Dec 31 '12 08:12

Djentleman


2 Answers

Wow, this code is leaking handles like hell. You need to dispose all types that implement IDisposable, which is quite a lot of types in the System.Drawing assembly (GDI+):

private Bitmap GetSprite(bool anim, int tsIndex, int tileIdx)
{
    Rectangle cloneRect;
    string prefix = (anim) ? "A" : "S";
    using (Bitmap b = new Bitmap(prefix + tsIndex.ToString() + ".png"))
    {
        if (anim)
        {
            cloneRect = new Rectangle(BaseObjects.A_AnimSpriteSets[tsIndex].StaticRecs[tileIdx].X, BaseObjects.A_AnimSpriteSets[tsIndex].StaticRecs[tileIdx].Y, BaseObjects.A_AnimSpriteSets[tsIndex].RecWidth, BaseObjects.A_AnimSpriteSets[tsIndex].RecHeight);
        }
        else
        {
            cloneRect = new Rectangle(BaseObjects.A_StaticSpriteSets[tsIndex].StaticRecs[tileIdx].X, BaseObjects.A_StaticSpriteSets[tsIndex].StaticRecs[tileIdx].Y, BaseObjects.A_StaticSpriteSets[tsIndex].RecWidth, BaseObjects.A_StaticSpriteSets[tsIndex].RecHeight);
        }

        return b.Clone(cloneRect, b.PixelFormat);
    }
}

Also make sure that you have also disposed the bitmap returned by this function by wrapping the caller in a using statement:

using (Bitmap b = GetSprite(true, 0, 5))
{
    // do whatever you needed to do with the bitmap here
}
like image 81
Darin Dimitrov Avatar answered Sep 23 '22 19:09

Darin Dimitrov


The leaking handles would have eventually led to memory issues but they weren't the problem in this case (thanks to those who pointed them out anyway, learnt something new).

The problem was that, due to the way I load the actual images into memory, the images weren't being given enough time to fully load in most of my tests. The ones that succeeded were the ones that allowed enough time for the images to load.

I got around this with a simple try/catch.

private Bitmap GetSprite(bool anim, int tsIndex, int tileIdx) {
    string prefix;
    System.Drawing.Rectangle cloneRect;
    SpriteSet set;
    if (anim) {
        prefix = "A";
        set = BaseObjects.A_AnimSpriteSets[tsIndex];
    } else {
        prefix = "S";
        set = BaseObjects.A_StaticSpriteSets[tsIndex];
    }
    cloneRect = new System.Drawing.Rectangle(set.StaticRecs[tileIdx].X, set.StaticRecs[tileIdx].Y, set.RecWidth, set.RecHeight);
    try {
        using (Bitmap b = new Bitmap(prefix + tsIndex.ToString() + ".png")) {
            return b.Clone(cloneRect, b.PixelFormat);
        }
    } catch (Exception ex) {
        MessageBox.Show("Error: " + ex.Message + "\n\nCause: " + "SpriteSet not yet loaded.");
        return null;
    }
}

This is all I need for this particular program.

Also, pstrjds, thanks for the cleanup ;) That messiness must have arisen during some refactor. Guess I just forgot about it.

like image 37
Djentleman Avatar answered Sep 23 '22 19:09

Djentleman