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....
}
Related
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!
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.
How can I repaint a panel in smooth?
I am using a timer that is invalidating the panel(panel1.Invalidate();) every 300ms, and then with the panel1_Paint event I am adding images to that panel the issue is that it looks like is jumping and I need to be moving one image on it as fast as I can.
This is a link of the screen-cast issue: http://screencast.com/t/HdtIV99YN
private void panel1_Paint(object sender, PaintEventArgs e)
{
PaintMapObstacles(e);
PaintMapStartAndGoal(e);
if (trayectoryIndex < 1000)
{
MapPoint point = GetTrayectoryPoint(0, trayectoryIndex);
e.Graphics.DrawImage(new Bitmap("robot.jpg"), point.X*squareSize, point.Y*squareSize, 60, 60);
trayectoryIndex++;
}
}
private void PaintMapStartAndGoal(PaintEventArgs e)
{
MapPoint start = new MapPoint { X = 0, Y = 0 };
MapPoint goal = new MapPoint { X = 7, Y = 8 };
// e.Graphics.DrawImage(new Bitmap("start.jpg"), start.X * squareSize, start.Y * squareSize, 60, 60);
e.Graphics.DrawImage(new Bitmap("goal.jpg"), goal.X * squareSize, goal.Y * squareSize, 60, 60);
isFirstRun = true;
}
private void PaintMapObstacles(PaintEventArgs e)
{
foreach (MapPoint mpoint in obstacles.Obstacles)
{
e.Graphics.DrawImage(new Bitmap("obstacle.png"), mpoint.X * squareSize, mpoint.Y * squareSize, 60, 60);
}
}
private void PanelTimer_Tick(object sender, EventArgs e)
{
panel1.Invalidate();
}
It is called "flicker", an artifact that's always around when you repaint a window from scratch. It is especially noticeable in your program because your painting code is so inefficient. You see the window's background getting drawn, erasing the old painting. Then slowly getting the bitmaps drawn back onto the background. The erasure step is visible to the eye and looks like flicker.
The general cure for flicker is double-buffering, composing the window content into a bitmap first, then quickly blitting the bitmap to the screen. It is a built-in feature for Winforms, the DoubleBuffered property turns it on. Double-buffering is not enabled by default for the Panel class, it was designed to be a container control that doesn't do painting on its own beyond drawing the background. A PictureBox will work just as well in your case, it has double-buffering enabled by default. Or you can turn on double-buffering for the Panel class, shown here.
You do want to eventually address the problems with your painting code, beyond it being very slow, it can crash your program with an OutOfMemoryException. A problem caused by the way you use the Bitmap class, it should be disposed after you used it. Always use the using statement for System.Drawing objects.
You'll make it a lot faster by creating the bitmaps just once, the form constructor is the best place. You make it really fast by prescaling the bitmaps to fit the grid and paying attention to the pixel format. PixelFormat.Format32bppPArgb is directly compatible with the frame buffer format of almost any modern video adapter, the bitmap can be directly copied into the frame buffer without conversion. Ten times faster than all the other formats. Conversion code is here.
I have 2 problems while trying to print from a WinForms application. The first is a very very bad quality no matter what I try. The second is that I have a big page margin from the top left corner and the winform is cutting. Any ideas? This is my code:
Bitmap MemoryImage;
public void GetPrintArea(Panel pnl)
{
MemoryImage = new Bitmap(pnl.Width, pnl.Height);
Rectangle rect = new Rectangle(0, 0, pnl.Width, pnl.Height);
pnl.DrawToBitmap(MemoryImage, new Rectangle(0, 0, pnl.Width, pnl.Height));
}
protected override void OnPaint(PaintEventArgs e)
{
if (MemoryImage != null)
{
e.Graphics.DrawImage(MemoryImage, 0, 0);
base.OnPaint(e);
}
}
void printdoc1_PrintPage(object sender, PrintPageEventArgs e)
{
e.Graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
e.Graphics.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBilinear;
e.Graphics.PixelOffsetMode = System.Drawing.Drawing2D.PixelOffsetMode.HighQuality;
Rectangle pagearea = e.PageBounds;
e.Graphics.DrawImage(MemoryImage, (pagearea.Width / 2) - (this.panel1.Width / 2), this.panel1.Location.Y);
}
public void Print(Panel pnl)
{
panel1 = pnl;
GetPrintArea(pnl);
printPreviewDialog1.Document = printdoc1;
printPreviewDialog1.ShowDialog();
}
private void button2_Click(object sender, EventArgs e)
{
Print(this.panel1);
}
This comes up over and over again. There just is no magic solution, although eventually the problem is likely to disappear. The emergence of "retina" displays is pivotal.
The core issue is that monitors have a resolution that's drastically worse than printers. A typical printer has a resolution of 600 dots per inch. Which makes it capable of printing 6600 x 5100 individual pixels on a piece of paper. Much, much more than what a monitor can display, a full HD monitor tops out at 1920 x 1080 pixels. Roughly a factor of 5 worse, give or take.
This works out poorly when you print what shows up on a monitor on a piece of paper and try to keep it the same size. Inevitably, because of the lack of pixels on the monitor, each one pixel from the monitor is printed as a 5x5 blob on paper. If you try to keep the pixel mapping one-to-one, you will get a razor-sharp copy on paper. But it has turned into a postage-stamp.
Inevitably, the printout looks very grainy due those pixel blobs. What looks especially poor is text. Operating systems use lots of tricks to make text look good on monitors with poor resolution. Anti-aliasing is standard, tricks like ClearType were designed to take advantage of monitor physics that can help increase the perceived resolution. This no longer works when the text is printed, those anti-aliasing pixels turn into blobs and become very visible, completely ruining the effect. Especially bad for ClearType text on a color printer, the red and blue color fringes now can be clearly seen.
The only decent approach is to render to a printer using the actual resolution and not the monitor resolution. Like using the PrintDocument class in .NET. Using a report generator can help avoid having to write the code for it.
You should draw yourself on the Graphics object you get when the PrintDocument prints. That gives you all the control you need. Still everything Hans Passant said applies here as well...
Keep in mind that this is the simplest implementation that merely demo's what can be achieved, I'm not claiming that this is the easiest/best/most productive way... my code doesn't take multiple pages, controls contained in contaimers or controls not of the type Label and PictureBox.
I used the Draw... methods from System.Drawing.Graphics
sligthly adapted from the code above to get this working:
public void GetPrintArea(Panel pnl, Graphics gr)
{
// scale to fit on width of page...
if (pnl.Width > 0)
{
gr.PageScale = gr.VisibleClipBounds.Width/pnl.Width;
}
// this should recurse...
// just for demo so kept it simple
foreach (var ctl in pnl.Controls)
{
// for every control type
// come up with a way to Draw its
// contents
if (ctl is Label)
{
var lbl = (Label)ctl;
gr.DrawString(
lbl.Text,
lbl.Font,
new SolidBrush(lbl.ForeColor),
lbl.Location.X, // simple based on the position in the panel
lbl.Location.Y);
}
if (ctl is PictureBox)
{
var pic = (PictureBox)ctl;
gr.DrawImageUnscaledAndClipped(
pic.Image,
new Rectangle(
pic.Location.X,
pic.Location.Y,
pic.Width,
pic.Height));
}
}
}
void printdoc1_PrintPage(object sender, PrintPageEventArgs e)
{
e.Graphics.SmoothingMode = Drawing2D.SmoothingMode.HighQuality;
e.Graphics.InterpolationMode =Drawing2D.InterpolationMode.HighQualityBilinear;
e.Graphics.PixelOffsetMode = Drawing2D.PixelOffsetMode.HighQuality;
GetPrintArea(panel1, e.Graphics);
}
You can actually print sharper controls by applying scaling to them on a "vector" level instead of bitmap level.
This snapshot shows the result of the following technique (please don't mind my Win2000-ish UI :-) ):
What we do is to iterate through the control's ControlCollection in a very similar fashion as rene shows in his answer.
But - in addition we apply scale to location, size and font to the control itself before we draw its bitmap to a preset size bitmap which in this case is 5 times bigger (4 times would represent about 300 DPI which is the effective print resolution on most printers).
The reason for this is to keep the thin line on the control sharp on print, or we could just scale the bitmap itself which wouldn't give us any benefit resolution-wise. By scaling font we reduce the anti-alias effect and can provide a better print quality.
To do so you can first in the button's click event setup the following:
//this will produce 5x "sharper" print
MemoryImage = new Bitmap((Panel1.Width * 5), (Panel1.Height * 5));
Using Graphics g = Graphics.FromImage(MemoryImage) {
ScaleControls(Panel1, g, 5);
};
PrintPreviewDialog1.Document = printdoc1;
PrintPreviewDialog1.ShowDialog();
Now in the ScaleControls function, which is recursive, we scale location, size and font in order to make each control itself in higher resolution before we draw them to the bitmap:
private void ScaleControls(Control c, ref Graphics g, double s)
{
//To detach controls for panels, groupboxes etc.
List<Control> hold = null;
foreach (Control ctrl in c.Controls) {
if (ctrl is GroupBox || ctrl is Panel) {
//backup reference to controls
hold = new List<Control>();
foreach (Control gctrl in ctrl.Controls) {
hold.Add(gctrl);
}
ctrl.Controls.Clear();
}
//backup old location, size and font (see explanation)
Point oldLoc = ctrl.Location;
Size oldSize = ctrl.Size;
Font oldFont = ctrl.Font;
//calc scaled location, size and font
ctrl.Location = new Point(ctrl.Location.X * s, ctrl.Location.Y * s);
ctrl.Size = new Size(ctrl.Size.Width * s, ctrl.Height * s);
ctrl.Font = new Font(ctrl.Font.FontFamily, ctrl.Font.Size * 5,
ctrl.Font.Style, ctrl.Font.Unit);
//draw this scaled control to hi-res bitmap
using (Bitmap bmp = new Bitmap(ctrl.Size.Width, ctrl.Size.Height)) {
ctrl.DrawToBitmap(bmp, ctrl.ClientRectangle);
g.DrawImage(bmp, ctrl.Location);
}
//restore control's geo
ctrl.Location = oldLoc;
ctrl.Size = oldSize;
ctrl.Font = oldFont;
//recursive for panel, groupbox and other controls
if (ctrl is GroupBox || ctrl is Panel) {
foreach (Control gctrl in hold) {
ctrl.Controls.Add(gctrl);
}
ScaleControls(ctrl, g, s);
}
}
}
and finally in the event handler for printing:
double scale = MemoryImage.Width / e.PageBounds.Width;
e.Graphics.DrawImage(MemoryImage, 0, 0,
Convert.ToInt32(MemoryImage.Width / scale),
Convert.ToInt32(MemoryImage.Height / scale));
Now, in this example we scale the controls in-place. This is not ideal of course as they will appear to live their own life while we do a print preview.
Ideally we would clone each control as we iterated and discard it after we drew to bitmap. This would also eliminate the need to backup geometries. But for the example of principle I left it as-is. I'll leave it to you to clone etc.
The reason for detaching controls is that if we don't (as the code is now - this can surely be changed by providing another method of iterating, ie. pre-scale cloned controls) the non-scaled in the f.ex. GroupBox control will be printed first, then the scaled ones will show on top of those when they are iterated. This because we DrawToBitmap the GroupBox before scaling its controls. This is something I'll leave for you to handle.
The bitmap we're working on will not necessary fit the resolution of the print that the user end up with from setting up the print dialog, but we get a higher resolution to work with which in turn yield a better result than the poor screen bitmap resolution we have initially.
You would of course need to add support for special cases for other controls than Panel and GroupBox that can hold other controls, image controls and so forth.
I spent days looking for a way to print a panel and its contents in high quality. This didnt work, and I tried other peoples codes and they were all either wrong or just bad quality, until I found this:
http://rkinfopedia.blogspot.com/2008/07/printing-contents-of-panel-control.html
just put the eventhandler inside the print button click event handler, and include the print method in it too, like this:
private void button3_Click(object sender, EventArgs e)
{
printdoc1.PrintPage += new PrintPageEventHandler(printdoc1_PrintPage);
Print(panel1);
}
and place an if statement inside the override OnPaint method, like this:
protected override void OnPaint(PaintEventArgs e)
{
if (MemoryImage != null)
{
e.Graphics.DrawImage(MemoryImage, 0, 0);
base.OnPaint(e);
}
}
rest is fine as is, and youll finally get almost perfect print quality
Just wanted to share this gem, you are welcome internet stranger!
Thank you Mr. Rakesh!
i am trying to move a image in picture box. i added panel to my application and also added picture box in panel. i opened an image.if the image size is big.i want to see the particular portion of image. so how can i move the image up and down (without using scroll bars) to see the particular portion of image?
You can add controls like move left, move right, move up, move down with associated actions to move the image within your picturebox. An example of how to do this for moving the image to the right is shown below. You can implement these action with mouse down and mouse up events so that the user just presses the appropriate buttons to move the picture as he wants. Also note that once you reach the maximum dimensions of the image, you can change the rectangular region to that within image bounds.
int ff = 0; //number of positions to move
Bitmap b2;
private void button1_Click(object sender, EventArgs e)
{
if (ff == 0) { b2 = new Bitmap(pictureBox1.Image);} //original image as bitmap b2
Bitmap b1 = new Bitmap(pictureBox1 .Width ,pictureBox1.Height ); //new bitmap with rectangular region of original image
Rectangle r1 = new Rectangle(ff++, 0, pictureBox1.Width, pictureBox1.Height );
Graphics g = Graphics.FromImage(b1);
g.DrawImage(b2, 0, 0, r1, GraphicsUnit.Pixel);
g.Dispose();
pictureBox1.Image = null;
pictureBox1.Image = (Image)b1;
pictureBox1.Refresh();
}
Not sure if it really answers your question, but this seems like a fun reason to play with Reactive Extensions (Rx). This video demonstrates nicely how well this stuff works with asynchronous events like mouse-input.