I have C# app, but the painting job is done within C++ project (this is how it has to be).
So I added to my window PictureBox, and passed the handle to the drawing function. It works fine.
However when the control is invalidated (I move the window outside the screen and then move it back), I get in result partially empty window. AFAIK there are two approaches for this -- repaint or cache the image.
Repainting could be costly process (the image does not change often, but processing is time consuming, think of 3d renderer), so I would like to simply cache the image.
If I remember correctly from old days of programming directly with Win32 API all it takes is to set some flags. Is it possible with WinForms? If yes, how?
This will help:
Bitmap getControlSurface(Control ctl)
{
bitmap = new Bitmap(ctl.Width, ctl.Height);
using (Graphics g = Graphics.FromImage(bitmap))
{
Point pScreen = PointToScreen(ctl.Location);
g.CopyFromScreen(pScreen, Point.Empty, ctl.Size);
}
return bitmap;
}
If you know when the surface has been refreshed you can use it like this:
if ( pictureBox1.Image != null) pictureBox1.Image.Dispose();
pictureBox1.Image = getControlSurface(pictureBox1);
The only catch is that the PictureBox actually must be completely visible on the screen!
Related
I have a problem on creating bitmap image out of my winform application.
Situation:
I have a UserControl named as "CanvasControl" that accepts OnPaint method acting as canvas for my Draw Pad application. Inside this user control I have a function "PrintCanvas()" that will create a screenshot image of the UserControl into PNG file. Below is the PrintCanvas() function:
public void PrintCanvas(string filename = "sample.png")
{
Graphics g = this.CreateGraphics();
//new bitmap object to save the image
Bitmap bmp = new Bitmap(this.Width, this.Height);
//Drawing control to the bitmap
this.DrawToBitmap(bmp, new Rectangle(0, 0, this.Width, this.Height));
bmp.Save(Application.StartupPath +
#"\ExperimentFiles\Experiment1" + filename, ImageFormat.Png);
bmp.Dispose();
}
This user control (CanvasControl) is called out inside my main form where user will draw something and have an option to save afterwards using a save button. The save button will call out the "PrintCanvas()" function of the UserControl.
I get the output image file as expected, but the problem is it was a blank image.
What I have tried so far:
To test that it is not a syntax issue, I tried to transfer the PrintCanvas() function into my main form and surprisingly I get an image of the whole main form on file but the UserControl is not visible there.
Is there any other setup i missed out to make a winform UserControl printable?
UPDATE: (DRAWING ROUTINES)
User control acting as canvas - code here
The code in the question gave a first hint but the code in the link showed the source of the problem: You use a 'wrong' instance of the Graphics object for drawing:
protected override void OnPaint(PaintEventArgs e)
{
// If there is an image and it has a location,
// paint it when the Form is repainted.
Graphics graphics = this.CreateGraphics();
..
This is one of the most common mistakes with winforms graphics! Never use CreateGraphics ! You always should draw onto the control surface with the Graphics object in a Paint or DrawXXX event. These events have a parameter e.Graphics which is the only one that can draw persistent graphics.
Persistent means that it will always be refreshed when necessary, not just when you trigger it. This is a nasty error because everything seems to work until you come upon a situation when an outside event makes redrawing necessary:
Minimizing and then maximizing the form
Moving it off the screen and back again
Calling DrawToBitmap
...
Note that all will only really work if you use the valid and current Graphics object from the PaintEventArgs e parameter.
So, the solution is simple:
protected override void OnPaint(PaintEventArgs e)
{
// If there is an image and it has a location,
// paint it when the Form is repainted.
Graphics graphics = e.Graphics(); // << === !!
..
But what is the CreateGraphics good for? It is only good for luring newbies into that error??
Not quite; here are some uses for it:
Drawing non-persistent graphics like a rubber-band rectangle or a special mouse cursor
Measuring text sizes without actually drawing it with a TextRenderer or the MeasureString method
Querying the screen or Bitmap resolution with Graphics.DpiX/Y
and probably some others I can't think of at the moment..
So for normal drawing onto controls always use the e.Grapahics object! You can pass it on to subroutines to make the code more structured, but do not try to cache it; it needs to be current!
I have a very basic problem. I have a variable of type: Bitmap. This variable updates with every frame received from the webcam. This variable is declared as follows:
Bitmap img=eventArgs.Frame;
I have verified that the above line is working properly.
All I need to know now is how to set my PictureBox on my form to this Bitmap image. I have tried the following:
pbImg.Image=img;
This doesn't work. Any help would be greatly appreciated.
Bitmaps generated by a camera normally only have a very short life-time. They are only valid while the event handler runs, the camera capture driver replaces the bitmap with a new frame. Pretty essential to avoid excessive memory usage.
You must therefore make a deep copy of the image so that it can survive in the PictureBox and still get painted after the event call completed. Like this:
Bitmap img = new Bitmap(eventArgs.Frame);
if (pbImg.Image != null) pbImg.Image.Dispose();
pbImg.Image = img;
I have a WinForm application 'Bouncing Balls' , and I need to paint the balls
on a bitmap and present the bitmap on this form.
I have a plusButton that adds new ball, and i'm saving each new ball in a list.
Now, the Form_Paint method is telling to each ball to draw himself, it works fine
until there are a lot of balls and the all application become very slow..
Here is my Code:
The paint method of the form code:
private void Form1_Paint(object sender, PaintEventArgs e)
{
ballsArray.drawImage(bmp,e, ClientRectangle);
}
NOTE: ballsArray is from type AllBalls, this is a class that wraps the ball methods, inside his c'tor i'm creating a list that keeps each ball. the bmp, is created when the form is loading - on Form_Load() method.
The drawImage of ballsArray code:
public void drawImage(Bitmap bmp,PaintEventArgs e, Rectangle r)
{
foreach (Ball b in allBalls)
{
b.drawImage(bmp,e, r);
}
}
The drawImage of Ball code:
public void drawImage(Bitmap bmp, PaintEventArgs e, Rectangle r)
{
using (Graphics g = Graphics.FromImage(bmp))
{
e.Graphics.FillEllipse(brush, ballLocation);
g.DrawImage(bmp, 0, 0);
}
}
NOTE: ballLocation is a rectangle that represent the location of the ball in each
step of movement..
So what I'm doing wrong? What causing the application to be slowly?
I have a constraint to draw everything on the bitmap and present it on the form.
I'm also passing the bitmap that I create when the form is loading, because I need to draw each on it.
Some basic techniques to make this fast:
Don't double-buffer yourself and especially don't double-buffer twice. The double-buffering you get by setting the form's DoubleBuffer property to true is superior to most any double-buffering you'd do yourself. The buffer is highly optimized to work efficiently with your video adapter's settings. So completely drop your bmp variable and draw to the e.Graphics you got from the Paint event handler argument.
You are not using the passed r argument. Possibly intended to support clipping invisible balls. The one you want to pass is e.ClipRectangle, you can skip painting balls that are completely outside of this rectangle. While that's an optimization, it isn't one that's commonly useful when you use the Aero theme and you do get inconsistent redraw rates so you might want to skip that one.
It isn't very clear why you use both Graphics.FillEllipse and Graphics.DrawImage when you draw the ball. The image ought to overlap the circle so just remove FillEllipse.
Pay a lot of attention to the Bitmap object that stores the ball graphic. First thing you want to make sure is that it is drawn with the exact size of the image so it doesn't have to be rescaled. Rescaling is very expensive. While you don't have any rescaling in your DrawImage() call, you will still get it if the resolution of the bitmap is not the same as the resolution of your video adapter. The next step will solve that
The pixel format of the ball bitmap is very important. You want one that permits copying the bitmap straight to video memory without any format conversion. On any modern machine, that format is PixelFormat.Format32bppPArgb. The difference is enormous, it draws ten times faster than any of the other ones. You won't get this format from an image resource you added, you'll have to create that bitmap when your program starts up. Check this answer for the required code.
You ought to be able to render at least 15 times faster when you follow these guidelines. If that's still enough then you do need to turn to DirectX, it has the unbeatable advantage of being able to store the ball graphic in video memory so you don't get the expensive blt from main memory to video memory.
DrawImage on Paint (or for that matter on MouseMove) is very bad design.
Graphics.DrawImage is expensive operation, and to the screen it is extra expensive.
To improve your user experience (slowness), You should paint on MouseDown/MouseUp events.
In addition, First draw to MemoryBuffer in your drawImage method and after preparing the final image, draw it once on the UI. This technique is known as double buffering.
Don't Flicker! Double Buffer! - CodeProject
In addition you can also look at BitBlit Native API for fast color/image transfer to screen.
A minimalistic c# example is here
Enable double-buffering on your form (DoubleBuffered = true).
I think I am fundamentally misunderstanding the way render targets work. In my understanding RenderTargets are just Textures that the spritebatch draw calls draw to.
So I tried this code to render GUI windows in order to make sure they can only draw in their client area and its cropped outside that.
for (int i = Controls.Count - 1; i >= 0; i--)
{
RenderTarget2D oldTarget;
if (graphics.GetRenderTargets().Count() == 0) oldTarget = null;
else oldTarget = (RenderTarget2D)graphics.GetRenderTargets()[0].RenderTarget; // Get the old target being used.
graphics.SetRenderTarget(canvas); //set the target to a temporary RT
graphics.Clear(Color.Black); // Clear it
Control c = Controls[i]; // Get the current control (a form in this case)
c.Draw(spriteBatch, gameTime); // Draw it to the temp RT
graphics.SetRenderTarget(oldTarget); // Set the RT back to the main RT
Vector2 dest = c.DrawCoOrds(); // Gets the draw coordinates of the control
spriteBatch.Begin();
spriteBatch.Draw(canvas, new Rectangle((int)dest.X, (int)dest.Y, c.Bounds.Width, c.Bounds.Height), new Rectangle((int)dest.X, (int)dest.Y, c.Bounds.Width, c.Bounds.Height), Color.White);
// take the rect from the temp RT and draw it to the main RT.
spriteBatch.End();
}
However this code only draws the last form in the list which means it must be clearing the main RT somehow but i dont understand why. I only call clear when the RT is set to the temp canvas.
i think the best method to draw gui controls is with ScissorRectangle, because lets draw only inside that rectangle, that can be the client area of the gui control.
MSDN: GraphicsDevice.ScissorRectangle
You need to enable this funcionality through a RasterizerState.
RasterizerState ScissorState = new RasterizerState()
{
ScissorTestEnabled = true;
}
Before you draw, call SpriteBatch.Begin with this state.
A video of my own gui running in a xbox360 :)
How did you create your render target and back buffer? By default you can't write to a render target multiple times after changing to a different render target. This is why:
http://blogs.msdn.com/b/shawnhar/archive/2007/11/21/rendertarget-changes-in-xna-game-studio-2-0.aspx
You can change the default behavior by creating your render targets with RenderTargetUsage.PreserveContents., and the back buffer by overriding GraphicsDeviceManager.PrepareDeviceSettings., changing GraphicsDeviceInformation.PresentationParameters.RenderTargetUsage, as described in the link. Although I believe overriding those settings is done differently in XNA 4.
All that being said, changing away from the default behavior has performance considerations, and is not recommended. You should find a way to do this differently. One possibility would be to create a separate render target for each of your windows, draw all of them, switch to the back buffer and draw the render targets to it.
A better option would be to use the scissor rectangle rasterizer state as proposed by #Blau.
I need a solution how could i make a Print Screen of my WinForm on C# and export it as PNG.
Bests
I think this blog post will help.
using (Bitmap bitmap = new Bitmap(ParentForm.Size.Width, ParentForm.Size.Height))
{
using (Graphics g = Graphics.FromImage(bitmap))
{
g.CopyFromScreen(new Point(ParentForm.DesktopLocation.X, ParentForm.DesktopLocation.Y), new Point(0, 0), ParentForm.Size);
}
bitmap.Save(#"C:\test.jpg", ImageFormat.Jpeg);
}
Never tried it, but i'd think you should be able to call OnPaint(args) with a PaintEventArgs you create, that includes a Graphics for the image you want to draw on, and the ClipRectangle encompassing the whole area of the form.
This would only work if your form properly processes paint messages (ie: if it stores enough info to be able to repaint the window fully at will), and it may only get the client area (ie: it might not get the title bar or menus).