Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Overwrite Existing Jpeg File/Replace Existing Jpeg File with edited jpeg file

I have build a program which allows me to insert comment and the title of an Image through System.Image.Drawing so right now, I have trouble trying to overwrite the existing Jpeg file with the one that has comment and title added into it, but I am encountering error while deleting the file, so I'm not sure what to do as I have tried disposing the file but I cant saved it in that case, due to the fact that I disposed it too early, but I cant saved it because the existing file name is not deleted so I'm kinda stuck in the middle right now.

Here are my codes for it:

public string EditComment(string OriginalFilepath, string newFilename)
{
       
    image = System.Drawing.Image.FromFile(OriginalFilepath);
    PropertyItem propItem = image.PropertyItems[0];
    using (var file = System.Drawing.Image.FromFile(OriginalFilepath))
    {
        propItem.Id = 0x9286;  // this is the id for 'UserComment'
        propItem.Type = 2;
        propItem.Value = System.Text.Encoding.UTF8.GetBytes("HelloWorld\0");
        propItem.Len = propItem.Value.Length;
        file.SetPropertyItem(propItem);
        PropertyItem propItem1 = file.PropertyItems[file.PropertyItems.Count() - 1];
        file.Dispose();
        image.Dispose();
        string filepath = Filepath;
        if (File.Exists(@"C:\Desktop\Metadata"))
        {
            System.IO.File.Delete(@"C:\Desktop\Metadata");
        }
        string newFilepath = filepath + newFilename;
        file.Save(newFilepath, ImageFormat.Jpeg);//error appears here
        return filepath;      
     }
}

The Error shown are:

An exception of type 'System.ArgumentException' occurred in System.Drawing.dll but was not handled in user code

Additional information: Parameter is not valid.

like image 815
Marko233 Avatar asked Mar 08 '23 00:03

Marko233


1 Answers

The problem is that opening an image from file locks the file. You can get around that by reading the file into a byte array, creating a memory stream from that, and then opening the image from that stream:

public string EditComment(string originalFilepath, string newFilename)
{
    Byte[] bytes = File.ReadAllBytes(originalFilepath);
    using (MemoryStream stream = new MemoryStream(bytes))
    using (Bitmap image = new Bitmap(stream))
    {
        PropertyItem propItem = image.PropertyItems[0];
        // Processing code
        propItem.Id = 0x9286;  // this is the id for 'UserComment'
        propItem.Type = 2;
        propItem.Value = System.Text.Encoding.UTF8.GetBytes("HelloWorld\0");
        propItem.Len = propItem.Value.Length;
        image.SetPropertyItem(propItem);
        // Not sure where your FilePath comes from but I'm just
        // putting it in the same folder with the new name.
        String newFilepath;
        if (newFilename == null)
            newFilepath = originalFilePath;
        else
            newFilepath = Path.Combine(Path.GetDirectory(originalFilepath), newFilename);
        image.Save(newFilepath, ImageFormat.Jpeg);
        return newFilepath;
    }
}

Make sure you do not dispose your image object inside the using block as you did in your test code. Not only does the using block exist exactly so you don't have to dispose manually, but it's also rather hard to save an image to disk that no longer exists in memory. Similarly, you seem to open the image from file twice. I'm just going to assume all of those were experiments to try to get around the problem, but do make sure to clean those up.

The basic rules to remember when opening images are these:

  • An Image object created from a file will lock the file during the life cycle of the image object, preventing the file from being overwritten or deleted until the image is disposed.
  • An Image object created from a stream will need the stream to remain open for the entire life cycle of the image object. Unlike with files, there is nothing actively enforcing this, but after the stream is closed, the image will give errors when saved, cloned or otherwise manipulated.

Contrary to what some people believe, a basic .Clone() call on the image object will not change this behaviour. The cloned object will still keep the reference to the original source.


Note, if you actually want a usable image object that is not contained in a using block, you can use LockBits and Marshal.Copy to copy the byte data of the image object into a new image with the same dimensions and the same PixelFormat, effectively making a full data clone of the original image. (Note: I don't think this works on animated GIF files) After doing that, you can safely dispose the original and just use the new cleanly-cloned version.

There are some other workarounds for actually getting the image out, but most of them I've seen aren't optimal. These are the two most common other valid workarounds for the locking problem:

  • Create a new Bitmap from an image loaded from file using the Bitmap(Image image) constructor. This new object will not have the link to that file, leaving you free to dispose the one that's locking the file. This works perfectly, but it changes the image's colour depth to 32-bit ARGB, which might not be desired. If you just need to show an image on the UI, this is an excellent solution, though.
  • Create a MemoryStream as shown in my code, but not in a using block, leaving the stream open as required. Leaving streams open doesn't really seem like a good idea to me. Though some people have said that, since a MemoryStream is just backed by a simple array, and not some external resource, the garbage collector apparently handles this case fine...

I've also seen some people use System.Drawing.ImageConverter to convert from bytes, but I looked into the internals of that process, and what it does is actually identical to the last method here, which leaves a memory stream open.

like image 144
Nyerguds Avatar answered Mar 29 '23 23:03

Nyerguds