0

I need a generic function to convert images to a target format. (Format8bppIndexed in this case) The goal is to be able to handle a reasonable range of regular .NET supported images. We have many clients with hundreds of Terabytes of images of varying types and I plan to loop through them all with this code.

Here is an example Image I am trying to convert which throws the errors: Single page JPG example

I realize this code has multiple inner try-catches, however I wanted to illustrate the problem.

Within each try below I have comments showing the exception and error I receive.

public static Bitmap ConvertToFormat(this Bitmap Source, PixelFormat TargetFormat)
{
    try
    {
        //This throws OutOfMemoryException: "Out of memory."
        return Source.Clone(new Rectangle(0, 0, Source.Width, Source.Height), TargetFormat);
    }
    catch (OutOfMemoryException)
    {
        try
        {
            MemoryStream ResultStream = new MemoryStream();

            // This throws ExternalException: "A generic error occurred in GDI+"
            Source.Save(ResultStream, ImageFormat.Gif);

            ResultStream.Position = 0;
            return new Bitmap(ResultStream);
        }
        catch (ExternalException)
        {
            // this is just an attempt to break the process down further to try and find the cause:

            ImageCodecInfo myImageCodecInfo = GetCodecInfo(ImageFormat.Gif);
            EncoderParameters myEncoderParameters = new EncoderParameters(2);
            myEncoderParameters.Param[0] = new EncoderParameter(Encoder.Compression, (long)EncoderValue.CompressionLZW); ;
            myEncoderParameters.Param[1] = new EncoderParameter(Encoder.Quality, 0L);

            MemoryStream ResultStream = new MemoryStream();

            // This throws ExternalException: "A generic error occurred in GDI+"
            Source.Save(ResultStream, myImageCodecInfo, myEncoderParameters);

            ResultStream.Position = 0;
            return new Bitmap(ResultStream);
        }
    }
}

private static ImageCodecInfo GetCodecInfo(ImageFormat TargetFormat)
{
    return ImageCodecInfo.GetImageEncoders().ToList().Find(
        delegate (ImageCodecInfo codec) 
            { return codec.FormatID == TargetFormat.Guid; });
}

I know the source image is good as I can read the pixels just fine using LockBits(). I am considering using a loop to create a new image pixel by pixel using this, but I would prefer to use the more direct clone option as it automatically handles color palette creation, color matching and dithering.

UPDATE:
I found the code causing the issue, but I am unsure why. I did not want the file to be locked, so I was using the code below to load the image into memory and then unlock the file. Apparently this was causing issues, but only when calling the Clone() method. When I use that same image with LockBits() it allows me to access every pixel via the memory pointer just fine and also it displays just fine in a PictureBox. Does anyone know why this method is causing a .Clone() error and also how can I load an Image file into memory and then immediatly release the file?

using (Stream s = File.OpenRead(SourceFileName))
{
    return (Bitmap)Bitmap.FromStream(s);
}
Moon
  • 1,141
  • 2
  • 11
  • 25
  • Which version of .Net are you using? With .Net 4.5, using your initial code and the image provided, I can save as an 8bpp indexed png without any exception thrown. – Joel Rondeau Jan 21 '16 at 15:51
  • Hmm.. I was set to 4.6.1 x64, But I just tried Any CPU 4.5, 4.5.1, 4.5.2, 4.6 and none of them worked.. – Moon Jan 21 '16 at 16:21
  • If you download the image from here does it work? (Guessing that the image above was modified by SO/Imgur when uploaded) – Joel Rondeau Jan 21 '16 at 16:22
  • I thought of that too and I tried it immediatly after posting it. If you can get this code to work, there must be something different with my configuration and yours. I have another server I can try this on - it will take me a while to set everything up though. – Moon Jan 21 '16 at 16:24
  • Well, given that it's an OutOfMemory exception, I should note that I have 32GB of memory on this machine. – Joel Rondeau Jan 21 '16 at 16:27
  • I have 16GB, but I am pretty sure that is not the problem. It's a tiny image and I can open it up with many other functions. I will test on that other server soon and let you know. – Moon Jan 21 '16 at 16:36
  • I agree, as I just built for 4.6 and forgot to turn off "prefer 32 bit" and it still ran just fine. – Joel Rondeau Jan 21 '16 at 16:42
  • Okay, It's working on the other server with VS 2012, built to x64 and .NET 4.5. I will start to try other options to see why it's not running locally. – Moon Jan 21 '16 at 18:04
  • Well, I tried many options and nothing worked. I also copied the built files (including the config file identifying to use .net 4.5) from the other server where it worked over to my local system and they also error on my system. SO, I'm thinking there's an issue with my .net install or some compatibility issue with my OS (Win8.1 64bit) or maybe my graphics drivers since it's a GDI error? – Moon Jan 21 '16 at 19:01
  • It's probably a lot simpler to convert it to grayscale paletted yourself, so you're sure you know what it's doing. As for the locking of the file, you can get around that with a deep data clone. See the `CloneImage` / `CopyMemory` functions in this answer: https://stackoverflow.com/a/45100442/395685 – Nyerguds Jul 19 '17 at 06:48

0 Answers0