Consider this code for loading, modifying and saving a Bitmap image:
using (Bitmap bmp = new Bitmap("C:\\test.jpg"))
{
bmp.RotateFlip(RotateFlipType.Rotate180FlipNone);
bmp.Save("C:\\test.jpg");
}
it runs without any exception.
But consider this one:
using (Bitmap bmp = new Bitmap("C:\\test.jpg"))
{
using (Bitmap bmpClone = (Bitmap)bmp.Clone())
{
//You can replace "bmpClone" in the following lines with "bmp",
//exception occurs anyway
bmpClone.RotateFlip(RotateFlipType.Rotate180FlipNone);
bmpClone.Save("C:\\test.jpg");
}
}
It ends in an ExternalException with this message: "A generic error occurred in GDI+".
What's wrong here? Any kind of lock on opened file? If so, why the first block works? What is the proper code for cloning a System.Drawing.Bitmap while we may need to edit main object or its clone in the memory and still have them both loaded in memory?
Yes, the file is locked when the first bitmap object is loaded and thus bmpClone.Save() to the same file fails because you have a logical deadlock.
When opening Bitmaps by filename, the file is locked throughout the life of the Bitmap. If you use a stream, the stream must remain open.
Update:
If you wish to have two bitmaps in memory for use outside of the scope of the method you are in, then you won't be using a using block.
Create the first image from file, and then clone it. Utilize them as needed throughout your UI lifecycle but make sure you clean them up using Dispose() when they are no longer needed so that underlying resources are released.
Also, from MSDN:
Saving the image to the same file it
was constructed from is not allowed
and throws an exception
That's pretty awkward. If the object created using clone() keeps information on the image source (e.g. a handle on the original file) or you can't otherwise unlock the file while using the Bitmap instances, then you'll probably have to either save to a new file, or open from a temporary copy of the original.
Try this instead:
// ... make a copy of test.jpg called test_temp.jpg
Bitmap bmpOriginal = new Bitmap("C:\\test_temp.jpg"))
Bitmap bmpClone = (Bitmap)bmp.Clone();
// ... do stuff
bmpClone.RotateFlip(RotateFlipType.Rotate180FlipNone);
bmpClone.Save("C:\\test.jpg");
// ... cleanup
bmpOriginal.Dispose();
bmpClone.Dispose();
This is how I copy Bitmaps:
[DllImport("kernel32.dll", EntryPoint = "CopyMemory")]
static extern void CopyMemory(IntPtr Destination, IntPtr Source, uint Length);
public static Bitmap KernellDllCopyBitmap(Bitmap bmp, bool CopyPalette = true)
{
Bitmap bmpDest = new Bitmap(bmp.Width, bmp.Height, bmp.PixelFormat);
if (!KernellDllCopyBitmap(bmp, bmpDest, CopyPalette))
bmpDest = null;
return bmpDest;
}
/// <summary>
/// Copy bitmap data.
/// Note: bitmaps must have same size and pixel format.
/// </summary>
/// <param name="bmpSrc">Source Bitmap</param>
/// <param name="bmpDest">Destination Bitmap</param>
/// <param name="CopyPalette">Must copy Palette</param>
public static bool KernellDllCopyBitmap(Bitmap bmpSrc, Bitmap bmpDest, bool CopyPalette = false)
{
bool copyOk = false;
copyOk = CheckCompatibility(bmpSrc, bmpDest);
if (copyOk)
{
BitmapData bmpDataSrc;
BitmapData bmpDataDest;
//Lock Bitmap to get BitmapData
bmpDataSrc = bmpSrc.LockBits(new Rectangle(0, 0, bmpSrc.Width, bmpSrc.Height), ImageLockMode.ReadOnly, bmpSrc.PixelFormat);
bmpDataDest = bmpDest.LockBits(new Rectangle(0, 0, bmpDest.Width, bmpDest.Height), ImageLockMode.WriteOnly, bmpDest.PixelFormat);
int lenght = bmpDataSrc.Stride * bmpDataSrc.Height;
CopyMemory(bmpDataDest.Scan0, bmpDataSrc.Scan0, (uint)lenght);
bmpSrc.UnlockBits(bmpDataSrc);
bmpDest.UnlockBits(bmpDataDest);
if (CopyPalette && bmpSrc.Palette.Entries.Length > 0)
bmpDest.Palette = bmpSrc.Palette;
}
return copyOk;
}
public static bool CheckCompatibility(Bitmap bmp1, Bitmap bmp2)
{
return ((bmp1.Width == bmp2.Width) && (bmp1.Height == bmp2.Height) && (bmp1.PixelFormat == bmp2.PixelFormat));
}
## ImageCopyBenchmark ##
Image Size: {Width=1024, Height=1024}.
Image PixelFormat: Format8bppIndexed.
Bitmap.Clone(): 0,00 ms (Not a DeepCopy... Same pixel data - look here)
Bitmap.Clone() + RotateFlip (to guet a deep copy): 2,02 ms
KernellDllCopyBitmap: 0,52 ms (THE BEST!)
MarshalCopyBitmap: 2,21 ms
You could also load the bitmap without file locking using the simple workaround:
using (Stream s = File.OpenRead(#"\My Documents\My Pictures\Waterfall.jpg"))
Bitmap _backImage = (Bitmap)Bitmap.FromStream(s);
Here is my vanity method:
private static unsafe Bitmap DuplicateBitmap(Bitmap inputBitmap)
{
byte[] buffer = new byte[inputBitmap.Height * inputBitmap.Width *
Image.GetPixelFormatSize(inputBitmap.PixelFormat) / 8];
fixed (byte* p = buffer)
{
BitmapData b1Data = new BitmapData()
{
Scan0 = (IntPtr)p,
Height = inputBitmap.Height,
Width = inputBitmap.Width,
PixelFormat = inputBitmap.PixelFormat,
Stride = inputBitmap.Width * Image.GetPixelFormatSize(inputBitmap.PixelFormat) / 8,
};
inputBitmap.LockBits(new Rectangle(Point.Empty, inputBitmap.Size),
ImageLockMode.ReadOnly | ImageLockMode.UserInputBuffer, inputBitmap.PixelFormat, b1Data); // copy out.
Bitmap b2 = new Bitmap(b1Data.Width, b1Data.Height, b1Data.Stride, inputBitmap.PixelFormat, b1Data.Scan0);
inputBitmap.UnlockBits(b1Data);
return b2;
}
}
10% faster (depending on bitmap size...)
Related
I have tried to dispose and use the 'using' statement to prevent high memory usage. However, it perplexes me when my memory doesn't clear everything.
Below is the method where I save the bitmap pictures into an Excel worksheet.
I ran 2 simulations
I deleted the bitmap list before it comes in here and it uses 200mb+.
I loaded the bitmap list and it uses 600+mb (normal) before this method. After going into loop in this method, it adds another 600mb, totaling to 1.2GB. After exiting this method, it goes down to 600mb+. What am i missing out because I feel that the memory should be around 200mb - 300mb only.
In the code, I used 2 'using' statements to enable auto dispose of the image and the stream.
Thank you for helping!
private void SaveBitMapIntoExcelSht(ref List<Bitmap> bmpLst, IXLWorksheet wksheet, int pixH)
{
string inputCell;
using (MemoryStream stream = new MemoryStream())
{
for (int i = 0; i < bmpLst.Count; i++)
{
Console.WriteLine(GC.GetTotalMemory(true));
inputCell = "A" + (30).ToString();
using (Image image = Image.FromHbitmap(bmpLst[i].GetHbitmap()))//Convert bitmap to hbitmap and store as a stream to save directly into the excel file.
{
Console.WriteLine(GC.GetTotalMemory(true));
// Save image to stream.
image.Save(stream, ImageFormat.Png);
IXLPicture pic = wksheet.AddPicture(stream, XLPictureFormat.Png);
pic.MoveTo(wksheet.Cell(inputCell));
pic.Delete();
pic.Dispose();
image.Dispose();
GC.Collect();
}
}
}
foreach (var bmp in bmpLst)
{
bmp.Dispose();
}
bmpLst.Clear();
Dispose();
GC.Collect();
Console.WriteLine(GC.GetTotalMemory(true));
}
EDIT: ANSWER
For those who are interested, you may find the code below that works.
Previously I got the hbitmap in the using statement but there wasnt a reference to it, therefore I created a var handle so that I can delete it.
Add this in your class
[System.Runtime.InteropServices.DllImport("gdi32.dll")]
public static extern bool DeleteObject(IntPtr hObject);
using (MemoryStream stream = new MemoryStream())
{
for (int i = 0; i < bmpLst.Count; i++)
{
Console.WriteLine(GC.GetTotalMemory(true));
inputCell = "A" + (i * numberOfCells + 1).ToString();
//using (Image image = Image.FromHbitmap(bmpLst[i].GetHbitmap()))
var handle = bmpLst[i].GetHbitmap();
using (Image image = Image.FromHbitmap(handle))//Convert bitmap to hbitmap and store as a stream to save directly into the excel file.
{
// Save image to stream.
image.Save(stream, ImageFormat.Png);
pic = wksheet.AddPicture(stream, XLPictureFormat.Png);
pic.MoveTo(wksheet.Cell(inputCell));
try
{
var source = System.Windows.Interop.Imaging.CreateBitmapSourceFromHBitmap(handle, IntPtr.Zero, Int32Rect.Empty, System.Windows.Media.Imaging.BitmapSizeOptions.FromEmptyOptions());
}
finally
{
DeleteObject(handle);
}
}
}
}
From the documentation of GetHBitmap
You are responsible for calling the GDI DeleteObject method to free the memory used by the GDI bitmap object. For more information about GDI bitmaps, see Bitmaps in the Windows GDI documentation.
and the documentation of FromHbitmap
The FromHbitmap method makes a copy of the GDI bitmap; so you can release the incoming GDI bitmap using the GDI DeleteObject method immediately after creating the new Image.
I.e. you are creating a GDI object you are never removing. So you need to call the pInvoke function DeleteObject on the IntPtr
[DllImport("gdi32.dll", EntryPoint = "DeleteObject")]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool DeleteObject([In] IntPtr hObject);
Or use some other way to convert a bitmap to an image.
As a rule of thumb, if you ever see anything returning IntPtr it may represent some unmanaged resource, and if so you need to be extra careful to check if you need to manually dispose/delete something.
Another rule of thumb is to use a memory profiler if you suspect a memory leak. It might not have found this issue since it involves pointers to unmanaged memory, but it should be the first step to investigate.
Images extracted using PdfPig are the type of XObject Image or InlineImage (both inherit from IPdfImage). I would like to save and display them in a simple WPF application. In order to do so, I would need to have them in more accessible form, for example BitmapImage format. What is the correct way to achieve that? Library documentation does not help here and my miserable attempts were unsuccessful.
I haven't tested any of this, but it should at least put you on the right path if it doesn't work.
Looking at the PdfPig source on GitHub I can see both XObjectImage and InlineImage have a function TryGetPng. From the looks of it, I would assume that this byte array would match up with the contents of a normal PNG file, which means you should be able to load it straight into a BitmapImage.
Taking some code from this answer. Something like this might work:
InlineImage pdfImage;
byte[] png;
if (pdfImage.TryGetPng(out png))
{
var bitmap = (BitmapSource)new ImageSourceConverter().ConvertFrom(png);
}
Note: both classes also have a TryGetBytes method, which might work in place of TryGetPng. I'm just not sure what format the output of TryGetBytes is in, so I'd be more confident with TryGetPng. Still, I'd try both if one doesn't work.
FWIW, by trial and error, my current approach is to start with TryGetPng and fall back to RawBytes if it fails. I then interpret the extracted bytes as a System.Drawing.Image. I don't use TryGetBytes at all. Here's my code (F#, but should be easy to convert to C#):
let bytes =
match pdfImage.TryGetPng() with
| true, bytes -> bytes
| _ -> Seq.toArray pdfImage.RawBytes
use stream = new MemoryStream(bytes)
use image = Image.FromStream(stream)
I find the following code for me works in most cases. It simply tries all three options available to extract an image (TryGetPng, TryGetBytes and rawBytes) and converts those to an BmpSource.
private static BitmapSource TryGetImage(IPdfImage image)
{
BitmapSource bmp;
byte[] bytes;
if (image.TryGetPng(out bytes))
{
bmp = (BitmapSource)new ImageSourceConverter().ConvertFrom(bytes);
Debug.WriteLine("Converted using TryGetPng.");
}
else
{
IReadOnlyList<byte> iroBytes;
if (image.TryGetBytes(out iroBytes))
{
bmp = (BitmapSource)new ImageSourceConverter().ConvertFrom(bytes);
Debug.WriteLine("Converted using TryGetBytes.");
}
else
{
var rawB=image.RawBytes.ToArray<Byte>();
Bitmap nbmp;
using (var ms = new MemoryStream(rawB))
{
nbmp = new Bitmap(ms);
}
bmp = ConvertBmpToBmpSource(nbmp);
Debug.WriteLine("Converted using RawBytes.");
}
}
return bmp;
}
public static BitmapSource ConvertBmpToBmpSource(Bitmap bitmap)
{
var bitmapData = bitmap.LockBits(
new Rectangle(0, 0, bitmap.Width, bitmap.Height),
System.Drawing.Imaging.ImageLockMode.ReadOnly, bitmap.PixelFormat);
var bitmapSource = BitmapSource.Create(
bitmapData.Width, bitmapData.Height,
bitmap.HorizontalResolution, bitmap.VerticalResolution,
PixelFormats.Bgr24, null,
bitmapData.Scan0, bitmapData.Stride * bitmapData.Height, bitmapData.Stride);
bitmap.UnlockBits(bitmapData);
return bitmapSource;
}
I am working on to upload and save a thumbnail copy of that image in a thumbnail folder.
I am using following link:
http://weblogs.asp.net/markmcdonnell/archive/2008/03/09/resize-image-before-uploading-to-server.aspx
but
newBMP.Save(directory + "tn_" + filename);
is causing exception "A generic error occurred in GDI+."
I have tried to give permission on folder, also tried to use a new separate bmp object when saving.
Edit:
protected void ResizeAndSave(PropBannerImage objPropBannerImage)
{
// Create a bitmap of the content of the fileUpload control in memory
Bitmap originalBMP = new Bitmap(fuImage.FileContent);
// Calculate the new image dimensions
int origWidth = originalBMP.Width;
int origHeight = originalBMP.Height;
int sngRatio = origWidth / origHeight;
int thumbWidth = 100;
int thumbHeight = thumbWidth / sngRatio;
int bannerWidth = 100;
int bannerHeight = bannerWidth / sngRatio;
// Create a new bitmap which will hold the previous resized bitmap
Bitmap thumbBMP = new Bitmap(originalBMP, thumbWidth, thumbHeight);
Bitmap bannerBMP = new Bitmap(originalBMP, bannerWidth, bannerHeight);
// Create a graphic based on the new bitmap
Graphics oGraphics = Graphics.FromImage(thumbBMP);
// Set the properties for the new graphic file
oGraphics.SmoothingMode = SmoothingMode.AntiAlias; oGraphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
// Draw the new graphic based on the resized bitmap
oGraphics.DrawImage(originalBMP, 0, 0, thumbWidth, thumbHeight);
Bitmap newBitmap = new Bitmap(thumbBMP);
thumbBMP.Dispose();
thumbBMP = null;
// Save the new graphic file to the server
newBitmap.Save("~/image/thumbs/" + "t" + objPropBannerImage.ImageId, ImageFormat.Jpeg);
oGraphics = Graphics.FromImage(bannerBMP);
// Set the properties for the new graphic file
oGraphics.SmoothingMode = SmoothingMode.AntiAlias; oGraphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
// Draw the new graphic based on the resized bitmap
oGraphics.DrawImage(originalBMP, 0, 0, bannerWidth, bannerHeight);
// Save the new graphic file to the server
bannerBMP.Save("~/image/" + objPropBannerImage.ImageId + ".jpg");
// Once finished with the bitmap objects, we deallocate them.
originalBMP.Dispose();
bannerBMP.Dispose();
oGraphics.Dispose();
}
When either a Bitmap object or an Image object is constructed from a
file, the file remains locked for the lifetime of the object. As a
result, you cannot change an image and save it back to the same file
where it originated.
http://support.microsoft.com/?id=814675
A generic error occurred in GDI+, JPEG Image to MemoryStream
Image.Save(..) throws a GDI+ exception because the memory stream is closed
http://alperguc.blogspot.in/2008/11/c-generic-error-occurred-in-gdi.html
EDIT:
just writing from memory...
save to an 'intermediary' memory stream, that should work
e.g. try this one - replace
Bitmap newBitmap = new Bitmap(thumbBMP);
thumbBMP.Dispose();
thumbBMP = null;
newBitmap.Save("~/image/thumbs/" + "t" + objPropBannerImage.ImageId, ImageFormat.Jpeg);
with something like:
string outputFileName = "...";
using (MemoryStream memory = new MemoryStream())
{
using (FileStream fs = new FileStream(outputFileName, FileMode.Create, FileAccess.ReadWrite))
{
thumbBMP.Save(memory, ImageFormat.Jpeg);
byte[] bytes = memory.ToArray();
fs.Write(bytes, 0, bytes.Length);
}
}
This error message is displayed if the path you pass to Bitmap.Save() is invalid (folder doesn't exist etc).
// Once finished with the bitmap objects, we deallocate them.
originalBMP.Dispose();
bannerBMP.Dispose();
oGraphics.Dispose();
This is a programming style that you'll regret sooner or later. Sooner is knocking on the door, you forgot one. You are not disposing newBitmap. Which keeps a lock on the file until the garbage collector runs. If it doesn't run then the second time you try to save to the same file you'll get the klaboom. GDI+ exceptions are too miserable to give a good diagnostic so serious head-scratching ensues. Beyond the thousands of googlable posts that mention this mistake.
Always favor using the using statement. Which never forgets to dispose an object, even if the code throws an exception.
using (var newBitmap = new Bitmap(thumbBMP)) {
newBitmap.Save("~/image/thumbs/" + "t" + objPropBannerImage.ImageId, ImageFormat.Jpeg);
}
Albeit that it is very unclear why you even create a new bitmap, saving thumbBMP should already be good enough. Anyhoo, give the rest of your disposable objects the same using love.
In my case the bitmap image file already existed in the system drive, so my app threw the error "A Generic error occured in GDI+".
Verify that the destination folder exists
Verify that there isn't already a file with the same name in the destination folder
Check your folder's permission where the image is saved
Right cLick on folder then go :
Properties > Security > Edit > Add-- select "everyone" and check Allow "Full Control"
I was facing the same issue A generic error occurred in GDI+ on saving while working on MVC app, I was getting this error because I was writing wrong path to save image, I corrected saving path and it worked fine for me.
img1.Save(Server.MapPath("/Upload/test.png", System.Drawing.Imaging.ImageFormat.Png);
--Above code need one change, as you need to put close brackets on Server.MapPath() method after writing its param.
Like this-
img1.Save(Server.MapPath("/Upload/test.png"), System.Drawing.Imaging.ImageFormat.Png);
GDI+ exceptions occured due to below points
Folder access issue
Missing properties of images
If folder issue - please provide access to application
If Missing properties then use below code
Code 1
using (Bitmap bmp = new Bitmap(webStream))
{
using (Bitmap newImage = new Bitmap(bmp))
{
newImage.Save("c:\temp\test.jpg", ImageFormat.Jpeg);
}
}
Code 2
using (Bitmap bmp = new Bitmap(webStream))
{
using (Bitmap newImage = new Bitmap(bmp))
{
newImage.SetResolution(bmp.HorizontalResolution, bmp.VerticalResolution);
Rectangle lockedRect = new Rectangle(0, 0, bmp.Width, bmp.Height);
BitmapData bmpData = newImage.LockBits(lockedRect, ImageLockMode.ReadWrite, bmp.PixelFormat);
bmpData.PixelFormat = bmp.PixelFormat;
newImage.UnlockBits(bmpData);
using (Graphics gr = Graphics.FromImage(newImage))
{
gr.SmoothingMode = SmoothingMode.HighQuality;
gr.InterpolationMode = InterpolationMode.HighQualityBicubic;
gr.PixelOffsetMode = PixelOffsetMode.HighQuality;
}
foreach (var item in bmp.PropertyItems)
{
newImage.SetPropertyItem(item);
}
newImage.Save("c:\temp\test.jpg", ImageFormat.Jpeg);
}
}
Different between code 1 and code 2
Code - 1 : it will just create image and can open it on normal image viewer
the image can't open in Photoshop
Image size will be double
Code - 2 : to open image in image edition tools use code
by using code 1 it just create images but it not assign image marks.
I always check/test these:
Does the path + filename contain illegal characters for the given filesystem?
Does the file already exist? (Bad)
Does the path already exist? (Good)
If the path is relative: am I expecting it in the right parent directory (mostly bin/Debug ;-) )?
Is the path writable for the program and as which user does it run? (Services can be tricky here!)
Does the full path really, really not contain illegal chars? (some unicode chars are close to invisible)
I never had any problems with Bitmap.Save() apart from this list.
I had a different issue with the same exception.
In short:
Make sure that the Bitmap's object Stream is not being disposed before calling .Save .
Full story:
There was a method that returned a Bitmap object, built from a MemoryStream in the following way:
private Bitmap getImage(byte[] imageBinaryData){
.
.
.
Bitmap image;
using (var stream = new MemoryStream(imageBinaryData))
{
image = new Bitmap(stream);
}
return image;
}
then someone used the returned image to save it as a file
image.Save(path);
The problem was that the original stream was already disposed when trying to save the image, throwing the GDI+ exeption.
A fix to this problem was to return the Bitmap without disposing the stream itself but the returned Bitmap object.
private Bitmap getImage(byte[] imageBinaryData){
.
.
.
Bitmap image;
var stream = new MemoryStream(imageBinaryData))
image = new Bitmap(stream);
return image;
}
then:
using (var image = getImage(binData))
{
image.Save(path);
}
I got it working using FileStream, get help from these
http://alperguc.blogspot.in/2008/11/c-generic-error-occurred-in-gdi.html
http://csharpdotnetfreak.blogspot.com/2010/02/resize-image-upload-ms-sql-database.html
System.Drawing.Image imageToBeResized = System.Drawing.Image.FromStream(fuImage.PostedFile.InputStream);
int imageHeight = imageToBeResized.Height;
int imageWidth = imageToBeResized.Width;
int maxHeight = 240;
int maxWidth = 320;
imageHeight = (imageHeight * maxWidth) / imageWidth;
imageWidth = maxWidth;
if (imageHeight > maxHeight)
{
imageWidth = (imageWidth * maxHeight) / imageHeight;
imageHeight = maxHeight;
}
Bitmap bitmap = new Bitmap(imageToBeResized, imageWidth, imageHeight);
System.IO.MemoryStream stream = new MemoryStream();
bitmap.Save(stream, System.Drawing.Imaging.ImageFormat.Jpeg);
stream.Position = 0;
byte[] image = new byte[stream.Length + 1];
stream.Read(image, 0, image.Length);
System.IO.FileStream fs
= new System.IO.FileStream(Server.MapPath("~/image/a.jpg"), System.IO.FileMode.Create
, System.IO.FileAccess.ReadWrite);
fs.Write(image, 0, image.Length);
For me it was a permission problem. Somebody removed write permissions on the folder for the user account under which the application was running.
Create folder path image/thumbs on your hard disk => Problem solved!
I used below logic while saving a .png format. This is to ensure the file is already existing or not.. if exist then saving it by adding 1 in the filename
Bitmap btImage = new Bitmap("D:\\Oldfoldername\\filename.png");
string path="D:\\Newfoldername\\filename.png";
int Count=0;
if (System.IO.File.Exists(path))
{
do
{
path = "D:\\Newfoldername\\filename"+"_"+ ++Count + ".png";
} while (System.IO.File.Exists(path));
}
btImage.Save(path, System.Drawing.Imaging.ImageFormat.Png);
I encountered this error while trying to convert Tiff images to Jpeg. For me the issue stemmed from the tiff dimensions being too large. Anything up to around 62000 pixels was fine, anything above this size produced the error.
for me it was a path issue when saving the image.
int count = Directory.EnumerateFiles(System.Web.HttpContext.Current.Server.MapPath("~/images/savedimages"), "*").Count();
var img = Base64ToImage(imgRaw);
string path = "images/savedimages/upImages" + (count + 1) + ".png";
img.Save(Path.Combine(System.Web.HttpContext.Current.Server.MapPath(path)));
return path;
So I fixed it by adding the following forward slash
String path = "images/savedimages....
should be
String path = "/images/savedimages....
Hope that helps anyone stuck!
from msdn: public void Save (string filename); which is quite surprising to me because we dont just have to pass in the filename, we have to pass the filename along with the path for example: MyDirectory/MyImage.jpeg, here MyImage.jpeg does not actually exist yet, but our file will be saved with this name.
Another important point here is that if you are using Save() in a web application then use Server.MapPath() along with it which basically just returns the physical path for the virtual path which is passed in. Something like: image.Save(Server.MapPath("~/images/im111.jpeg"));
I use this solution
int G = 0;
private void toolStripMenuItem17_Click(object sender, EventArgs e)
{
Directory.CreateDirectory("picture");// هذه العملية للرسم بدون ان يحذف بقية الرسومات
G = G + 1;
FormScreen();
memoryImage1.Save("picture\\picture" + G.ToString() + ".jpg");
pictureBox1.Image = Image.FromFile("picture\\picture" + G.ToString() + ".jpg");
}
The code below solved my problem
pictureBox1.Image=myImage;
Bitmap bmp = new Bitmap(pictureBox1.Image);
bmp.Save("C:\\Users/super/Desktop/robin.jpg");
I have the Image of a PictureBox pointing to a certain file "A". At execution time I want to change the Image of the PictureBox to a different one "B" but I get the following error:
"A first chance exception of type 'System.IO.IOException' occurred in mscorlib.dll
Additional information: The process cannot access the file "A" because it is being used by another process."
I'm setting the Image as follows:
pbAvatar.Image = new Bitmap(filePath);
How can I unlock the first file?
Here is my approach to opening an image without locking the file...
public static Image FromFile(string path)
{
var bytes = File.ReadAllBytes(path);
var ms = new MemoryStream(bytes);
var img = Image.FromStream(ms);
return img;
}
UPDATE: I did some perf tests to see which method was the fastest. I compared it to #net_progs "copy from bitmap" answer (which seems to be the closest to correct, though does have some issues). I loaded the image 10000 times for each method and calculated the average time per image. Here are the results:
Loading from bytes: ~0.26 ms per image.
Copying from bitmap: ~0.50 ms per image.
The results seem to make sense since you have to create the image twice using the copy from bitmap method.
UPDATE:
if you need a BitMap you can do:
return (Bitmap)Image.FromStream(ms);
This is a common locking question widely discussed over the web.
The suggested trick with stream will not work, actually it works initially, but causes problems later. For example, it will load the image and the file will remain unlocked, but if you try to save the loaded image via Save() method, it will throw a generic GDI+ exception.
Next, the way with per pixel replication doesn't seem to be solid, at least it is noisy.
What I found working is described here: http://www.eggheadcafe.com/microsoft/Csharp/35017279/imagefromfile--locks-file.aspx
This is how the image should be loaded:
Image img;
using (var bmpTemp = new Bitmap("image_file_path"))
{
img = new Bitmap(bmpTemp);
}
I was looking for a solution to this problem and this method works fine for me so far, so I decided to describe it, since I found that many people advise the incorrect stream approach here and over the web.
Using a filestream will unlock the file once it has been read from and disposed:
using (var fs = new System.IO.FileStream("c:\\path to file.bmp", System.IO.FileMode.Open))
{
var bmp = new Bitmap(fs);
pct.Image = (Bitmap) bmp.Clone();
}
Edit: Updated to allow the original bitmap to be disposed, and allow the FileStream to be closed.
THIS ANSWER IS NOT SAFE - See comments, and see discussion in net_prog's answer. The Edit to use Clone does not make it any safer - Clone clones all fields, including the filestream reference, which in certain circumstances will cause a problem.
You can't dispose / close a stream while a bitmap object is still using it. (Whether the bitmap object will need access to it again is only deterministic if you know what type of file you are working with and exactly what operations you will be performing. -- for example for SOME .gif format images, the stream is closed before the constructor returns.)
Clone creates an "exact copy" of the bitmap (per documentation; ILSpy shows it calling native methods, so it's too much to track down right now) likely, it copies that Stream data as well -- or else it wouldn't be an exact copy.
Your best bet is creating a pixel-perfect replica of the image -- though YMMV (with certain types of images there may be more than one frame, or you may have to copy palette data as well.) But for most images, this works:
static Bitmap LoadImage(Stream stream)
{
Bitmap retval = null;
using (Bitmap b = new Bitmap(stream))
{
retval = new Bitmap(b.Width, b.Height, b.PixelFormat);
using (Graphics g = Graphics.FromImage(retval))
{
g.DrawImage(b, Point.Empty);
g.Flush();
}
}
return retval;
}
And then you can invoke it like such:
using (Stream s = ...)
{
Bitmap x = LoadImage(s);
}
As far as I know, this is 100% safe, since the resulting image is 100% created in memory, without any linked resources, and with no open streams left behind in memory. It acts like any other Bitmap that's created from a constructor that doesn't specify any input sources, and unlike some of the other answers here, it preserves the original pixel format, meaning it can be used on indexed formats.
Based on this answer, but with extra fixes and without external library import.
/// <summary>
/// Clones an image object to free it from any backing resources.
/// Code taken from http://stackoverflow.com/a/3661892/ with some extra fixes.
/// </summary>
/// <param name="sourceImage">The image to clone</param>
/// <returns>The cloned image</returns>
public static Bitmap CloneImage(Bitmap sourceImage)
{
Rectangle rect = new Rectangle(0, 0, sourceImage.Width, sourceImage.Height);
Bitmap targetImage = new Bitmap(rect.Width, rect.Height, sourceImage.PixelFormat);
targetImage.SetResolution(sourceImage.HorizontalResolution, sourceImage.VerticalResolution);
BitmapData sourceData = sourceImage.LockBits(rect, ImageLockMode.ReadOnly, sourceImage.PixelFormat);
BitmapData targetData = targetImage.LockBits(rect, ImageLockMode.WriteOnly, targetImage.PixelFormat);
Int32 actualDataWidth = ((Image.GetPixelFormatSize(sourceImage.PixelFormat) * rect.Width) + 7) / 8;
Int32 h = sourceImage.Height;
Int32 origStride = sourceData.Stride;
Boolean isFlipped = origStride < 0;
origStride = Math.Abs(origStride); // Fix for negative stride in BMP format.
Int32 targetStride = targetData.Stride;
Byte[] imageData = new Byte[actualDataWidth];
IntPtr sourcePos = sourceData.Scan0;
IntPtr destPos = targetData.Scan0;
// Copy line by line, skipping by stride but copying actual data width
for (Int32 y = 0; y < h; y++)
{
Marshal.Copy(sourcePos, imageData, 0, actualDataWidth);
Marshal.Copy(imageData, 0, destPos, actualDataWidth);
sourcePos = new IntPtr(sourcePos.ToInt64() + origStride);
destPos = new IntPtr(destPos.ToInt64() + targetStride);
}
targetImage.UnlockBits(targetData);
sourceImage.UnlockBits(sourceData);
// Fix for negative stride on BMP format.
if (isFlipped)
targetImage.RotateFlip(RotateFlipType.Rotate180FlipX);
// For indexed images, restore the palette. This is not linking to a referenced
// object in the original image; the getter of Palette creates a new object when called.
if ((sourceImage.PixelFormat & PixelFormat.Indexed) != 0)
targetImage.Palette = sourceImage.Palette;
// Restore DPI settings
targetImage.SetResolution(sourceImage.HorizontalResolution, sourceImage.VerticalResolution);
return targetImage;
}
To call, simply use:
/// <summary>Loads an image without locking the underlying file.</summary>
/// <param name="path">Path of the image to load</param>
/// <returns>The image</returns>
public static Bitmap LoadImageSafe(String path)
{
using (Bitmap sourceImage = new Bitmap(path))
{
return CloneImage(sourceImage);
}
}
Or, from bytes:
/// <summary>Loads an image from bytes without leaving open a MemoryStream.</summary>
/// <param name="fileData">Byte array containing the image to load.</param>
/// <returns>The image</returns>
public static Bitmap LoadImageSafe(Byte[] fileData)
{
using (MemoryStream stream = new MemoryStream(fileData))
using (Bitmap sourceImage = new Bitmap(stream)) {
{
return CloneImage(sourceImage);
}
}
Here's the technique I'm currently using, and seems to work best. It has the advantage of producing a Bitmap object with the same pixel format (24-bit or 32-bit) and resolution (72 dpi, 96 dpi, whatever) as the source file.
// ImageConverter object used to convert JPEG byte arrays into Image objects. This is static
// and only gets instantiated once.
private static readonly ImageConverter _imageConverter = new ImageConverter();
This can be used as often as needed, as follows:
Bitmap newBitmap = (Bitmap)_imageConverter.ConvertFrom(File.ReadAllBytes(fileName));
Edit:
Here's an update of the above technique: https://stackoverflow.com/a/16576471/253938
(The accepted answer is wrong. When you try to LockBits(...) on the cloned bitmap eventually you will encounter GDI+ errors.)
I see only 3 ways to get out of this:
copy your file to a temporary file and open that the easy way new Bitmap(temp_filename)
open your file, read image, create a pixel-size-pixelformat copy (don't Clone()) and dispose the first bitmap
(accept the locked-file-feature)
I suggest to use PixelMap (available on NuGet)
or Github
Very easy to use and much faster than standard Bitmap from .NET
PixelMap pixelMap = new PixelMap(bild);
pictureBox1.Image = pixelMap.GetBitmap();
Read it into the stream, create bitmap, close the stream.
I am trying to improve our web applications image upload feature which stores images in to a database as a byte array and then reads them out later and puts them in to an HTML image tag to be displayed.
In order to display all the images uploaded we have a separate set of methods to retrieve a thumbnail image which involves reading out of the database, converting to a memory stream and then using that to create a C# Image followed by the .GetThumbnail method before converting it back to a byte array via another memory stream object.
A grid loads up all the data (image name, description, category, etc...) then calls a separate URL with the image id to retrieve a thumbnail image. This URL returns an C# MVC ImageResult. When I call this URL by itself it loads up the correct thumbnail with no problem. However, when I call the grid it loads up other images fine and then falls over with a Out Of Memory Exception. If I skip over this it will continue to load up the other images ok too.
At first I thought it might be due to leaving one of the streams open but everything is enclosed in a using with Dispose() and Close() called on both memory streams (the first to convert it to an image, the second to convert it back to a byte array) in finally blocks.
I am completely out of ideas as it looks like the byte array is the same, the method being called is the same but in one instance it works and the other it doesn't.
I have copied offending code which is the method that converts the image to a thumbnail via our Image object (passed in as the 4th parameters) consistently falls over on the line System.Drawing.Image.FromStream(ms) line.
private static void ConvertToImage(int size, bool fixWidth, bool fixHeight, Image img)
{
byte[] picbyte = img.Img;
using (MemoryStream ms = new MemoryStream(picbyte))
{
try
{
System.Drawing.Image image = System.Drawing.Image.FromStream(ms);
int width = image.Width;
int height = image.Height;
if (fixWidth && !fixHeight)
{
height = (int)Math.Round(((decimal)height / width) * size);
width = size;
}
if (fixHeight && !fixWidth)
{
width = (int)Math.Round(((decimal)width / height) * size);
height = size;
}
if ((fixWidth && fixHeight) || (!fixWidth && !fixHeight))
{
width = size;
height = size;
}
IntPtr ptr = Marshal.AllocHGlobal(sizeof(int));
int ptrInt = 0;
Marshal.WriteInt32(ptr, ptrInt);
Marshal.FreeHGlobal(ptr);
MemoryStream ms2 = new MemoryStream();
using (image = image.GetThumbnailImage(width, height, delegate () { return false; }, ptr))
{
try
{
image.Save(ms2, System.Drawing.Imaging.ImageFormat.Png);
img.Img = ms2.ToArray();
img.MIMEType = "image/png";
image.Dispose();
}
finally
{
ms2.Close();
ms2.Dispose();
}
}
}
finally //Ensure we close the stream if anything happens.
{
ms.Close();
ms.Dispose();
}
}
}
GDI (the thing classes like Image and Bitmap are wrappers for) is bad about throwing a OutOfMemoryExecption when a better exception would have been the better named, non exsitant, OutOfHandlesException.
When working with images in .NET you MUST always dispose of your resources, the objects you are working with are often classes that do not take a lot of managed memory but hold on to limited unmanaged resources. Because they do not put much memory pressure on the Garbage Collector if you create a lot of them you can easily run out of GDI handles before the GC runs and collects them.
At the top of your function you do
System.Drawing.Image image = System.Drawing.Image.FromStream(ms);
then later on you do
using (image = image.GetThumbnailImage(width, height, delegate () { return false; }, ptr))
This is causing you to lose the reference to the first Image object without disposing it. Use a different variable name for your thumbnail and put that first image in a using block.
P.S. Your .Close(); and .Dispose() calls are unnessesary. Putting the disposeable objects inside a using block does both those operations, you can get rid of all of your try-finally blocks and get rid of the extra image.Dispose() call. Also your ptr is not correct, the MSDN states you should be passing in IntPtr.Zero not the value 0 written to a pointer.
Here is a quickly updated version that has all the fixes.
private static void ConvertToImage(int size, bool fixWidth, bool fixHeight, Image img)
{
byte[] picbyte = img.Img;
using (MemoryStream ms = new MemoryStream(picbyte))
using (System.Drawing.Image image = System.Drawing.Image.FromStream(ms))
{
int width = image.Width;
int height = image.Height;
if (fixWidth && !fixHeight)
{
height = (int)Math.Round(((decimal)height / width) * size);
width = size;
}
if (fixHeight && !fixWidth)
{
width = (int)Math.Round(((decimal)width / height) * size);
height = size;
}
if ((fixWidth && fixHeight) || (!fixWidth && !fixHeight))
{
width = size;
height = size;
}
using(MemoryStream ms2 = new MemoryStream())
using (var thumnailImage = image.GetThumbnailImage(width, height, delegate () { return false; }, IntPtr.Zero))
{
thumnailImage.Save(ms2, System.Drawing.Imaging.ImageFormat.Png);
img.Img = ms2.ToArray();
img.MIMEType = "image/png";
}
}
}