Drawing on PictureBox - c#

in a UserControl I have a PictureBox and some other controls. For the user control which contains this picturebox named as Graph I have a method to draw a curve on this picture box:
//Method to draw X and Y axis on the graph
private bool DrawAxis(PaintEventArgs e)
{
var g = e.Graphics;
g.DrawLine(_penAxisMain, (float)(Graph.Bounds.Width / 2), 0, (float)(Graph.Bounds.Width / 2), (float)Bounds.Height);
g.DrawLine(_penAxisMain, 0, (float)(Graph.Bounds.Height / 2), Graph.Bounds.Width, (float)(Graph.Bounds.Height / 2));
return true;
}
//Painting the Graph
private void Graph_Paint(object sender, PaintEventArgs e)
{
base.OnPaint(e);
DrawAxis(e);
}
//Public method to draw curve on picturebox
public void DrawData(PointF[] points)
{
var bmp = Graph.Image;
var g = Graphics.FromImage(bmp);
g.DrawCurve(_penAxisMain, points);
Graph.Image = bmp;
g.Dispose();
}
When application starts, the axis are drawn. but when I call the DrawData method I get the exception that says bmp is null. What can be the problem?
I also want to be able to call DrawData multiple times to show multiple curves when user clicks some buttons. What is the best way to achive this?
Thanks

You never assigned Image, right? If you want to draw on a PictureBox’ image you need to create this image first by assigning it a bitmap with the dimensions of the PictureBox:
Graph.Image = new System.Drawing.Bitmap(Graph.Width, Graph.Height);
You only need to do this once, the image can then be reused if you want to redraw whatever’s on there.
You can then subsequently use this image for drawing. For more information, refer to the documentation.
By the way, this is totally independent from drawing on the PictureBox in the Paint event handler. The latter draws on the control directly, while the Image serves as a backbuffer which is painted on the control automatically (but you do need to invoke Invalidate to trigger a redraw, after drawing on the backbuffer).
Furthermore, it makes no sense to re-assign the bitmap to the PictureBox.Image property after drawing. The operation is meaningless.
Something else, since the Graphics object is disposable, you should put it in a using block rather than disposing it manually. This guarantees correct disposing in the face of exceptions:
public void DrawData(PointF[] points)
{
var bmp = Graph.Image;
using(var g = Graphics.FromImage(bmp)) {
// Probably necessary for you:
g.Clear();
g.DrawCurve(_penAxisMain, points);
}
Graph.Invalidate(); // Trigger redraw of the control.
}
You should consider this as a fixed pattern.

Related

Panel to Image (DrawToBtimap doesn't work) [duplicate]

This question already has an answer here:
DrawToBitmap returning blank image
(1 answer)
Closed 3 years ago.
I'm trying to make an application similar to Paint.
Everything works well, but I have a problem with saving the image to a file.
The function for saving works okay, the file saves in the selected location, but it is empty when something is drawn.
It works only when I change the background color, then the image saves with this color.
When I 'draw' something like that
the saved image looks like this
Code:
private void saveToolStripMenuItem_Click(object sender, EventArgs e)
{
int width = pnl_Draw.Width;
int height = pnl_Draw.Height;
Bitmap bm = new Bitmap(width, height);
SaveFileDialog sf = new SaveFileDialog();
sf.Filter = "Bitmap Image (.bmp)|*.bmp|Gif Image (.gif)|*.gif|JPEG Image (.jpeg)|*.jpeg|Png Image (.png)|*.png|Tiff Image (.tiff)|*.tiff|Wmf Image (.wmf)|*.wmf";
sf.ShowDialog();
var path = sf.FileName;
pnl_Draw.DrawToBitmap(bm, new Rectangle(0, 0, width, height));
bm.Save(path, ImageFormat.Jpeg);
}
Drawing:
private void pnl_Draw_MouseMove(object sender, MouseEventArgs e)
{
if(startPaint)
{
//Setting the Pen BackColor and line Width
Pen p = new Pen(btn_PenColor.BackColor,float.Parse(cmb_PenSize.Text));
//Drawing the line.
g.DrawLine(p, new Point(initX ?? e.X, initY ?? e.Y), new Point(e.X, e.Y));
initX = e.X;
initY = e.Y;
}
}
Where is your "g" (System.Drawing.Graphics?) object come from when you DrawLine on it? I also can't see where you fill the background color, but I have the suspiction that your drawing gets discarded/overwritten - but given the little code visible here, it's hard to tell.
I'd just suggest what worked for me in the past:
Use a Bitmap object to draw your lines etc into, not the panel directly. And when you want to save it, just save the bitmap.
To make the drawing visible on your panel, call Invalidate(...) on your panel after a new line stroke was made, with the bounding rectangle around the line stroke as the update-rectangle passed to Invalidate.
In the OnPaint handler of your panel, then make sure to only draw that portion that's new, e.g. that rectangle I mentioned. This will be passed as the clip bounds of the OnPaint call. If only the changed portion of the whole image is drawn in the OnPaint handler, it will be much faster than always drawing the whole bitmap onto the panel.
For that, you need to creage a graphics object from the drawn-to bitmap, and keep both the bitmap and the graphics object alive throughout your drawing session (i.e. don't let it get garbage collected by not having references to them somewhere)
Something roughly like this:
// assuming you're all doing this directly in your main form, as a simple experimental app:
// Somewhere in your form class:
Bitmap drawingBitmap;
Graphics gfx;
// in your form class' constructor, AFTER the InitializeComponent() call, so the sizes are known:
// using a pixelformat which usually yields good speed vs. non-premultiplied ones
drawingBitmap = new Bitmap( pnl.Width, pnl.Height, System.Drawing.Imaging.PixelFormat.Format32bppPArgb );
gfx = Graphics.FromImage( drawingBitmap );
private void pnl_Draw_MouseMove(object sender, MouseEventArgs e)
{
if(startPaint)
{
//Setting the Pen BackColor and line Width
Pen p = new Pen(btn_PenColor.BackColor,float.Parse(cmb_PenSize.Text));
//Drawing the line.
var p1 = new Point(initX ?? e.X, initY ?? e.Y);
var p2 = new Point(e.X, e.Y);
gfx.DrawLine( p, p1, p2 ); // !!! NOTE: gfx instance variable is used
initX = e.X;
initY = e.Y;
// makes the panel's Paint handler being called soon - with a clip rectangle just covering your new little drawing stroke, not more.
pnl.Invalidate( new Rectangle( p1.X, p1.Y, 1+p2.X-p1.X, 1+p2.Y-p1.Y ));
}
}
private void pnl_Paint(object sender, PaintEventArgs e)
{
var g = e.Graphics;
// use the clip bounds of the Graphics object to draw only the relevant area, not always everything.
var sourceBounds = new RectangleF( g.ClipRectangle.X, g.ClipRectangle.Y, g.ClipRectangle.Width, g.ClipRectangle.Height );
// The bitmap and drawing panel are assumed to have the same size, to make things easier for this example.
// Actually you might want have a drawing bitmap of a totally unrelated size, and need to apply scaling to a setting changeable by the user.
g.DrawImage( drawingBitmap, g.ClipRectangle.X, g.ClipRectangle.Y, sourceBounds );
}
Here is a little bit about that, although pretty old article. But GDI+ hasn't change that much since then, as it's a wrapper around the Win32 drawing stuff.
https://www.codeproject.com/Articles/1355/Professional-C-Graphics-with-GDI
NOTE: What I don't have time right now to check and do not remember is whether you are allowed to, at the same time:
have an "open" graphics object of a bitmap and draw into the bitmap
paint that same bitmap onto something else (like the panel)
save the bitmap to file
You'd have to check that, and manage the existence of the graphics object with its claws on your drawing bitmap accordingly.

DrawImage method of Graphics class is making unintended clippings in the original image

I have this code to add bitmap to a picturebox
pictureBoxTemp.Image = finalImage;
Where finalImage variable is a bitmap holding the image. When I use this method. The result is good. Here is the image for this code:
And then, I decided to add zoom functionality so it is necessary to add a paint event on my picturebox.. This is my code for the paint event:
protected void pictureBox_Paint(object sender, PaintEventArgs e, ref ZoomInfo imageZoom)
{
//Conditions to avoid to proceed further.
if (((PictureBox)sender).Image == null) { return; }
float futurePosX = (imageZoom.translateX) * (1 - imageZoom.zoom) / imageZoom.zoom;
float futurePosY = (imageZoom.translateY) * (1 - imageZoom.zoom) / imageZoom.zoom;
Graphics g = e.Graphics;
g.Clear(Color.Transparent);
g.PixelOffsetMode = System.Drawing.Drawing2D.PixelOffsetMode.HighQuality;
g.ScaleTransform(imageZoom.zoom, imageZoom.zoom);
g.DrawImage(((PictureBox)sender).Image, futurePosX, futurePosY);
}
By default, futurePosY and futurePosX are both 0 so g.DrawImage() will draw it in 0,0 position. When paint is called, image is changed to this:
Notice that there is now a black region on the bottom of the image which means that the image is clipped.
I don't know for now why DrawImage() is clipping my image but when use pictureBox.Image = Bitmap again, the image fits perfectly on pictureBox. Any suggestions how to correct this?

C# Picturebox Image is null when I draw a shape in Paint event [duplicate]

in a UserControl I have a PictureBox and some other controls. For the user control which contains this picturebox named as Graph I have a method to draw a curve on this picture box:
//Method to draw X and Y axis on the graph
private bool DrawAxis(PaintEventArgs e)
{
var g = e.Graphics;
g.DrawLine(_penAxisMain, (float)(Graph.Bounds.Width / 2), 0, (float)(Graph.Bounds.Width / 2), (float)Bounds.Height);
g.DrawLine(_penAxisMain, 0, (float)(Graph.Bounds.Height / 2), Graph.Bounds.Width, (float)(Graph.Bounds.Height / 2));
return true;
}
//Painting the Graph
private void Graph_Paint(object sender, PaintEventArgs e)
{
base.OnPaint(e);
DrawAxis(e);
}
//Public method to draw curve on picturebox
public void DrawData(PointF[] points)
{
var bmp = Graph.Image;
var g = Graphics.FromImage(bmp);
g.DrawCurve(_penAxisMain, points);
Graph.Image = bmp;
g.Dispose();
}
When application starts, the axis are drawn. but when I call the DrawData method I get the exception that says bmp is null. What can be the problem?
I also want to be able to call DrawData multiple times to show multiple curves when user clicks some buttons. What is the best way to achive this?
Thanks
You never assigned Image, right? If you want to draw on a PictureBox’ image you need to create this image first by assigning it a bitmap with the dimensions of the PictureBox:
Graph.Image = new System.Drawing.Bitmap(Graph.Width, Graph.Height);
You only need to do this once, the image can then be reused if you want to redraw whatever’s on there.
You can then subsequently use this image for drawing. For more information, refer to the documentation.
By the way, this is totally independent from drawing on the PictureBox in the Paint event handler. The latter draws on the control directly, while the Image serves as a backbuffer which is painted on the control automatically (but you do need to invoke Invalidate to trigger a redraw, after drawing on the backbuffer).
Furthermore, it makes no sense to re-assign the bitmap to the PictureBox.Image property after drawing. The operation is meaningless.
Something else, since the Graphics object is disposable, you should put it in a using block rather than disposing it manually. This guarantees correct disposing in the face of exceptions:
public void DrawData(PointF[] points)
{
var bmp = Graph.Image;
using(var g = Graphics.FromImage(bmp)) {
// Probably necessary for you:
g.Clear();
g.DrawCurve(_penAxisMain, points);
}
Graph.Invalidate(); // Trigger redraw of the control.
}
You should consider this as a fixed pattern.

Windows Form, display indicator over a dynamically generated bitmap

I have a usercontrol that displays a gradient of colors which, once created will be constant.
The usercontrol doesn't contain any controls, not sure if i need to add a picturebox or dynamically add one.
Over that image, I'd like to display a line that will display what the current result is. I have no problem creating the gradient image on the map, however I'd like to somehow cache it so everytime I update the indicator (call CurrentValue from parent form), it will put the indicator line above the gradient image. This is updating about 30 times a second, thus, as of how the code below is working, it's repainting the gradient everytime, which is flickering.
Here's a code sample:
namespace Maps.UserControls
{
public partial class UserControlLegend : UserControl
{
private double m_CurrentValue;
public double CurrentValue
{
get
{
return m_CurrentValue;
}
set
{
m_CurrentValue = value;
RefreshValue();
}
}
public UserControlLegend()
{
InitializeComponent();
}
private void UserControlLegend_Paint(object sender, PaintEventArgs e)
{
if (b == null)
{
g = e.Graphics;
b = new System.Drawing.Bitmap(menuWidth, menuHeight, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
// Code here that draws Menu
// Cache bitmap here?
g.Dispose();
}
}
private void RefreshValue()
{
this.Refresh();
g = this.CreateGraphics();
g.DrawImage(b, 0, 0);
//Code to Calcuate current Indicator Location
int x3 = 0;
// Draws current indicator correctly
g.DrawLine(new Pen(new SolidBrush(Color.Black)), this.Width / 2 - 15, x3, this.Width / 2 - 5, x3);
g.Dispose();
}
}
}
Explained above in comments, used a bitmap, and just set the x,y of the control.
First, I'd suggest you set your Control's property DoubleBuffered to True, so that flickering goes away. However, if you don't draw on the Control itself, that will be not useful at all. Drawing on a PictureBox is better, however, becuase it is automatically DoubleBuffered.
Second, you are painting into a new Bitmap every time, which is very bad in terms of memory, since the Bitmap is a few megabytes in size. I'd suggest you have a single Bitmap initialized in the constructor, and a single Graphics, created from that Bitmap in the constructor, too. Every time the paint accurs, just clear the old Graphics g and then draw onto it again and again. g Graphics and b Bitmap should be Disposed one time only, when the entire Control is Disposed.
This may inhance your code.

how to clear panel in a simple paint application

I am working on a simple windows forms paint application. I am having problem in clearing the panel. The code i am using to draw is
private void panel1_Paint(object sender, PaintEventArgs e)
{
Graphics g = Graphics.FromImage(tempDraw);
Pen myPen = new Pen(foreColor, lineWidth);
g.DrawLine(myPen, x1, y1, x2, y2);
myPen.Width = 100;
myPen.Dispose();
e.Graphics.DrawImageUnscaled(tempDraw, 0, 0);
g.Dispose();
}
How to clear the panel?
Are drawing in the paint handler of the Panel instance? If not then calling Invalidate on the panel would do.
But you will probably be persisting the drawing items and so to clear them you would need to delete what has been drawn and then call Invalidate. You could also fill the Panel with a particular color using FillRect but that would be a dirty workaround and not fit your final design.
You should also check out CodeProject.com for examples like this one to give you an idea on what needs to be handled when creating a drawing app like this.
EDIT:
Per the edited answer, you cannot clear the panel with the existing logic. You are painting inside Paint handler of the form which will happen any time it needs to be redrawn. This means that you should change your approach. You need some sort of condition inside the Paint handler which decides whether or not it will paint anything at all. This is where the persistence of drawing objects comes in. If you want to create a drawing program then you will have to handle the mouse Down, Up and Move events over the panel objects and store the data in a points array. (As an example of one type of drawing.) Then in your Paint handler if the Points[] is not empty you draw the points. Otherwise you draw nothing... which ends up in an empty container. Then if you need to clear the drawing you delete the contents of the Points array and call Invalidate on the Panel. That will clear the persisted data and repaint to nothing.
You can use
Panel1.Invalidate();
But there is a problem with this, after you call this function it clears all the graphics from the panel, but it also recalls the function i.e.
private void panel1_Paint(object sender, PaintEventArgs e)
{
//This function is recalled after Panel1.Invalidate();
}
So solution is to make your paint code in some other function
private void MyDrawing()
{
Graphics g = Graphics.FromImage(tempDraw);
// if above line doesn't work you can use the following commented line
//Graphics g = Graphics.Panel1.CreateGraphics();
Pen myPen = new Pen(foreColor, lineWidth);
g.DrawLine(myPen, x1, y1, x2, y2);
myPen.Width = 100;
myPen.Dispose();
Panel1.Graphics.DrawImageUnscaled(tempDraw, 0, 0);
g.Dispose();
}
You'll have to draw over the panel again with whatever base colour you're using eg. white\grey with the Graphics.FillRectangle method:
// Create solid brush.
SolidBrush whiteBrush = new SolidBrush(Color.White);
// Create location and size of rectangle.
// Fill rectangle to screen.
e.Graphics.FillRectangle(whiteBrush, panel.Location.X, panel.Location.Y, panel.Width, panel.Height);
this.Invalidate();

Categories

Resources