Section 3 - Implementing a Pseudo-Datamatrix Optical Writer/Reader (Optional)

This section is optional. Ultimatly, it will be replaced by sections 6B.5 through 6B.7. You may proceed directly to those if you wish.

6B.3.1 The Groundwork:  An Interface and an Image Class

Our goal is to provide a toolkit for barcode reader/writer development -- tools that our company can use for its internal hardware and software products, and tools that we can sell to other companies as APIs (application programming interfaces).

6B.3.2 BarcodeIO Interface

We begin by defining an interface, i.e., an abstract class with all pure virtual functions and no data, that will contain the requisite method signatures needed by any barcode reader/writer class that future programmers might design. Like all interfaces, we do not define the behavior of the methods. We only provide their names and some documentation in order to guide implementers as to their intended effects.

The basic idea is this:  The abstract class will proclaim that its derived class must have two fundamental read operations, one for scanning an image (clearly intended for later translation into a text string), and another for reading some text (intended for later conversion to an image label that will be printed).  This represents both halves of the read process.  Sometimes we want to create a label, so we ask for a string to encode.  Other times we want to read a label so we scan in an image. 

We have two public methods signatures for these two operations:

Next, the interface requires two public output methods, one for reporting the text message and another for "printing" a label. 

These will be called:

Finally, we will add two public methods that bridge the gap between the two extremes.  These will cause the translation of a text message to and from the image data.  They don't have any input/output impact, but do represent a change in the state of the object, allowing us to be certain that the text we just read-in has been converted to a barcode image, or conversely, that the image we just scanned has been translated into some text. 

These methods are:

We will call the abstract class BarcodeIO, and, in summary, here is a complete specification for the class interface:

BarcodeIO

Any class that is derived from BarcodeIO is expected to store some version of an image and some version of the text associated with that image.  I will describe each method signature here because it will make the spec of the derived subclass shorter.  However, descriptions of pure virtual functions don't pack any punch in practice.  For us, though, they should be considered "spec":

Important

Because this abstract class refers to the BarcodeImage class, which is not yet defined, you have to have an advanced prototype of this class before BarcodeIO's declaration.  This is done by placing a simple prototype:

class BarcodeImage;

at the top of the file, or anywhere before it is used in BarcodeIO.

6B.3.3 BarcodeImage Class for Holding Images

Next we want to define a data structure that will be used to store, internally, an image.  Let's be clear about what we are doing. This class is not the ultimate DataMatrix class that is going to subclass from BarcodeIO and do the translation between text and graphics.   This current class is only about the graphics data, and doesn't even really have to be used for barcodes.  We could have called it TwoDimImage, for example.   Come to think of it, I did this a few days ago for you.  You can use any and all of that code and do a search-and-replace on the name of the class.  As you'll see, there will need to be some changes, though, so it's not as easy as search ...replace ... hand-in.  What fun would that be?

The intended use of this class is to be used as a member object in our ultimate class, DataMatrix.  Make sure you are clear on this.  We are not going to subclass from this, we are going to use it as one small part, a member object, of the target class DataMatrix.

We will call this image class BarcodeImage.  Because BarcodeImage will, just like our friend TwoDimImage, contain deep data, it must supply all deep data operations, namely, a copy constructor, an assignment operator and a destructor.

  

Data for BarcodeImage

Since imageData is ultimately going to represent a 2-D array (i.e., a matrix), I will call it imageData[][] in some places throughout the discussion.  Let's talk about how we are going to use imageData[][] to hold our image, internally.   Let's say we have a very small 3x3 image:

* *
 * 
***

(That's my attempt at an image of the face of a student, hungry for knowledge.)

Now, our internal imageData[][] is a large (30 tall by 65 wide) array.  Where do we place this small 3x3 array within imageData[][]?  We will place it in the lower-left corner.  All "signal" (the blanks and asterisks of this little 3×3 image) will be packed into that corner.  Here is the positioning of this "signal":

Col# -> |0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 ... -> ... 63 64
|
Row #       |
-----       |-----------------------------------------------------------------------
0          |
1          |
2          |     example: imageData[2][19] is here ---->   X
3          |
4          |
5          |
6          |
7          |
8          |
9          |
10          |
11          |
12          |
13          |
14          |
15          |
16          |
17          |
18          |
19          |    (everything above and to the right of the signal is white = false)
20          |
21          |
22          |
23          |
25          |
26          |
27          |*   *
28          |  *      <--- actual "signal" is packed lower left, row MAX_HEIGHT-1 and up
29          |* * *
------------------------------------------------------------------------

Of course, I am using '*' to mean true, and blank ' ' to mean false.  The array is completely false except for a few elements in the lower-left.   The color is only used to help you see the actual signal, not meant to indicate true or false.  One final, important observation:  the signal is not stored in the imageData[][] in such a way that the interesting part, the signal, starts with imageData[0][0]If you place data there, you will not get the program to work.  The above picture tells you everything you need to know in order to store, read and write the image

One thing that any downstream method looking just at this large imageData[] matrix cannot know, based only on what it contains, is where the actual signal ends.  (Maybe a few rows of white (false) above or to the right of the last black (*) are signal.)   That kind of information will be dealt with elsewhere.  All we need to do here, in this class, is to know what to do with a rectangular image that we get (in a constructor, for example) that might be smaller than the full 30 x 65 internal array:  we pack it into the lower left.  But when we display it (if we were to do so in this class), or do anything else, we'll treat the entire 30 x 65 element array as valid data. 

   Methods for BarcodeImage

Notice that the "money" constructor takes a string array, as input.  Usually, a constructor takes data identical to the internal data.  However a 2D array of bools is really a pain to write out, while an array of strings is a breeze.  An example that shows how you would create and display a BarcodeImage using this constructor and display() follows:

int main()
{
string sImageIn[13] =
{
"                                      ",
"                                      ",
"                                      ",
"* * * * * * * * * * * * * * * * *     ",
"*                                *    ",
"**** * ****** ** ****** *** ****      ",
"* ********************************    ",
"*    *   *  * *  *   *  *   *  *      ",
"* **    *      *   **    *       *    ",
"****** ** *** **  ***** * * *         ",
"* ***  ****    * *  **        ** *    ",
"* * *   * **   *  *** *   *  * **     ",
"**********************************    "
};

BarcodeImage bc(sImageIn, 13);

bc.display();
}

Here is the output of a successful run:

console shot

This example anticipates a couple things.  First, it looks like Datamatrix format (why?).  Second, the writer of this example chose to "pack" the signal in the lower-left of the string array.  That might not have been the case - there is no law requiring the client to not have spaces below, or to the left of, the signal.  Even if we were using Datamatrix, the image might not be left-lower pre-packed!  But, in the simple program we write in our lab, we will see one option where we expect the client to give us string arrays with this lower-left justification.  This expectation makes writing the class methods easier.

Oh, and by the way, don't be lulled into a false sense of complacency.  The lower-left packing of this string sImageIn array by the client does very little to help us translate the data to a lower-left packing of the bool imageData[][] array.  We will need very careful and meticulous counting in order to make this translation.  That's why we are making the big bucks.

6B.3.4 Steps in Implementing BarcodeImage

As you know, BarcodeImage is very similar to TwoDimImage which I have completely implemented for you. 

Here are the key differences.

This class, BarcodeImage, can be written and debugged all by itself, independent of the other components of today's lesson.  Start with TwoDimImage and make changes slowly and carefully.