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

Creating alternate GUI using Vector Art

0.00/5 (No votes)
6 Feb 2000 1  
Create visually complex, yet programmatically simple, non-rectangular GUIs
  • Download source files - 198 Kb
  • Download ArtCode Unsupported Application (Create your own polygons!) - 253 Kb
  • Sample Image - flowerpower.gif

    Introduction

    As Windows Programmers we know the following to be true:

    • All Windows must be rectangle.
    • All GUI components must have a 3D-chiseled-chrome-look.
    • GUIs look old and tired if they don't follow the look of the most current MS-Office suite or most current version of IE.
    • If you deviate from the above it will require a large amount of work, add a large amount of bugs, will little or no functional benefit.

    Luckily, this "facts" aren't true. I personally believe that we are starting to see a transition from hard-coded, MS-looking GUIs to more unique and sometimes photo-realistic GUIs for applications. Currently you can see this trend in applications that are "skinned" and commercial applications like the photo-realistic Gizmos. When Apples Os X is release in about a year, I believe we will see an explosion of alternate GUIs for both the Apple and Microsoft platforms.

    What Technologies Can Be Used for Alternate GUIs

    I feel that part of my job, as a software engineer, is to prepare for this future. To prepare I have been looking at several technologies:

    • GDI Graphics - This means hard-coding your components with MoveTo, LineTo and other familiar GDI calls. Most of the articles in www.codeproject.com that relate to custom GUI components use this technique. This technique is very flexible, however changes directly effect compiled code.
    • Bitmap Graphics - This technique uses bitmaps and hit regions to create a GUI component. This is a common technique - several articles can be found on www.codeproject.com that discusses this technique. This technique can be used with "skinning" to apply the actual graphical elements of the GUI at runtime. WinAmp is a good example of using this technique. This technique is straightforward (though I wouldn't say it's trivial). However bitmaps don't scale well, so if your GUI needs to resize you usually have to define bitmaps for each of the sizes you want to support.
    • Vector Graphics - Vector graphic components are just as easy for a graphic artist to create as bitmap graphics. With the right tools, it is trivial to convert vector graphics into a form that is directly usable in your application. Vector graphics have several advantages. They are easy to paint, they are easy to turn into regions, they are easy to hit-test, and they translate, rotate and scale easily. This article shows how to use this technique to create an interesting GUI. This approach should work with "skinning". Hopefully you will see how trivial vector graphics are to used in applications.
    • Rendered Graphics - This technique allows GUI components to be rendered on the fly. This means there is a definition for the components of the image that are rendered using an rendering engine such as DirectX or OpenGl. This technique is applied currently to games. However there is no reason that photo-realistic GUI components couldn't use this same approach. This approach should work with "skinning".

    Creating an GUI using Vector Art

    How to Get Vector Art

    I'm employed at a company where I have the good fortune to work closely with Industrial Artists and Human Factors Engineers. For the last several years, part of my job has been converting the Industrial Artist's art and Human Factors Engineer's behavior into elements for Windows applications. This has reinforced my opinion that most programmers are lousy artists and even worse interface-designers (just so you know I include myself in this broad, overly simplified generalization).

    At my company the preferred tool for use by an Industrial Artist is Adobe Illustrator. This tool allows the creation of art as bitmaps, vectors, or a combination of both. It is my observation is that artists prefer working in vector art. This is for practical reasons. Vectors can be defined as individual objects that can be ordered, translated, rotated, and scaled independently.

    I've also observed that Windows programmers have a mental block. All graphic problems are solved with bitmaps. Personally I hate bitmaps. They don't easily scale or rotate. They tend to be large. And you have to associate touch regions with the bitmap if you want to define any interesting behavior to them. In other word, bitmaps are difficult to work with and the result is often poor.

    Since the artists I associate with prefer working with vector art anyway, I've been asking for the vector form of their art for my projects. I then run the files through a tool that I've written that extracts the vectors and text and converts them into C++ code. This article shows how to use this type of data to create interesting applications.

    Disclaimer - Do not assume that the project shown here is either an example of good art or of good Human Factors design. It's just an example of what you can do with vectors.

    Thinking in Vectors

    Vector art is drawn the same way a painter paints. Elements of the image are painted from back to front with the later elements possibly obscuring some of the earlier elements. This means that you should never change the order that items are drawn. It also means that you should hit-test in the reverse order that items are drawn.

    An Example Polygon Conversion

    The following is an example of the output of a red circle. I created the drawing with CorelDraw. Saved the output as a Windows Meta File. I then run that file though a tool that I wrote that extracts, polygons, polylines, and text. The following code is output from this tool.

    #include "StdAfx.h"
    
    #include "ArtCode.h" // Contains code that does hit-test, drawing, and region 
    
    creation.
    
    static RECT bounds = {20, 20, 15980, 15740};
    
    static POINT Polygon0Points[] = {
    	{8000, 15740}, {8409, 15730}, {8814, 15700}, {9212, 15650}, {9605, 15580}, 
    {9990, 15492}, {10368, 15386}, {10739, 15262}, {11101, 15121}, {11454, 14963}, 
    	{11798, 14789}, {12133, 14600}, {12456, 14395}, {12769, 14176}, {13071, 
    13942}, {13361, 13694}, {13638, 13434}, {13903, 13161}, {14154, 12875}, 
    {14391, 12578}, 
    	{14614, 12270}, {14822, 11951}, {15015, 11622}, {15191, 11283}, {15351, 
    10935}, {15495, 10578}, {15620, 10213}, {15728, 9841}, {15818, 9461}, 
    {15888, 9074}, 
    	{15939, 8682}, {15970, 8284}, {15980, 7880}, {15970, 7477}, {15939, 7078}, 
    {15888, 6686}, {15818, 6299}, {15728, 5920}, {15620, 5547}, {15495, 5182}, 
    	{15351, 4825}, {15191, 4478}, {15015, 4139}, {14822, 3810}, {14614, 3490}, 
    {14391, 3182}, {14154, 2885}, {13903, 2600}, {13638, 2326}, {13361, 2066}, 
    	{13071, 1819}, {12769, 1585}, {12456, 1365}, {12133, 1160}, {11798, 971}, 
    {11454, 797}, {11101, 639}, {10739, 498}, {10368, 374}, {9990, 268}, 
    	{9605, 180}, {9212, 111}, {8814,  60}, {8409,  30}, {8000,  20}, {7590,  
    30}, {7186,  60}, {6787, 111}, {6395, 180}, {6009, 268}, 
    	{5631, 374}, {5261, 498}, {4899, 639}, {4545, 797}, {4201, 971}, {3867, 
    1160}, {3543, 1365}, {3230, 1585}, {2929, 1819}, {2639, 2066}, 
    	{2361, 2326}, {2097, 2600}, {1846, 2885}, {1608, 3182}, {1386, 3490}, 
    {1178, 3810}, {985, 4139}, {808, 4478}, {648, 4825}, {505, 5182}, 
    	{379, 5547}, {272, 5920}, {182, 6299}, {112, 6686}, { 61, 7078}, { 30, 
    7477}, { 20, 7880}, { 30, 8284}, { 61, 8682}, {112, 9074}, 
    	{182, 9461}, {272, 9841}, {379, 10213}, {505, 10578}, {648, 10935}, {808, 
    11283}, {985, 11622}, {1178, 11951}, {1386, 12270}, {1608, 12578}, 
    	{1846, 12875}, {2097, 13161}, {2361, 13434}, {2639, 13694}, {2929, 13942}, 
    {3230, 14176}, {3543, 14395}, {3867, 14600}, {4201, 14789}, {4545, 14963}, 
    	{4899, 15121}, {5261, 15262}, {5631, 15386}, {6009, 15492}, {6395, 15580}, 
    {6787, 15650}, {7186, 15700}, {7590, 15730}, {8000, 15740}, 
    };
    
    static PolygonEntry Polygon0 = {
    	"Polygon0",
    	{20, 20, 15980, 15740},
    	RGB(218, 37, 29),
    	1, // PolyFillMode
    
    	129,
    	Polygon0Points,
    };
    
    // Draw Vector image - will scale to clientRect so if you want
    
    // to maintain the images aspect ratio please make sure the clientRect
    
    // passed also maintains that aspect ratio of the object.
    
    void DrawObject(CDC* pDC, const CRect& clientRect) {
    	Draw(pDC, Polygon0, clientRect, bounds);
    }
    
    // Creates a region. Regions can be used for several things.
    
    // However the result of this call is usually used to create a
    
    // Window Region.
    
    void CreateRegion(CRgn& rgn, const CRect& clientRect) {
    	CreateRegion(rgn, Polygon0, clientRect, bounds);
    }
    
    // TRUE on an element in the vector drawing is hit.
    
    BOOL HitTestObject(const CPoint& point, const CRect& clientRect) {
    	if (HitTest(Polygon1, clientRect, bounds, point)) return TRUE;
    	return FALSE;
    }
    
    // Returns the aspect ratio of the image. To use this you do the following:
    
    // newWidth = AspectRatio()*window.Height();
    
    double AspectRatio() {
    	CRect objectRect(bounds);
    	return ((double) objectRect.Width())/((double) objectRect.Height());
    }

    Creating A Dialog Application

    To modify your dialog app all you need to do is set the window region to the regions of the combined vectors and then handle OnPaint messages. The logical place to set the window region is in OnInitDialog.

    OnInitDialog

    This code does the following:

    • Sets the windows rect to the aspect ratio of the vector drawing.
    • Gets the region for the combined vectors in the drawing.
    • Sets the window region to the region of the drawing.
    BOOL CFlowerPowerDlg::OnInitDialog()
    {
    	SetIcon(m_hIcon, TRUE);			// Set big icon
    
    	SetIcon(m_hIcon, FALSE);		// Set small icon
    
    
    	m_downItem = None;
    	
    	// Maintain aspect ratio of drawing
    
    	CRect rect;
    	GetWindowRect(&rect);
    	rect.right = ((int) (rect.Height()*AspectRatio()))+rect.left;
    	CRgn wndRgn;
    
    	// Size Window to new aspect ratio
    
    	MoveWindow(&rect, FALSE);
    
    	// Get Screen Rect and convert to client coords
    
    	GetWindowRect(&rect);
    	ScreenToClient(&rect);
    	
    	// Create a region for our window in client coords
    
    	CreateRegion(wndRgn, rect);
    
    	// Set Window region in client coords - the MS docs say this call is in 
    
    	// Screen Coords, but only client coords work.
    
    	SetWindowRgn(wndRgn, TRUE);
    	
    	CDialog::OnInitDialog();
    	return TRUE;  // return TRUE  unless you set the focus to a control
    
    }
    

    OnPaint

    OnPaint is the stock output from the AppWizard expect for the non-iconic drawing. When drawing non-icon version of the dialog you do the following:

    • Get the Windows dc
    • Create a memdc (to eliminate flickering on redraws)
    • Get the Window Rect (we are taking over the entire dialog area not just the client).
    • Cover it to Client Coords
    • Draw the Vector Image
    void CFlowerPowerDlg::OnPaint() 
    {
    	if (IsIconic())
    	{
    		CPaintDC dc(this); // device context for painting
    
    
    		SendMessage(WM_ICONERASEBKGND, (WPARAM) dc.GetSafeHdc(), 0);
    
    		// Center icon in client rectangle
    
    		int cxIcon = GetSystemMetrics(SM_CXICON);
    		int cyIcon = GetSystemMetrics(SM_CYICON);
    		CRect rect;
    		GetClientRect(&rect);
    		int x = (rect.Width() - cxIcon + 1) / 2;
    		int y = (rect.Height() - cyIcon + 1) / 2;
    
    		// Draw the icon
    
    		dc.DrawIcon(x, y, m_hIcon);
    	}
    	else
    	{
    		if (!m_bDialogUp) {
    			CWindowDC dc(this);
    			CMemDC pDC(&dc);
    			CRect rect;
    			GetWindowRect(&rect);
    			ScreenToClient(&rect);
    			DrawObject(&pDC, rect);
    			CDialog::OnPaint();
    		}
    	}
    }

    Creating Your Own Vector Code

  • Download ArtCode Unsupported Application - 253 Kb
  • To create your own C++ code out of vector art I've included the application I've written called ArtCode.exe. Just so you know, this application is complete hack. It was designed for one purpose, to turn vector art to code with the least work possible with the tools that I already had. I had Adobe Illustrator and CorelDraw. Because of that, the process of getting your vector art to C++ requires a reasonably current copy of CoreDraw (I've successfully used versions 6-9 for this). Here's the process (and yes it is quirky -- but it works):

    • Import your vector art into CorelDraw
    • Select the components you wish to convert to code.
    • Export the selected graphic component in Adobe Illustrator format (this step reduces the number of graphic primitives to the ones supported in the ArtCode.exe program).
    • Import that Adobe Illustrator File
    • Export the art as a Windows Metafile (I suggest you convert text to curve).
    • Open the Windows Metafile with ArtCode.exe
    • Select the Edit|Copy command (this generates the code and places it on the clipboard)
    • Paste the code into VC++
    • Add the ArtCode.h and ArtCode.cpp files from the FlowerPower project to your project.

    Additional Resources

    Rotation, translation, and Scaling of vector art is easy, but if you don't recall how to do this from your linear algebra class (mine was 20+ years ago) you will need either a good computer graphics book or a good linear algebra book. All you do is set up a transformation matrix and do a matrix multiply to perform one or more of these operations. Once you see it you'll discover how simple this really is. A decent calculator helps too (I love my TI-92).

    Comments about the Tool

    The ArtCode.exe program is completely unsupported. Please do not send me email asking for changes or to help you troubleshoot some problem you are having with it. It is very, very unlikely that I will respond to any such email. If you do get stuck and absolutely need help you might try one of the discussion forums on www.codeproject.com.

    The tool will not work if you try to open your run-of-the-mill Windows Metafiles. The tool supports a very, very small subset of Windows Metafile commands so you are guaranteed to get no graphics or a subset of your graphics if you import a typical WMF. The step of exporting to Adobe Illustrator format first is absolutely required in most cases.

    Conclusion

    The point of this article isn't to cause any serious changes in how you design GUIs. The goal is to stimulate some thought about where GUI development is going, and to help you prepare for the future that you see coming. It also should show you how much easier working with vector graphics can be than working with bitmaps graphics.

    Caveats

    You may use the code you find included in this project anyway you like. However, the art was derived from my legal copy of CorelDraw. I believe that it is legal for me to use the derived version of the art in my code, however please don't use this art in your project unless you own CorelDraw.

    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