8

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()
Djentleman
  • 1,137
  • 5
  • 13
  • 28
  • 2
    You need to call `Dispose` on the `Bitmap B` after you have cloned it. Bitmap's use unmanaged resources and that could be lead to memory pressure issues. I would wrap it with a using statement. – pstrjds Dec 31 '12 at 08:24
  • Thanks, that seems to have fixed it. Change that into an answer? – Djentleman Dec 31 '12 at 08:27
  • You should add a try/catch to the `Main` of your application and either dump the exception to the console or log it so that you can see the stack trace and know exactly where it came from. – pstrjds Dec 31 '12 at 08:28
  • Wait, never mind. Hasn't fixed it. A few more tests and back to square one. – Djentleman Dec 31 '12 at 08:29
  • You could also make this code a but more readable by adding an if statement at the top that would set both the prefix and the source rectangle (which must also be called Rectangle and conflict with the System.Drawing.Rectangle) this way you don't have the if inside the using and you can make the rectangle creation look like '(sourceRec.X, sourceRec.Y...') as opposed to the big nasty array indexing thing. – pstrjds Dec 31 '12 at 08:39
  • Can you add the stack trace for the exception? Is it coming from within this method? Are you leaking other GDI objects somewhere? Are you creating Graphics objects and not disposing, Pens, Brushes, etc? – pstrjds Dec 31 '12 at 09:07
  • StackTrace added. I'm disposing of the Bitmaps as soon as they are no longer needed and I'm still getting the error. Not using any other GDI objects as far as I can find. Definitely not creating graphics objects. – Djentleman Dec 31 '12 at 09:20

4 Answers4

6

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
}
Darin Dimitrov
  • 1,023,142
  • 271
  • 3,287
  • 2,928
  • I was trying to clean the code up a little bit as well as add the using. Oh well, will upvote yours :) – pstrjds Dec 31 '12 at 08:36
  • Modified the code as suggested on all calls to GetSprite() and still getting the exact same error at the exact same place. And now I'm only getting red exes in place of images. – Djentleman Dec 31 '12 at 08:40
  • You haven't explained how you are using those images. In the example I provided you cannot use them outside the using statement. I guess that you are attempting to use the result outside, that's why you are getting Red xses. – Darin Dimitrov Dec 31 '12 at 08:41
  • From my answer: 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. Not from my answer: I then display that sprite (the result of `GetSprite()`) in a PictureBox. I want said PictureBox to change dynamically as I select sprite indices from a ListBox. – Djentleman Dec 31 '12 at 08:48
  • Alright, so you are displaying the result of this function inside a PictureBox. Then you should not dispose it immediately. You should dispose the old picturebox bitmap, just before assigning the new one: `pictureBox1.Image.Dispose(); pictureBox.Image = GetSprite(...)`. – Darin Dimitrov Dec 31 '12 at 08:50
  • Ok I think I get it. I'll have to add in a couple events but I should get there. I'll post an update when I do. Thanks! – Djentleman Dec 31 '12 at 08:54
  • Solution isn't working and I'm really getting stumped here. Updated the Q. – Djentleman Dec 31 '12 at 09:33
  • It looks like there's problem with the argument passed to the Bitmap constructor. That's supposed to be some filename, but did you ensure that the file actually exists and is a valid PNG file? – Darin Dimitrov Dec 31 '12 at 10:00
  • Yeah that's on the right track. Added an answer with my solution. Oversight on my part. – Djentleman Dec 31 '12 at 10:09
4

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.

Djentleman
  • 1,137
  • 5
  • 13
  • 28
1

I found also this trying to look for answers. be aware that it can also throw the exception when:

stream contains a PNG image file with a single dimension greater than 65,535 pixels.

http://msdn.microsoft.com/en-us/library/z7ha67kw.aspx

1

For me, this was due to referencing a relative path, such as @"Resources\ImageName.png". In debug mode, I had no issues loading the image, but as OP suggests, this may actually be a resource load time issue. I was correctly using and disposing my objects, but this is not actually relevant to the issue. As soon as it was run inside of Release the error "Parameter is not valid" would rear its ugly head again.

I suspect that the compiler optimisations are such that in debug mode the image stream is indeed able to be expanded in time for the constructor to parse, but under release build the path still needs to undergo expansion at the time the cctor is ready to parse the path, which leads to the path is invalid error. Simply changing the path from a relative, to a fixed, or pre-expanded path resolves the issue.

paddywan
  • 111
  • 2