Generic GDI+ Error: Avoided when calling GC.Collect() - c#

I have this piece of Code ("ruta1" and "ruta2" are strings containing the path to different images):
Bitmap pic;
Bitmap b = new Bitmap(SomeWidth, SomeHeight);
g = Graphics.FromImage(b);
g.FillRectangle(new SolidBrush(Color.White), 0, 0, b.Width, b.Height);
b.Save(ruta2, ImageFormat.Jpeg);
g.Dispose();
b.Dispose();
b = new Bitmap(OtherWidth, OtherHeight);
pic = new Bitmap(ruta2);
g = Graphics.FromImage(b);
g.FillRectangle(new SolidBrush(Color.White), 0, 0, b.Width, b.Height);
g.DrawImage(pic, 0, 0, pic.Height, pic.Width);
pic.Dispose();
b.Save(ruta2, ImageFormat.Jpeg);
g.Dispose();
b.Dispose();
pic = new Bitmap(ruta1);
b = new Bitmap(AnotherWidth, AnotherHeight);
g = Graphics.FromImage(b);
g.FillRectangle(new SolidBrush(Color.White), 0, 0, b.Width, b.Height);
int k = 1;
// I just draw the "pic" in a pattern on "b"
for (h = 0; h <= b.Height; h += pic.Height)
for (w = 0; w <= b.Width; w += pic.Width)
g.DrawImage(pic, w, h, k * pic.Width, k * pic.Height);
pic.Dispose();
GC.Collect(); // If I comment this line, I get a "generic GDI+ Error" Exception
b.Save(ruta1, ImageFormat.Jpeg);
g.Dispose();
b.Dispose();
It doesn't matter if I set pic = null after disposing it, if I don't call the Garbage Collector, I got a "Generic GDI+ Error" exception. Only when I call the Garbage Collector, my program goes ok, all times.
Can somebody explain this behavior? Does it depend on the .Net framework version?
I'm using Visual C# Express 2008, with .Net framework 3.5

First, it would be nice if you were using the "using" keyword to scope the use of your disposable objects (like Bitmap and Graphics), instead of calling Dispose() manually on each. Using "using" is better for lots of reasons like cleaning up stuff when an exception is thrown, but it also greatly helps for code readability.
Second, GDI brushes are also IDisposable objects, so you shouldn't create-and-forget them. Do this instead:
using (var brush = new SolidBrush(Color.White))
{
g.FillRectangle(brush, 0, 0, width, height)
}
...or better yet, create your brushes at the start of your application, and hang on to them until the end (but don't forget to dispose of them too). IIRC, creating/disposing brushes impacts performance quite a lot if done frequently.
Third, I believe your bug is in the 2nd section:
You open image "ruta2"
You create a new image
You draw the contents of "ruta2" inside that new image
You save that new thing on top of the "ruta2" file, but the original image from step 1 is still loaded, and probably has some handle on the "ruta2" file, so you need to dispose of it before you overwrite the file.
If you refactor that 2nd section like this, it should work:
using (var b = new Bitmap(OtherWidth, OtherHeight))
using (var g = Graphics.FromImage(b))
{
using (var brush = new SolidBrush(Color.Red))
{
g.FillRectangle(brush, 0, 0, b.Width, b.Height);
}
using (var pic = new Bitmap(ruta2))
{
g.DrawImage(pic, 0, 0, pic.Height, pic.Width);
}
b.Save(ruta2, ImageFormat.Jpeg);
}

Related

System.OutOfMemoryException occurred in System.Drawing.dll with big quantity of picture

I have a folder with height quantity of geolocated file, I want to make a KML with the placemark.
No problem for the XML(KML) document, but the image are big and i want create a thumb(1/10) and small image (1/4) in separated folder.
The image are 4000x3000 allround 5mb, for little quantity not have problem but when the folder containg more of 35 image (in debug) i have the problem.
I have try with disposable object used with Using, GC.collect to only image elaboration, but nothing.
I have folder to elaborate with 1400 image or more...is possible make to work this application with it?
public static class MyClass_img
{
public static void ResizeImage(Image image, int width, int height, string pathImage)
{
using (var destImage = new Bitmap(width, height))
{
destImage.SetResolution(image.HorizontalResolution, image.VerticalResolution);
using (var graphics = Graphics.FromImage(destImage))
{
graphics.CompositingMode = CompositingMode.SourceCopy;
graphics.CompositingQuality = CompositingQuality.HighQuality;
graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
graphics.SmoothingMode = SmoothingMode.HighQuality;
graphics.PixelOffsetMode = PixelOffsetMode.HighQuality;
using (var wrapMode = new ImageAttributes())
{
wrapMode.SetWrapMode(WrapMode.TileFlipXY);
var destRect = new Rectangle(0, 0, width, height);
graphics.DrawImage(image, destRect, 0, 0, image.Width, image.Height, GraphicsUnit.Pixel, wrapMode);
}
}
destImage.Save(pathImage);
}
GC.Collect();
}
public static string imageReduce(Image img, string path, int percentValue, string extension, string folder)
{
var dirName = new DirectoryInfo(path).Parent.Name;
string pathImg = folder + dirName + "_" + Path.GetFileNameWithoutExtension(path) + extension;
AprItalia_img.ResizeImage(img, img.Width / percentValue, img.Height / percentValue, pathImg);
return pathImg;
}
}
You do not have to call GC.Collect().
As per MSDN,
Always call Dispose before you release your last reference >to the Image. Otherwise, the resources it is using will not >be freed until the garbage collector calls the Image >object's Finalize method
So in your case,it seems to be going for finalize.Also are you also releasing the Image object by calling it's dispose?

Apply ScaleTransform to Graphics GDI+

I have put this simple code together to draw a line. Now I want to apply a ScaleTransform to it by a factor of 10; but the code below doesn't work.
var bitmap = new Bitmap(pictureBox1.Size.Width, pictureBox1.Size.Height);
var g = Graphics.FromImage(bitmap);
pictureBox1.Image = bitmap;
var pn = new Pen(Color.Wheat, -1);
g.DrawLine(pn, 0, 0, 10, 10);
pn.Dispose();
// I'm trying to scaletransform here!
g.ScaleTransform(10, 10);
Update:
What is the correct way to update the changes? I'm not getting any results from this :(
g.ScaleTransform(1, 1);
pictureBox1.Invalidate();
You must apply the transformation BEFORE drawing the line!
var g = Graphics.FromImage(bitmap);
g.ScaleTransform(10, 10);
using (pn = new Pen(Color.Wheat, -1)) {
g.DrawLine(pn, 0, 0, 10, 10);
}
Transformations are applied to the transformation matrix of the graphics object (g.Transform).
Also make use of the using statement in order to dispose the resources. It will even dispose the pen if an exception should occur or if the using statement-block should be left with a return or break statement.

How to paint over a bitmap with a brush - C#/.NET

This seems like it should be simple, but I can't seem to find any way to do it. I have a custom WinForms control that has an overridden paint method that does some custom drawing.
I have a Bitmap in memory, and all I want to do is paint over the whole thing with a HashBrush, but preserve the alpha channel, so that the transparent parts of the bitmap don't get painted.
The bitmap in memory is not a simple shape, so it will not be feasible to define it as a set of paths or anything.
EDIT: In response to showing the code, there is a lot of code in the paint routine, so I'm only including a relevant snippet, which is the method in question. This method gets called from the main paint override. It accepts a list of images which are black transparency masks and combines them into one, then it uses a ColorMatrix to change the color of the combined image it created, allowing it to be overlayed on top of the background. All I want to accomplish is being able to also paint hashmarks on top of it.
private void PaintSurface(PaintEventArgs e, Image imgParent, List<Image> surfImgs, Rectangle destRect, ToothSurfaceMaterial material)
{
using (Bitmap bmp = new Bitmap(imgParent.Width, imgParent.Height,
System.Drawing.Imaging.PixelFormat.Format32bppPArgb))
{
using (Graphics g = Graphics.FromImage(bmp))
{
foreach (Image img in surfImgs)
{
g.DrawImage(img, System.Drawing.Point.Empty);
}
}
ColorMatrix matrix = new ColorMatrix(
new float[][] {
new float[] { 0, 0, 0, 0, 0},
new float[] { 0, 0, 0, 0, 0},
new float[] { 0, 0, 0, 0, 0},
new float[] { 0, 0, 0, 0.7f, 0},
new float[] { material.R / 255.0f,
material.G / 255.0f,
material.B / 255.0f,
0, 1}
});
ImageAttributes imageAttr = new ImageAttributes();
imageAttr.SetColorMatrix(matrix);
Rectangle r = GetSizedRect(imgParent, destRect);
e.Graphics.DrawImage(bmp,
r,
0,
0,
bmp.Width,
bmp.Height,
GraphicsUnit.Pixel, imageAttr);
}
}
The solution I ended up using was the following method. First I combine the individual masks into one, then create a new Bitmap and paint the whole thing with the HatchBrush, finally iterate through the mask and set the alpha values on the newly generated bitmap based on the mask.
private Bitmap GenerateSurface(Image imgParent, List<Image> surfImgs, ToothSurfaceMaterial material)
{
Bitmap mask = new Bitmap(imgParent.Width, imgParent.Height,
System.Drawing.Imaging.PixelFormat.Format32bppPArgb);
using (Graphics g = Graphics.FromImage(mask))
{
foreach (Image img in surfImgs)
{
g.DrawImage(img, System.Drawing.Point.Empty);
}
}
Bitmap output = new Bitmap(mask.Width, mask.Height,
System.Drawing.Imaging.PixelFormat.Format32bppPArgb);
using (Graphics g = Graphics.FromImage(output))
{
if (material.HatchStyle != null)
{
HatchBrush hb = new HatchBrush((HatchStyle)material.HatchStyle, material.FgColor, material.BgColor);
g.FillRectangle(hb, new Rectangle(0, 0, output.Width, output.Height));
}
else
{
SolidBrush sb = new SolidBrush(material.FgColor);
g.FillRectangle(sb, new Rectangle(0, 0, output.Width, output.Height));
}
}
var rect = new Rectangle(0, 0, output.Width, output.Height);
var bitsMask = mask.LockBits(rect, ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
var bitsOutput = output.LockBits(rect, ImageLockMode.ReadWrite, PixelFormat.Format32bppArgb);
unsafe
{
int offset = 0;
for (int y = 0; y < mask.Height; y++)
{
byte* ptrMask = (byte*)bitsMask.Scan0 + y * bitsMask.Stride;
byte* ptrOutput = (byte*)bitsOutput.Scan0 + y * bitsOutput.Stride;
for (int x = 0; x < mask.Width; x++)
{
offset = 4 * x + 3;
ptrOutput[offset] = (byte)(ptrMask[offset] * 0.7);
}
}
}
mask.UnlockBits(bitsMask);
output.UnlockBits(bitsOutput);
return output;
}
I think you don't need any ColorMatrix which is overkill, you just need a ColorMap, here is the code which may not suit your requirement but should give you the idea. That's because I possibly don't understand your problem well, if you have any problem, just leave some comment and I'll try to improve the answer:
ImageAttributes imgA = new ImageAttributes();
ColorMap cm = new ColorMap();
cm.OldColor = Color.Black
cm.NewColor = Color.FromArgb((byte)(0.7*255), Color.Green);
imgA.SetRemapTable(new ColorMap[] {cm });
GraphicsUnit gu = GraphicsUnit.Pixel;
g.DrawImage(imageToDraw,new Point[]{Point.Empty,
new Point(backImage.Width/2,0),
new Point(0,backImage.Height/2)},
Rectangle.Round(imageToDraw.GetBounds(ref gu)),
GraphicsUnit.Pixel, imgA);
the new Point[] is an array of 3 Points used to locate the destination Rectangle.
The code above is used to Draw the imageToDraw on top of the backImage and convert and color of Black to the color Green with Opacity = 70%. That's what you want to fulfill your code.
UPDATE
This may be what you want, in fact your code doesn't show what you want, it just shows what you have which doesn't implement anything related to your problem now. I deduce this from your very first description in your question. The input is an image with background color (which will be made partially transparent later) being Black. Now the output you want is an image with all the non-Black region being painted with a HatchBrush. This output will then be processed to turn the Black background to a partially transparent background.
public void PaintHatchBrush(Bitmap input, HatchBrush brush){
using(Graphics g = Graphics.FromImage(input)){
g.Clip = GetForegroundRegion(input, Color.Black);
GraphicsUnit gu = GraphicsUnit.Pixel;
g.FillRectangle(brush, input.GetBounds(ref gu));
}
}
//This is implemented using `GetPixel` which is not fast, but it gives you the idea.
public Region GetForegroundRegion(Bitmap input, Color backColor){
GraphicsPath gp = new GraphicsPath();
Rectangle rect = Rectangle.Empty;
bool jumpedIn = false;
for (int i = 0; i < bm.Height; i++) {
for (int j = 0; j < bm.Width; j++) {
Color c = bm.GetPixel(j, i);
if (c != backColor&&!jumpedIn) {
rect = new Rectangle(j, i, 1, 1);
jumpedIn = true;
}
if (jumpedIn && (c == backColor || j == bm.Width - 1)) {
rect.Width = j - rect.Left;
gp.AddRectangle(rect);
jumpedIn = false;
}
}
}
return new Region(gp);
}
//Usage
HatchBrush brush = new HatchBrush(HatchStyle.Percent30, Color.Green, Color.Yellow);
PaintHatchBrush(yourImage, brush);
//then yourImage will have HatchBrush painted on the surface leaving the Black background intact.
//This image will be used in the next process to turn the Black background into 70%
//opacity background as you did using ColorMatrix (or more simply using ColorMap as I posted previously)

How do I return a Bitmap type?

I have this method that is supposed to take a screenshot and return the image to the calling method.
public static Bitmap TakeScreenshot(int x, int y, int height, int width)
{
Rectangle bounds = new Rectangle(0, 0, height, width);
Bitmap bitmap;
using (bitmap = new Bitmap(bounds.Width, bounds.Height))
{
using (Graphics g = Graphics.FromImage(bitmap))
{
g.CopyFromScreen(new Point(x, y), Point.Empty, bounds.Size);
}
}
return bitmap;
}
The problem is that when I try to save the picture:
Bitmap bitmap = MyClass.TakeScreenshot(0, 0, 200, 200);
bitmap.Save(#"C:\test.jpg", ImageFormat.Jpeg);
Then I get an error at the save-method.
ArgumentException was unhandled.
Parameter is not valid.
It works fine if I try to save it inside the method like this:
public static Bitmap TakeScreenshot(int x, int y, int height, int width)
{
Rectangle bounds = new Rectangle(0, 0, height, width);
using (Bitmap bitmap = new Bitmap(bounds.Width, bounds.Height))
{
using (Graphics g = Graphics.FromImage(bitmap))
{
g.CopyFromScreen(new Point(x, y), Point.Empty, bounds.Size);
}
bitmap.Save(#"c:\begin.tiff", ImageFormat.Tiff);
}
}
What am I missing here?
In your first example, the Bitmap has been disposed via the using statement, you are then saving afterwards.
In the second example, you are saving before the disposal.
All you should need to do is not wrap the bitmap in a using statement, Instead, either leave it for the garbage collector, or call .Dispose() after you've saved it.
Personally, for items that implement the IDisposable interface, I tend to make sure Dispose is called, unless my usage dictates keeping it alive.
The Bitmap is being disposed prior to being returned - that is to say, you're returning an IDisposable object that has already had Dispose called:
using (bitmap = new Bitmap(bounds.Width, bounds.Height))
{
}
//using block makes sure Dispose is called when
//out of scope so this bitmap is no more
return bitmap;
If you want to use the bitmap outside of scope of the method, then you'll have to "release ownership" (create it, but don't be responsible for destroying within that scope) and allow the caller to manage it, and dispose it accordingly.
If you're declaring and accessing the Bitmap object outside the using block, then don't use a using block :) I would have coded it like this:
public static Bitmap TakeScreenshot(int x, int y, int height, int width)
{
Rectangle bounds = new Rectangle(0, 0, height, width);
Bitmap bitmap = new Bitmap(bounds.Width, bounds.Height))
Graphics g = Graphics.FromImage(bitmap);
g.CopyFromScreen(new Point(x, y), Point.Empty, bounds.Size);
return bitmap;
}

C# Drawstring clear rectangle Or no rectangle? possible

here is the code i'm using, i really need to make it so the text does not appear inside of a box. is that possible?
int width = (int)g.MeasureString(line, f).Width;
int height = (int)g.MeasureString(line,f).Height;
b = new Bitmap(b, new Size(width, height));
g = Graphics.FromImage(b);
g.Clear(Color.Empty);
g.DrawString(line,f, new SolidBrush(Color.Black), 0, 0);
b.Save(savepoint+line+".tif", System.Drawing.Imaging.ImageFormat.Tiff);
g.Flush();
What i mean is there can be no Rectangle around the text that is converted to image. So I need to watch it to the same color to create the illusion there is no box, or to never write out that rectangle period.
Use the color Transparent for the backgtround and a file format that supports transparency, like PNG:
var measure = g.MeasureString(line, f);
int width = (int)measure.Width;
int height = (int)measure.Height;
using (Bitmap b = new Bitmap(width, height)) {
using (Graphics bg = Graphics.FromImage(b)) {
bg.Clear(Color.Transparent);
using (Brush black = new SolidBrush(Color.Black)) {
bg.DrawString(line, f, black, 0, 0);
}
}
b.Save(savepoint+line+".png", System.Drawing.Imaging.ImageFormat.Png);
}
I notice that you did overwrite your Graphics instance, didn't dispose the objects that you create, and called Graphics.Flush for no apparent reason...

Categories

Resources