Here is the thing I try to achieve:
Upload png image to server
Save it;
Load the saved image, generate thumbnail;
Save it in different location;
Everything is fine until I have to save the thumbnail. At first I thought that it is the folder permissions or something, so to verify I tried to save it in a MemmoryStream and again I get this "generic GDI+ error", no InnerException or some descriptive StackTrace.
I was thinking that there is something to do with disposing the original Bitmap but still the same. Here is the code:
postedFile.SaveAs(fullFilePath);
FileStream fs = new FileStream(fullFilePath, FileMode.Open);
Image image = Bitmap.FromStream(fs);
Image thumb = image.GetThumbnailImage(thumbsWidth, thumbsHeight, AbortThumbnailPicture, IntPtr.Zero);
image.Dispose();
fs.Dispose();
using (MemoryStream ms = new MemoryStream())
{
thumb.Save(ms, ImageFormat.Png); //*** HERE THROWS THE EXCEPTION ***
using (FileStream fstream = new FileStream(fullThumbsPath, FileMode.Create, FileAccess.Write))
{
ms.WriteTo(fstream);
fstream.Close();
}
ms.Close();
}
// The GetThumbnailImage callback
private bool AbortThumbnailPicture()
{
return true;
}
I don't know what else to do please help.
Thank all of you that commented on my question, your comments lead me to the right problem.
So this line of code is just wrong: Image thumb = image.GetThumbnailImage(thumbsWidth, thumbsHeight, AbortThumbnailPicture, IntPtr.Zero);
Now I'm using more standardized code that gets the job done very well, here it is:
public static Image ResizeImage(Image image, Size size, bool preserveAspectRatio = true)
{
int newWidth;
int newHeight;
if (preserveAspectRatio)
{
var originalWidth = image.Width;
var originalHeight = image.Height;
var percentWidth = size.Width / (float)originalWidth;
var percentHeight = size.Height / (float)originalHeight;
var percent = percentHeight < percentWidth ? percentHeight : percentWidth;
newWidth = (int)(originalWidth * percent);
newHeight = (int)(originalHeight * percent);
}
else
{
newWidth = size.Width;
newHeight = size.Height;
}
Image newImage = new Bitmap(newWidth, newHeight);
using (var graphicsHandle = Graphics.FromImage(newImage))
{
graphicsHandle.InterpolationMode = InterpolationMode.HighQualityBicubic;
graphicsHandle.SmoothingMode = SmoothingMode.HighQuality;
graphicsHandle.PixelOffsetMode = PixelOffsetMode.HighQuality;
graphicsHandle.DrawImage(image, 0, 0, newWidth, newHeight);
}
return newImage;
}
Related
I am using C#,MVC5 and I am uploading image from my web application but I realize that I have performance issues because I don't optimize them and I need to fix it and is important to keep the quality.
Below you can see the results of the report why is slow.
How can I do it?
I am saving the files into a path locally with the below code.
string imgpathvalue = ConfigurationManager.AppSettings["RestaurantPath"];
string path = System.IO.Path.Combine(Server.MapPath(imgpathvalue));
if (!Directory.Exists(path))
{
Directory.CreateDirectory(path);
}
string pic = System.IO.Path.GetFileName(restaurantImg.FileName.Replace(" ", "_").Replace("%", "_"));
path = System.IO.Path.Combine(Server.MapPath(imgpathvalue), pic);
// file is uploaded
restaurantImg.SaveAs(path);
I have try the code below but I am getting the error "A generic error occurred in GDI+."
System.Drawing.Bitmap bmpPostedImage = new System.Drawing.Bitmap(restaurantImg.InputStream);
System.Drawing.Image objImage = ResizeImages.ScaleImage(bmpPostedImage, 81);
using (var ms = new MemoryStream())
{
objImage.Save(ms, objImage.RawFormat);
//ResizeImages.getImage(ms.ToArray());
}
public static System.Drawing.Image ScaleImage(System.Drawing.Image image, int maxHeight)
{
var ratio = (double)maxHeight / image.Height;
var newWidth = (int)(image.Width * ratio);
var newHeight = (int)(image.Height * ratio);
var newImage = new Bitmap(newWidth, newHeight);
using (var g = Graphics.FromImage(newImage))
{
g.DrawImage(image, 0, 0, newWidth, newHeight);
}
return newImage;
}
You are missing some of the code to resize your image correctly. Appending is a function that correctly resizes images depending on the Width and Height Values you give to it (in this example the image gets resized to 120*120 if possible).
Function Call:
ResizeImage("Path to the Image you want to resize",
"Path you want to save resizes copy into", 120, 120);
To make a function call like that possible we need to write our function. Which takes the image from the sourceImagePath and creates a new Bitmap.
Then it calculates the factor to resize the image and depending on if either the width or height is bigger it gets adjusted accordingly.
After that is done we create a new BitMap fromt he sourceImagePath and resize it. At the end we also need to dispose the sourceImage, the destImage and we also need to dispose of the Graphics Element g that we used for different Quality Settings.
Resize Function:
private void ResizeImage(string sourceImagePath, string destImagePath,
int wishImageWidth, int wishImageHeight)
{
Bitmap sourceImage = new Bitmap(sourceImagePath);
Bitmap destImage = null;
Graphics g = null;
int destImageWidth = 0;
int destImageHeight = 0;
// Calculate factor of image
double faktor = (double) sourceImage.Width / (double) sourceImage.Height;
if (faktor >= 1.0) // Landscape
{
destImageWidth = wishImageWidth;
destImageHeight = (int) (destImageWidth / faktor);
}
else // Port
{
destImageHeight = wishImageHeight;
destImageWidth = (int) (destImageHeight * faktor);
}
try
{
destImage = new Bitmap(sourceImage, destImageWidth, destImageHeight);
g = Graphics.FromImage(destImage);
g.InterpolationMode =
System.Drawing.Drawing2D.InterpolationMode.HighQualityBilinear;
g.SmoothingMode =
System.Drawing.Drawing2D.SmoothingMode.HighQuality;
g.PixelOffsetMode =
System.Drawing.Drawing2D.PixelOffsetMode.HighQuality;
g.CompositingQuality =
System.Drawing.Drawing2D.CompositingQuality.HighQuality;
g.DrawImage(sourceImage, 0, 0, destImageWidth, destImageHeight);
// Making sure that the file doesn't already exists.
if (File.Exists(destImagePath)) {
// If it does delete the old version.
File.Delete(destImagePath);
}
destImage.Save(destImagePath);
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine("*** ERROR-Terror: " + ex.Message)
}
finally
{
if (g != null) { g.Dispose(); g = null; }
if (destImage != null) { destImage.Dispose(); destImage = null; }
}
sourceImage.Dispose();
sourceImage = null;
}
I've seen a ton of stackoverflow articles for reducing image size, but none of them maintain the original image type (or so I've found). They usually have steps to reduce pixel dimensions, reduce image quality, and convert to a specific type of image (usually jpeg).
I have a group of images that I need to resize. They have various image types, and the filenames are all stored in a database, which makes converting from one image type to another somewhat problematic. I can't just change the filename from png to jpg because then the database won't point at a real file.
Doe anyone have an example of how to resize / reduce images to '256 kilobytes' and maintain the original image type?
For examples, here is the code I'm currently fiddling with.
public static byte[] ResizeImageFile(Image oldImage, int targetSize) // Set targetSize to 1024
{
Size newSize = CalculateDimensions(oldImage.Size, targetSize);
using (Bitmap newImage = new Bitmap(newSize.Width, newSize.Height, PixelFormat.Format24bppRgb))
{
using (Graphics canvas = Graphics.FromImage(newImage))
{
canvas.SmoothingMode = SmoothingMode.AntiAlias;
canvas.InterpolationMode = InterpolationMode.HighQualityBicubic;
canvas.PixelOffsetMode = PixelOffsetMode.HighQuality;
canvas.DrawImage(oldImage, new Rectangle(new Point(0, 0), newSize));
MemoryStream m = new MemoryStream();
newImage.Save(m, ImageFormat.Jpeg);
return m.GetBuffer();
}
}
}
Maybe there is a way I can get file fileinfo or mime type first and then switch on the .Save for the type of image?
Here is what I came up with (based on some examples that I found online that weren't 100% complete.
private void EnsureImageRequirements(string filePath)
{
try
{
if (File.Exists(filePath))
{
// If images are larger than 300 kilobytes
FileInfo fInfo = new FileInfo(filePath);
if (fInfo.Length > 300000)
{
Image oldImage = Image.FromFile(filePath);
ImageFormat originalFormat = oldImage.RawFormat;
// manipulate the image / Resize
Image tempImage = RefactorImage(oldImage, 1200); ;
// Dispose before deleting the file
oldImage.Dispose();
// Delete the existing file and copy the image to it
File.Delete(filePath);
// Ensure encoding quality is set to an acceptable level
ImageCodecInfo[] encoders = ImageCodecInfo.GetImageEncoders();
// Set encoder to fifty percent compression
EncoderParameters eps = new EncoderParameters
{
Param = { [0] = new EncoderParameter(Encoder.Quality, 50L) }
};
ImageCodecInfo ici = (from codec in encoders where codec.FormatID == originalFormat.Guid select codec).FirstOrDefault();
// Save the reformatted image and use original file format (jpeg / png / etc) and encoding
tempImage.Save(filePath, ici, eps);
// Clean up RAM
tempImage.Dispose();
}
}
}
catch (Exception ex)
{
this._logger.Error("Could not resize oversized image " + filePath, ex);
}
}
private static Image RefactorImage(Image imgToResize, int maxPixels)
{
int sourceWidth = imgToResize.Width;
int sourceHeight = imgToResize.Height;
int destWidth = sourceWidth;
int destHeight = sourceHeight;
// Resize if needed
if (sourceWidth > maxPixels || sourceHeight > maxPixels)
{
float thePercent = 0;
float thePercentW = 0;
float thePercentH = 0;
thePercentW = maxPixels / (float) sourceWidth;
thePercentH = maxPixels / (float) sourceHeight;
if (thePercentH < thePercentW)
{
thePercent = thePercentH;
}
else
{
thePercent = thePercentW;
}
destWidth = (int)(sourceWidth * thePercent);
destHeight = (int)(sourceHeight * thePercent);
}
Bitmap tmpImage = new Bitmap(destWidth, destHeight, PixelFormat.Format24bppRgb);
Graphics g = Graphics.FromImage(tmpImage);
g.InterpolationMode = InterpolationMode.HighQualityBilinear;
g.DrawImage(imgToResize, 0, 0, destWidth, destHeight);
g.Dispose();
return tmpImage;
}
I have doubts that this part of code causes memory leak:
public FileResult ShowCroppedImage(int id, int size)
{
string path = "~/Uploads/Photos/";
string sourceFile = Server.MapPath(path) + id + ".jpg";
MemoryStream stream = new MemoryStream();
var bitmap = imageManipulation.CropImage(sourceFile, size, size);
bitmap.Save(stream, System.Drawing.Imaging.ImageFormat.Jpeg);
Byte[] bytes = stream.ToArray();
return File(bytes, "image/png");
}
How could I make a test to see if this piece of code is the cause?
EDIT:
public Image CropImage(string sourceFile, int newWidth, int newHeight)
{
Image img = Image.FromFile(sourceFile);
Image outimage;
int sizeX = newWidth;
int sizeY = newHeight;
MemoryStream mm = null;
double ratio = 0;
int fromX = 0;
int fromY = 0;
if (img.Width < img.Height)
{
ratio = img.Width / (double)img.Height;
newHeight = (int)(newHeight / ratio);
fromY = (img.Height - img.Width) / 2;
}
else
{
ratio = img.Height / (double)img.Width;
newWidth = (int)(newWidth / ratio);
fromX = (img.Width - img.Height) / 2;
}
if (img.Width == img.Height)
fromX = 0;
Bitmap result = new Bitmap(sizeX, sizeY);
//use a graphics object to draw the resized image into the bitmap
Graphics grPhoto = Graphics.FromImage(result);
//set the resize quality modes to high quality
grPhoto.CompositingQuality = System.Drawing.Drawing2D.CompositingQuality.HighQuality;
grPhoto.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;
grPhoto.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
//draw the image into the target bitmap
//now do the crop
grPhoto.DrawImage(
img,
new System.Drawing.Rectangle(0, 0, newWidth, newHeight),
new System.Drawing.Rectangle(fromX, fromY, img.Width, img.Height),
System.Drawing.GraphicsUnit.Pixel);
// Save out to memory and get an image from it to send back out the method.
mm = new MemoryStream();
result.Save(mm, System.Drawing.Imaging.ImageFormat.Jpeg);
img.Dispose();
result.Dispose();
grPhoto.Dispose();
outimage = Image.FromStream(mm);
return outimage;
}
I would write it as
public FileResult ShowCroppedImage(int id, int size)
{
string path = "~/Uploads/Photos/";
string sourceFile = Server.MapPath(path) + id + ".jpg";
using (MemoryStream stream = new MemoryStream())
{
using (Bitmap bitmap = imageManipulation.CropImage(sourceFile, size, size))
{
bitmap.Save(stream, System.Drawing.Imaging.ImageFormat.Jpeg);
Byte[] bytes = stream.ToArray();
return File(bytes, "image/png");
}
}
}
to ensure that stream.Dispose & bitmap.Dispose are called.
Might want to call stream.dispose(); after Byte[] bytes = stream.ToArray();.
Given the question was how to detect memory leaks/usage, I'd recommend writing a method that calls your function recording the memory usage before and after:
public void SomeTestMethod()
{
var before = System.GC.GetTotalMemory(false);
// call your method
var used = before - System.GC.GetTotalMemory(false);
var unreclaimed = before - System.GC.GetTotalMemory(true);
}
Before will measure the memory usage before your function runs. The used variable will hold how much memory your function used before the garbage collector was run and unreclaimed will tell you how many bytes your function used even after trying to clean up your objects.
I suspect used will be high and unreclaimed will not - putting a using around your memory stream as the other posters suggest should make them closer although bear in mind you still have a byte array holding on to memory.
I'm trying to write some code that deletes an image off the hard-disk once the user clicks on some delete button. Sometimes I get the following exception and sometimes I do not. And when I actually do, if I try to delete it again, it does work most of the time.
This is the exception:
System.IO.IOException: The process cannot access the file because it
is being used by another process.
I guess I should provide some details on what is happening exactly:
User uploads an image, which is then displayed on the screen so the user can see what he/she has just uploaded.
A delete button is shown to the user in case he/she decides that they do not really want to upload this image.
When the user clicks the delete button, I call a method that deletes the image and all of its previously created thumbs.
Finally, the image is remove from the screen and the user can upload other images.
I'm not sure how I could solve this problem because the exception does not provide any information about which other process is holding onto the file. Any ideas?
UPDATE:
public byte[] ResizeImageToBytes(string path, int size, string name)
{
var newImage = Image.FromFile(path);
int newWidth; int newHeight;
if (size == 470)
{
if (newImage.Height != 250)
{
newWidth = (int)Math.Round(newImage.Width * (100 / (newImage.Height / 250)) * 0.01);
newHeight = 250;
}
else
{
newWidth = newImage.Width;
newHeight = newImage.Height;
}
}
else
{
if (newImage.Width > newImage.Height)
{
newWidth = size;
newHeight = newImage.Height*size/newImage.Width;
}
else
{
newWidth = newImage.Width*size/newImage.Height;
newHeight = size;
}
}
var thumb = new Bitmap(newWidth, newHeight);
var gfx = Graphics.FromImage(thumb);
gfx.CompositingQuality = CompositingQuality.HighQuality;
gfx.SmoothingMode = SmoothingMode.HighQuality;
gfx.InterpolationMode = InterpolationMode.HighQualityBicubic;
var rect = new Rectangle(0, 0, newWidth, newHeight);
gfx.DrawImage(newImage, rect);
var ms = new MemoryStream();
thumb.Save(ms, newImage.RawFormat);
return ms.GetBuffer();
}
public void SaveImage(byte[] toSave, string path)
{
using (var ms = new MemoryStream())
{
ms.Write(toSave, 0, toSave.Length);
using(var theImage = Image.FromStream(ms))
{
theImage.Save(path);
}
}
}
[HttpPost]
public ActionResult Upload()
{
var newFile = System.Web.HttpContext.Current.Request.Files["Filedata"];
string guid = Guid.NewGuid() + newFile.FileName;
string itemImagesFolder = Server.MapPath(Url.Content("~/Content/ItemImages/"));
string fileName = itemImagesFolder + "originals/" + guid;
newFile.SaveAs(fileName);
string finalPath;
foreach (var dim in ImageDimensionsList.Options)
{
var bytes = _imageService.ResizeImageToBytes(fileName, dim.Width, guid);
finalPath = itemImagesFolder + dim.Title + "/" + guid;
_imageService.SaveImage(bytes, finalPath);
}
return Content(guid);
}
You aren't disposing any of the disposable resources you are working with in your ResizeImageToBytes method. This leaves leaking handles in your application and of course locked files. Try this:
public byte[] ResizeImageToBytes(string path, int size, string name)
{
using (var newImage = Image.FromFile(path))
{
int newWidth; int newHeight;
if (size == 470)
{
if (newImage.Height != 250)
{
newWidth = (int)Math.Round(newImage.Width * (100 / (newImage.Height / 250)) * 0.01);
newHeight = 250;
}
else
{
newWidth = newImage.Width;
newHeight = newImage.Height;
}
}
else
{
if (newImage.Width > newImage.Height)
{
newWidth = size;
newHeight = newImage.Height * size / newImage.Width;
}
else
{
newWidth = newImage.Width * size / newImage.Height;
newHeight = size;
}
}
using (var thumb = new Bitmap(newWidth, newHeight))
using (var gfx = Graphics.FromImage(thumb))
{
gfx.CompositingQuality = CompositingQuality.HighQuality;
gfx.SmoothingMode = SmoothingMode.HighQuality;
gfx.InterpolationMode = InterpolationMode.HighQualityBicubic;
var rect = new Rectangle(0, 0, newWidth, newHeight);
gfx.DrawImage(newImage, rect);
using (var ms = new MemoryStream())
{
thumb.Save(ms, newImage.RawFormat);
return ms.GetBuffer();
}
}
}
}
As far as your SaveImage method is concerned, well, this method seems redundant to me as it already exists in the .NET framework. It's called File.WriteAllBytes:
public void SaveImage(byte[] toSave, string path)
{
File.WriteAllBytes(path, toSave);
}
During the upload routine, are you using the using statement around your file reader? if no, then the file resource is not returned to the OS thus, it is still being "used".
To find information about what other process is holding onto the file, you should use a tool like ProcessExplorer (sysinternals.com). It has this handy search feature (Find > Find Handle or DLL) where you enter a string like the filename and it will search all handles and tell you which process has the handle open.
Often when I run into an unexpected error like yours, this is the first place I go.
Hope this helps,
John
I'm using mvc2 and I would like to use action in controller, for example ShowSmallImage) and when I type www.url.com/ShowSmallImage that in browser the output is an image.
I tried something like this:
public Bitmap CreateThumbnail()
{
Image img1 = Image.FromFile(#"C:...\Uploads\Photos\178.jpg");
int newWidth = 100;
int newHeight = 100;
double ratio = 0;
if (img1.Width > img1.Height)
{
ratio = img1.Width / (double)img1.Height;
newHeight = (int)(newHeight / ratio);
}
else
{
ratio = img1.Height / (double)img1.Width;
newWidth = (int)(newWidth / ratio);
}
//a holder for the result
Bitmap result = new Bitmap(newWidth, newHeight);
//use a graphics object to draw the resized image into the bitmap
using (Graphics graphics = Graphics.FromImage(result))
{
//set the resize quality modes to high quality
graphics.CompositingQuality = System.Drawing.Drawing2D.CompositingQuality.HighQuality;
graphics.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;
graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
//draw the image into the target bitmap
graphics.DrawImage(img1, 0, 0, result.Width, result.Height);
}
return result;
}
As a result I get only System.Drawing.Bitmap in browser. I suppose I need to set response/content type of the page but have no idea how to do it...
Thanks,
Ile
Create a fileresult and return the stream to the bitmap & set the content type:
private FileResult RenderImage()
{
MemoryStream stream = new MemoryStream();
var bitmap = CreateThumbnail();
bitmap.Save(stream, System.Drawing.Imaging.ImageFormat.Bmp);
Byte[] bytes = stream.ToArray();
return File(bytes, "image/png");
}
In a controller, say ResourceController you could have an Action that returns a FileResult. Like so
public FileResult Thumbnail()
{
var bitmap = // Your method call which returns a Bitmap
var ms = new MemoryStream();
bitmap.Save(ms, ImageFormat.Png);
return new FileStreamResult(ms, "image/png");
}
Then you can call http://www.mysite.com/Resource/Thumbnail.