Bitmap.Save stops working after some hours - c#

We have started facing an issue in the past months where the method System.Drawing.Bitmap.Save(string filename) stops working after the server has been running for several hours. The hours after which it starts failing are proportional to the server load.
This code involved with that call has been working perfectly well for several years, it gets and image from the user, resizes it and saves it to disk. The application is an ASP.Net application developed .Net Framework 3.5.
When the call to the Bitmap.Save fails, it raises the infamous exception:
ERROR MESSAGE: Error genérico en GDI+. STACK TRACE:
en System.Drawing.Image.Save(String filename, ImageCodecInfo encoder, EncoderParameters encoderParams)
en System.Drawing.Image.Save(String filename, ImageFormat format)
en System.Drawing.Image.Save(String filename)
I've been reading a lot of posts about that exception, and I am fully aware that this exception is raised in many different situations. It is a generic exception that is raised for many different reasons (i.e when the application has no permisison to write to a folder, when the code is trying to save the image in the same file it is in reading from, etc) but none of those scenarios is our case.
When we realied the method was failing, we had just released a new version of our application, so we initially thought the error could be in our code (despite that part had not been changed, we thought we had changed something else that was interfering and producing the error), but the more we have investigated the less we understand when the error happens.
It is also important to mention that at the same time the error appeared, we also updated our servers with the latest windows updates, where I see also two suspicious ones:
KB2929755 http://support.microsoft.com/kb/2929755/en-us
Out of memory when you load some image resources in a Windows application
KB2957503 http://support.microsoft.com/kb/2957503/en-us
MS14-036: Description of the security update for Windows 7, Windows Server 2008 R2, Windows Server 2008, Windows Vista, and Windows Server 2003: June 10, 2014
This ones updates the GDIplus.dll on the server to avoid some security issues.
My feeling is that something on the server is not being released, like if some piece of code were leaking handlers or memory, and at some point the server runs out of them and can no longer save the Bitmap. When the error appears, any call to Bitmap.Save from any part of the application raises that exception. Rebooting the IIS solves the problem until the next time, however, that's not the case if we only restart the application pool.
If it can help in any way, we are using the HiQPdf library (version 6.8.0.0) to convert some HTML documents to PDF. This tool creates a new process for every conversion and killing it when finished, and supposedly releasing all the resources used in a proper way.
Is someone exeperiencing any similar issues?
Has someone had any issues with the Windows updates described?
I forgot to mention also that we tried resizing the images using WPF APIs, instead of GDI+ but we were facing the same problem, after several hours the server started raising an error (although this time the message was Excepción de HRESULT: 0x88982F8A), so we reverted it back to our original code.
Any help would be appreciated!
EDIT:
Actually, the code that peforms the image resizing and saving is the following. All variables are within using blocks, but for the casted image to a Bitmap, which I assume gets disposed anyway:
using (Image originalLogo = Image.FromStream(fuLogo.PostedFile.InputStream))
{
using (Image resizedLogo = ImageUtil.ResizeImage(originalLogo, maxWidth, maxHeight))
{
((System.Drawing.Bitmap)resizedLogo).Save(newFilePath);
}
}
Could the casting to Bitmap require creating an intermediate variable to force its dispose too, or that is not needed?
using (Image originalLogo = Image.FromStream(fuLogo.PostedFile.InputStream))
{
using (Image resizedLogo = ImageUtil.ResizeImage(originalLogo, maxWidth, maxHeight))
{
using (Bitmap bitmap=((System.Drawing.Bitmap)resizedLogo))
{
bitmap.Save(newFilePath);
}
}
}
Thanks,
Enric
EDIT:
I'm pasting below the code for the ResizeImage method:
public static System.Drawing.Image ResizeImage(System.Drawing.Image originalImage, int maxWidth, int maxHeight)
{
int imgWidth = originalImage.Width;
int imgHeight = originalImage.Height;
if (originalImage.Height<maxHeight && originalImage.Width<maxWidth) return originalImage;
double widhtRatio = ((double)maxWidth) / ((double)imgWidth);
double heightRatio = ((double)maxHeight) / ((double)imgHeight);
int targetWidth, targetHeight;
if (widhtRatio < heightRatio)
{
targetWidth = (int)(imgWidth * widhtRatio);
targetHeight = (int)(imgHeight * widhtRatio);
}
else
{
targetWidth = (int)(imgWidth * heightRatio);
targetHeight = (int)(imgHeight * heightRatio);
}
//Not all pixel formats are supported, therefore, using the
//originalImage pixel format raises an exception if the pixel format is not supported.
//By not specifying it, the Bitmap constructor will use one compatible with the FromImage method.
//For more info see: http://forums.asp.net/p/1195630/2066153.aspx
// Image resizedImage = new Bitmap(targetWidth, targetHeight, originalImage.PixelFormat);
System.Drawing.Image resizedImage = new Bitmap(targetWidth, targetHeight);
using (Graphics riGraphics = Graphics.FromImage(resizedImage))
{
riGraphics.CompositingQuality = CompositingQuality.HighQuality;
riGraphics.SmoothingMode = SmoothingMode.HighQuality;
Rectangle rectangle = new Rectangle(0, 0, targetWidth, targetHeight);
riGraphics.DrawImage(originalImage, rectangle);
riGraphics.Flush();
}
return resizedImage;
}

I've got an absolute stab in the dark for you:
In your application code, is the bitmap object wrapped in a using or explicitly disposed of after the Save() is called? I've seen hard to trace exceptions thrown from memory leaks in the past when dealing with IDisposible objects when they're used in repetition and don't get properly disposed of. These circumstances always seemed to be intermittent like what you're describing and took tons of digging before realizing the solution was as simple as calling Bitmap.Dispose() (or putting it back after having it accidentally deleted during a re-factor or code update).
However unlikely, because it sounds like your code worked before having windows updates applied...I thought I'd throw that out there.

Related

Emgu cv high resolution images stitching Issue

I am using EmguCV library to stitch images. Its working fine for small images but getting exception when processing high resolution images or images above 20MB size or even if I try to process more than 30 images it fails.
Libraries I am using
Emgu.CV.UI
Emgu.CV.UI.GL
Emgu.CV.World
opencv_core2410
opencv_imgproc2410
Code
List<Image<Bgr, Byte>> sourceImages = new List<Image<Bgr,byte>>();
foreach (string path in ImgPath)
sourceImages.Add(new Image<Bgr, Byte>(path));
using (Stitcher stitcher = new Stitcher(false))
{
using (VectorOfMat vm = new VectorOfMat())
{
Mat result = new Mat();
vm.Push(sourceImages.ToArray());
stitcher.Stitch(vm, result);
if (result.Bitmap != null)
{
result.Bitmap.Save(Application.StartupPath + "\\imgs\\StitchedImage.png");
}
else
{
MessageBox.Show("Some thing went wrong"); return null;
}
}
}
Exception
((Emgu.CV.MatDataAllocator)(result))._dataHandle.Target' threw an exception of type 'System.InvalidOperationException
Image
I was fairly certain that you are running into a memory issue and so I went ahead and made a simple console app targeting .Net 4.7.2, using the latest EmguCV package from NuGet, which is version 3.4.3.3016, and using the code below on sample data from Adobe, which can be downloaded here. If I compile as "AnyCPU" with "prefer 32 bit" and run this code against the rio image set (I loaded up the png's for the test purposes) and let it run the memory will slowly jump up until it hits close to 3 GB and then shortly thereafter crashes giving the exception about the refcount. Pretty clearly a memory issue. I then recompiled targeting 64 bit and was able to successfully run the code. The memory usage peaked out around 6 GB. So, with that in mind, I would be fairly certain that your issue is also memory related. You have yet to answer the question on whether you are building a 64 bit app or not, but based on what you are seeing, I would guess that you are not. So, the solution to your problem is to compile as 64 bit and then be sure you have enough memory. With the rio test set it jumped up to close to 6 GB. Without having your images I can't tell you how large it might grow, but these type of operations are pretty memory intensive - so more is better. This would explain both the issue with large image files and the issue with a large number of small image files. I was able to successfully process sets of images that were between 10 and 20 images using a 32 bit build, but as soon as I moved to the 50+ image sets it would only work with a 64 bit build due to the memory requirements.
var images = Directory.EnumerateFiles(#"C:\test\adobe\rio", "*.png", SearchOption.TopDirectoryOnly).Select(x => new Mat(x)).ToArray();
using(var stitcher = new Stitcher(false))
{
using (var vm = new VectorOfMat(images))
{
var result = new Mat();
stitcher.Stitch(vm, result);
result.Bitmap.Save(#"C:\test\adobe\rio_stitched.jpg", System.Drawing.Imaging.ImageFormat.Jpeg);
result.Dispose();
}
}
foreach (var image in images) { image.Dispose(); }

Why is PictureBox.Load locking image on some systems?

(Please see the edit on the bottom of the question, if you do not want to read the whole story.)
Hi,
I am new to stackoverflow. Don’t get me wrong, I use it quite often. But up until now I never actually posted something. This is because I did not have something new/useful to say and my English is not that good. The first thing (might have) changed, the latter did not.
I ran into a problem at a customer's Windows 7 system quite recently. I was shipping a C# .Net 4.0 Windows Forms application via ClickOnce. Basically, it is an application that creates a bitmap file and shows it to the user. If the bitmap exists prior to the creation, the existing file gets deleted first. After that the new file is created and loaded by a PictureBox.
The following thing occurred at the customer’s system: After starting the application the first creation succeeds – the second and all following ones do not. The file cannot be deleted, because some process is blocking it. This process is the application itself.
System.IO.IOException: The process cannot access the file “filename” because it is being used by another process.
Well, of course that is nothing unusual. The thing is I tested the application on several systems. None showed this exception. And until now I am unable to see an code error.
So I looked a little bit closer on the customer’s system: The only difference I could find is, that they changed the users folder so that they are not located on the windows partition, but on a different one (C:\Users --> D:\Users). I searched for an instruction on the internet and did the same thing on one of my test systems. To my surprise I got the same exception when I ran my application on it.
With that I could change my code so that the exception does not occur anymore. But I do not understand why that is. So maybe there is something wrong with my code and the error just reveals itself under that special circumstances. Or maybe the code is okay and the reason lies somewhere else. I just hoped that you might be able to help me.
So here is some code I put together, that shows the same behavior. I used 3 buttons, an OpenFileDialog and a PictureBox on a Form. First thing to do is to choose an image file. By pressing one of the two remaining buttons it gets copied into the main folder of the application. After being copied it is shown by the PictureBox. By the way, it does not seem to matter if it is a ClickOnce-application or a “normal” one.
String m_FileName;
private void btnChooseFile_Click(object sender, EventArgs e) {
if(openFileDialog1.ShowDialog() == System.Windows.Forms.DialogResult.OK) { // If file was chosen, set file name for later use and activate buttons.
m_FileName = "Test" + Path.GetExtension(openFileDialog1.FileName);
}
}
private void button1_Click(object sender, EventArgs e) {
// This is not working.
if(this.pictureBox1.Image != null) {
//Image img = this.pictureBox1.Image; // I was not sure, if maybe the pictureBox somehow prevents the disposing of the image, as long as it's assigned to it.
//this.pictureBox1.ImageLocation = null; // So I set them null, both the image and the image location.
//this.pictureBox1.Image = null;
//img.Dispose(); // Then I disposed the image.
this.pictureBox1.Image.Dispose(); // The short version. It is not working either way.
this.pictureBox1.Image = null;
}
(new FileInfo(openFileDialog1.FileName)).CopyTo(m_FileName, true); // But still this is where the Exception occurs.
this.pictureBox1.Load(m_FileName);
}
private void button2_Click(object sender, EventArgs e) {
//This is working.
if(this.pictureBox1.Image != null) {
//Image img = this.pictureBox1.Image;
//this.pictureBox1.Image = null;
//img.Dispose();
this.pictureBox1.Image.Dispose();
this.pictureBox1.Image = null;
}
(new FileInfo(openFileDialog1.FileName)).CopyTo(m_FileName, true);
pictureBox1.Image = Image.FromFile(m_FileName);
}
What happens now is the following: If I start the application and click button1 twice, I will get the exception (on the second click). If I start it and click button2 twice, I will not. If I start the application and click buttons1 first and after that button2, I will get the exception. So, the Picture.Load-Function somehow blocks the file, even if I dispose it.
When I searched on the internet, I found an article from microsoft: http://support.microsoft.com/kb/309482/en-us. But it does not hit the bull's eye.
Take into account, that both versions are working on all my test machines. I just get the exception when I change the users folder to a non-windows-partition.
Why is that? And where is the difference in the presented versions?
Edit
Okay, because of the first and only reaction so far, it seems to me, that it is still not clear, what really happens: If I take the above code, put it in a Windows Forms application, compile it and run it on different computers (at work, at home, does not matter) it works - both button1 and button2 (with the Click-functions linked to them) can be used as often as I like - no exception thrown. If I run the application on a computer, where I changed the users folder, and click button1 the second time - bam - IOException, file locked by process. Button2 works as long as I do not press button1.
The first answer implies, that I should get the locking on every system. But I DO NOT (as long as I do not change the users folder)! I tested it on every single computer I could get my hands on - no IOException. I set up a new system, just to rule out some special changes to the systems in my company - both buttonx_Click functions worked - no exception either. I even compiled the program on another computer - same behavior. The only three systems to throw that exception were the ones with the changed users folder.
So far I have no clue, why this difference in behavior occurs. Can somebody help me?
Anybody?
Yes, this is normal. Happens on any operating system, doesn't have anything to do with the Users folder location. The PictureBox.Load() method was intented to be used to load images from locations other than the file system. Like a web site. Which is slow, it avoids freezing the UI while the download is taking place.
It internally uses a FileStream when it discovers that the url you pass is actually a file and not a website name. This FileStream does not get disposed until the PictureBox itself is disposed or you call the Load() method again. A requirement because Image.FromStream() requires the stream to remain readable until the image is no longer used. It is this FileStream that keeps a lock on the file. Disposing the PictureBox.Image is not enough to also dispose the FileStream, the Image object doesn't know that it is being displayed inside a picture box.
There are several ways to solve this problem:
Use the Image property instead of Load(), assign it from Image.FromFile(). Disposing the Image now also releases the lock on the file. As you found out
Keep a dummy image around, one that perhaps displays a "Loading..." bitmap. Load() it first to release the lock on the file
Dispose the PictureBox and recreate it.
This works and unlocks the file
Image img= Image.FromFile(mypath);
Graphics g = pictureBox1.CreateGraphics();
g.DrawImage(img,0,0);
img.Dispose();

Issues with VNCSharpWpf for WPF in Microsoft Surface

first of all, I have been learning Microsoft Surface for about 1-2 months now and my project requires me to look into incorporating the use of a VNC viewer into my Surface Application.
I have looked into VNCSharp and VNCSharpWpf from VNC control for WPF application and I'm currently using VNCSharpWpf as it has better user interaction in the WPF environment although the performance is somewhat lacking compared to the viewers out there.
Here is my question, is there any difference between Microsoft Surface WPF and the default WPF in how they handle framebuffer/threads ?
I noticed that when the client attempts to draw the rectangle in the Surface environment, it will cause an exception where by the rectangle to be updated has 0 width and height.
However, when I test it on the sample code the author of VNCSharpWPF provides (WPF on Window ), the error never occur.
I tried to workaround by setting and if clause to only draw if the width and height of the rectangle decoded is not 0. Although it prevents the application from crashing, it will results in dead pixel around the screen whenever there are changes in the screen in the server-end.
I've been stuck with this situation for 1-2 weeks already and have ran out of ideas and is in need of some guidance on where I should look into
Or is there is any cool VNC viewer/server out there that I can use for my Surface project that I've missed out ?
I've been having the same issue with VNCSharp WPF on a PC, and when tested VNC Sharp for WinForms, then it worked OK.
Furthermore, When I've tested VNCSharp for WPF on Debug, then it works OK, but failed on Release.
I've wasted several hours debugging it (I've learned some parts of the VNC protocol for that matter, since I've found out that it somehow reads the width and the height of the remote device from the wrong location in the netowrk stream).
The bug is related to floats comparison. It depends on the machine you have (it might work well on some machines and on others it might not)
Please look at VncClient, Line 349:
if (rfb.ServerVersion == 3.8) rfb.ReadSecurityFailureReason();
If you, while debugging, put a breakpoint there, you will see that rfb.ServerVersion is 3.8f
ServerVersion returns a calcualted float:
public float ServerVersion {
get {
return (float) verMajor + (verMinor * 0.1f);
}
}
You should expect that since ServerVersion is 3.8, then it will execute
ReadSecurityFailureReason which reads some extra bytes that are needed for the code to work, but on Release (Ctrl+F5, since in Visual Studio Release, while the code is being debugged, it will probably work OK) those extra bytes will not be read, so the width and height will be read from the wrong location on the stream, causing it to be 0px over 0px
For demontrating my point, please take the following code, and compile it as x86 (I'm assuming that you have an x64 machine and an x64 OS, since this is the situation here):
class Program
{
static void Main(string[] args)
{
SomeVersion someVersion = new SomeVersion(3, 8);
if (someVersion.Version == 3.8f)
{
Console.WriteLine("Version is 3.8");
}
Console.ReadLine();
}
}
public class SomeVersion
{
private int _major;
private int _minor;
public SomeVersion(int major, int minor)
{
_major = major;
_minor = minor;
}
public float Version
{
get
{
return (float)_major + (_minor * 0.1f);
}
}
}
Run the code in Debug x86 (both with visual studio debugger and with Ctrl+F5)
You should see that you get the message: "Version is 3.8" on both cases.
Now change it to Release x86... Run it with F5. You should get the message.
Now run it with Ctrl + F5... WTF??, no message!
In order to fix the bug in the Vnc Sharp WPF, I've took the class RfcProtocol, and added another function:
public bool CompareVersion(int major, int minor)
{
return major == verMajor && minor == verMinor;
}
Now on VNC Client (both line 188 and 349), I've changed the code so it will compare using the new function, instead of comparing 2 floats.

Fellow Oak DICOM - changing image window level

I am not an experienced programmer, just need to add a DICOM viewer to my VS2010 project. I can display the image in Windows Forms, however can't figure out how to change the window center and width. Here is my script:
DicomImage image = new DicomImage(_filename);
int maxV = image.NumberOfFrames;
sbSlice.Maximum = maxV - 1;
image.WindowCenter = 7.0;
double wc = image.WindowCenter;
double ww = image.WindowWidth;
Image result = image.RenderImage(0);
DisplayImage(result);
It did not work. I don't know if this is the right approach.
The DicomImage class was not created with the intention of it being used to implement an image viewer. It was created to render preview images in the DICOM Dump utility and to test the image compression/decompression codecs. Maybe it was a mistake to include it in the library at all?
It is difficult for me to find fault in the code as being buggy when it is being used for something far beyond its intended functionality.
That said, I have taken some time to modify the code so that the WindowCenter/WindowWidth properties apply to the rendered image. You can find these modifications in the Git repo.
var img = new DicomImage(fileName);
img.WindowCenter = 2048.0;
img.WindowWidth = 4096.0;
DisplayImage(img.RenderImage(0));
I looked at the code and it looked extremely buggy. https://github.com/rcd/fo-dicom/blob/master/DICOM/Imaging/DicomImage.cs
In the current buggy implementation setting the WindowCenter or WindowWidth properties has no effect unless Dataset.Get(DicomTag.PhotometricInterpretation) is either Monochrome1 or Monochrome2 during Load(). This is already ridiculous, but it still cannot be used because the _renderOptions variable is only set in a single place and is immediately used for the _pipeline creation (not giving you chance to change it using the WindowCenter property). Your only chance is the grayscale _renderOptions initialization: _renderOptions = GrayscaleRenderOptions.FromDataset(Dataset);.
The current solution: Your dataset should have
DicomTag.WindowCenter set appropriately
DicomTag.WindowWidth != 0.0
DicomTag.PhotometricInterpretation == Monochrome1 or Monochrome2
The following code accomplishes that:
DicomDataset dataset = DicomFile.Open(fileName).Dataset;
//dataset.Set(DicomTag.WindowWidth, 200.0); //the WindowWidth must be non-zero
dataset.Add(DicomTag.WindowCenter, "100.0");
//dataset.Add(DicomTag.PhotometricInterpretation, "MONOCHROME1"); //ValueRepresentations tag is broken
dataset.Add(new DicomCodeString(DicomTag.PhotometricInterpretation, "MONOCHROME1"));
DicomImage image = new DicomImage(dataset);
image.RenderImage();
The best solution: Wait while this buggy library is fixed.

Alternatives to System.Drawing for use with ASP.NET?

After several days of tracking down bizarre GDI+ errors, I've stumbled across this little gem on MSDN:
Classes within the System.Drawing namespace are not supported for use within a Windows or ASP.NET service. Attempting to use these classes from within one of these application types may produce unexpected problems, such as diminished service performance and run-time exceptions.
I don't know whether "ASP.NET service" means "web application" in this context, but "diminished service performance" certainly seems to cover the random assortment of "A generic error occurred in GDI+" and "Out of memory" errors that my app is throwing - intermittent, non-reproducible errors reading and writing JPEG images that - in many cases - were actually created by System.Drawing.Imaging in the first place.
So - if GDI+ can't read and write JPEG files reliably in a Web app, what should I be using instead?
I want users to be able to upload images (JPEG required, other formats nice-to-have), resample them reliably, and display useful error messages if anything goes wrong. Any ideas? Are the System.Media namespaces from WPF worth considering?
EDIT: Yeah, I know GDI+ works "most of the time". That's not good enough, because when it fails, it does so in a way that's impossible to isolate or recover from gracefully. I am not interested in examples of GDI+ code that works for you: I am looking for alternative libraries to use for image processing.
There is an excellent blog post including C# code about using the ImageMagick graphics library through Interop over at TopTen Software Blog. This post deals specifically with running ASP.net on linux under mono; however, the C# code should be perfectly copy-paste-able, the only thing you'll need to change is the Interop attributes if you are running under windows referencing a window binary (DLL).
ImageMagick® is a software suite to create, edit, compose, or convert
bitmap images. It can read and write images in a variety of formats
(over 100) including DPX, EXR, GIF, JPEG, JPEG-2000, PDF, PhotoCD,
PNG, Postscript, SVG, and TIFF. Use ImageMagick to resize, flip,
mirror, rotate, distort, shear and transform images, adjust image
colors, apply various special effects, or draw text, lines, polygons,
ellipses and Bézier curves.
There is also an ImageMagick .Net development project on codeplex that wraps up everything for you. But it doesn't show active development since 2009, so it may be lagging behind the current ImageMagick library version. For a small trivial resizing routine, I'd probably stick with the interop. You just need to watch your implementation carefully for your own memory leak or unreleased resources (the library itself is well tested and vetted by the community).
The library is free and open source. The Apache 2 license appears to be compatible with both personal and commercial purposes. See ImageMagick License Page.
The library is totally cross platform and implements many powerful image handling and transformation routines that are not found in GDI+ (or not implemented under mono) and has a good reputation as an alternative for ASP.net image processing.
Update: Looks like there is an updated version of a .NET wrapper here: http://magick.codeplex.com/
Yes, use the WPF System.Windows.Media classes. Being fully managed they don't suffer the same problems as the GDI stuff.
Here's an excerpt from some MVC code I use to render gradients, to give you an idea how to get from a WPF Visual to a PNG:
using System;
using System.IO;
using System.Web.Mvc;
using System.Windows;
using System.Windows.Media;
using System.Windows.Media.Imaging;
namespace MyMvcWebApp.Controllers
{
public class ImageGenController : Controller
{
// GET: ~/ImageGen/Gradient?color1=red&color2=pink
[OutputCache(CacheProfile = "Image")]
public ActionResult Gradient(Color color1, Color color2, int width = 1, int height = 30, double angle = 90)
{
var visual = new DrawingVisual();
using (DrawingContext dc = visual.RenderOpen())
{
Brush brush = new LinearGradientBrush(color1, color2, angle);
dc.DrawRectangle(brush, null, new Rect(0, 0, width, height));
}
return new FileStreamResult(renderPng(visual, width, height), "image/png");
}
static Stream renderPng(Visual visual, int width, int height)
{
var rtb = new RenderTargetBitmap(width, height, 96, 96, PixelFormats.Default);
rtb.Render(visual);
var frame = BitmapFrame.Create(rtb);
var encoder = new PngBitmapEncoder();
encoder.Frames.Add(frame);
var stream = new MemoryStream();
encoder.Save(stream);
stream.Position = 0;
return stream;
}
}
}
You can find a very good article from a Microsoft Employee here: Resizing images from the server using WPF/WIC instead of GDI+ that proposes to use WPF instead of GDI+. It's more about thumbnailing but it's overall the same issues.
Anyway, at the end it states this:
I contacted the WPF team to have the final word on whether this is
supported. Unfortunately, it's not, and the documentation is being
updated accordingly. I apologize about any confusion this may have
caused. We're looking at ways to make that story more acceptable in
the future.
So WPF is also unsupported in web apps and still is I believe :-S
ImageSharp
ImageSharp is an open-source cross-platform 2D graphics library. It's written in C# on top of the new .NET Standard, with no dependency on any OS-specific API.
It's currently still in pre-release on MyGet (you'll have to add the package source in the VS options or a NuGet.config file), but we are already using it with some very positive results.
Most of the issues I have read about pertain to resources not being disposed properly.
I have used variants of this code time and time again with no issues from web applications:
public void GenerateThumbNail(HttpPostedFile fil, string sPhysicalPath,
string sOrgFileName,string sThumbNailFileName,
System.Drawing.Imaging.ImageFormat oFormat, int rez)
{
try
{
System.Drawing.Image oImg = System.Drawing.Image.FromStream(fil.InputStream);
decimal pixtosubstract = 0;
decimal percentage;
//default
Size ThumbNailSizeToUse = new Size();
if (ThumbNailSize.Width < oImg.Size.Width || ThumbNailSize.Height < oImg.Size.Height)
{
if (oImg.Size.Width > oImg.Size.Height)
{
percentage = (((decimal)oImg.Size.Width - (decimal)ThumbNailSize.Width) / (decimal)oImg.Size.Width);
pixtosubstract = percentage * oImg.Size.Height;
ThumbNailSizeToUse.Width = ThumbNailSize.Width;
ThumbNailSizeToUse.Height = oImg.Size.Height - (int)pixtosubstract;
}
else
{
percentage = (((decimal)oImg.Size.Height - (decimal)ThumbNailSize.Height) / (decimal)oImg.Size.Height);
pixtosubstract = percentage * (decimal)oImg.Size.Width;
ThumbNailSizeToUse.Height = ThumbNailSize.Height;
ThumbNailSizeToUse.Width = oImg.Size.Width - (int)pixtosubstract;
}
}
else
{
ThumbNailSizeToUse.Width = oImg.Size.Width;
ThumbNailSizeToUse.Height = oImg.Size.Height;
}
Bitmap bmp = new Bitmap(ThumbNailSizeToUse.Width, ThumbNailSizeToUse.Height);
bmp.SetResolution(rez, rez);
System.Drawing.Image oThumbNail = bmp;
bmp = null;
Graphics oGraphic = Graphics.FromImage(oThumbNail);
oGraphic.CompositingQuality = CompositingQuality.HighQuality;
oGraphic.SmoothingMode = SmoothingMode.HighQuality;
oGraphic.InterpolationMode = InterpolationMode.HighQualityBicubic;
Rectangle oRectangle = new Rectangle(0, 0, ThumbNailSizeToUse.Width, ThumbNailSizeToUse.Height);
oGraphic.DrawImage(oImg, oRectangle);
oThumbNail.Save(sPhysicalPath + sThumbNailFileName, oFormat);
oImg.Dispose();
}
catch (Exception ex)
{
Response.Write(ex.Message);
}
}
You may have a look at http://gd-sharp.sourceforge.net/ which is a wrapper for the GD library. I haven't tested it but it seems promising.
I've had good behavior from the Cairo library (http://www.cairographics.org) in an ASP.Net webserver environment. I actually moved to cairo from WPF due to WPF's poor memory usage model for web-based stuff.
WPF actually tends to run your worker process out of memory. None of the WPF objects implement IDisposable, and many of them reference unmanaged memory that's only freed via a finalizer. Heavy use of WPF (especially if your server is significantly CPU-taxed) will eventually run you out of memory because your finalizer queue gets saturated. When I was profiling my app, for instance, the finalization queue had upwards of 50,000 objects on it, many of them holding references to unmanaged memory. Cairo has behaved much better for me, and its memory usage pattern has been much more predictable than WPF's.
If you're interested in using cairo, grab the libs from GTK+'s website. They have an x86 as well as an x64 set of binaries.
The only downside is that cairo can't read/write JPG natively; however, you could easily adapt WPF's stuff for reading/writing JPG and do the resampling/scaling/drawing/whatever else using Cairo.
Aspose.Drawing is a drop-in replacement for System.Drawing that is fully managed and can be safely used in web applications. (I'm one of the developers.)

Categories