|
|||||||||||||||||||||||
|
|||||||||||||||||||||||
|
Announcements
Chapters
Services
Feature Zones
|
IntroductionHello. 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
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 FormSimply put the controls 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 InterestDrawing Objects: Extending Double BufferYou 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.
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:
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 Rendering RichTextBox (RTF) on a Graphic ObjectAnother interesting point in this project is the rendering of the RTF contained in a rich text box. The [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 //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 Undo/RedoI 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.
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)
Now I focus on image drawing (method //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
Update 1
Update 2
Update 3
Update 4
|
||||||||||||||||||||||