KNOW-HOW GDI Shell v. 1.0 (C) Stepan S.Vartanov, 1995 - 96 1. Features. * KNOW-HOW GDI Shell v. 1.0 is the WINAPI GDI functions wrapper. Its primary purpose is to hide drawing details, such as scaling, scrolling and rotations of the image. * ver. 1.0 supports vector graphics only (no bitmaps). * KNOW-HOW GDI Shell is a class library, you can instantiate its classes or derive your own classes from them. * Ver. 1.0 was not tested with Windows 95 - it will be changed in ver. 1.2 * Ver. 1.0 uses Borland BGI fonts for text output (zoom, rotate are available). TRUE TYPE fonts support will be added in ver. 1.1 and will be available in COMMERCIAL (non-SHAREWARE) version only. 2. Usage. As with any class library there are few possible approaches. Some suggested approaches are described below. See also KH_DEMO project. We are going to produce picture of the 'bird'. // Put this code in application main file // This let you to init drawTool only once #include "drawtool.h" ... YourApplication::Init() { ... // Create drawTool in application's destructor drawTool = new KH_Paint(); ... } YourApplication::~YourApplication() { .... delete drawTool; .... } Use drawTool drawing functions: #include "drawtool.h" MyWindow::draw() { drawTool->line(100, 100, 200, 300) } Another approach (used in example) uses inheritance: #include "bgipaint.h" class MyWindow : public KH_Paint { ... drawPig(x, y); // Your function ... }; .... MyWindow::drawPig(int x, int y) { line(100, 100, 200, 300); .... } 3. Classes and Functions. File BGI_FONT.H Class encapsulates BGI fonts (*.chr) description. BGI fonts are not documented. I use existing format. struct _export BGI_Font : public AbstractGraphics { ... // Deformation of an image uchar multx, multy, divx, divy; // Create object and load() (BGI) font. BGI_Font(char* fileName = 0); ~BGI_Font(); // Remove old and load new font. Fomt should // exist, or NULL is loaded void load(char* fileName); // Character output int draw_char(int x,int y,char c, int dir=0); // Line of characters int outtextxy(int x, int y, char* str, int dir = 0); // Overload when API accessible void outtext(char* str) {} // Width of text, pixels (not rotated) int gettextwidth(char* str); // Heigth of text, pixels (not rotated) int getheight() { return CapitalHeight; } // Set character deformation void setusercharsize(int mx, int dx, int my, int dy) {multx=mx; multy=my; divx=dx; divy=dy; } // Set text justification void settextjustify(int j,int k) {h_just=j;v_just = k; } }; FILE DRAWTOOL.H Defines global drawing object - in the case if you do not want to use inheritance. This is not part of library - just an example.: #ifndef __KH_PAINT_H_ #define __KH_PAINT_H_ #include "bgipaint.h" extern KH_Paint* drawTool; FILE BGIPAINT.H User will probably work only with this file. Real API functions of Windows are available to this class(es). // Convenient names - you can use any set - Windows, if your are more used to it, or Borland BGI-like names: #define SOLID_FILL 1 #define SOLID_LINE PS_SOLID #define DASHED_LINE PS_DASH #define DOTTED_LINE PS_DOT #define CENTER_LINE PS_DASHDOT // Do not use HS_BDIAGONAL, HS_FDIAGONAL, // VS_VERTICAL, HS_HORIZONTAL HS_CROSS and // HS_DIAGCROSS !!! #define LINE_FILL 0 #define HATCH_FILL 2 #define SLASH_FILL 3 #define XHATCH_FILL 4 #define BKSLASH_FILL 5 #define LTSLASH_FILL 6 #define LEFT_TEXT 0 #define CENTER_TEXT 1 #define RIGHT_TEXT 2 #define BOTTOM_TEXT 0 #define TOP_TEXT 2 #define HS_SOLID SOLID_FILL enum COLORS { BLACK, BLUE, GREEN, CYAN, RED, MAGENTA, BROWN, LIGHTGRAY, DARKGRAY, LIGHTBLUE, LIGHTGREEN, LIGHTCYAN, LIGHTRED, LIGHTMAGENTA, YELLOW, WHITE }; // Use this structure to reference collors in 16 colors scale struct color_to_16 { int color; COLORREF colorref; }; // Descriptions of colors static color_to_16 tricolors[16] = { { BLACK, 0 }, { BLUE, RGB(0, 0, 128) }, { GREEN, RGB(0, 128, 0) }, { CYAN, RGB(0, 128, 128) }, { RED, RGB(128, 0, 0) }, { MAGENTA, RGB(74, 23, 74) }, { BROWN, RGB(128, 64, 64) }, { LIGHTGRAY, RGB(128, 128, 128) }, { DARKGRAY, RGB(64, 64, 64) }, { LIGHTBLUE, RGB(0, 0, 255) }, { LIGHTGREEN, RGB(0, 255, 0) }, { LIGHTCYAN, RGB(0, 255, 255) }, { LIGHTRED, RGB(255, 0, 0) }, { LIGHTMAGENTA, RGB(255, 0, 255) }, { YELLOW, RGB(255,255,30) }, { WHITE, RGB(255, 255, 255) } }; // There are some reasons why this class is not part of // KH_Paint. However under normal circumstances user will never use it // directly struct _export To_Paint { int PenSize; int PenStyle; COLORREF color; int pattern_num; int fill_color; COLORREF BrushColor; HDC DC; ///////////////////////////////// To_Paint() { pattern_num = SOLID_FILL; PenSize = 1; PenStyle = PS_SOLID; color = RGB(0,0,0); BrushColor = RGB(0,0,0); } ///////////////////////////////// int getx() { POINT p; ::GetCurrentPositionEx(DC, &p); return p.x; } int gety() { POINT p; ::GetCurrentPositionEx(DC, &p); return p.y; } COLORREF getcolorref() { return color; } int getcolor(COLORREF col); int getcolor() { return getcolor(color); } void setcolor(int r, int g, int b) { color = RGB(r,g,b); } void setcolor(int c) { color = tricolors[c].colorref; } void putpixel(int x, int y) { ::SetPixel(DC, x, y, getcolorref()); } void setlinestyle(int width, int style) { PenSize = width; PenStyle = style; } void setlinestyle(int style, unsigned int, int width) { PenSize = width; PenStyle = style; } void setfillstyle(int style, int col) { pattern_num = style; fill_color = col; BrushColor = tricolors[col].colorref; } void setfillstyle(int style, COLORREF col) { pattern_num = style; BrushColor = col; } void moveto(int x, int y) { ::MoveTo(DC, x, y); } void lineto(int x, int y); void fillpoly(int numpoints, int* polypoints); void drawpoly(int numpoints, int far* points); }; File BGIPAINT.H KH_Paint class could be considered as the main class in the library. struct _export KH_Paint : public To_Paint, public Paint { KH_Paint() : To_Paint(), Paint() {} int getx(); int gety(); loc get_CP(); void putpixel(int x, int y); void line(int xstart, int ystart, int xend, int yend) { moveto(xstart, ystart); lineto(xend, yend); } void lineto(int x, int y); void moveto(int x, int y); void circle(int x, int y, int radius) { ellipse(x, y, 0, 360, radius, radius);} void ellipse(int x, int y, int stangle, int endangle, int xr, int yr); void rectangle(int left, int top, int right, int bottom); void drawpoly(int numpoints, int far* points); void fillpoly(int numpoints, int far* points) { int f = fill; fill = ON; drawpoly(numpoints,points); fill = f; } void bar3d(int l, int t, int r, int b, int d, int top); virtual void outtext(char* str, int dir = 0); void set_fill(int state) { fill = state; } }; FILE PAINT.H Encapsulates axes transformation routines. Rotation could be simply processed if it is single operation. To use nested rotations we use stack of rotations info structures. Example of complex rotation: void f1(int x, int y, int alpha) { ... perform rotation ... } void f2(int x, int y, int alpha) { rotate(10,10,90); f1(x, y, alpha); } Function f2() calls rotation procedure and then calls f1(), which calls another rotation. We could a) cancel first rotation and b) add first and second rotations to complex transformation. Library will handle the stack of rotations for you. Some additional facilities were added. Functions could set error code in kh_error_code variable. The zoom and addzoom are deformation coefficients. You could use zoom in any part of program. Addzoom is used only once to set the additional deformation of the whole picture (for example if context is changed from screen to printer), for preview and so on. class _export Paint : public BGI_Font, public Trigonometry { public: loc zoom; // Image deformation loc add_zoom; // Additional deformation loc lt; // Scroll of part of picture loc add_scroll; // Left - top clip of // the whole picture int fill; // Fill flag int mirror; // Mirror reflection public: loc center; // Rotation center int alpha; // Rotation angle bool R_STACK; // Use or not stack of // rotations Stack* r_stack; // Stack of rotations Paint(); ~Paint() { delete r_stack; } loc get_add_zoom() { return add_zoom; } loc get_zoom() { return zoom; } // X coord. if no rotation void set_mirror(int m) { mirror = m; } void set_stack(bool r) { R_STACK = r; r_stack->flash(); rotate(loc(0, 0), 0);} void set_stack_soft(bool r) { R_STACK = r; } void set_zoom(double x, double y) { zoom.X = x * add_zoom.X; zoom.Y = y * add_zoom.Y; } // No out-of-range // control !!! void set_add_zoom(double x, double y) { add_zoom.X = 100 * x; add_zoom.Y = 100 * y; } void set_scroll(int x, int y) { lt.X = x; lt.Y = y; } void set_add_scroll(int x, int y) { add_scroll.X = x; add_scroll.Y = y; } void rotate(loc c, int a); void rotate(int x, int y, int alpha) { rotate(loc(x, y), alpha); } void set_fill(bool f) { fill = f; } // Using alpha and center returns rotated // coordinates loc rot(int x, int y); // Return completely transformed point: rotated, // zoomed and scrolled loc transform(int x, int y); }; 4. Discussion HOW TO DRAW (C++ drawing using extended set of graphical primitives). Introduction. There are many problems requiring the use of vector graphics. It is usually compact, fast and easy to scale. It is good... But its advantages for the user very often become the headache (should I use 'pain in the head' instead?) for the programmer, who have to draw it using direct calls to graphical primitives. This headache could become a nightmare if the program requires rotations, reflections and scaling of the image. Of the complex one... The usual solution is object-oriented approach. Objects remember its coordinates, rotation angles and so on. But with this approach you need a lot of code and of data as well: containers, coordinates you will never use and more. For example, we can create class "Line" with "left", "top", "right" and "bottom" data members. If we need to draw 1000 parallel lines, most of this coordinates will be absolutely unnecessary. As the alternative you can add new facilities to the set of graphical primitives you use. Coordinate transformations become system - not user - supported. In this article I describe the shell for any vector graphics library "KNOW- HOW.GRAPHICS. This shell is platform-independent (DOS / Windows), and written on C++. Using it, programmer still should call the low-level drawing functions like lineto(), moveto() and so on, but the system hides such details as rotation, scaling, reflection and scrolling. *) Demo version of library is available on http://home.istar.ca/~stepanv/Know_how.htm (Capital K in Know_how) Tasks. As mentioned above, we need code to hide from user four coordinate transforming operations: reflection, scrolling, scaling and rotation. It should also be platform-independent. Coordinates transformation. Rotation. From the point of speed, the less effective is rotations, and especially nested rotations. *) Saying "nested rotations" I mean group of rotations with different centers and different angles. This tool could be very useful. For example, to draw some image, requiring rotation, we use the f1() function (LISTING 1). Now we need to draw few figures, rotated to some (different) angles. If we do not have support for nested rotations, we have to calc all coordinates manually. With this mechanism, the system can do it for us. LISTING 1 * f1(...) * { * for(int i = 0; i < 6; i++) * { * rotate(x, y, i ); * putchar(str[i]; * endrotate(); // See later * } * } * f2(...) * { * rotateON(); * for(int i = 0; i < 5; i++) * { * rotate(x, y, 360 / 5); * f1(...); * } * rotateOFF(); * } The stack of rotations is activates with the rotateON() call. After this moment, all the rotate info will be kept in the stack. Drawing primitives will use it automatically. The code above could be optimized. If rotation center (x, y) is always the same, we need only 2 rotations (one in f1 and one in f2). To remove rotation info from stack we can use the following code (LISTING 2). LISTING 2. * ... * rotate(x, y, i * 360 / 5); * f1(...); * endrotate(); The call to endrotate() is equal to stack pop() command. It removes the last stack info. Trigonometry. To speed up drawing we can overload standard C trigonometric functions, like sin() and cos() which work with the argument of type double. Instead we shell use array of pre-calculated values of sinus with the step 1 degree. Additional acceleration could be achieved if instead of array of double values in the range -1 to 1, we'll use array of integer values of sin(x) * 10000. This approach could dramatically, in some cases hundred times, speed up the calculations. Precision is enough for most tasks. Reflections. Having the rotation support we do not need to support reflections with "any" mirror. Reflection with vertically oriented mirror in combination with the rotation could do the same job. KNOW-HOW.GRAPHICS uses mirror(x) function to set the mirror. Scrolling and scaling. This transformations are trivial. But it is desirable to give the user the opportunity to use it in any part of the drawing procedure, and in the same time to have possibility for scaling of all the image. This problem could be solved by keeping the information in two parameters for every of this procedures. For example: * loc zoom(100, 100) // Keep local zoom info * loc add_zoom(1, 1) // Keep additional zoom factor We use the agreement that zoom() could be called at any time to set the local scaling coefficient, and add_zoom() - only once, at the beginning of drawing. The resulted scaling factor is the multiplication of zoom and add_zoom factors. You can think about the stack of scaling information 2 elements depth, where first element is fixed. *) The loc structure I refer to is one of the few, defined in the GEOM.H file of the library as * struct loc * { * int X, Y; * ... The ... here means constructors, assignment and comparison operators and more. Platform-independent code. At the first - abstract - level of library we use virtual place holders for graphical primitives. They do nothing and should be overloaded. LISTING 3. * class Abstract * { * virtual void lineto(...) {} The next level contains more sophisticated primitives, which does not require direct calls of platform-dependent drawing functions and could be implemented as combinations of calls for level1 functions. For example, rectangle(...) could be implemented as calls to moveto() / lineto() functions (LISTING 4). LISTING 4. * class Abstract1 : publis Abstract * { * void rectangle(...); * ... At the same level of abstraction we could add text output support. KNOW-HOW.GRAPHICS supports BGI fonts. Borland have not published BGI fonts format, but it is well-known. To draw we need moveto() and lineto() calls only. We already have abstract prototypes for this functions, so it is not difficult to write cross-platform version with rotated and so on fonts (LISTING 5). In ver. 1.1 TRUE TYPE fonts should be available. LISTING 5. * class KH_BGI_Fnt : public Abstract * { * putchar(...); * outtextxy(...); * gettextwidth(...); * ... *) Actually KNOW-HOW.GRAPHICS use a bit more complex approach. The BGI font header contains the isFilled byte, so the character drawing function check this flag and uses lineto / moveto if font is not filled and drawpoly / fillpoly otherwise. Next level in this hierarchy supports the coordinates transformation. For example, for the line drawing function (LISTING 6). LISTING 6. * Paint :: LineTo(loc p) * { * transform(&p); * lineto(p); * } The transform() function perform the coordinate transformation described above. Drawing of polygons. Both DOS BGI and Windows GDI includes polygon drawing functions. To be able to transform this figures we should use transform() call for every element of the array of coordinates (LISTING 8). (DOS is no longer supported) LISTING 8. * loc p; * for(int i = 0; i < 2*n; i++) * { * transform(&p); * array[i] = p.X; * array[i + 1] = p.Y; * drawpoly(array, n); * .... This code fragment belongs to one of abstract levels and call virtual drawpoly() function - place holder. In the derivated class it will be overloaded with ::drawpoly (BGI) or ::Polygon (GDI) depending on compiler directives. Drawing of ellipses. If ellipse axes have the same direction as coordinate axes, the task is trivial. In the other case we can use call to drawpoly() or fillpoly() functions passing the array of 360 points (1 degree step). Using the "fast" trigonometry of our library make this procedure effective enough. The resolution is also sufficient for most tasks and could be increased if necessary. Using KNOW-HOW.GDI in the applications. Like for every object-oriented system there are two possibilities. We can create object of the "drawing" class and use calls to its drawing functions. LISTING 10. * KH_Paint* painter = new KH_Paint(); * ... * painter->lineto(loc_p); The second approach uses derivation. We derive our class from the "drawing" one and than may use its functions directly (LISTING 11). LISTING 11. * class MyClass : public KH_Paint * ... * MyClass::draw() { line(...); ... }