Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Simple Vector Shapes

0.00/5 (No votes)
20 Apr 2016 1  
A 2D vector shapes and RTFcontrol editor

Screenshot - vectshapes1.jpg

Introduction

Hello. This is my 3rd article, after this one and this one. I propose a simple 2D vector graphic editor. It manages simple graphic objects like rectangles, lines and ellipses, other than images and RTF text. It shows most of the capabilities of GDI+, such as color transparency, pen style, start/end line cap and so on. All of the images shown in this article have been realized with the tool I propose.

Using the Code

Screenshot - vectshapes2.jpg

VectShape is the UserControl used to show and manipulate the objects. It contains the mouse event logic and the "extended" double buffer logic for rendering. It exposes a big set of methods. These methods are used mainly by toolBox, which is the control used to manipulate the shapes. Shapes is a collection of Ele. Ele is the base class for all of the objects: rectangles, boxText, Lines, Ellipses and imageBoxes. RichForm2 is the form used to edit RTF text. It's a simple form with a rich text box and a tool strip. Go to here for a better example of an RTF editor.

GDI+

When searching The Code Project for GDI+, you'll find many good articles that explain what GDI+ is and the basis of drawing in .NET. In this article, I do not address these basic problems. An in-depth article on GDI+ can be found here.

Add Controls to Your Own Form

Simply put the controls vectShaper and toolBox into your form. Then link toolBox to vectShaper in the form (i.e. Form1) constructor:

public Form1()
{
    InitializeComponent();

    // added for linking toolbox to vectshapes
    this.toolBox1.setVectShape(this.vectShapes1);            
}

Now you're ready to start drawing some shapes.

Points of Interest

Drawing Objects: Extending Double Buffer

You can find many in-depth articles about Double Buffering (DB) on The Code Project. With the following 2 images, I will demonstrate how double buffering works. The idea of DB is to make the drawing on a memory bitmap buffer and then draw the buffer over the control.

Screenshot - vectshapes3.jpg Screenshot - vectshapes4.jpg

In this project, I extended the double buffer with two memory buffers. One is for drawing static objects and one is for storing dynamic/moving objects. See the next image:

Screenshot - vectshapes5.jpg

  1. backBuffG=Graphics.fromimage(backBuff)
  2. draws static objects over backBuffG
  3. buffG=Graphics.fromimage(buff)
  4. draws bitmap backBuffG over buffG
  5. draws moving objects over buffG
  6. ctrG=ctr.CreateGraphics()
  7. draws bitmap buffG over ctrG

backBuff is declared outside of the control redraw method. By STATIC objects, I mean all objects that we know aren't going to be moved/added/deleted/updated. In this kind of application, all of the objects that are not selected are static objects. Steps 1 and 2 are done only when we update the static objects. So, we don't redraw static objects in the paint method, but only the bitmap that "contains" them (Step 4).

Screenshot - vectshapes7.jpg

In this example, the blue objects are static. The selected objects -- the green selection rectangle and the handle rectangle -- are all dynamic/moving objects. So when I add/remove/update an object, such as in the MouseUp event of the control, I redraw the control including Steps 1 and 2. When I move a selected object -- i.e. in the MouseMove event or in the OnPaint event of the control -- I do not need to perform Steps 1 and 2. The static objects are drawn because I stored them in backBuff.

Rendering RichTextBox (RTF) on a Graphic Object

Another interesting point in this project is the rendering of the RTF contained in a rich text box. The Graphics.DrawString method lets you do something like that, but with a significant limitation. You can specify only one font, color or dimension in the same string. So, it does not draw truly RTF text. I was interested in rendering RTF text, so I Googled it and found this. It explains how to print the RTF contained in a rich text box control. The article contains the code for creating RichTextBoxPrintCtrl and demonstrates how to use it:

[DllImport("USER32.dll")]
private static extern IntPtr SendMessage(IntPtr hWnd, int msg, 
    IntPtr wp, IntPtr lp);

// I extended the RichTextBoxPrintCtrl code by adding a Draw method:

public int Draw(int charFrom, int charTo, Graphics g, 
    int x, int y, int x1, int y1, double conversion)
{
    //Calculate the area to render and print
    RECT rectToPrint;
    rectToPrint.Top =(int)(y * conversion);
    rectToPrint.Bottom = (int)(y1 * conversion);
    rectToPrint.Left = (int)(x * conversion);
    rectToPrint.Right = (int)(decimal)(x1 * conversion);

    //Calculate the size of the page
    RECT rectPage;
    rectPage.Top = (int)(y * conversion);
    rectPage.Bottom = (int)(y1 * conversion);
    rectPage.Left = (int)(x * conversion);
    rectPage.Right =  (int)(x1 * conversion);
            
    IntPtr hdc = g.GetHdc();

    FORMATRANGE fmtRange;
    fmtRange.chrg.cpMax = charTo;//Indicate character from to character to 
    fmtRange.chrg.cpMin = charFrom;
    fmtRange.hdc = hdc;          //Use the same DC for measuring & rendering
    fmtRange.hdcTarget = hdc;    //Point at printer hDC
    fmtRange.rc = rectToPrint;   //Indicate the area on page to print
    fmtRange.rcPage = rectPage;  //Indicate size of page
            

    IntPtr res = IntPtr.Zero;

    IntPtr wparam = IntPtr.Zero;
    wparam = new IntPtr(1);

    //Get the pointer to the FORMATRANGE structure in memory
    IntPtr lparam = IntPtr.Zero;
    lparam = Marshal.AllocCoTaskMem(Marshal.SizeOf(fmtRange));
    Marshal.StructureToPtr(fmtRange, lparam, false);

    //Send the rendered data for printing 
    res = SendMessage(Handle, EM_FORMATRANGE, wparam, lparam);

    //Free the block of memory allocated
    Marshal.FreeCoTaskMem(lparam);

    //Release the device context handle obtained by a previous call
    g.ReleaseHdc(hdc);
            
    //Return last + 1 character printer
    return res.ToInt32();
}

After that, there was a problem. The background color of the control RichetextBox was also rendered, while I was interested rendering only the text. So I found this, which explains how to make a control transparent:

//START : make this control trasparent
[DllImport("kernel32.dll", CharSet = CharSet.Auto)]
static extern IntPtr LoadLibrary(string lpFileName);

protected override CreateParams CreateParams
{
    get
    {
        CreateParams prams = base.CreateParams;
        if (LoadLibrary("msftedit.dll") != IntPtr.Zero)
        {
            prams.ExStyle |= 0x020; // transparent
            prams.ClassName = "RICHEDIT50W";
        }
           return prams;
    }
}
// END

Et voilà. At this point, we have all we need to render real RTF text on a graphic object. I called it BoxTesto; it is a box (rectangle) with a start and an end point and all of the properties derived from the Ele class. In addition, it has an RTF property that contains a string with the RTF text. When I draw a BoxTesto, I create a "transparent" RichTextBoxPrintCtrl control and assign RichTextBoxPrintCtrl.rtf with BoxTesto.rtf. Then I call RichTextBoxPrintCtrl.Draw.

Undo/Redo

I choose to implement the undo/redo as a double-linked list. Each item on the list contains a reference to the object of interest, an old copy of the object and a new copy of the object. In the next image, I present the undo/redo list in blue and the modified objects in black.

Screenshot - vectshapes6.jpg

Every time I change an object, I store an element in the undo/redo list. When I need to undo an action, I copy the OLD object properties over the interested object properties. Then I go back one element in the undo/redo list. When I need to redo an action, I go forward one element in the undo/redo list. Then I copy the NEW object properties over the object referenced by the element. The "lazy" way: in my old project, I implemented undo/redo like a circular array where the objects stored were the entire collection of graphic objects. That was an easy and fast solution, but too memory-hungry.

Drawing Images (Rotation/Transparency)

Screenshot - vectshapes8.jpg

Now I focus on image drawing (method Draw, class ImgBox, file Shapes.cs) to show how to implement image rotation and transparency:

//get the back color from the first pixel (UP-LEFT)
Color backColor = this.img.GetPixel(0, 0); 
//Create a tmp Bitmap and a graphic object
// the dimension of the tmp bitmap must permit the rotation of img
int dim = 
    (int)System.Math.Sqrt(img.Width * img.Width + img.Height * img.Height);
Bitmap curBitmap = new Bitmap(dim, dim);
Graphics curG = Graphics.FromImage(curBitmap);

if (this.Rotation >
 0)
{
    // activate the rotation on the graphic obj
    Matrix X = new Matrix();
    X.RotateAt(this.Rotation, new PointF(curBitmap.Width / 2, 
        curBitmap.Height / 2));
    curG.Transform = X;        
    X.Dispose();
}
// I draw img over the tmp bitmap 
curG.DrawImage(img, (dim - img.Width) / 2, (dim - img.Height) / 2, 
    img.Width, img.Height);

if (this.Trasparent)
    curBitmap.MakeTransparent(backColor); // here I perform trasparency

curG.Save();
// I draw the tmp bitmap on canvas
g.DrawImage(curBitmap, new Rectangle(this.X + dx, 
    this.Y + dy, this.X1 - this.X, this.Y1 - this.Y));
    
curG.Dispose();
curBitmap.Dispose();

History

  • 15 June, 2007 -- Original version posted

Update 1

  1. Added ZoomIN/ZoomOut
  2. Added right mouse click canvas movement
  3. Added image rotation
  4. Added Transparent Image property

Update 2

  1. Added obj --> Group and obj --> deGroup functions
  2. Added a rotation handle for object rotation
    Screenshot - vectshapes9.jpg
  3. Added File--> Save selected objects / File --> Load objects
  4. Created Help page

Update 3

  1. Added Gradient Line Fill Color, i.e. draw a shape, fill it and then set the UseGradientLineColor property to true
  2. Added Group Resize / Rotation and Zoom Management
  3. Can show a Group like a GraphicPath, i.e. select some Lines and then select them, group them and set the graphPath property to true
  4. Added example *.shape files. Load them from File --> Load
    Screenshot - vectshapes10.jpg

Update 4

  1. Added Free Hand Pen Tool
  2. Added Obj-->Poly--> X/Y Mirror
  3. Added Obj-->Poly--> Merge
  4. Added Obj-->Poly--> Extract Points

Update 5

  1. Added Draw Graph Tool

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here