|

Introduction
My first article! I hope that this code will help someone... I searched through the web and I found many developers (like me) looking for a way to implement line drawing/editing, that seems to be a thing very hard to implement, due to C# limitations in this topic... GDI+ doesn't seem to come in help very much, and I decided to make a solution by myself, that seems to work pretty fine :D. Here it is.
Once Upon a Time...
I started thinking about a Line Custom Control, but the result was very ugly, due to problems about transparency and Z-order (you cannot select a control that's under another one)... I then realized that the line itself had to be written directly to the PictureBox in order to avoid these problems, and the PictureBox.Image must be rewritten every time...
When the DrawLinebutton has been clicked, two red markers are placed in the PictureBox and a line is drawn. The two markers are actually two custom controls, inherited from UserControl, but they simply are a red square (that can be personalized, a cross, a circle, etc,), with no code inside. At the marker control creation, I attach three mouse events (MouseDown, MouseMove, MouseUp) in order to allow user to drag them around the PictureBox. Each time a marker is moved, all the lines (and the background image too) are redrawn. Very simple, isn't it?
Let's see some code... private struct Line
{
public MarkControl mark1;
public MarkControl mark2;
public int Width;
}
This object represents a line drawn on the PictureBox. It contains information about the two markers (their position, property Center) and the line width (editable through right mouse buttons). Each line drawn is added to an array of Lines; each time the image is redrawn, all the lines are redrawn using this info.
private void Redraw()
{
if(bmpBack!=null)
image.Image = (Bitmap)bmpBack.Clone();
else
{
image.Image = new Bitmap(image.Width,image.Height);
Graphics.FromImage(image.Image).Clear(Color.White);
}
foreach(Line l in Lines)
{
DrawLine(l);
}
image.Refresh();
}
And this is the DrawLine function:
private void DrawLine(Line line)
{
Graphics g = null;
g = Graphics.FromImage(image.Image);
g.DrawLine(new Pen(Color.Black,(float)line.Width),
line.mark1.Center.X,line.mark1.Center.Y,
line.mark2.Center.X,line.mark2.Center.Y);
g.Dispose();
}
Amazing, it works! But there is a problem... When drawing lines on a big surface, like a desktop background, the program runs very slow, because the image is too heavy to be reloaded all the time... Then? I wrote a new Redraw(), that redraws all the lines in the same way, but refreshes only the region of the image that has been modified, through the Invalidate(Region region) method of the PictureBox... This trick code has to be a little enhanced, but it works very fine for now! If someone has a better idea, please tell me! Here's the code:
private void Redraw(Line line, Point p)
{
Graphics.FromImage(image.Image).DrawImage(bmpBack,0,0,
image.Image.Width, image.Image.Height);
foreach(Line l in Lines)
{
DrawLine(l);
}
Region r = getRegionByLine(line,p);
image.Invalidate(r);
image.Update();
}
private Region getRegionByLine(Line l, Point p)
{
GraphicsPath gp = new GraphicsPath();
gp.AddPolygon(new Point[]{l.mark1.Center,l.mark2.Center,p,l.mark1.Center});
RectangleF rf = gp.GetBounds();
gp.Dispose();
rf.Inflate(100f,100f);
return new Region(rf);
}
Line l is the line that has been moved, p is the Point where the marker has been moved... I calculate the region getting the triangle from the line and the point, then getting a rectangle that contains it, and then inflating it for a better result...
Lines Only?
This project can be modified as well, for other geometric figures like Circles, Ellipses, Rectangles, Polygons, and so on! Add more markers and use them as geometric points!
The End
Yeah, that's all folks ;) I hope that this article will help someone as other Code Project articles have helped me in my work everyday. Please send me feedbacks or/and advice! butch.er@tin.it.
Ciao!
| You must Sign In to use this message board. |
|
| | Msgs 1 to 25 of 38 (Total in Forum: 38) (Refresh) | FirstPrevNext |
|
|
 |
|
|
I have found great help from your article. In 'MarkControl' I need to dispaly some text under image, I place a 'Label' control and set it's value when control initilize (In constructor). It's working fine, but when characters increases I need to Wrap the Text. Because as number of charters increase it display it in single line. I'm unable to do Text wraping in label control.
Please help to figure out this problem.
Riz
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Hi, I'm very happy that you've find useful my "3-years-old" article
However, if you have the need to place a label, you can draw it, instead of placing a label control. Using the Graphics.DrawString(...), you can write text directly to the background, then rewriting it while moving the marker or at the end of it's movement (in the mouseup event) for better performances.
In this way, you can draw the text as you wish.
Bye!
Sam:"Better to reign in hell, than to serve in heaven", Milton. Max:"Heaven is a place were nothing ever happens", David Byrne.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Hi, I changed the PictureBoxSizemode from Normal to Zoom because I want to load a big images, but when I load my image and then I add some lines, the marks are not on the beggining and end of the line but the line is in the correct position/pixels.
Do you know how to move the marks to the correct position before add them??
I think you need to calculate the zoom ratio that the picturebox makes to the image and then recalculate the position of the marks respect the image and the line, but I don't know how to make it.
Can you help me please?
You can see it changing the sizemode to the picturebox and loading a big image and adding some lines.
Thanks
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Hi!
I think you have to do a 2D geometric transformation, trasform any single point (mark.center) and then redraw lines...
Uhm... but you've just said that lines draw themselves in the correct way, then, there is something that I don't understand... Lines just draw from marker-to-marker... Give me an hint
Sam:"Better to reign in hell, than to serve in heaven", Milton. Max:"Heaven is a place were nothing ever happens", David Byrne.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Hi, If you give me your mail I'll send you various screenshots about the problem.
Also you can see the problem first changing the property 'sizemode' of the picturebox to 'Zoom' and after loading a big Image or photo, and finally adding some lines to the image.
Give me your mail please. my mail is: jmpxupi@gmail.com
thanks
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Now I have two more problems.
I) Redraw() function works very fast when the pictureBox.Size = (747, 486) as I will increase the size of pictureBox like in my program pictureBox.Size = (1500, 1500). Redraw() function will take some more time while drawing lines?
II) I want to add a functionality in my program that when user want to see the relations between Entities and calculation points reached the top bound or left bound of PictureBox area it should automatically increase the pictureBox size. I used System.Windows.Forms.Panel control for achieving scrolling in my project, but as I increase the size of pictureBox when drawing points reached to the limitation of pictureBox it increase the size from bottom or right while I want it should increase from top and left.
Thanks in advance
Riz
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Your article is great. I have found great help out of it. But the technique u used becomes slower when number of record increases.
Actually I'm developing an application where user can see the associated relations and move the entities on MouseMove. Relationships have of two types I) 1 to 1 relationship II) 1 to many relationship
1 to 1 shown with Line, while 1 to many shown with Curve. Text should be displayed using MidPoint formula wit the help of Graphics.TranslateTransform() function.
Problem occurs when number of relationships exceed from 50.Redrawing does not work smoothly. Please let me know if u have any other idea to make it more faster on MouseMove.
Thanks in advance
Riz
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Ahrr, the usual problem ;D Performances!
Right, I'll try to give you some advice
1) Don't use "redraw all" thing while MouseMove event. Try to do it in the overrided OnPaint of the paintbox, setting a timer (this way you can set the redrawing frequency, hopefully increasing performances) or/and try to improve the GetRegionByLine to invalidate a smaller area...
2) GDI+ sucks, let me say it... If you can, try newest technologies, DirectX, opengl, etc...
3) There is a open source project, a photoshop-like application called Paint.Net, you can download both source and app... I hadn't seen the source code yet, but maybe you would find something useful...
Thank ya and bye! ;D
Sam:"Better to reign in hell, than to serve in heaven", Milton. Max:"Heaven is a place were nothing ever happens", David Byrne.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Thanks for suggestions.
Now I have two more problems.
I) Redraw() function works very fast when the pictureBox.Size = (747, 486) as I will increase the size of pictureBox like in my program pictureBox.Size = (1500, 1500). Redraw() function will take some more time while drawing lines?
II) I want to add a functionality in my program that when user want to see the relations between Entities and calculation points reached the top bound or left bound of PictureBox area it should automatically increase the pictureBox size. I used System.Windows.Forms.Panel control for achieving scrolling in my project, but as I increase the size of pictureBox when drawing points reached to the limitation of pictureBox it increase the size from bottom or right while I want it should increase from top and left.
Thanks in advance
Riz
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Hello,Mr.Dbutch
I repair your code,now I can draw a line and code below.now,how to change line's color when mouse move to line above ,and how to open a form when mouse click line.Thank you very mouch .
code:
/* * ******************************* * Line Editor by Marco Bellini * * * * Please send me feedbacks! : * * Dbutch.er@tin.it * * * * ...and share Knowledge! * * * * ******************************* */
using System; using System.Drawing; using System.Collections; using System.ComponentModel; using System.Windows.Forms; using System.Data;
using System.IO; using System.Drawing.Drawing2D; using System.Drawing.Imaging;
namespace LineEditor {
public class Form1 : System.Windows.Forms.Form { private IContainer components; private System.Windows.Forms.PictureBox image; private System.Windows.Forms.Button button1; Bitmap bmpBack = null; private bool isSelected = false; private int _X, _Y; private ArrayList Lines = null;
private struct Line { public MarkControl mark1; public MarkControl mark2; public int Width; }
public Form1() { InitializeComponent();
Lines = new ArrayList(); bmpBack = new Bitmap(image.Width, image.Height); Graphics.FromImage(bmpBack).Clear(Color.White); image.Image = (Bitmap)bmpBack.Clone();
this.SetStyle(ControlStyles.DoubleBuffer, true); this.SetStyle(ControlStyles.UserPaint, true); this.SetStyle(ControlStyles.AllPaintingInWmPaint, true); }
protected override void Dispose(bool disposing) { if (disposing) { if (components != null) { components.Dispose(); }
} base.Dispose(disposing); }
#region Windows Form Designer generated code private void InitializeComponent() { this.components = new System.ComponentModel.Container(); this.image = new System.Windows.Forms.PictureBox(); this.button1 = new System.Windows.Forms.Button(); ((System.ComponentModel.ISupportInitialize)(this.image)).BeginInit(); this.SuspendLayout(); // // image // this.image.BackColor = System.Drawing.SystemColors.ActiveCaptionText; this.image.BorderStyle = System.Windows.Forms.BorderStyle.Fixed3D; this.image.Location = new System.Drawing.Point(19, 17); this.image.Name = "image"; this.image.Size = new System.Drawing.Size(480, 431); this.image.TabIndex = 3; this.image.TabStop = false; // button1 // this.button1.Location = new System.Drawing.Point(509, 17); this.button1.Name = "button1"; this.button1.Size = new System.Drawing.Size(134, 43); this.button1.TabIndex = 11; this.button1.Text = "Draw Line"; this.button1.Click += new System.EventHandler(this.button1_Click); //
// Form1 // this.AutoScaleBaseSize = new System.Drawing.Size(6, 14); this.AutoScroll = true; this.BackColor = System.Drawing.SystemColors.Control; this.ClientSize = new System.Drawing.Size(657, 242); this.Controls.Add(this.button1); this.Controls.Add(this.image); this.Name = "Form1"; this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent; this.Text = "Line Editor"; ((System.ComponentModel.ISupportInitialize)(this.image)).EndInit(); this.ResumeLayout(false);
} #endregion
[STAThread] static void Main() { Application.Run(new Form1()); }
private void button1_Click(object sender, System.EventArgs e) { try { //Adds red marks that are the beginning/end of the line MarkControl mark1 = new MarkControl(); mark1.Location = new Point(50, 50); image.Controls.Add(mark1);
MarkControl mark2 = new MarkControl(); mark2.Location = new Point(100, 100); image.Controls.Add(mark2);
//Line Struct contains the information for a single line Line line = new Line(); line.mark1 = mark1; line.mark2 = mark2; line.Width = 1;
//Events for moving marks mark1.MouseUp += new System.Windows.Forms.MouseEventHandler(this.Mark_MouseUp); mark1.MouseDown += new System.Windows.Forms.MouseEventHandler(this.Mark_MouseDown); mark1.MouseMove += new System.Windows.Forms.MouseEventHandler(this.Mark_MouseMove);
mark2.MouseUp += new System.Windows.Forms.MouseEventHandler(this.Mark_MouseUp); mark2.MouseDown += new System.Windows.Forms.MouseEventHandler(this.Mark_MouseDown); mark2.MouseMove += new System.Windows.Forms.MouseEventHandler(this.Mark_MouseMove);
//Adds Line object to an arraylist Lines.Add(line); Redraw(); } catch (Exception ex) { MessageBox.Show(ex.Message); } }
//Simply draws a line private void DrawLine(Line line) { Graphics g = null;
g = Graphics.FromImage(image.Image); g.DrawLine(new Pen(Color.Black, (float)line.Width), line.mark1.Center.X, line.mark1.Center.Y, line.mark2.Center.X, line.mark2.Center.Y); g.Dispose(); }
//Redraws all the lines and a part of the background private void Redraw(Line line, Point p) {
Graphics.FromImage(image.Image).DrawImage(bmpBack, 0, 0, image.Image.Width, image.Image.Height);
foreach (Line l in Lines) { DrawLine(l); } image.Update(); }
//Redraws all the lines and the background too private void Redraw() { if (bmpBack != null) image.Image = (Bitmap)bmpBack.Clone(); else { image.Image = new Bitmap(image.Width, image.Height); Graphics.FromImage(image.Image).Clear(Color.Transparent); }
foreach (Line l in Lines) { DrawLine(l); } image.Refresh(); }
private void Mark_MouseDown(object sender, MouseEventArgs e) { this.SuspendLayout(); isSelected = true; _X = e.X; _Y = e.Y; }
private void Mark_MouseMove(object sender, MouseEventArgs e) { if (isSelected) { MarkControl mc1 = (MarkControl)sender; Line l = getLineByMark(mc1);
Point p = new Point(e.X - _X + mc1.Left, e.Y - _Y + mc1.Top);
mc1.Location = p;
Redraw(l, p); } }
private void Mark_MouseUp(object sender, MouseEventArgs e) { isSelected = false; ResumeLayout(); Redraw(); }
//Retrieves a mark having the other one private MarkControl getOtherMark(MarkControl m) { foreach (Line l in Lines) { if (l.mark1 == m) return l.mark2;
if (l.mark2 == m) return l.mark1; }//Never happens  throw new Exception("No relative mark found"); }
//Retrieves a Line object having a mark private Line getLineByMark(MarkControl m) { foreach (Line l in Lines) { if (l.mark1 == m || l.mark2 == m) return l; }//Never happens  throw new Exception("No line found"); }
public class MarkControl : UserControl { private Container components = null;
public MarkControl() { InitializeComponent(); this.BackColor = Color.Red; }
public Point Center { get { return new Point(Location.X + 4, Location.Y + 4); } }
protected override void OnPaint(PaintEventArgs e) { // }
protected override void Dispose(bool disposing) { if (disposing) { if (components != null) { components.Dispose(); } } base.Dispose(disposing); }
#region Component Designer generated code
private void InitializeComponent() { this.SuspendLayout(); // // MarkControl // this.Name = "MarkControl"; this.Size = new System.Drawing.Size(9, 9); this.ResumeLayout(false);
} #endregion
}
} }
My email:junzhi_li@hotmail.com & remi_lijunzhi@yahoo.com.cn evryone can solve it ,thank you very much!!
strong man,Aha.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
The first additional function I would like to add to this project is a resizable graphic area to match the Form window size.
So I changed the anchor property of button1 to Top, Right and the anchor property of image to Top,Bottom,Left,Right.
Then I added the resize event: void ImageResize (object sender , System.EventArgs e) { bmpBack = new Bitmap (image.Width , image.Height) }
These changes introduced 2 new problems. 1 - now you see rubber banding whenever you drag a line end marker 2 - the ImageResize needs additional code to proportionally scale up or down the existing lines (as occurs with current commercial graphic software applications)
Any ideas on how to solve these problems?
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
You have to "transform" every single point of every single line, then redraw them all... How is your knowledge about analytic geometry? 
Or more easily, set the anchor of every Mark before inserting them and see what happens
Sam:"Better to reign in hell, than to serve in heaven", Milton. Max:"Heaven is a place were nothing ever happens", David Byrne.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
I started this way...
void ImageResize(object sender, System.EventArgs e) { double Scale1 = image.Width / bmpBack.Width; int LCount = Lines.Count; for (int i = 0 ; i < LCount ; i++) { Line l = (Line)Lines[i]; MarkControl mc1 = l.mark1; MarkControl mc2 = l.mark2; Point p1 = new Point(Convert.ToInt16(mc1.Location.X * Scale1) , Convert.ToInt16(mc1.Location.Y * Scale1)); mc1.Location = p1; Point p2 = new Point(Convert.ToInt16(mc2.Location.X * Scale1) , Convert.ToInt16(mc2.Location.Y * Scale1)); mc2.Location = p2;
but then hit 2 problems... that scaling factor isnt quite right and I havent figured out how to update the lines. All attempts so-far have had horrible results.
mmmm... I suppose this is far from the anchor idea you just mentioned... sigh... Thx for being there and even bigger thx for such a great idea by producing this article.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Solved.
Global variables to record the previous image size, although the data is probably available in other arguments.
private int iW = 400; private int iH = 540;
and a custom redraw in the resize to avoid another resize evernt that is caused by the existing redraw function
void ImageResize(object sender, System.EventArgs e) { if(image.Width != iW) { double Scale1 = Convert.ToDouble(image.Width) / Convert.ToDouble(iW); iW = image.Width; iH = image.Height; int LCount = Lines.Count; for (int i = 0 ; i < LCount ; i++) { Line l = (Line)Lines[i]; MarkControl mc1 = l.mark1; MarkControl mc2 = l.mark2; Point p1 = new Point(Convert.ToInt16(mc1.Location.X * Scale1) , Convert.ToInt16(mc1.Location.Y * Scale1)); mc1.Location = p1; Point p2 = new Point(Convert.ToInt16(mc2.Location.X * Scale1) , Convert.ToInt16(mc2.Location.Y * Scale1)); mc2.Location = p2; l.mark1 = mc1; l.mark2 = mc2; } // // custom redraw, as redraw void causes another resize! Graphics.FromImage(image.Image).Clear(Color.Transparent); foreach(Line l in Lines) { DrawLine(l); } image.Refresh(); } }

|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
i have no enouht time to read it throuth, but i can see that you use double buffer to draw your line. If your drawing lines are so many that you will feel slowly to refresh the picture.In Actually project, not only the so few lines,so efficency is your nature fault. If you get anothrer idea ,send me feedback with email:nn8137@163.com
|
| Sign In·View Thread·PermaLink | 1.00/5 (1 vote) |
|
|
|
 |
|
|
Ehm, I don't get it, nn. Speak in plain english, please, it isn't my mother language...
Sam:"Better to reign in hell, than to serve in heaven", Milton. Max:"Heaven is a place were nothing ever happens", David Byrne.
|
| Sign In·View Thread·PermaLink | 5.00/5 (1 vote) |
|
|
|
 |
|
|
Hi ..
I think u already show us how to solve problem with an easier way, btw have u got any chance to convert the code with more oop style ?
It nice to have it ...

regards
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Yeah, it would be nice, but I'm a codeproject noob... ehm... How can I update the article? 
The code is ready to be posted
Sam:"Better to reign in hell, than to serve in heaven", Milton. Max:"Heaven is a place were nothing ever happens", David Byrne.
|
| Sign In·View Thread·PermaLink | 2.00/5 (1 vote) |
|
|
|
 |
|
|
I was just looking for something like that (for a rotation tool for figures) - good idea !
But - working in standard C++(VStudio2005) - Ive a problem with the new operator
m_pImage = (Bitmap*) new Bitmap(rBounds.Width,rBounds.Height,&m_Graphics); always shows an error !
error C2660: 'Gdiplus::GdiplusBase::operator new' : function does not take 3 arguments
I dont understandwhy there is a problem with new and the Bitmap constructor
And new returns a pointer to the GDIBase object - so I need the Image* and not Image object - how does this work in your code without a pointer ?
anyone an idea what I do wrong Thanks a lot in advance
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
FriendOfAsherah wrote: how does this work in your code without a pointer ?
Because there aren't pointers in C# 
However, I'm not a C++.Net expert, but I've tried this code and it seems to work:
Graphics^ g = pictureBox1->CreateGraphics(); Bitmap bmp = gcnew Bitmap(100,100,g);
Otherwise, you can start programming in C#, instead of lashing yourself
Sam:"Better to reign in hell, than to serve in heaven", Milton. Max:"Heaven is a place were nothing ever happens", David Byrne.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
buth.er - thx for the hint ...
gcnew resulted in "undeclared identifier- compile error"
but this gave me an idea where to look in the MSDN and found out that
#ifdef DEBUG #define new DEBUG_NEW #endif
before new is used in the file solves the problem
Anyone here to convert tons of code from c++ to c# for free ?  So I have to continue using c++ for this mega-project
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Yo Butch.er,
This is a brilliant little piece of code! Helped me a lot! What i like about it is its so SIMPLE! Yeah i like simple easy to understand examples! Thank you!
bfengineer.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Thank ya ;D
Sam:"Better to reign in hell, than to serve in heaven", Milton. Max:"Heaven is a place were nothing ever happens", David Byrne.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Hi!
I thought someone here would be able to help me, I am trying to write a program in C# in which the user will be able to use an eraser to erase parts of a bitmap, like the one in Ms Paint to be clear.
The bitmap will only be white and red so if the user tries to erase something, it becomes white.
Also i was thinking..the matrix I will be using is made up of a matrix which does not always have the same size and what i do is to resize it so that it fits the picture box. However after the user edits the Bitmap I must be able to reflect the changes back to the original matrix..if I resize the bitmap back to its original size, will it maintain its original state, appart of course the pixels which have been modified by the user or will there be some serious distortions??
any suggestions?
thank you very much
Mark
|
| Sign In·View Thread·PermaLink | 2.00/5 (1 vote) |
|
|
|
 |
|
|
Have you seen the open source project Paint.Net? Google it, I think you'll find it very useful!
Sam:"Better to reign in hell, than to serve in heaven", Milton. Max:"Heaven is a place were nothing ever happens", David Byrne.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
General News Question Answer Joke ![]()
|