Programming Graphics
Typically a GUI application will at some point have cause to generate graphical
output. In AtOne the development of code to create graphical output is
significantly simplified through extensive encapusalation of WIN32 graphics
related API. Beyond this encapsulation AtOne further simplifies the task
through the extensive library of classes available to construct complex
graphics.
The Graphics Context
Before we can draw to the screen we need a graphics context. In WIN32 this
is obtained through calls to ::GetDC() and ::ReleaseDC().
In AtOne the graphics context is managed by the class GuiGraphicsContext
defined in guigdc.hpp. The class is defined as,
class GuiGraphicsContext : public OsiReferenceCount
{
.
.
.
public:
GuiGraphicsContext(GuiPalette* pPalette
= 0);
virtual ~GuiGraphicsContext();
const OsdMutex*
mutex() const;
virtual GuiGraphicsContextType
type() const = 0;
virtual bool
isType(GuiGraphicsContextType) const = 0;
virtual void
beginDraw() = 0;
virtual int
endDraw() = 0;
virtual bool
synchronisePalette();
HDC
handle() const;
int
drawCount() const;
bool
isValid() const;
int
numberOfPlanes() const;
int
bitsPerPixel() const;
//drawing object management methods
GuiBrush*
popBrush();
void
pushBrush(GuiBrush* pBrush);
GuiFont*
popFont();
void
pushFont(GuiFont* pFont);
GuiPen*
popPen();
void
pushPen(GuiPen* pPen);
GuiRegion*
popClip();
void
pushClip(GuiRegion* pRegion);
bool
moveClip(int x, int y);
GuiPalette*
palette() const;
void
palette(GuiPalette* pPalette);
//Point mapping methods
GuiPoint
mapPhysicalToLogical(const GuiPoint& rPt) const;
GuiRect
mapPhysicalToLogical(const GuiRect& rRect) const;
GuiPoint
mapLogicalToPhysical(const GuiPoint& rPt) const;
GuiRect
mapLogicalToPhysical(const GuiRect& rRect) const;
//Mapping mode methods
GuiMapMode
mapMode() const;
GuiMapMode
mapMode(GuiMapMode aMode);
GuiDimension
physicalExtext(int dx, int dy);
GuiDimension
physicalExtext(const GuiDimension& rExtent);
GuiPoint
physicalOrigin(int x, int y);
GuiPoint
physicalOrigin(const GuiPoint& rOrigin);
GuiDimension
logicalExtent(int dx, int dy);
GuiDimension
logicalExtent(const GuiDimension& rExtent);
GuiPoint
logicalOrigin(int x, int y);
GuiPoint
logicalOrigin(const GuiPoint& rOrigin);
GuiDimension
physicalExtext() const;
GuiPoint
physicalOrigin() const;
GuiDimension
logicalExtent() const;
GuiPoint
logicalOrigin() const;
//Device context infomation methods
int
mmHeight() const;
int
mmWidth() const;
int
pixelHeight() const;
int
pixelWidth() const;
int
pixelsPerInchX() const;
int
pixelsPerInchY() const;
GuiRect
clippingExtent() const;
virtual GuiRect
visibleArea() const = 0;
virtual bool
invalidateArea(const GuiRect& Rect, bool EraseBackground = true) =
0;
virtual GuiRect
invalidArea() const = 0;
//gdi path creation methods
bool
beginPath();
GuiPath*
endPath();
//bitmap related output methods
bool
bitBlt(int xDest, int yDest, int nWidth, int nHeight, const GuiGraphicsContext*
pGC, int xSrc, int ySrc, DWORD rop = SRCCOPY);
bool
bitBlt(const GuiPoint& rDestOrg, const GuiDimension& rDestSize,
const GuiGraphicsContext* pGC, const GuiPoint& rSrcOrg, DWORD rop =
SRCCOPY);
bool
stretchBlt(int xDest, int yDest, int DestWidth, int DestHeight, const GuiGraphicsContext*
pGC, int xSrc, int ySrc, int SrcWidth, int SrcHeight, DWORD rop = SRCCOPY);
bool
stretchBlt(const GuiPoint& rDestOrg, const GuiDimension& rDestSize,
const GuiGraphicsContext* pGC, const GuiPoint& rSrcOrg, const GuiDimension&
rSrcSize, DWORD rop = SRCCOPY);
bool
patBlt(int xDest, int yDest, int nWidth, int nHeight, DWORD rop = PATCOPY);
bool
patBlt(const GuiPoint& rDest, const GuiDimension& rDimension, DWORD
rop = PATCOPY);
bool
maskBlt(int xDest, int yDest, int nWidth, int nHeight, const GuiMemoryGC*
pGC, int xSrc, int ySrc);
bool
maskBlt(const GuiPoint& rDestOrg, const GuiDimension& rDestSize,
const GuiMemoryGC* pGC, const GuiPoint& rSrcOrg);
bool
colourBlt(int xDest, int yDest, int nWidth, int nHeight, const GuiColour&
rFillColour, const GuiMemoryGC* pGC, int xSrc, int ySrc);
bool
colourBlt(const GuiPoint& rDestOrg, const GuiDimension& rDestSize,
const GuiColour& rFillColour, const GuiMemoryGC* pGC, const GuiPoint&
rSrcOrg);
//Line output methods
GuiPoint
moveTo(int x, int y);
GuiPoint
moveTo(const GuiPoint& rPt);
bool
lineTo(int x, int y);
bool
lineTo(const GuiPoint& rPt);
GuiDrawMode
drawMode() const;
GuiDrawMode
drawMode(GuiDrawMode aMode);
//Ellipse output methods
bool
ellipse(int x1, int y1, int x2, int y2);
bool
ellipse(const GuiPoint& rTopLeft, const GuiPoint& rBottomRight);
bool
ellipse(const GuiRect& rRect);
//Arc output methods
bool
arc(int x1, int y1, int x2, int y2, int x3,int y3, int x4, int y4);
bool
arc(const GuiPoint& rPt1, const GuiPoint& rPt2, const GuiPoint&
rPt3, const GuiPoint& rPt4);
bool
arc(const GuiRect& rRect, const GuiPoint& rPt1, const GuiPoint&
rPt2);
//Miscelaneous output methods
bool
pie(const GuiRect& rRect, const GuiPoint& rPt1, const GuiPoint&
rPt2);
bool
fill(const GuiRect& rRect, const GuiBrush* pBrush = 0);
bool
frame(const GuiRect& rRect, const GuiBrush* pBrush = 0);
bool
chord(const GuiRect& rRect, const GuiPoint& rPt1, const GuiPoint&
rPt2);
bool
circle(const GuiPoint& rPt, int nRadius);
bool
icon(const GuiPoint& rPt, const GuiIcon* pIcon);
bool
bitmap(const GuiPoint& rPt, const GuiBitmap* pBitmap);
//polyline output methods
bool
polygon(const GuiPoint* pPoints, int nCount);
bool
polygon(const GuiRect& rRect);
bool
polyline(const GuiPoint* pPoints, int nCount);
GuiPolyFillMode
polyFillMode() const;
GuiPolyFillMode
polyFillMode(GuiPolyFillMode aMode);
//pixel output methods
GuiColour
pixel(int x, int y);
GuiColour
pixel(int x, int y, const GuiColour& rColour);
GuiColour
pixel(const GuiPoint& rPt1);
GuiColour
pixel(const GuiPoint& rPt1, const GuiColour& rColour);
//rectangle output methods
bool
rectangle(int x1, int y1, int x2, int y2);
bool
rectangle(const GuiPoint& rTopLeft, const GuiPoint& rBottomRight);
bool
rectangle(const GuiRect& rRect);
bool
roundRectangle(int x1, int y1, int x2, int y2, int CornerWidth, int CornerHeight);
bool
roundRectangle(const GuiPoint& rTopLeft, const GuiPoint& rBottomRight,
const GuiDimension& rCornerDim);
bool
roundRectangle(const GuiRect& rRect, const GuiDimension& rCornerDim);
//Text output methods
bool
text(int x, int y, const string pText);
bool
text(int x, int y, const string pText, int nLen);
bool
text(const GuiPoint& rPt, const string pText);
bool
text(const GuiPoint& rPt, const string pText, int nLen);
GuiColour
textColour() const;
GuiColour
textColour(const GuiColour& rColour);
GuiColour
backgroundColour() const;
GuiColour
backgroundColour(const GuiColour& rColour);
GuiTextMode
textBackgroundMode() const;
GuiTextMode
textBackgroundMode(GuiTextMode aMode);
GuiDimension
textDimension(const string pText) const;
GuiDimension
textDimension(const string pText, int nLen) const;
GuiTextAlign
textAlign() const;
GuiTextAlign
textAlign(const GuiTextAlign& anAlignment);
//gdi path output methods
bool
fillPath(const GuiPath* pGdiPath);
bool
strokePath(const GuiPath* pGdiPath);
bool
strokeAndFillPath(const GuiPath* pGdiPath);
};
This all the functionality of a WIN32 GDC is encapsulated into a relatively
compact class. Note that all primative graphics operations have corresponding
methods in GuiGraphicsContext and all drawing aids are managed and
selected into graphics contexts through corresponding stacks. That is,
we select and deselect pens, brushes, fonts, and clipping regions through
the methods pushPen(), popPen(), pushBrush(), popBrush(),
pushFont(),
popFont(),
pushClip()
and popClip(). The pens, brushes, fonts and clipping regions are
conveniently specified and created using the corresponding logical and
physical GDI object wrappers found in guigobj.hpp. Note that the
GuiGraphicsContext class is an abstract class which cannot be instanced.
Actual graphics contexts that can be used are derived from this class and
include GuiWindowGC,
GuiMemoryGC, GuiMetafileGC and
GuiPrinterGC, which correspond to window output, bitmap output,
metafile output and printer output respectively. When isDrawable(true)
is called on a GuiWindow object then that window will create a GuiWindowGC
that is used to draw to the window client area. Most of methods in GuiGraphicsContext
are self explanatory and will be discussed no further. Should you required
clarification of what a particular method does please refer to the code
in guigdc.hpp and guigdc.cpp.
The one aspect that is not self evident, however, is initiating graphical
output. When we perform graphical output in WIN32 we need a GDC and can
get one by calling ::GetDC() and subsequently release it calling
::ReleaseDC().
This process is mirrored in AtOne by calling the
beginDraw() and
endDraw()
methods of GuiGraphicsContext. The same rules follow. Therefore
when you wish to draw with a graphics context you must first call beginDraw(),
perform the appropriate graphical operations and then call endDraw()
to
complete drawing. Failure to do so will result in errant behaviour and
possible memory leakage. The only exception to this is when processing
paint messages in the paint() method of
GuiWindow. In this
method the beginPaint() method of GuiWindowGC has been called
by the framework to set up the GDC that you will draw with, and a corresponding
call is made to the endPaint() method after the paint() method
returns.
To output graphics to a window you will typically use the graphics context
owned by that window. However, another common and very useful technique
is drawing in memory to a bitmap. You can easily do this using the GuiMemoryGC
graphics context class. One major feature of this class that is curiously
absent from other frameworks is the ability to easily do transparent bit
blitting operations. In WIN32 this typically involves a reasonably complex
set of operations to acheive the desired output. In AtOne this has been
simplified through encapsulation making transparent blitting and the creating
of mask bitmaps a breeze. A sample of such graphics context usage is shown
below.
bool GuiStaticBitmap::onCreate(GuiWM_CREATE*
pMessage)
{
GuiResId
ResId(caption(), handleInstance());
GuiBitmap
Bitmap(ResId);
GuiLogPen
BorderPen(BorderColour);
GuiLogBrush NullBrush(StockBrushNull);
GuiLogBrush BackgroundBrush(TransparentColour);
GuiWindowGC* pWindowGC;
int
nBmpWidth = Bitmap.size().width();
int
nBmpHeight = Bitmap.size().height();
int
nBorderWidth = ((style() & GuiStaticBitmap_BORDER) != 0) ? 1
: 0;
int
nShadowWidth = ((style() & GuiStaticBitmap_SHADOW) != 0) ? 4
: 0;
GuiRect
BmpRect(0,
0,
nBmpWidth + nShadowWidth + 2 * nBorderWidth,
nBmpHeight + nShadowWidth + 2 * nBorderWidth);
//Create our window graphics context
pWindowGC = graphicsContext();
if (pWindowGC != 0)
{
int nNumberOfPlanes;
int nBitsPerPixel;
pWindowGC->beginDraw();
nNumberOfPlanes = pWindowGC->numberOfPlanes();
nBitsPerPixel
= pWindowGC->bitsPerPixel();
//Create our memory graphics
context for generating the bitmap image in
ImageGC = new GuiMemoryGC(GuiLogBitmap(BmpRect.width(),
BmpRect.height(), nBitsPerPixel, nNumberOfPlanes));
if (ImageGC != 0)
{
ImageGC->beginDraw();
//Draw the
bitmap onto the transparent colour background
ImageGC->pushBrush(BackgroundBrush.create());
ImageGC->fill(BmpRect);
ImageGC->textColour(TextColour);
ImageGC->backgroundColour(TransparentColour);
delete (ImageGC->popBrush());
ImageGC->bitmap(GuiPoint(0,
0), &Bitmap);
if ((style()
& GuiStaticBitmap_BORDER) != 0)
{
//Draw the bitmap border
ImageGC->pushPen(BorderPen.create());
ImageGC->pushBrush(NullBrush.create());
ImageGC->rectangle(0, 0, nBmpWidth + 2 * nBorderWidth, nBmpHeight + 2 *
nBorderWidth);
delete ImageGC->popBrush();
delete ImageGC->popPen();
}
if ((style()
& GuiStaticBitmap_SHADOW) != 0)
{
//Create a mask of the bitmap so that we can draw the bitmap shadow
ImageGC->createMask(TransparentColour);
//draw the shadow
ImageGC->pushBrush(BackgroundBrush.create());
ImageGC->fill(BmpRect);
delete ImageGC->popBrush();
ImageGC->colourBltTo(ImageGC, nShadowWidth, nShadowWidth, BmpRect.width(),
BmpRect.height(), ShadowColour, 0, 0);
ImageGC->bitmap(GuiPoint(0, 0), &Bitmap);
if ((style() & GuiStaticBitmap_BORDER) != 0)
{
//Draw the bitmap border
ImageGC->pushPen(BorderPen.create());
ImageGC->pushBrush(NullBrush.create());
ImageGC->rectangle(0, 0, nBmpWidth + 2 * nBorderWidth, nBmpHeight + 2 *
nBorderWidth);
delete ImageGC->popBrush();
delete ImageGC->popPen();
}
}
//Create a
mask of the bitmap
ImageGC->createMask(TransparentColour,
true);
ImageGC->endDraw();
}
pWindowGC->endDraw();
GuiRect NewExtent(extent());
NewExtent.right(NewExtent.left()
+ BmpRect.width());
NewExtent.bottom(NewExtent.top()
+ BmpRect.height());
extent(NewExtent);
}
return (true);
}
This onCreate() event handler loads a bitmap from resource, blits
it into a memory device context and applies other formatting depending
upon the control style and ultimately creates a transparent blit mask (in
the call to createMask()). Then in the paint message of the control
the bitmap is transparently bit blitted to the window client area with,
bool GuiStaticBitmap::paint(GuiWindowGC* pGC)
{
if (ImageGC != 0)
{
GuiRect Extent(pGC->visibleArea());
ImageGC->beginDraw();
pGC->maskBlt(0, 0, Extent.width(),
Extent.height(), ImageGC, 0, 0);
ImageGC->endDraw();
}
return (true);
}
Users of zApp the now defunct application framework and X-Motif users
(from which most of the ideas in zApp seem to have come from) may see a
stricking similarity in the code. Many of the concepts in zApp and X-Motif
have been borrowed in the development of AtOne Application Framework. Despite
the similarity there is much different in AtOne that is unique to AtOne.
This framework merely represents an agreggation of the best ideas that
I have identified.
Higher Level Graphics Architecture
In GUI programming it is generally very common to create the image of a
graphic in a different part of the code to where it is displayed (as in
the above example). Also it is quite common for the generating code to
be complex and time consuming so it is generally better to create a permanent
image of a graphic once and use that cached image every time a paint message
needs to be processed (rather than re-synthesize it in each paint message).
This is the essential motivation behind the Gfx group of classes
within AtOne. This set of classes provide high level wrappers around a
host of common graphical objects such as lines, polylines, polygons, rectangles
and text.
All classes within the architecture have GuiGfxObject as their base.
The definition of this class is,
class GuiGfxObject
{
protected:
mutable GuiRect
Extent;
int
ExtraWidth;
public:
GuiGfxObject();
GuiGfxObject(const GuiRect& rExtent,
int nExtraWidth);
GuiGfxObject(const GuiGfxObject&
rCopy);
virtual ~GuiGfxObject();
virtual GuiGfxObject* copy()
const;
virtual void
draw(GuiGraphicsContext* pGC, const GuiRect& rRect) const = 0;
virtual void
drawSolid(GuiGraphicsContext* pGC, const GuiRect& rRect, const GuiColour&
rColour) const = 0;
virtual void
outline(GuiGraphicsContext* pGC, const GuiRect& rRect) const = 0;
virtual GuiRect
physicalExtent() const;
virtual const GuiRect& extent()
const;
virtual const GuiPoint& origin()
const;
virtual bool
isIn(const GuiPoint& rPt) const;
virtual bool
isIn(const GuiRect& rRect) const;
virtual void
moveBy(const GuiPoint& rMovement);
};
Common ot all Gfx objects are the draw(), drawSolid()
and outline() drawing methods. The draw() method draws the
normal image of the object. The drawSolid() method draws the entire
object in one colour only. This method is useful in constructing a shadowed
image of a complex object. The outline() method draws a pen outline
to the object which you typically might use to draw a drag outline of a
complex object. Note that the isIn() methods are typically used
to determine when a Gfx object lies within an invalid region and
needs to be re-painted.
GuiGfxEllipse is typical of Gfx objects. This class represents
all possible ways to draw an ellipse in AtOne. The class is defined as,
class GuiGfxEllipse : public GuiGfxObject
{
protected:
GuiLogPen
LogPen;
GuiLogBrush
LogBrush;
public:
GuiGfxEllipse(const GuiRect& rRect,
const GuiLogPen& rPen, const GuiLogBrush& rBrush);
GuiGfxEllipse(const GuiGfxEllipse&
rCopy);
virtual ~GuiGfxEllipse();
virtual GuiGfxObject* copy()
const;
virtual void
draw(GuiGraphicsContext* pGC, const GuiRect& rRect) const;
virtual void
drawSolid(GuiGraphicsContext* pGC, const GuiRect& rRect, const GuiColour&
rColour) const;
virtual void
outline(GuiGraphicsContext* pGC, const GuiRect& rRect) const;
virtual const GuiPoint& origin()
const;
const GuiLogPen&
logPen() const;
const GuiLogBrush&
logBrush() const;
void
logPen(const GuiLogPen& rPen);
void
logBrush(const GuiLogBrush& rBrush);
};
The draw method for GuiGfxEllipse is,
void GuiGfxEllipse::draw(GuiGraphicsContext*
pGC, const GuiRect& rRect) const
{
if (isIn(rRect))
{
pGC->pushBrush(LogBrush.create());
pGC->pushPen(LogPen.create());
pGC->ellipse(Extent);
delete pGC->popPen();
delete pGC->popBrush();
}
}
As can be seen, Gfx objects merely encapsulate the method calls
required to draw given objects in AtOne in a manner similar to that of
a metafile in WIN32 but with greater flexibility. It is recommended that
you study the contents of guigfxob.hpp and guigfxob.cpp carefully
before doing any graphics programming. You may well find classes that will
simplify your graphics programming considerably. A further study of the
custom controls in guictrl.hpp and guictrl.cpp should also
give a clear outline of how to write graphics code in AtOne.
Programming Controls
"We use Zeus
for Windows and Watcom C/C++ 11.0 as our development environment of
choice..."
Paavo Jumppanen
Creator of AtOne Application Framework
This document was last modified on 1st September, 2001
Copyright (C) 2001, Paavo Jumppanen
All rights reserved. |