Download source files - 198 Kb
Download ArtCode Unsupported Application (Create your own polygons!) - 253 Kb
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,
129,
Polygon0Points,
};
void DrawObject(CDC* pDC, const CRect& clientRect) {
Draw(pDC, Polygon0, clientRect, bounds);
}
void CreateRegion(CRgn& rgn, const CRect& clientRect) {
CreateRegion(rgn, Polygon0, clientRect, bounds);
}
BOOL HitTestObject(const CPoint& point, const CRect& clientRect) {
if (HitTest(Polygon1, clientRect, bounds, point)) return TRUE;
return FALSE;
}
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);
SetIcon(m_hIcon, FALSE);
m_downItem = None;
CRect rect;
GetWindowRect(&rect);
rect.right = ((int) (rect.Height()*AspectRatio()))+rect.left;
CRgn wndRgn;
MoveWindow(&rect, FALSE);
GetWindowRect(&rect);
ScreenToClient(&rect);
CreateRegion(wndRgn, rect);
SetWindowRgn(wndRgn, TRUE);
CDialog::OnInitDialog();
return TRUE;
}
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);
SendMessage(WM_ICONERASEBKGND, (WPARAM) dc.GetSafeHdc(), 0);
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;
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.