Taquis - PC Base FFT Spectrum Analyzers, Oscilloscopes, Data Analysis, Data Acquisition, and Application Frameworks   AtOne Application Framework
"A Framework working with you, not against you..."

 
Home
Features
Programming
License
Downloads
Directions

Contact
info@taquis.com

 

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.