C# GDI+ generic error / external exception after s

2019-08-02 19:17发布

问题:

I have a little console application in C# that takes a pair of pictures from a folder (containing a few hundred images) in .jpg-format, merges them to one picture and stores the result as .tif in another folder. This process is repeated for all pictures in the source folder.

It works fine for a few loop iterations, but then I get an unhandled external exception with an GDI+ generic error (the inner exception is null) when trying to save the result. As I create new files for storing and as it works for a few pictures before crashing, I dont think this is caused by some permission problem or a lock on a file. I suspect it might be some memory issue, because when I use 24bit (Format24bppRgb) for the .tif, it crashes after approx 13 iterations, when I use 48bit (Format48bppRgb) it crashes after approx 8 iterations.

The current image to be saved when it crashes is already present on the disk as an empty .tif of less than 1KB. The application is not run from Visual Studio but directly from the .exe (as I have read of memory problems when creating large images in an application running from within Visual Studio)

The sizes of the source images range from 800x600 to 24 megapixels. Result sizes can be larger, as when a landscape oriented image is combined with a portrait oriented picture it results in a square image with the greater width and height of the source images. The machine where I run the application has 8GB of RAM, of which at max approx. 50% is used.

The loop looks like this:

  foreach (var f in files)
        {
            if ((count % 2) == 0)
            {
                bmp1 = new Bitmap(f);
                int index2 = Array.IndexOf(files, f) + 1;
                if (index2 >= files.Length) { break; }
                bmp2 = new Bitmap(files.ElementAt(index2));
                Merge2Rand(bmp1, bmp2,rand).Save(outputDir + "\\0\\out" + count + ".tif", myImageCodecInfo, myEncoderParameters);
                Console.WriteLine(count + " - " + watch.ElapsedMilliseconds / 60000 + "min");
            }
            count++;
        }

This is the merging function:

 public static Bitmap Merge2Rand(Bitmap bmp1, Bitmap bmp2, Random rand)
    {
        int newH = Math.Max(bmp2.Height, bmp1.Height);
        int newW = Math.Max(bmp2.Width, bmp1.Width);

        int offsetH1 = 0;
        if (bmp1.Height < newH) offsetH1 = rand.Next(0, (newH - bmp1.Height) + 1);
        int offsetW1 = 0;
        if (bmp1.Width < newW) offsetW1 = rand.Next(0, (newW - bmp1.Width) + 1);
        int offsetH2 = 0;
        if (bmp2.Height < newH) offsetH2 = rand.Next(0, (newH - bmp2.Height) + 1);
        int offsetW2 = 0;
        if (bmp2.Width < newW) offsetW2 = rand.Next(0, (newW - bmp2.Width) + 1);

        Bitmap newBMP = new Bitmap(newW, newH, System.Drawing.Imaging.PixelFormat.Format24bppRgb);
        for (int i = 0; i < newW; i++)
        {
            for (int j = 0; j < newH; j++)
            {
                Color p1;
                Color p2;

                if ((i>=offsetW1 && bmp1.Width+offsetW1 > i+offsetW1 ) && (j>=offsetH1 && bmp1.Height+offsetH1 > j+offsetH1))
                {
                    p1 = bmp1.GetPixel(i, j);
                }
                else
                {

                    p1 = Color.FromArgb(0, 0, 0, 0);
                }
                if ((i>=offsetW2 && bmp2.Width + offsetW2 > i +offsetW2) && (j>=offsetH2 && bmp2.Height + offsetH2 > j +offsetH2))
                {
                    p2 = bmp2.GetPixel(i, j);
                }
                else
                {
                    /
                    p2 = Color.FromArgb(0, 0, 0, 0);
                }
                int rVal = (p1.R + p2.R) / 2;
                int gVal = (p1.G + p2.G) / 2;
                int bVal = (p1.B + p2.B) / 2;
                newBMP.SetPixel(i, j, Color.FromArgb(0, rVal, gVal, bVal));
            }
        }
        return newBMP;

I use the following encoding for the .tif files:

ImageCodecInfo myImageCodecInfo;
        myImageCodecInfo = GetEncoderInfo("image/tiff");
        System.Drawing.Imaging.Encoder myEncoder;
        myEncoder = System.Drawing.Imaging.Encoder.Compression;
        EncoderParameters myEncoderParameters;
        myEncoderParameters = new EncoderParameters(1);
        EncoderParameter myEncoderParameter;
        myEncoderParameter = new EncoderParameter(myEncoder, (long)EncoderValue.CompressionLZW);
        myEncoderParameters.Param[0] = myEncoderParameter;



private static ImageCodecInfo GetEncoderInfo(String mimeType)
        {
            int j;
            ImageCodecInfo[] encoders;
            encoders = ImageCodecInfo.GetImageEncoders();
            for (j = 0; j < encoders.Length; ++j)
            {
                if (encoders[j].MimeType == mimeType)
                    return encoders[j];
            }
            return null;
        }

回答1:

The problem appears to be that you are repeatedly creating new bitmaps in each iteration and never disposes of them. This will result in a leak of GDI or USER Objects and memory as well. The using statement automatically disposes of objects when your code is done with them.

You need something like this (should be close):

foreach (var f in files)
{
    using (Bitmap bmp1 = new Bitmap(f))
    {
        int index2 = Array.IndexOf(files, f) + 1;
        if (index2 >= files.Length) { break; }

        using (Bitmap bmp2 = new Bitmap(files.ElementAt(index2)),
                bmp3 = Merge2Rand(bmp1, bmp2, rand))
        {
            bmp3.Save(outputDir + "\\0\\out" + count + ".tif", myImageCodecInfo, myEncoderParameters);
        }
    }
}

The using block for bmp3 is slight overkill, it could simply be declared and then .Dispose() after the save. As is, it shows how to "stack" 2 objects in one using block.

If something has the .Dispose() method it means it should be disposed of when your code is done with it. It also means you can use it in a using block to automatically dispose of it at the end.

See also C# using statement