Paint event organization/Design issues - c#

I have a program that edits images in different ways... i have a Paint event that is called every time i do this.Invalidate()...
My paint method looks like this:
private void EditImage_Paint(object sender, PaintEventArgs e)
{
if (isLOaded == true)
{
Graphics graphicsWindow; // reference to the graphic surface of this window
Graphics graphicsImage; // reference to in-memory surface
theImage = new Bitmap(Width, Height); // bitmap for window surface copy
graphicsWindow = e.Graphics; // get our current window's surface
graphicsImage = Graphics.FromImage(theImage); // create surfaces from the bitmaps
graphicsImage.DrawImage(firstLoaded, 0, 0, Width, Height);
if (isInvert == true)
{
theImage = InvertBitmap(theImage);
}
else if (isGrayscale == true)
{
theImage = GrayscaleBitmap(theImage);
}
else if (isThreshold == true)
{
theImage = ThresholdBitmap(theImage);
}
else if (isResize == true)
{
theImage = resizeImage(theImage, 10, 100);
}
else if (isFilterRed == true)
{
theImage = FilterRedBitmap(theImage);
}
else if (isFilterGreen == true)
{
theImage = FilterGreenBitmap(theImage);
}
else if (isFilterBlue == true)
{
theImage = FilterBlueBitmap(theImage);
}
graphicsWindow.DrawImage(theImage, 0, 0);
}
}
i have another area in my code which sets some boolean values to true or false within the Click events....(as my program uses winforms) and hence my program knows which method to call. However, i think putting all of these things inside of paint is just bad design. My issue is that i don't know how to pass in a bitmap image into a Click event? Is that possible? I would much rather deal with what happens in the Click events rather than within the paint methods. Any idea on how i could design this better?

You should keep your paint events as fast as possible.
You should store the final image in a field in the class and regenerate it when the options change.
You can then replace your entire Paint event with a simple PictureBox.
On a side note, you should either change the various Filter functions to modify the image in-place (preferred), or dispose the old image each time you create a new one.

Related

Draw border or outline on GraphicsPath?

I'm making a map drawing program that behaves a lot like a typical "Paint" program. I'd like for my coastlines to be prominent, so adding a border/outline around my painted path like in the following picture is what I'm aiming for:
The border should appear real-time as the user paints the "land".
Right now, my software behaves like this:
At the moment my painting works with Graphics.DrawLine(), but I'm guessing I need to add the lines to a GraphicsPath and trace the outline on the path somehow to get the desired effect.
Here's the important part of my code. All drawing is being done in a Canvas object I've made, which is just a PictureBox with some custom logic:
private void Canvas_MouseMove(object sender, MouseEventArgs e)
{
if (isMouseDown) // if a mouse button is being pressed
{
if (currentTool == Tools.LandWater) // if the current tool is the land placing tool
{
SetupGraphics(); // pretty much just gr = this.CreateGraphics();
Brush penBrush = null;
if (IsTextureBrush) // if the canvas object is going to use a texture brush or solid brush
{
Image texture = this.PaperTexture;
if (e.Button == MouseButtons.Left) texture = this.LandTexture1;
else if (e.Button == MouseButtons.Right) texture = this.WaterTexture; // if user right clicks, water is placed
penBrush = new TextureBrush(texture);
}
else
{
penBrush = new SolidBrush(BrushColour);
}
using (Pen p = new Pen(penBrush, this.BrushSize))
{
p.StartCap = System.Drawing.Drawing2D.LineCap.Round;
p.EndCap = System.Drawing.Drawing2D.LineCap.Round;
p.Alignment = System.Drawing.Drawing2D.PenAlignment.Inset;
gr.DrawLine(p, lastMouseLocation, e.Location);
}
lastMouseLocation = e.Location; // originally set in MouseDown as e.Location
}
}
}
Does anyone have a clue as to where I could even start? I looked around at some Win32 API calls and stuff but none of them work as intended.

How to stop Paint events from stacking up

I'm creating an application to schedule different tasks. These are displayed by drawing rectangles on a panel. This has to be responsive. So I need to draw and invalidate on every size change. When I reach a maximum height of my planning panel it autoscrolls.
The problem is when I grab the scrollbar and start scrolling away for a bit, when I release the scrollbar my whole application and computer freezes.
Most likely this is due to the onpaint event being called on every little scroll and stacking up, leaving the application to hang until they are all done.
Now my question is: How would I be able to fix this? Possibly by keeping the paint event from being called multiple times, but how?
The method called by the paint event:
private void panelPlanning_Paint(object sender, PaintEventArgs e)
{
for (int i = 0; i < userList.Count; i++)
{
Label temp = new Label();
temp.Text = userList[i].Text;
temp.Width = panelUsers.Width;
temp.Height = 50;
temp.BorderStyle = BorderStyle.FixedSingle;
temp.Location = new Point(0, i * 50);
temp.TextAlign = ContentAlignment.MiddleCenter;
panelUsers.Controls.Add(temp);
foreach (FullTask task in taskList)
{
if (task.AssignedTo == userList[i].Text && task.StartDate != "" && task.DueDate != "")
{
DateTime start = DateTime.ParseExact(task.StartDate, "dd/MM/yyyy", CultureInfo.InvariantCulture);
DateTime end = DateTime.ParseExact(task.DueDate, "dd/MM/yyyy", CultureInfo.InvariantCulture);
Brush brush;
if (task.Priority == Enums.priorities[2])
{
brush = Brushes.Yellow;
}
else if (task.Priority == Enums.priorities[1])
{
brush = new SolidBrush(Color.FromArgb(255, 0, 80, 123));
}
else
{
brush = Brushes.Red;
}
panelPlanning.CreateGraphics().FillRectangle(brush, new Rectangle((start.Subtract(dtPickerStart.Value).Days + 1) * labelDaysWidth, i * 50, (end.Subtract(start).Days + 1) * labelDaysWidth, 25));
}
}
}
}
You are adding new Label controls on every single paint event. Don't do this:
// Label temp = new Label();
// temp.Text = userList[i].Text;
// temp.Width = panelUsers.Width;
// temp.Height = 50;
// temp.BorderStyle = BorderStyle.FixedSingle;
// temp.Location = new Point(0, i * 50);
// temp.TextAlign = ContentAlignment.MiddleCenter;
// panelUsers.Controls.Add(temp);
Also, use the e.Graphics object supplied by the argument, not CreateGraphics,
The following line of code should only be executed once:
panelUsers.Controls.Add(temp);
You are constantly adding a new instance of temp to the panelUsers control.
You have to trace which FullTask actually has to be displayed on the screen. This can be achieved by looking on Control.ClientRectangle and keeping in mind current scroll position.
In this way from the all set of the FullTasks to draw you will get only that subset of tasks that actually visible on the screen, and draw only those ones.
This is a correct way of dealing with drawable artifacts which is implemented in more or less all drawing frameworks 2D or even 3D.
There is also concept of tracing if some element is covered (or part of it is covered) by another element, so it will be "culed", but in your case this doesn't seem to matter.
Pay attention also on fact that you adding controls. Do not do that. If the amount of FullTasks is big, just draw them with Graphics object.
as already Described by #LarsTech, don't add controls in the paint event, and use the supplied Graphics object instead of your own ...
If you still face performance problems, consider painting on a bitmap whenever you change something, and only draw that bitmap onto the screen in onPaint

C# Picturebox memory leak

I've looked through a few pages of similar inquiries, implemented most of the suggestions, but can't seem to find anything that's worked so far. Hopefully I'm not overlooking something glaringly obvious.
Right, so I'm using AForge.net to capture an image. It provides an event, which is triggered for each new frame received, which in my code looks like this:
private void videoSourcePlayer_NewFrame(object sender, ref Bitmap image)
{
framesRecieved++;
try
{
if (!stopCapturing)
{
if (pictureBox1.Image != null)
{
pictureBox1.Image.Dispose();
}
pictureBox1.Image = image.Clone(new Rectangle(0, 0, image.Width, image.Height), image.PixelFormat);
}
}
catch { }
finally { GC.Collect(); }
}
Memory usage is very stable so long as the window remains stationary, but as soon as I grab the window form and start moving it about, memory usage keeps going up. The reason I've been led to believe it might be related to the picturebox, is because as soon as I turn the "stopCapturing" bool to true, memory stops rising, even if I'm moving the window around the screen. "stopCapturing" is not used for anything else, and the event continues triggering as normal, the only difference is the image being displayed in the picturebox. I'm at a loss as to the cause, so any help would be appreciated.
PS: Not sure if it's related, but my workstation has 2 screens.
Bitmap.Clone() does a shallow copy, the actual bytes are still owned by the caller, so this could potentially cause all kind of troubles.
You need to do a deep copy.
For example, the AForge way:
Bitmap bmp = AForge.Imaging.Image.Clone(image);
Or the GDI+ way (could also use lockbits, etc. for better perfs):
Bitmap bmp = new Bitmap(image.Width, image.Height, image.PixelFormat);
Graphics g = Graphics.FromImage(bmp);
g.DrawImageUnscaled(image, Point.Empty);
I'm wondering why you're cloning the image at all. It strikes me that you should only allocate a new image when either pictureBox1.Image is null or when the dimensions or pixel format of the image change:
private bool BitmapChanged(Bitmap old, Bitmap new)
{
return old == null || old.PixelFormat != new.PixelFormat ||
old.Width != new.Width || old.Height != new.Height;
}
private Bitmap MakeSimilarBitmap(Bitmap source)
{
Bitmap copy = new Bitmap(source.Width, source.Height, source.PixelFormat);
return copy;
}
private void DrawOnto(Image im, Bitmap source)
{
using (Graphics g = Graphics.FromImage(im)) {
g.DrawImage(source, 0, 0);
}
}
then when you get a frame, you'll do something like this:
Image im = BitmapChanged(pictureBox1.Image as Bitmap, srcBmp) ?
MakeSimilarBitmap(image) : pictureBox1.Image;
DrawOnto(im, srcBmp);
bool same = im == pictureBox1.Image;
if (same)
pictureBox1.Invalidate();
else {
Image old = pictureBox1.Image;
pictureBox1.Image = im;
old.Dispose();
}

Graphics.DrawImage speed

In my program, I'm coding a basic image editor. Part of this allows the user to draw a rectangular region and I pop up a display that shows that region zoomed by 3x or so (which they can adjust further with the mouse wheel). If they right click and drag this image, it will move the zoom region around on the original image, basically acting as a magnifying glass.
The problem is, I'm seeing some serious performance issues even on relatively small bitmaps. If the bitmap showing the zoomed region is around 400x400 it's still updating as fast as mouse can move and is perfectly smooth, but if I mouse wheel the zoom up to around 450x450, it immediately starts chunking, only down to around 2 updates per second, if that. I don't understand why such a small increase incurs such an enormous performance problem... it's like I've hit some internal memory limit or something. It doesn't seem to matter the size of the source bitmap that is being zoomed, just the size of the zoomed bitmap.
The problem is that I'm using Graphics.DrawImage and a PictureBox. Reading around this site, I see that the performance for both of these is typically not very good, but I don't know enough about the inner workings of GDI to improve my speed. I was hoping some of you might know where my bottlenecks are, as I'm likely just using these tools in poor ways or don't know of a better tool to use in its place.
Here are some snippets of my mouse events and related functions.
private void pictureBox_MouseDown(object sender, MouseEventArgs e)
{
else if (e.Button == System.Windows.Forms.MouseButtons.Right)
{
// slide the zoomed part to look at a different area of the original image
if (zoomFactor > 1)
{
isMovingZoom = true;
// try saving the graphics object?? are these settings helping at all??
zoomingGraphics = Graphics.FromImage(displayImage);
zoomingGraphics.CompositingQuality = System.Drawing.Drawing2D.CompositingQuality.HighSpeed;
zoomingGraphics.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.Low;
zoomingGraphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighSpeed;
zoomingGraphics.PixelOffsetMode = PixelOffsetMode.HighSpeed;
}
}
}
private void pictureBox_MouseMove(object sender, MouseEventArgs e)
{
if (isMovingZoom)
{
// some computation on where they moved mouse ommitted here
zoomRegion.X = originalZoomRegion.X + delta.X;
zoomRegion.Y = originalZoomRegion.Y + delta.Y;
zoomRegionEnlarged = scaleToOriginal(zoomRegion);
// overwrite the existing displayImage to prevent more Bitmaps being allocated
createZoomedImage(image.Bitmap, zoomRegionEnlarged, zoomFactor, displayImage, zoomingGraphics);
}
}
private void createZoomedImage(Bitmap source, Rectangle srcRegion, float zoom, Bitmap output, Graphics outputGraphics)
{
Rectangle destRect = new Rectangle(0, 0, (int)(srcRegion.Width * zoom), (int)(srcRegion.Height * zoom));
outputGraphics.DrawImage(source, destRect, srcRegion, GraphicsUnit.Pixel);
if (displayImage != originalDisplayImage && displayImage != output)
displayImage.Dispose();
setImageInBox(output);
}
// sets the picture box image, as well as resizes the window to fit
void setImageInBox(Bitmap bmp)
{
pictureBox.Image = bmp;
displayImage = bmp;
this.Width = pictureBox.Width + okButton.Width + SystemInformation.FrameBorderSize.Width * 2 + 25;
this.Height = Math.Max(450, pictureBox.Height) + SystemInformation.CaptionHeight + SystemInformation.FrameBorderSize.Height * 2 + 20;
}
private void pictureBox_MouseUp(object sender, MouseEventArgs e)
{
else if (e.Button == System.Windows.Forms.MouseButtons.Right)
{
if (isMovingZoom)
{
isMovingZoom = false;
zoomingGraphics.Dispose();
}
}
}
As you can see, I'm not declaring a new Bitmap every time I want to draw something, I'm reusing an old Bitmap (and the Bitmap's graphics object, though I don't know if there is much cost with calling Graphics.FromImage repeatedly). I tried adding Stopwatches around to benchmark my code, but I think DrawImage passes functionality to another thread so the function claims to be done relatively quickly. I'm trying to Dispose all my Bitmap and Graphics objects when I'm not using them, and avoid repeated calls to allocate/deallocate resources during the MouseMove event. I'm using a PictureBox but I don't think that's the problem here.
Any help to speed up this code or teach me what's happening in DrawImage is appreciated! I've trimmed some excess code to make it more presentable, but if I've accidentally trimmed something important, or don't show how I'm using something which may be causing problems, please let me know and I'll revise the post.
The way I handle issues like that is when receiving the Paint event, I draw the whole image to a memory bitmap, and then BLT it to the window.
That way, all visual flash is eliminated, and it looks fast, even if it actually is not.
To be more clear, I don't do any painting from within the mouse event handlers.
I just set up what's needed for the main Paint handler, and then do Invalidate.
So the painting happens after the mouse event completes.
ADDED: To answer Tom's question in a comment, here's how I do it. Remember, I don't claim it's fast, only that it looks fast, because the _e.Graphics.DrawImage(bmToDrawOn, new Point(0,0)); appears instantaneous. It just bips from one image to the next.
The user doesn't see the window being cleared and then repainted, thing by thing.
It gives the same effect as double-buffering.
Graphics grToDrawOn = null;
Bitmap bmToDrawOn = null;
private void DgmWin_Paint(object sender, PaintEventArgs _e){
int w = ClientRectangle.Width;
int h = ClientRectangle.Height;
Graphics gr = _e.Graphics;
// if the bitmap needs to be made, do so
if (bmToDrawOn == null) bmToDrawOn = new Bitmap(w, h, gr);
// if the bitmap needs to be changed in size, do so
if (bmToDrawOn.Width != w || bmToDrawOn.Height != h){
bmToDrawOn = new Bitmap(w, h, gr);
}
// hook the bitmap into the graphics object
grToDrawOn = Graphics.FromImage(bmToDrawOn);
// clear the graphics object before drawing
grToDrawOn.Clear(Color.White);
// paint everything
DoPainting();
// copy the bitmap onto the real screen
_e.Graphics.DrawImage(bmToDrawOn, new Point(0,0));
}
private void DoPainting(){
grToDrawOn.blahblah....
}

Am I responsible for Disposing a BackgroundImage?

I have a windows form where I set the BackgroundImage property to a custom bitmap image.
private Image MakeCustomBackground()
{
Bitmap result = new Bitmap(100, 100);
using(Graphics canvas = Graphics.FromImage(result))
{
// draw the custom image
}
return result;
}
private void UpdateFromBackground()
{
this.BackgroundImage = MakeCustomBackground();
}
My question is, Image is disposable and I am creating it, does that mean that I must dispose of it? Or when I pass the image to the form, via BackgroundImage, does it take ownership and dispose of it when it no longer needs it?
Assuming that UpdateFromBackground() is called more than once, you probably should Dispose the old Image when (before) setting a new one. If you don't then the GC will do it eventually but that is less efficient. The Form will only Dispose the last BgImage you assigned.
private void UpdateFromBackground()
{
if (this.BackgroundImage != null)
{
this.BackgroundImage.Dispose();
}
this.BackgroundImage = MakeCustomBackground();
}

Categories