Section 3 - Implementing a Pseudo-Datamatrix Optical Writer/Reader (Optional)
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:
- void virtual scan( const BarcodeImage & bc ) = 0;
- void virtual readText( const string & text = "" ) = 0;
Next, the interface requires two public output methods, one for reporting the text message and another for "printing" a label.
These will be called:
- void virtual displayTextToConsole() = 0;
- void virtual displayImageToConsole() = 0;
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:
- void virtual generateImageFromText() = 0;
- void virtual translateImageToText() = 0;
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":
- void scan( const BarcodeImage & bc ) - accepts some image, represented as a BarcodeImage object to be described below, and stores a copy of this image. Depending on the sophistication of the subclass, the internally stored image might be an exact clone of the parameter, or a refined, cleaned and processed image. Technically, there is no requirement that an implementing subclass use a BarcodeImage object internally, although we will do so. For the basic DataMatrix option, it will be an exact clone. Also, no translation is done here - i.e., any text string that might be part of the subclass is not touched, updated or defined during the scan(), i.e., if the text part of the class had a value before the scan(), it will have that same value after the scan() - the scan() only updates the internal image. Getting the internal text member to match the newly scanned image comes later.
- void readText( const string & text = "" ) - accepts a text string to be eventually encoded in an image. No translation is done here - i.e., any BarcodeImage that might be part of the subclass is not touched, updated or defined during the reading of the text, i.e., if the image part of the class had a value before the readText(), it will have that same value after the readText()- the readText()only updates the text data. Getting the internal image to match the newly read text comes later.
- void generateImageFromText() - Not technically an I/O operation, this method looks at the internal text stored in the implementing subclass, and produces a companion BarcodeImage, internally (or an image in whatever format the implementing subclass uses). After this is called, we expect the object to contain a fully-defined image and text that are in agreement with each other.
- void translateImageToText() - Not technically an I/O operation, this method looks at the internal image stored in the implementing subclass and produces a companion text string, internally. After this is called, we expect the object to contain a fully defined image and text that are in agreement with each other.
- void displayTextToConsole() - prints out the text string to the console.
- void displayImageToConsole() - prints out the image to the console. In our implementation, we will do this in the form of a dot-matrix of blanks and asterisks, e.g.,
------------------------------------ |* * * * * * * * * * * * * * * * * | |* *| |**** * ***** **** **** ******** | |* *** ***************** ********| |* * ** * * * * * ** | |* * * * ** * * * ****| |* * * ** * * * * ** * | |** * *** ***** ** * * **| |**** * **** ** * * * * * | |**********************************| ------------------------------------
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
- public static consts ints MAX_HEIGHT and MAX_WIDTH; Set them to 30, and 65, respectively. These constants represent the maximum allowable dimensions of externally passed arrays and the exact internal dimensions of 2D data. These consts are required, but the values are up to you.
- private bool** imageData; Here is your payload. Instantiate the amount described using the above constants every time, even if an in-coming image is not that large. If the in-coming data is too large, instantiate memory anyway, but leave it blank (white). This data will be false for elements that are white (or blank), and true for elements that are anything else, particularly '*'s.
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
- Constructors. Two minimum, but you could have others:
- Default Constructor - instantiates a 2D array (MAX_HEIGHT × MAX_WIDTH) and stuffs it all with blanks (false).
- BarcodeImage( const string strData[], int height ) - an easy-to-use (but harder to implement) constructor that takes a 1D array of strings and converts it to the internal 2D array of bools. See the sample below and diagram above, for clarity on this.
HINTThis constructor is a little tricky because the incoming image is not the necessarily same size as the internal matrix. So, you have to pack it into the lower-left corner, causing a bit of stress if you don't like 2D counting. This is good 2D array exercise.
- Accessor and mutator for each bit in the image: bool getPixel( int row, int col ) and bool setPixel( int row, int col, bool value ); For the getPixel(), you can use the return value for both the actual data and also the error condition -- so that we don't "create a scene" if there is an error; we just return false.
- A display() method that shows the object in a border, with blanks, ' ', representing white/false and asterisks, '*' representing black/true. This is meant mostly for debugging and development purposes since BarcodeImage is supposedly independent of I/O. We include it as a corresponding method for our TwoDimImage::display() function.
- Usual deep data suite: copy constructor, destructor and assignment operator
- There may be others -- these are the crucial ones.
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:

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.
- The int ** data of the TwoDimImage is an int matrix, while the bool ** imageData of BarcodeImage is a bool matrix.
- When you display() your BarcodeImage object, you must test for true/false and output '*' or ' ', accordingly. You don't simply output the data.
- get/setElement() is replaced by get/setPixel() which work on bool, not int, data.
- bool checkSize(const string strData[], int height) should be the new version of TwoDimImage's checkSize(). I didn't specify it above, because it is a private helper method, but it is just as useful in the new context. It has a different signature, but has to test for essentially the same things, adjusted for the new parameter types.
- The constructor, BarcodeImage::BarcodeImage(const string strData[], int height), while not much longer than the prior constructor, TwoDimImage(int **intData, int width, int height), it is much more subtle and requires a lot of thought, counting, pencil, paper and thinking. You have to move data from the client's string array into the lower left corner of the private bool member array, and this "ain't" easy. But you are qualified to do it, and it is now the time for you to prove it to yourself.
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.