Save modified image in the same file - c#

I'm making an app that adds (draws) some text on an image stored initially in a file. I managed to modify the image and save it in another file, but I'm trying not to create a second file, but store the result in the original file, although when I try to do that, as said in the Microsoft documentation website, that will result in an Exception being thrown (because the Image.Save method does not allow to write in the same file the image was created from). Is there a way to do it properly?
Right now I've made a workaround, saving the modified image into another file, and then I deleting it and changing the name of the second file to the original one.
Here's my code.
Image image = Image.FromFile(originalFile);
Graphics graphics = Graphics.FromImage(image);
...
graphics.DrawString(line, ...);
...
image.Save(newFile);
graphics.Dispose();
image.Dispose();
File.Delete(originalFile);
File.Move(newFile, originalFile);

The docs for Image.FromFile do say "The file remains locked until the Image is disposed."
Your method is fine (though File.Replace would probably be better than deleting and moving).
If you do want to unlock the file earlier, another option would be to clone the image once it's opened, dispose of the "file-backed" image, and use that image for your drawing.

Related

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.
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.

A generic error occurred in GDI+ during Bitmap.Save

First off, I've read through the existing StackOverflow answers on this specific issue. The consensus from those answers seems to be about permissions or existing files, etc... I've eliminated all of those issues.
Basically, the flow here is as follows:
The app takes a 24-bit PNG file and reading it into a Bitmap object, bmpOriginal.
The app saves bmpOriginal to a memory stream using the JPG encoder (obtained from looping through ImageCodecInfo.GetImageEncoders() until I found the one with the "image/jpeg" MimeType.
The app creates a new Bitmap, bmpOptimized, from the memory stream in step 2 and displays it in a PictureBox. So far, so good - everything works exactly as intended and I can even see JPG compression artifacts in the new Bitmap, so I know the encoder is working.
Later on in the code flow, the user clicks a button and it should save bmpOptimized to a new file, using the Bitmap.Save() method.
When I run this, it throws an error about "A generic error occurred in GDI+".
I double-checked to make sure the folder was writeable and that the file didn't already exist. In fact, the app actually does create a file in the right location, but it's empty (0 bytes).
The only other thing that seemed odd was that the bmpOriginal and bmpOptimized both have the same RawFormat value:
{[ImageFormat: b96b3caf-0728-11d3-9d7b-0000f81ef32e]}
...even though bmpOriginal comes from the PNG and bmpOptimized is from the encoded JPEG.
EDIT:
The code looks like this:
public Bitmap bmpOriginal;
public Bitmap bmpOptimized;
...
// Step 1
bmpOriginal = new Bitmap("foo.png");
// Step 2
using(MemoryStream ms = new MemoryStream())
{
ImageCodecInfo _jpgEncoder = _getEncoder("image/jpeg");
EncoderParameters _encoderParams = new EncoderParameters(1);
_encoderParams.Param[0] = new EncoderParameter(Encoder.Quality, 70L);
bmpOriginal.Save(ms, _jpgEncoder, _encoderParams);
// Step 3
bmpOptimized = new Bitmap(ms);
}
And then later on when the user clicks the button for step 4:
bmpOptimized.Save("bar.jpg");
...that's where the error happens.
Note that this edit is simply adding in the simplified version of the flow, since people are apparently downvoting this question for lack of source code. The one person who commented actually provided the correct answer - I needed to preserve the MemoryStream until after I saved the optimized bitmap.
Try this below
Please check whether memory stream didn't disposed until bmpOptimized image getting save in the desired location. If problem persist, then Check if path exists.

How to save image stream in c#?

I have a imagestream in c# and i want to save them on the hard drive using the c# code. when i trying to do that i found Out of Memory whennever i have much enough Memory.
so i am sure that my code leak the resources so can someone show me how i can do that
HttpPostedFileBase file
file.SaveAs(location);
Image image = Image.FromFile(location,false);
image.Save(location, System.Drawing.Imaging.ImageFormat.Png);
image.fromFile line [3] caused Exception that out of Memory. can someone show me how i can do this in c#.
The file come from PNG using Ajax Request are come as octet type as Mime type so how i can do that.
Assuming you want to convert the image to PNG (otherwise there's no need to save the image once, reload it and re-save it again), it might be a good idea to avoid loading the image twice. See if something like this helps:
Image.FromStream(file.InputStream, false).Save(location, System.Drawing.Imaging.ImageFormat.Png);
file.SaveAs(location);
already saved the image at the given location or threw an exception, so the following lines are unnecessary.
Bitmap bmp = new Bitmap(file.InputStream);
bmp.Save(filename, ImageFormat.Png);

Implementing IDisposable in File.Delete method?

I want to delete a file on my computer but the app throws an exception that The directory is busy. My first thought is to implement a dispose method in my class which does a file delete.
Knows anybody how to do this?
It's actually a Picturebox which displays a image and if you click on a button beside the image you can remove it, thus deleting it. It's propably that which blocks my filepath.
picobx.Image = Image.FromFile(//path)
Is there any solution to this?
PS. I want to dispose the resource so that I can delete...
do like this:
File.Delete(// path )
The Image.FromFile method actually locks the file thats why you can't delete it. The best would be to read the image as a byte array using bytes = File.ReadBytes(filename), create a memorystream from the byte array, pass the memory stream to Image.FromStream to display the image and then delete it with the button click.
OK - I see what you are tring to do.
Image itself implements IDisposable. You don't need to implement IDisposable yourself, you just have to make sure you either call Dispose on your image, or use the "using" syntax.
The problem is that until the image - the returned reference from Image.FromFile(//path) - is disposed of, it still locks the underlying file so you will always get the exception "The process cannot access the file"
What you intend to do is the following, I suppose:
try {
File.Delete(yourFile);
}
catch (IOException ex) {
// Do something with the exception here, like showing the user that the file can't be deleted)
}
A file that i.e. is in use or in an directory with insufficient rights can't be deleted. So if you get an error, you should look why the file is in use / not accessible, whatever.
Based on your edit, I suggest to do the following:
Image myImage = Image.FromFile(//path);
picobx.Image = myImage;
//click button
picobx.Image = null;
myImage.Dispose();
File.Delete(//path);
As you have suggested you will need to dispose of the image before you can delete it something like this:
Image i = this.pictureBox1.Image;
this.pictureBox1.Image = null;
i.Dispose();
File.Delete(filePath);
Edit I posted this without reading his new edit: I see you're using Image.FromFile.
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.
You're going to have to dispose of the Image before you can delete it.
Image.FromFile
The file remains locked until the Image is disposed.
private Image image;
in your image load function:
image = Image.FromFile("path");
picobx.Image = image;
this goes in your delete method;
image.Dispose;
File.Delete("path");
--- No longer relevant ---
If you're reading the file from a stream and converting the stream to an image. Then you try and delete the file without closing the stream you will get an IOException.
System.IO.IOException: The process cannot access the file 'File Here' because it is being used by another process..
and example of what could be causing this
StreamReader file = new StreamReader(#"C:\trans.txt");
file.ReadToEnd();
File.Delete(#"C:\trans.txt");

Saving a modified image to the original file using GDI+

I was loading a Bitmap Image from a File. When I tried to save the Image to another file I got the following error "A generic error occurred in GDI+". I believe this is because the file is locked by the image object.
Ok so tried calling the Image.Clone function. This still locks the file.
hmm. Next I try loading a Bitmap Image from a FileStream and load the image into memory so GDI+ doesn't lock the file. This works great except I need to generate thumbnails using Image.GetThumbnailImage method it throws an out of memory exception. Apparently I need to keep the stream open to stop this exception but if I keep the stream open then the file remains locked.
So no good with that method. In the end I created a copy of the file. So now I have 2 versions of the file. 1 I can lock and manipulate in my c# program. This other original file remains unlocked to which I can save modifications to. This has the bonus of allowing me to revert changes even after saving them because I'm manipulating the copy of the file which cant change.
Surely there is a better way of achieving this without having to have 2 versions of the image file. Any ideas?
Well if you're looking for other ways to do what you're asking, I reckon it should work to create a MemoryStream, and read out the FileStream to it, and load the Image from that stream...
var stream = new FileStream("original-image", FileMode.Open);
var bufr = new byte[stream.Length];
stream.Read(bufr, 0, (int)stream.Length);
stream.Dispose();
var memstream = new MemoryStream(bufr);
var image = Image.FromStream(memstream);
Or something prettier to that extent.
Whether or not that's the way you should go about solving that problem, I don't know. :)
I've had a similar problem and wound up fixing it like this.
I have since found an alternative method to clone the image without locking the file. Bob Powell has it all plus more GDI resources.
//open the file
Image i = Image.FromFile(path);
//create temporary
Image t=new Bitmap(i.Width,i.Height);
//get graphics
Graphics g=Graphics.FromImage(t);
//copy original
g.DrawImage(i,0,0);
//close original
i.Dispose();
//Can now save
t.Save(path)
I had a similar problem. But I knew, that I will save the image as a bitmap-file. So I did this:
public void SaveHeightmap(string path)
{
if (File.Exists(path))
{
Bitmap bitmap = new Bitmap(image); //create bitmap from image
image.Dispose(); //delete image, so the file
bitmap.Save(path); //save bitmap
image = (Image) bitmap; //recreate image from bitmap
}
else
//...
}
Sure, thats not the best way, but its working :-)

Categories

Resources