Anyone who follows me on Facebook knows about my wargame graphics work. Knowing the amount of work I was going to commit to, I decided to forego hiring an artist or relying on someone else’s vehicle and ordnance graphics portfolio. I decided to go ahead and do it myself.
I’m glad I did. I’m nowhere near completion, however. In fact, I’m far from it. However, I have full control over the quality of what I produce, for my own game designs and for others.
I’m going to talk today about the toolkits I produced and how I generate my wargame graphics.
At the lowest level, I rely heavily on an open source library called Cairo. Cairo is a 2D graphics rendering engine written in C. It contains many drawing primitives that can draw pixels onto a 2D surface and then emit that surface to a variety of output formats, including PNG and SVG image files and PDF documents. Using these capabilities, I can write common code that emits wargame counters (pieces) into individual PNG files or as sets of pieces within a PDF countersheet.
Working in the C language can be tedious, however. While it is a powerful low-level language, it lacks built-in support for higher level constructs like character strings (aka words) or other objects. Plus, C language libraries and programs must be compiled in order to be tested. This makes software development using C a 3-step process: edit-compile-debug. Due to the low-level nature of C, this cycle is repeated many, many times as one ends up debugging the implementation of high-level objects instead of the processes that use them.
For my wargame graphics work, and for a lot of other projects, I rely on the Ruby language. Ruby is a scripting language, which means that there is no compilation step. The software development process devolves to edit-debug. The trade-off is that Ruby scripts, like scripts written in other languages, won’t be as fast as a C program. However, if you’re not doing millions of calculations within your project, a scripting language is usually fast enough. The shortened edit-debug cycle is certainly a benefit, as well as the support for higher-level constructs, such as Arrays and Hashes, that are built in to the language.
Thankfully, there is a Ruby binding to the Cairo library. It is called rcairo. This Ruby library allows me to write Ruby code that can take advantage of the power of the base Cairo library.
While rcairo allows me to write Ruby scripts to generate sophisticated graphics, it is a raw interface. In order to better manage how I use rcairo, I created three new Ruby source files:
- Canvas.rb
- defines the Canvas class
- GraphicsState.rb
- defines the GraphicsState class
- MarkingObjects.rb
- defines a set of object primitives for marking objects and to define drawable areas
The Canvas class creates and controls the surface and drawing context upon which I draw. It contains the output format binding (PNG, SVG, PDF, …), the output filename, the dimensions of the surface as well as the color table that will be referenced by subsequent drawing operations. It creates and relies on an instance of GraphicsState to manage the underlying Cairo::Context class that rcairo provides. It also creates a Viewport object, one of the objects defined in MarkingObjects, within which all drawing will take place.
The GraphicsState class creates and controls various drawing attributes. It controls such properties as:
- the origin of the coordinate system within the surface
- coordinate grid scaling
- coordinate grid rotation
- the fill rule
- the stroke width
- the stroke location when stroking a path (inside, outside, on)
- the scale of the stroke
- line properties: capping, joining, dash style
- current font
- layout method (xy, gravity)
- position within the parent object to place a subsequent object (if layout method is xy)
- position within the parent object to place a subsequent object (if layout method is gravity), values are: northwest, north, northeast, west, center, east, southwest, south, southeast
- orientation of the text layout
- padding to place around a subsequent when placing within a parent object
- word wrapping style for laying out text
- autofit scaling for text and image placement in a parent object, values are shrink, expand, tryshrink
The GraphicsState object, in addition to maintaining the various properties available to drawing object, will also handle the ultimate drawing operations to the underlying Cairo::Context.
While GraphicsState handles various properties and the final emission of drawing operations to Cairo::Context, the MarkingObjects define a set of various objects for both layout and marking operations. The marking objects are:
- Viewport
- Rectangle
- Circle
- Ellipse
- Hexagon
- Polygon
- Path
- Text
- Image
- CallbackRegion
Viewport is a very low-level object which is created by the Canvas object. This is a rectangular object which fits the exact dimensions of the Canvas.
Rectangle is a rectangular object. It supports both normal corners as well as rounded corners.
Circles, ellipses, hexagons, and polygons are self-explanatory.
Path is defined as a text string which describes a path according to rules that are found in SVG and XPS specifications.
Text and images are discrete objects which are placed within other objects.
A CallbackRegion is a special object which is defined by dynamic code written in Ruby.
A fundamental design decision made during the development of MarkingObjects is that each object, apart from Text and Image, can serve as a parent layout object for other layout or marking objects. Rectangles can be laid out within rectangles. Hexagons can be laid out within circles. Paths can be laid out within Polygons. The shape of the parent object will serve as a clipping region for the underlying marking objects.
One item that I glossed over was the gravity method of laying out objects. Gravity allows for marking object placement within a parent object according to a specific location vector. For example, one could place a path which is centered with a hexagon. For wargame graphics, it becomes easy for placing a number, a text string, centered within a circle.
Another item that deserves mention is the concept of scaling. While coordinate scaling is a basic idea when it comes to drawing graphics, for the marking objects I designed the concept of unit scaling. Unit scaling is used to set the size of the object to be created based on the size of the object you’re putting it into. So, if you want to create a rectangle that is half the height and a quarter of the width of the parent rectangle, you could specify a scale value of 100, a height value of 50 and a width value of 25 for the new rectangle’s dimensions.
Unit scaling can also be used for fonts specifiers. For example, if you want to place a text object within a circle, you could specify a font scale of 100 and a font size of 50, which would create a text object that is half the height of the circle’s dimensions.
The combination of Canvas, GraphicsState and MarkingObjects provides a base upon which I can build numerous graphics projects.