0

Ok, so I know I should be using the ImageMagick DLL... and I'm learning, testing, and working up to doing that. But in the mean time I'm using the inefficient method of calling Imagemagick's convert.exe via a process.

It works fine with hundreds of images when I'm testing. But then I move my WindowsForms program to a faster machine and it crashes every time at the same point.

It's a two step Process call. The first time loop through all the files and produce the PNGs. Then I loop through all the PNGs and composite it with a background and output a JPG. But EVERY time at exactly 22 images it errors "System.OutOfMemoryException: Out of memory." Is there something that is filling up that I need to kill or something?

    foreach (string file in files)
            {
                try
                {
                    string captureImg = Path.GetFileName(file);
                    string maskImg = file.Replace("X.JPG", "Y.JPG");
                    string OutputImage = string.Format("{0}.png", Path.GetFileNameWithoutExtension(captureImg));
                    string output = Path.Combine(destFolder, OutputImage);
                    //MessageBox.Show(output);
                    progressBarImage.Value = progressBarImage.Value + 1;
                    lblStatus.Text = string.Format("Image {0} of {1}", progressBarImage.Value, maxFiles);
                    makePNG(file, maskImg, output);
                    Application.DoEvents();

                }
                catch (Exception)
                {
                }
            }

            if (chkBG.Checked)
            {
                //try
                //{
                    string JPGdir = Path.Combine(destFolder, "JPGs");
                    string[] PNGfiles = Directory.GetFiles(destFolder, "*C.PNG");

                    lblProgress.Text = "Generating JPGs with Background";
                    progressBarImage.Value = 0;
                    progressBarImage.Maximum = files.Length;
                    message = "PNG and JPG Export Complete";
                    if (!Directory.Exists(JPGdir))
                    {
                        Directory.CreateDirectory(JPGdir);
                    }
                    foreach (string PNGfile in PNGfiles)
                    {
                        Application.DoEvents();
                        string outputJPG = string.Format("{0}.jpg", Path.GetFileNameWithoutExtension(PNGfile));
                        string result = Path.Combine(JPGdir, outputJPG);
                        progressBarImage.Value += 1;
                        lblStatus.Text = string.Format("Image {0} of {1}", progressBarImage.Value, files.Length);
                        makeJPG(PNGfile, txtBackground.Text, result);
                        //MessageBox.Show(PNGfile);

                    }

private void makePNG(string source, string mask, string output)
        {
            if (!source.EndsWith("Y.JPG"))
            {
                Process proc = new Process();
                string appPath = Path.GetDirectoryName(Application.ExecutablePath);
                proc.EnableRaisingEvents = false;
                proc.StartInfo.FileName = @"""C:\Program Files\ImageMagick-6.9.0-Q16\convert.exe""";
                proc.StartInfo.Arguments = string.Format(@"{0} {1} -alpha off -compose  copy-opacity -level 5%  -composite {2}", source, mask, output);
                proc.StartInfo.UseShellExecute = false;
                proc.StartInfo.RedirectStandardOutput = true;
                proc.StartInfo.CreateNoWindow = true;
                proc.Start();
                proc.WaitForExit();

            }
        }

        private void makeJPG(string source, string background, string output)
        {
            float BGimg = Image.FromFile(background).Height;
            float SubjectImg = Image.FromFile(source).Height;
            float ResultHeight = 100 * (BGimg / SubjectImg);
            int Height = Convert.ToInt32(ResultHeight);


            Process procJPG = new Process();
            string appPath = Path.GetDirectoryName(Application.ExecutablePath);
            procJPG.EnableRaisingEvents = false;
            procJPG.StartInfo.FileName = @"""C:\Program Files\ImageMagick-6.9.0-Q16\convert.exe""";
            procJPG.StartInfo.Arguments = string.Format(@"{1} ( {0} -resize {3}% ) -gravity South -composite {2}", source, background, output, Height);
            procJPG.StartInfo.UseShellExecute = false;
            procJPG.StartInfo.RedirectStandardOutput = true;
            procJPG.StartInfo.CreateNoWindow = true;
            procJPG.Start();
            procJPG.WaitForExit();
        }
Matt Winer
  • 495
  • 9
  • 26
  • Why are you redirecting standard out? – SLaks Jan 23 '15 at 16:43
  • `Application.DoEvents();` try not to use that.. it's not good practice – MethodMan Jan 23 '15 at 16:52
  • @MethodMan I disagree. `Application.DoEvents()` is perfectly fine *if used appropiatedly* (using it appropiatedly is the hard part though ;-) ) – Jcl Jan 23 '15 at 16:56
  • @Jcl http://stackoverflow.com/questions/5181777/use-of-application-doevents happy reading.. – MethodMan Jan 23 '15 at 16:58
  • @MethodMan I've read a lot about it (including those that say "it's evil and never use it"), but in my many years doing Windows.Forms, I've used it successfully in many occasions, specially when dealing directly with the message queues to handle complex features not present in WinForms. It's basically the equivalent to the old `PeekMessage` loop we used in direct Win32 C++, and if you understand the implications of using it, there's nothing wrong to it. It's the "bad usage of DoEvents()" what makes it evil, not the function per-se – Jcl Jan 23 '15 at 17:00
  • not interested in getting into a debate or conversation on this .Cheers – MethodMan Jan 23 '15 at 17:05
  • not to high jack my own thread, but would be best way to have a loop update the progressbar? – Matt Winer Jan 23 '15 at 17:15
  • @MattWiner the best way would be doing the processing in a thread, and update the progressbar using `Invoke`. For an easy way of doing this, check `BackgroundWorker` or the `Task Parallel Library`, or, if you are aiming to get a headache if you've never used them, the preferred way would be using `async/await`. Just remember to use `Invoke` or `BeginInvoke` if accessing anything on the UI from a different thread – Jcl Jan 23 '15 at 17:23
  • Links to documentation of [BackgroundWorker](https://msdn.microsoft.com/en-us/library/system.componentmodel.backgroundworker(v=vs.110).aspx), the [Task Parallel Library](https://msdn.microsoft.com/en-us/library/dd460717(v=vs.110).aspx) and [async/await](https://msdn.microsoft.com/en-us/library/hh191443.aspx) for completeness sake – Jcl Jan 23 '15 at 17:29

1 Answers1

5

At first glance, you are using Image.FromFile twice in makeJPG() and not disposing the objects. Image.FromFile will usually create unmanaged GDI+ handles that need to be disposed.

From the documentation:

The file remains locked until the Image is disposed.

So at first glance I'd assume you are just loading too much images in memory, and I'd try:

private void makeJPG(string source, string background, string output)
{
  using(var backgroundImg = Image.FromFile(background))
  using(var sourceImg = Image.FromFile(source))
  {
    float BGimg = backgroundImg.Height;
    float SubjectImg = sourceImg.Height;
    float ResultHeight = 100 * (BGimg / SubjectImg);
    int Height = Convert.ToInt32(ResultHeight);


    Process procJPG = new Process();
    string appPath = Path.GetDirectoryName(Application.ExecutablePath);
    procJPG.EnableRaisingEvents = false;
    procJPG.StartInfo.FileName = @"""C:\Program Files\ImageMagick-6.9.0-Q16\convert.exe""";
    procJPG.StartInfo.Arguments = string.Format(@"{1} ( {0} -resize {3}% ) -gravity South -composite {2}", source, background, output, Height);
    procJPG.StartInfo.UseShellExecute = false;
    procJPG.StartInfo.RedirectStandardOutput = true;
    procJPG.StartInfo.CreateNoWindow = true;
    procJPG.Start();
    procJPG.WaitForExit();
  }
}

Since you are actually not using the images (just getting their height), you could make those using blocks smaller, but I'll leave that to you.

... but ...

About the OutOfMemoryException

Also, you say it happens EXACTLY at 22 images (which would be weird for getting out of memory, unless the images are consistently big), but reading the same documentation:

If the file does not have a valid image format or if GDI+ does not support the pixel format of the file, this method throws an OutOfMemoryException exception.

So make sure the 22nd image (either the "source" or the "background", depending on where it throws) has a correct format

Jcl
  • 27,696
  • 5
  • 61
  • 92