im currently working on a school project which is basically a game that involves GDI-Winforms animations.
I chose my game to be something like prison break where you are trying to escape the prison while guards with light-torches are walking around the room.
So, i have the Guard class which represents the guard and has A Path to walk on. Furthermore, the guard can rotate in 90deg. angles. (animated to a certain degree)
When i rotate the guard, i actually rotate the Graphics object that was passed through the Form_Paint event:
void Game_Paint(object sender, PaintEventArgs e)
{
e.Graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
ply.Draw(e.Graphics); // Draws the player
grd.Draw(e.Graphics); // Draws the guard
e.Graphics.DrawPolygon(Pens.Red, grd.GetPath()); // Draws the guard's path
}
When i was dealing with only 1 guard, it was working great. Smooth and did the exactly what he was supposed to do.
When i tried to add 1 more guard, they started to freak out. Soon enough i figured out that its because im sending both guards to draw the same instance of Graphics. Which also means, both of them are rotating it.
Lets say one is rotating by -90, the other will too, but his angle class member variable will not be -90, then all hell breaks loose.
Rotation of graphics method (sits inside the guard class):
public Graphics RotateGuard(Graphics g, Point pivot, float angle) // returns the rotated Graphics object
{
if (!float.IsNaN(angle))
{
using (Matrix m = new Matrix())
{
m.RotateAt(angle, pivot);
g.Transform = m;
}
}
return g;
}
The next thing i did was to give each of them this.CreateGraphics().
void Game_Paint(object sender, PaintEventArgs e)
{
e.Graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
ply.Draw(e.Graphics); // Draws the player
foreach (Guard grd in this.guards )
{
grd.Draw(this.CreateGraphics()); // Draws the guard
e.Graphics.DrawPolygon(Pens.Red, grd.GetPath()); // Draws the guard's path
}
}
Then everything worked just fine. The only thing is, it seemed like it was really heavy for the GPU to process or something. It drew the guard once about every 5 frames less than he supposed to be drawn.
I googled but couldn't find anything but people saying that "There is no need to clone a Graphics object", but still, i can't think of any better WORKING solution.
How can i solve this problem nicely?
Thanks in advanced.
CreateGraphics() is a really bad idea. You should do all the drawing to the Graphics passed by PaintEventArgs. So your initial code is just fine. However, what you need to do is to ensure that every object that receives a Graphics in its Draw method leaves it unchanged after doing its job. This can be achieved by using Graphics.Save and Graphics.Restore like this
class Guard
{
public void Draw(Graphics g)
{
var state = g.Save();
try
{
// The actual drawing code
}
finally
{
g.Restore(state);
}
}
}
An efficient way is to maintain one overall transform and have a matrix for each thing you wish to draw. Prior to drawing you multiply the current transform with the object's transform. Afterwards you reset the transform before drawing next.
void Game_Paint(object sender, PaintEventArgs e)
{
e.Graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
var g = e.Graphics;
g.ResetTransform();
g.MultiplyTransform (playerMatrix);
ply.Draw(e.Graphics); // Draws the player
g.ResetTransform();
foreach (Guard grd in this.guards )
{
g.MultiplyTransform (grd.Matrix);
grd.Draw(this.CreateGraphics()); // Draws the guard
e.Graphics.DrawPolygon(Pens.Red, grd.GetPath()); // Draws the guard's path
g.ResetTransform();
}
}
Such concepts are similar to how things are done in 3D graphics such as Direct3D; XNA; OpenGL and Unity3D.
After drawing with the rotated Graphics tool object simply call e.Graphics.ResetTransform().
Also you may want to look into Graphics.Save() and Graphics.Restore() if you have made a few settings you want to return to.. It can save few states and when done with them bring them back up. Very nice, at least if you keep count of what you are doing.
And, of course you could undo the Translation/Rotation by doing the reverse calls in the reverse order, but the other methods are simpler and made for just your case.
Note that Graphics doesn't contain any graphics, it is a tool used to draw into an associated Bitmap or onto a control's surface..
Finally: Never, never use CreateGraphics !!! Its results are non-persistent, which you want only very rarely..
I figured out what it was, then i used what #Ivan did.
void Game_Paint(object sender, PaintEventArgs e)
{
SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
e.Graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
var saved = e.Graphics.Save();
ply.Draw(e.Graphics); // Draws the player
foreach (Guard grd in this.guards )
{
grd.Draw(e.Graphics); // Draws the guard
e.Graphics.Restore(saved);
e.Graphics.DrawPolygon(Pens.Red, grd.GetPath()); // Draws the guard's path
}
}
All i had to do is instead of using this.DoubleBuffered i used SetStyle(ControlStyles.OptimizedDoubleBuffer, true); which at first i thought both does the same, but evidently it does not.
Then, i saved the current graphics state and re-drew it.
Thanks a lot for everyone!
Related
I do have following issue with my graphics object.
EDIT:
I do have a picturebox_image (imageRxTx) which is a live stream from a camera. What I do in the paint event is to draw some lines on top of the image imageRxTx (not shown in the code below). This works so far without problem.
Now I need to check for circles in imageRxTx and therefore I have to use the method ProcessImage() which needs a Bitmap as parameter. Unfortunately I do not have the Bitmap image but rather the handle (hDC) to my imageRxTx.
Question: How can I get the imageRxTx from my graphics-object and "convert" it to a bitmap-image which I need to use in the method ProcessImage(Bitmap bitmap)? This method needs to be called continously in the paint-event in order to check the live-stream of my camera (imageRxTx).
Here my code:
private void imageRxTx_paint(object sender, PaintEventArgs e)
{
var settings = new Settings();
// Create a local version of the graphics object for the PictureBox.
Graphics Draw = e.Graphics;
IntPtr hDC = Draw.GetHdc(); // Get a handle to image_RxTx.
Draw.ReleaseHdc(hDC); // Release image_RxTx handle.
//Here I need to send the picturebox_image 'image_RxTx' to ProcessImage as Bitmap
AForge.Point center = ProcessImage( ?....? );
}
// Check for circles in the bitmap-image
private AForge.Point ProcessImage(Bitmap bitmap)
{
//at this point I should read the picturebox_image 'image_RxTx'
...
The video image is updated here:
private void timer1_Elapsed(object sender, EventArgs e)
{
// If Live and captured image has changed then update the window
if (PIXCI_LIVE && LastCapturedField != pxd_capturedFieldCount(1))
{
LastCapturedField = pxd_capturedFieldCount(1);
image_RxTx.Invalidate();
}
}
As the title suggests, your main problem is a (common) misconception about what a Graphics object is.
So far I can draw to my graphics object without problem
No! A 'Graphics' object does not contain any graphics. It is only the tool used to draw graphics onto a related Bitmap. So you don't draw onto the Graphics object at all; you use it to draw onto imageRxTx, whatever that is, probably the surface of some Control or Form..
This line is using an often confusing rather useless format of the Bitmap constructor:
Bitmap bmp = new Bitmap(image_RxTx.Width, image_RxTx.Height, Draw);
The last parameter is doing next to nothing; its only function is to copy the Dpi setting. In particular it does not clone or copy any content from 'Draw', which, as you know now, a Graphics object doesn't have anyway, nor any of its other settings. So yes, the bmp Bitmap is still empty after that.
If you want to draw into bmp you need to use a Graphics object that is actually bound to it:
using (Graphics G = Graphics.FromImage(bmp)
{
// draw now..
// to draw an Image img onto the Bitmap use
G.DrawImage(img, ...);
// with the right params for source and destination!
}
None of this should probably happen in the Paint event! But all the preparation code is unclear as to what you really want to do. You should explain just what is the source of the drawing and what is the target!
If instead you want to get the stuff you draw onto image_RxTx into a Bitmap you can use this method somwhere outside (!) the Paint event:
Bitmap bmp = new Bitmap(image_RxTx.Width, image_RxTx.Height);
image_RxTx.DrawToBitmap(bmp, image_RxTx.ClientRectangle);
This will use the Paint event to draw the control into a Bitmap. Not that the result includes the whole PictureBox: The BackgroundImage, the Image and the surface drawing!
Update: To get the combined content of the PictureBox, that is both its Image and what you have drawn onto the surface, you should use the code above (the last 2 lines) in the Tick event of a Timer or right after the line that triggers the Paint event. (You didn't show us how that happens.) You can't acutally put it in the Paint event itself, as it will use the Paint event and therefore would cause an infinite loop!
The method Graphics.CopyFromScreen is probably what you're looking for.
var rect = myControl.DisplayRectangle;
var destBitmap = new Bitmap(rect.Width, rect.Height, PixelFormat.Format24bppRgb);
using (var gr = Graphics.FromImage(destBitmap))
{
gr.CopyFromScreen(myControl.PointToScreen(new Point(0, 0)), new Point(0, 0), rect.Size);
}
I'm experiencing a discrepancy between a GraphicsPath drawn in World coordinates on a UserControl and the results of GraphicsPath.IsVisible() to Hit Test the shape with the mouse.
I performed a little test that made a map of where IsVisible() returned true, relative to the GraphicsPath shape that was drawn. The results show a very "low resolution" version of the shape I'm drawing.
Link to shared Google Drive image showing the results:
http://goo.gl/zd6xiM
Is there something I'm doing or not doing correctly that's causing this?
Thanks!
Here's the majority of my OnMouseMove() event handler:
protected override void OnMouseMove(MouseEventArgs e)
{
//base.OnMouseMove(e);
debugPixel = Point.Empty;
PointF worldPosition = ScreenToWorld(PointToClient(Cursor.Position));
if (_mouseStart == Point.Empty) // Just moving mouse around, no buttons pressed
{
_objectUnderMouse = null;
// Hit test mouse position against each canvas object to see if we're overtop of anything
for (int index = 0; index < _canvasObjects.Count; index++) // Uses front to back order
{
NPCanvasObject canvasObject = _canvasObjects[index];
if (canvasObject is NPCanvasPart)
{
NPCanvasPart canvasPart = (canvasObject as NPCanvasPart);
NPPart part = canvasPart.Part;
GraphicsPath gp = canvasPart.GraphicsPath;
// Set the object under the mouse cursor, and move it to the "front" so it draws on top of everythign else
if (gp.IsVisible(worldPosition))
{
// DEBUG
debugPixel.X = e.X;
debugPixel.Y = e.Y;
_objectUnderMouse = canvasObject;
_canvasObjects.MoveItemAtIndexToFront(_canvasObjects.IndexOf(canvasObject));
break; // Since we're modifying the collection we're iterating through, we can't reliably continue past this point
}
}
}
}
else
{
...
}
}
Later in my drawing code I draw a pixel whenever debugPixel != Point.Empty . I temporarily suppressed clearing before drawing so I could see them all.
Some other info that may be asked, or could be helpful to troubleshoot:
I've tried different Graphics.InterpolationMode settings but that doesn't seem to have any effect
I've applied a TranslateTransform and ScaleTransform to the main drawing Graphics but the underlying HitTest map seems to scale and translate equal to the GraphicsPath
For my main drawing canvas, Graphics.PageUnit = GraphicsUnit.Inch, except when I'm doing pixel-based overlay stuff
I thought I had researched this thoroughly enough, but apparently not. Shortly after posting this question I did another search with slightly different terms and found this:
http://vbcity.com/forums/t/72877.aspx
...which was enough to clue me in that the GraphicsPath and my main drawing Graphics were not the same. Using the overloaded GraphicsPath.IsVisible(PointF, Graphics) solved this problem very nicely.
Essentially it was trying to check against a very aliased (pixelated) version of my shape that had been scaled to the same size but not smoothed.
For a project, I'm making a game and in it I have a scrolling map. The map moves left and right and is redrawn in a picturebox so that I can have a large map in a small picturebox. The top portion of the map is transparent so that I can change the sky colour later on. However when I move the map, the transparent part glitches out.
Original map before moving
After moving the map a bit
As you can see, everything above the tree line gets stretched, that is because that is where the transparency starts. The picturebox's parent is the form and the form is light blue, which is why the background is light blue.
Here is my code for moving the picture/redrawing it onto the picturebox:
private void timerTick_Tick(object sender, EventArgs e)
{
move();
//Draws new portion of the map
g.DrawImage(image, new Rectangle(0, 0, pbMap.Width, pbMap.Height), new Rectangle(imageX, imageY, pbMap.Width, pbMap.Height), GraphicsUnit.Pixel);
//Refreshes
pbMap.Image = bmp;
}
private void move()
{
//Right arrow events
if (right)
{
imageX += mapSpeed;
//Makes sure the picture stays within borders
if (imageX >= (imageWidth - pbMap.Width))
{
imageX = imageWidth - pbMap.Width;
}
}
//Left arrow events
if (left)
{
imageX -= mapSpeed;
//Makes sure the picture stays within borders
if (imageX <= 0)
{
imageX = 0;
}
}
}
Can anyone help explain the glitching?
Try calling g.Clear() with your sky color before the g.DrawImage() call. I think it's just drawing on top of itself and that's causing the smearing.
To me it seems like you are redrawing over and over without clearing the display from the previous draw! What type of framework are you using to develop that? Does it have a custom drawing class? As tesserex suggested more specifically call g.Clear() and u will be fine.
You shouldn't need g.Clear in this case because you're Re-Drawing a new image every time.
My bet is that imageX is greater then (imageWidth - pbMap.Width) so it will not enter the IF, therefore it will redraw the same as before.
Note: I don't know how you create g but if you use .CreateGraphics() don't forget to Dispose()
Cheers
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....
}
I am trying to make a simple graph for my application which shows real-time data for every 100 ms. So I thought I could draw the graph line using the DrawCurve method and started with the following code:
class BufferedPanel : Panel
{
public BufferedPanel()
{
this.DoubleBuffered = true; //to avoid flickering of the panel
}
}
class Form2: Form
{
BufferedPanel panel1 = new BufferedPanel();
List<Point> graphPoints = new List<System.Drawing.Point>();
private void Form2_Load(object sender, EventArgs e)
{
this.panel1.Paint += new System.Windows.Forms.PaintEventHandler(this.panel1_Paint);
}
private void panel1_Paint(object sender, PaintEventArgs e)
{
using (Graphics g = e.Graphics)
{
Point[] points = graphPoints.ToArray();
g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
if (points.Length > 1)
g.DrawCurve(graphPen, points);
}
}
private void worker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
graphPoints.Add(new System.Drawing.Point(counter * steps, (int)(float)e.UserState)); //x - regular interval decided by a constant; y - simulated by background worker
panel1.Refresh();
counter++;
}
}
As of now, I am simulating the values of graphPoints from a background worker thread. My problem is that, I could not get the graph lines visible when I doublebuffer my panel. It works well when I set doublebuffering to false. I am new to drawing using Graphics. So I am not very sure of how it works. Please help me in this.
Also, I would like to achieve AutoScrolling when the graphlines reach to end of the panel. Could you suggest an idea on how to do it?
This is an image of my working graph:
using (Graphics g = e.Graphics)
That's bad. That destroys the Graphics object that was passed to your Paint event handler. Nothing can be done with that object when your event handler returns, it is a dead parrot. So don't expect anything to work afterwards, including what needs to happen when you turn on double-buffering, the buffer needs to be drawn to the screen to be visible.
There's a simple rule to using the using statement or the Dispose() method correctly. If you create an object then you own it and it is yours to destroy it. Hands off it you didn't create it.
Some evidence that you are also getting it wrong with the "graphPen" variable. Pens are definitely an object that you create and destroy in a Paint event handler. Don't store one, that just needlessly occupies space in the GDI heap, that isn't worth the few microseconds needed to create one. You'd definitely use the using statement for the pen.
So the quick fix is:
var g = e.Graphics;