9

UPDATED

I used below solutions (loading Image from stream), but get new problem. img object is absolutely correct Image class instance with all field filled with correct values. But calling

img.Save("path/to/new/image.bmp");

on it results in new exception for GDI+ (System.Runtime.InteropServices.ExternalException, in GDI+ interface) - I get error message but is in polish, am not sure how to translate it.

Original question

I have problem with C# .NET Framework 2.0

Basically I'am trying to achieve:

Image img = Image.FromFile("Path/To/Image.bmp");
File.Delete("Path/To/Image.bmp"); // Exception, the file is in use!

It is important for me to keep copy of image in memory when original file was deleted. I though it is somehow odd that .NET still lock file on hard disc despite it is no longer required for any operation (entire image is now in memory, isn't it?)

So I tried this solution:

Image img = new Image(Image.FromFile("Path/To/Image.bmp")); // Make a copy
                    // this should immiedietaly destroy original loaded image
File.Delete("Path/To/Image.bmp"); // Still exception: the file is in use!

I can do:

Image img = null;
using(Image imgTmp = Image.FromFile("Path/To/Image.bmp"))
{
    img = new Bitmap(imgTmp.Width, imgTmp.Height, imgTmp.PixelFormat);
    Graphics gdi = Graphics.FromIage(img);
    gdi.DrawImageUnscaled(imgTmp, 0, 0);
    gdi.Dispose();
    imgTmp.Dispose(); // just to make sure
}
File.Delete("Path/To/Image.bmp"); // Works fine
// So I have img!

But this seems to me almost like using nuke to kill bug... and raises another problem: GDI poorly support Drawing palette-based images onto each other (and palette ones are majority in my collection).

Am I doing something wrong? Is any better way to have Image in memory and original file deleted from hard disk?

PiotrK
  • 4,210
  • 6
  • 45
  • 65

5 Answers5

17

This should do the trick:

  Image img = null;
  using (var stream = File.OpenRead(path)) {
    img = Image.FromStream(stream);
  }
  File.Delete(path);

UPDATE: Don't use the code above!

I've found the related knowledge base article: http://support.microsoft.com/?id=814675

The solution is to really copy the bitmap as outlined in the article. I've coded the two ways that the article mentions (the first one was the one you were doing, the second one is the one in your answer, but without using unsafe):

public static Image CreateNonIndexedImage(string path) { 
  using (var sourceImage = Image.FromFile(path)) { 
    var targetImage = new Bitmap(sourceImage.Width, sourceImage.Height, 
      PixelFormat.Format32bppArgb); 
    using (var canvas = Graphics.FromImage(targetImage)) { 
      canvas.DrawImageUnscaled(sourceImage, 0, 0); 
    } 
    return targetImage; 
  } 
} 

[DllImport("Kernel32.dll", EntryPoint = "CopyMemory")] 
private extern static void CopyMemory(IntPtr dest, IntPtr src, uint length); 

public static Image CreateIndexedImage(string path) { 
  using (var sourceImage = (Bitmap)Image.FromFile(path)) { 
    var targetImage = new Bitmap(sourceImage.Width, sourceImage.Height, 
      sourceImage.PixelFormat); 
    var sourceData = sourceImage.LockBits(
      new Rectangle(0, 0, sourceImage.Width, sourceImage.Height), 
      ImageLockMode.ReadOnly, sourceImage.PixelFormat); 
    var targetData = targetImage.LockBits(
      new Rectangle(0, 0, sourceImage.Width, sourceImage.Height), 
      ImageLockMode.WriteOnly, targetImage.PixelFormat); 
    CopyMemory(targetData.Scan0, sourceData.Scan0, 
      (uint)sourceData.Stride * (uint)sourceData.Height); 
    sourceImage.UnlockBits(sourceData); 
    targetImage.UnlockBits(targetData); 
    targetImage.Palette = sourceImage.Palette;
    return targetImage; 
  } 
} 
Jordão
  • 55,340
  • 13
  • 112
  • 144
  • 1
    Thanks, it works flawleslly and is faster than my solution :) – PiotrK Sep 09 '10 at 19:26
  • 1
    WHOW!!! You have my BIG thanks. This works like a charm :) (the solution with DllImport) thx a lot :) – Jan Macháček Mar 07 '14 at 15:44
  • The image produced by this function does lose its RawFormat (which tells you the format the image was saved as). This can be a problem if you want to save it to a stream, which will give an exception when you pass the stream and the RawFormat of the image. Also, when saving the image, it will always default to png unless specified otherwise. – user886079 Aug 18 '14 at 20:46
  • 1
    The indexed image code needs fixing: the colour palette isn't copied. Thankfully, colour palettes are actually _created_ by the getter instead of referencing the internal object, so you can just get the palette out of the original image and then give it to the new one. – Nyerguds Aug 21 '14 at 11:06
  • @Jordão No prob, this code really helped me out. But, yeah, you should add `targetImage.Palette = sourceImage.Palette` before the return. – Nyerguds Aug 21 '14 at 21:51
  • It seems this can be done without the external call to `CopyMemory`, using `Marshal.Copy(...)`. The only disadvantage is that it doesn't do direct pointer-to-pointer copying, so it needs a temp byte array. – Nyerguds Feb 24 '15 at 14:28
  • Hello @Nyerguds: I can add the code that you mentioned before about the Palette, but I'm not able to test it now. Please vouch that it works. – Jordão Feb 24 '15 at 16:11
  • 1
    I used it in an app of mine, and it works perfectly :) – Nyerguds Feb 25 '15 at 09:02
  • 1
    Seems like the non-indexed version is acting really weird on my side... it actually cut out a piece of my original image and upscaled that. I can't seem to fix it, but simply using the mem copy for both types works fine. All I needed to do was check the 8bpp depth before handling the palette. – Nyerguds Feb 25 '15 at 11:22
  • targetImage.Palette = sourceImage.Palette; this line throws me "Parameter invalid exception". pls help.... – A Coder Dec 17 '15 at 09:42
  • @SanthoshKumar: try removing the line or look [here](http://stackoverflow.com/questions/15267758/setting-image-palette-throws-gdi-generic-error-exception-if-the-image-doesnt-h) for ideas. – Jordão Dec 17 '15 at 10:47
  • @Jordão: When i remove either the line or try the link i'm getting "Out of memory" exception. – A Coder Dec 17 '15 at 10:59
  • Please look [here](http://stackoverflow.com/a/3848178/31158) for some info regarding that problem. – Jordão Dec 17 '15 at 11:06
  • I agree @Nyerguds, my image is being scaled that way. – Mike Flynn Dec 01 '21 at 17:28
  • @MikeFlynn I figured out since then that this happens when the DPI is set differently on the two images. – Nyerguds Dec 02 '21 at 02:10
12

Your problem is that the new Image still knows where it came from, having been given the file handle from the old Image's copy constructor, and so the runtime still knows it has an open handle to the file.

You might be able to work around this behavior with a Stream instead:

Image image;
FileStream myStream = new FileStream(path);

try
{
    image = Image.FromStream(myStream);
}
finally
{    
    myStream.Close();
    myStream.Dispose();
}

//test that you have a valid Image and then go to work.

Here's a cleaner version with a using clause:

Image image;
using(FileStream myStream = new FileStream(path))
{
    image = Image.FromStream(myStream);
}
//a using clause calls Dispose() at the end of the block,
//which will call Close() as well

Caveat emptor; I have not tested this, no guarantee that it'll solve the problem, but this seems reasonable. Working directly with the Stream gives you, and not the image implementation, control over the file handle, so you can make sure your program releases resources when YOU want.

KeithS
  • 70,210
  • 21
  • 112
  • 164
  • Thanks! Verified this solution (after minor scope fix for `myStream`). This is far simpler than the accepted answer and doesn't require manually copying memory. Too bad Msft didn't toss this variation in as a parameter to `Image.FromFile()` ! – nothingisnecessary Jul 01 '14 at 22:08
  • 6
    Nope... GDI+ errors keep popping up after the stream is disposed. – Nyerguds Aug 21 '14 at 11:04
  • You need to Close the stream in the using statement. Using only disposes it does not break connections to databases or close files. – Lawrence Thurman Nov 30 '16 at 13:34
  • This answer waister me an entire day!!! try Graphics.FromImage after disposing the Bitmap - it will pop an OutOfMemory exception note2 - on .Net 4.6 it worked. the error occured on 3.5... – ephraim Nov 07 '19 at 13:45
  • MSDN states that [the resource an image is based on needs to remain available during the entire life time of an image](https://learn.microsoft.com/en-us/dotnet/api/system.drawing.bitmap.-ctor?view=netframework-3.5). So the stream can absolutely not be closed, in any case. – Nyerguds Jan 26 '20 at 14:35
1

as share other way

try
{
var img = Image.FromFile(s);
var bmp = new Bitmap(img);
img.Dispose();
File.Delete(s);
}
catch { }
farshad saeidi
  • 365
  • 2
  • 6
0

just put

GC.Collect();

at the end it should work fine

Mohammed Atif Sami
  • 1,140
  • 8
  • 12
0

This works fine, the downside is that it requires "unsafe" compilation.

The version when Image is loaded from stream that is killed when loading is done results in unable to save image to disc via classic GDI+

public static unsafe Image LoadImageSafe(string path)
{
    Image ret = null;
    using (Image imgTmp = Image.FromFile(path))
    {
        ret = new Bitmap(imgTmp.Width, imgTmp.Height, imgTmp.PixelFormat);
        if (imgTmp.PixelFormat == PixelFormat.Format8bppIndexed)
        {
            ColorPalette pal = ret.Palette;
            for (int i = 0; i < imgTmp.Palette.Entries.Length; i++)
                pal.Entries[i] = Color.FromArgb(imgTmp.Palette.Entries[i].A,
                    imgTmp.Palette.Entries[i].R, imgTmp.Palette.Entries[i].G,
                    imgTmp.Palette.Entries[i].B);
            ret.Palette = pal;
            BitmapData bmd = ((Bitmap)ret).LockBits(new Rectangle(0, 0,
                imgTmp.Width, imgTmp.Height), ImageLockMode.WriteOnly,
                PixelFormat.Format8bppIndexed);
            BitmapData bmd2 = ((Bitmap)imgTmp).LockBits(new Rectangle(0, 0,
                imgTmp.Width, imgTmp.Height), ImageLockMode.ReadOnly,
                PixelFormat.Format8bppIndexed);

            Byte* pPixel = (Byte*)bmd.Scan0;
            Byte* pPixel2 = (Byte*)bmd2.Scan0;

            for (int Y = 0; Y < imgTmp.Height; Y++)
            {
                for (int X = 0; X < imgTmp.Width; X++)
                {
                    pPixel[X] = pPixel2[X];
                }
                pPixel += bmd.Stride;
                pPixel2 += bmd2.Stride;
            }

            ((Bitmap)ret).UnlockBits(bmd);
            ((Bitmap)imgTmp).UnlockBits(bmd2);
        }
        else
        {
            Graphics gdi = Graphics.FromImage(ret);
            gdi.DrawImageUnscaled(imgTmp, 0, 0);
            gdi.Dispose();
        }
        imgTmp.Dispose(); // just to make sure
    }
    return ret;
}
PiotrK
  • 4,210
  • 6
  • 45
  • 65
  • I believe using `Marshal.Copy(...)` actually solves the unsafe thing. It's also probably more efficient than byte-by-byte copying. – Nyerguds Feb 26 '15 at 09:11