Midrange News for the IBM i Community


Printing BMP Image Files from RPG Published by: Bob Cozzi on 02 Aug 2011 view comments
© 2011 Robert Cozzi, Jr. All rights reserved.

© Robert Cozzi, Jr. All rights reserved. Reproduction/Redistribution prohibited.
A midrangeNews.com Publication

Printing to ASCII Printers from RPG

Normally when you are printing, you use a classic Externally Describe Print File (*PRTF) and write to it, and it prints. If a PC-family printer is used, the Client Access printer session emulates an System i compatible printer and everything seems to work. We do have limitations, however; there is the issue of printing graphics. To do that you need to use one of the IBM APIs such as Host Transform or Image Conversion and then send the resulting data to the printer. PDF printing is finally "native" on this architecture, but has been around previously via PDF/Write from Export Ventures. But I've talked about PDF in a pervious issue of RPG Report is I'll save us the redundancy.

Recently I had the challenge of printing an image to an ASCII printer from RPG. The legacy application used a decade old Visual Basic (VB) program to capture a customer's signature and then print that signature. The design of that VB app was appropriate for the technology available in its day, however, like so many .Net or Visual Basic applications, they lifespan is much more limited that RPG and System i applications. Code written in the 1980s in RPGIII will still run today, and run pretty fast, where as old VB code would have a difficult time being loaded into the contemporary development environments.

Regardless of the debatable decision to use a VB application for a shop that requires a box as sophisticated as System i, the goal was achieved and worked. But today the  requirements changed. So unlike many classic host applications, the best choice here was to throw out the VB app and do something else. The decision I made was to attempt print the captured graphic image directly from the System i; this meant printing from within an RPG or C program running on the System i, directly to a PC printer.

Fortunately the cards fell in my favor, and I was able to accomplish this task, but it wasn't easy. There were 2 major technical obstacles and human obstacle. The human obstacle was me--I had never done anything like this before, and had spent the last decade producing HTML/Web based output from RPG. So going back to Print files and attempting to print a graphic image from within RPG was, well let's just say I felt challenged.

SAVE on RPG IV Subprocedure Training from Bob Cozzi

Just knowing that a feature exists isn't good enough, you have to understand it in order to use it productively.

Some developers take 5 or 6 hours (others take 5 or 6 days) to complete a task involving Subprocedures. After viewing my seminar on DVD, they become familiar with and understand how to use subprocedures--without the huge trial-and-error waist of time that often accompanies new technology.

This offer is open to everyone, but MidrangeNews.com Members can save $200 off the regular non-member price.

Check it out today:

Ad

Sadly I learned that even in 2011 the Print Support on System i was still decades behind the rest of the world, as it was in the 1990s. Sure there's the intriguing QimgCvtImg and rest of the Image Print Transform APIs but try to find some examples of how to use them, good luck with that. Here's the API description:

IBM Convert Image (QIMGCVTI, QimgCvtImg) API

This API converts an image (graphic file such as a BMP, GIF or TIFF) to another format and optionally prints the image. It requires a control structure as well as input and output data structures. The control structure looks like the following:

     D QIMC0100        DS                  Qualified Inz
     D  size                         10I 0 
     D  imgFormat                     8A   Inz('IMGF0100')
     D  operation                    10I 0
     D  appendHandle                 32A
     D  feedbackLen                  10I 0
     D  fbFormat                      8A
     D  Reverse                      10I 0
     D  Color_Reduce                 10I 0
     D  Resize                       10I 0
     D  Stretch                      10I 0
     D  HJustify                     10I 0
     D  VJustify                     10I 0
     D  KeepColor                    10I 0
     D  KeepQuality                  10I 0
     D  cancelOnError                10I 0 Inz(1)
     D  SevLvl                       10I 0 Inz(20)

Remember, when using the QRPGLESRC source members of the QSYSINC library, many  (all?) of the data structures provided by IBM are not completely accurate. They have missing subfields, data structure names create conflict with the API names making prototyping frustrating, and above all they always use the never-correct "B" data-types along with from/to column notation. Many APIs don't work correctly when larger values are stored in these 15-year since deprecated "B" data-types. Why does IBM continue to add new content in what is blatantly obsolete source code? Because they continue to create the RPG IV API data structures using a conversion tool that has apparently not been updated since Clinton was in his first term as President. But I digress.

Loading a BMP Image into RPG

Back to my challenge. I needed a way to replace that nearly 10 year old VB program with a contemporary approach--one that would (hopefully) continue to work for at least 10 more years. But I didn't want to be held hostage to Windows--although Client Access and it's printer support is, as of this writing, supported only under Windows. My solution: Attempt to print directly from RPG to an ASCII attached Printer, connected via Client Access.

The data captured from a tablet is stored in BMP (bitmap) image format. Wikipedia suggests the format for BMP files varies, but our image is in most common form of BMP. The first 2 bytes contain the ASCII 'BM' characters, followed by size information and a header that contains the a description of the bitmap image (height, width, colors, etc.) and finally the pixel array (the bitmap image itself). All I needed to do was to accurately translate the C data structure in the Wikipedia article into RPG IV. The results of that effort follow:

     D BMP_Struct_T    DS                  Qualified Inz
     D  hdr                                LikeDS(BitmapFileHeader)
     D  info                               LikeDS(BitmapInfoHeader)
     D***data                     32765A

     D BITMAPfileHeader...
     D                 DS                  Qualified Inz
     D  magicID                       2A
     D  file_Size                    10U 0
     D  reserved1                     5U 0
     D  reserved2                     5U 0
     D  image_Offset                 10U 0

     D BITMAPinfoHeader...
     D                 DS                  Qualified Inz
     D  info_Size                    10u 0
     D  width                        10u 0
     D  height                       10u 0
     D  planes                        5u 0
     D  bitsPerPixel                  5u 0
     D  compress_type                10u 0
     D  Image_Size                   10u 0
     D  XPixsPerMeter                10u 0
     D  yPixsPerMeter                10u 0
     D  colors                       10u 0
     D  ImportantColors...
     D                               10u 0

I create a data structure "template" named BMP_STRUCT_T that contains the other two BMP structures. If you read in a BMP file and overlay it on top of that data structure, it should line up nicely, but there's a gotcha!

In the bitmap file itself, the measurements (size, height, width, etc.) are stored in, what's called "little-endian". This means that these integer values are stored backwards in the 4-byte integer variables; making the values stored in these integer variable, wrong. So guess what, you have to use that little-endian to big-endian conversion tool in RPG xTools or you can use the other solution I illustrate later in this article.

The helper subprocedures include HIBYTE and LOBYTE, and HIWORD and LOWORD. I use these kinds of tools when I wrote C/C++ code for Windows programs, so they felt familiar and easy to use. So I mimicked their functionality in RPG IV.  Here's the code for the HI/LO BYTE subprocedures.

     D short_T         DS                  Qualified
     D  value                         5U 0
     D  hiWord                        3U 0 Overlay(value)
     D  loWord                        3U 0 Overlay(value:*NEXT)

     P hiByte          B
     D hiByte          PI             3U 0
     D  shortValue                    5U 0 Const

      **************************************************
      **  Returns the HIByte from a
      **  a 2-byte big-endian integer value.
      **************************************************
     D short           DS                  LikeDS(short_T) Inz
      /free
          short.value = shortValue;
          return short.hiWord;
      /end-free
     P hiByte          E


     P loByte          B
     D loByte          PI             3U 0
     D  shortValue                    5U 0 Const

      **************************************************
      **  Returns the LOByte from a
      **  a 2-byte big-endian integer value.
      **************************************************
     D short           DS                  LikeDS(short_T) Inz
      /free
          short.value = shortValue;
          return short.loWord;
      /end-free
     P loByte          E

Once a BMP image is loaded, the first thing you need to do is check the 2-characters at the beginning of the file. According to the specification, they must equal the letters "BM". But this is not an EBCDIC "BM" it is an ASCII "BM". To do that, rather than convert the data from ASCII to EBCDIC using the iconv() API, I simply compare it to the hexadecimal string. This is, apparently exactly how its is most often recommend that you do it--compare it to the ASCII hex value equivalent of "BM" which is X'424D'.

After making sure you have a BMP image loaded, you can convert the measurements from little endian format to big endian. Once that's accomplished, the structure should contain valid values.

The next thing to do is to locate the actual pixel array that is the bitmap image. With monochrome bitmaps, this is typically a serials of X'FF' and X'00' byte values. X'FF' for white, and X'00' for black (I know it seems opposite, doesn't it?)

Then you have to understand how the image is stored. Often it is stored upside down, sometimes it is flipped left-to-right, and occasionally it is reverse image--it all depends on the software that produced the image itself. Once you understand how the image is stored, you can re-arrange the pixel array, rotate the image or XOR the bits to turn black into white, and white into black. For my situation, I'm printing a signature captured at a cash register at a retail outlet. That signature is stored on the PC as a BMP file, then I stuff it into a BLOB and send it up to the System i DB2 file using ODBC. The RPG program reads the BLOB using SQL (the only way you can read BLOB data into an RPG program is through embedded SQL).

For some reason, SQL still thinks its the 1970s--they limit the amount of information you can retrieve from a BLOB to 32k. There are techniques that allow you to get more than that, but their a pain in the you know what. RPG supported up to 64k variables and 16MB of space since roughly v4r4, and as of v7r1 it supports 16MB variables. Hey SQL! Try and keep up, would you!

Fortunately my signatures were between 6kb and 25kb so the SQL 32k limit wasn't a show stopper. If it is for you, you can always store the image on the IFS and read it using an IFS API. Here's an except of how I read a BLOB containing a BMP image into a variable in RPG IV:


     D imageType       S              5I 0
     D bmpSig          S                   SQLTYPE(BLOB:32760)
      /free
          select imgFmt, image INTO :imageType, :bmpSig
            FROM SIGNATURES WHERE TRANS = :transID
      /end-free

The field named BMPSIG contains a keyword that allows BLOB fields to be read into RPG. The SQLTYPE keyword accepts different keywords for the first parameter, and the maximum size for the 2nd parameter. I selected 32760. SQL generates a structure that is similar to the way we used to have to handle variable-length fields. The first data structure subfield is the length, the 2nd is the data itself. In our case the generated fields are:

     D bmpSig          DS            
     D bmpSig_LEN                    10I 0
     D bmpSig_DATA                32760A

The length of the data in the BLOB field is returned to the BMPSIG_LEN subfield, and the data itself is returned into the BMPSIG_DATA subfield. The BMPSIG_DATA contains the BMP image just as if we had read the BMP image from a file on the IFS, into the BMPSIG_DATA variable. If we inspected the first 2 bytes of the BMPSIG_DATA variable, it will contain X'424D' (i.e., ASCII "BM").

Printing to an ASCII Printer

Now that we have the BMP how do we print it?

It turns out that long ago, IBM added a transparency attribute to printer file fields. Fields declared in an Externally Described Print file can include the TRNSPY ("transparency") keyword.  This basically passes anything through directly to the printer without modifying it. Data that is already in ASCII (for example) is passed straight through to the printer. Normally it would be converted from EBCDIC to ASCII and then passed to the printer. However that would really screw things up in this scenario. TRNSPY saves us from a lot of grief.

Only use the TRNSPY keyword on fields that will contain printer control codes or as in our case, an ASCII data stream that contains a BMP image. Do not use TRNSPY on traditional text output.

Program Described Print files are a little more complex--they require that a special "I'm sending transparent data" code prefix what is being sent. That code is a hexadecimal X'03' value and must be followed by the length of the data that is being sent. The length must fit inside a 1-byte integer (3U0 value) so the maximum length of the ASCII data (print line) is 255, but in our example its actually limited to about 1/5th of that.

     D asciiPrt        DS                   Qualified
     D   trnspy                       3U 0  Inz('X'03') 
     D   length                       3U 0
     D   data                        54A

To output transparent data to a program-described print file, move the data to be written to the asciiprt.data subfield, and store the length of the data in the asciiprt.length field, as follows:

     FmyPrinter O    F   56        PRINTER OFLIND(*INOF)
      /free
           asciiPrt.data = bmpData; 
           asciiPrt.length = %len(bmpData);
           except OUTPUT;
     OmyPrinter E            OUTPUT  
     O                       asciiPrt            +0

In my example, the BMP image (a signature) is being printed on an classic register printer (3-inch "tape") similar to what you would see in any retailer. Our printers allow up to 56 characters per line, hence the printer size of 56.

The ASCIIPRT data structure contains 3 subfields:

  1. Transparency command X'03'
  2. Length of the output stream
  3. The output stream itself

The total of all three pieces of information cannot exceed the total size of the printer definition. So in our example, we are restricted to 54 characters of ASCII data per output operation.

FreshBooks

Printing

With a bitmap image, you would obviously have more than 56 characters. Breaking it up into chunks to send to the printer is simply a matter of looping through the data and moving it to the output buffer.

     D asciiPrt        DS                   Qualified
     D   trnspy                       3U 0  Inz('X'03') 
     D   length                       3U 0
     D   data                        54A
     D szOutput        S             54A   Varying
     D szDataOut       S          32760A   Varying
      /free
           szDataOut = %subst(bmpSig_Data : 1 : bmpSig_Len);
           maxBytes = %size(szOutput)-2;
           dow (szDataOut <> '');
              if (%len(szDataOut) <= maxBytes);
                 if (%len(szDataOut) > 0);
                    szOutput = szDataOut;
                 else;
                    szOutput = '';
                 endif;
                 szDataOut = '';
              else;
                 szOutput  = %subst(szDataOut:1: maxBytes);
                 szDataOut = %subst(szDataOut : %Len(szOutput)+1);
              endif;
              if (%len(szOutput) > 0);
                 asciiPrt.data = szOutput;
                 asciiPrt.length = %len(szOutput) + 2;
                 EXCEPT OUTPUT;
              endif;
           enddo;  
      /end-free
     OmyPrinter E            OUTPUT  
     O                       asciiPrt            +0

Parsing the Bitmap 

Just like Windows Printer Drivers, each printer requires its own unique preparation when printing. There are standardized printer languages such as PCL 5 and PostScript and most printers today will accept one form or the other of these two languages. But printing images on a printer often requires that the image be converted into a compatible format. That is what the Host Transform and Convert Image APIs attempt to accomplish. To print a signature on a 3-inch paper receipt, however, requires that the image be sent directly to the printer. To do that, I've consolidated everything in this week's RPG Report into a callable API. It generates a print stream compatible with EPSON TM-88V printers. If another printer is used, it would be relatively easy to wrap the BMP image in that printer stream, since the BMP parsing and loading is already done. The API accepts either a BMP data stream (a field containing a BMP image) or the name of a BMP file on the IFS.

Since it is customized to EPSON printers, I've decided to not publish it at this time. But if there is sufficient demand for it, I may make it available to Premium subscribers in the future. Leave feedback with your request.

Call Me

Bob Cozzi is available for consulting/contract development or on-site RPG IV, SQL, and CGI/Web training. Currently many shops are contracting with Cozzi for 1 to 3 days of Q&A and consulting with their RPG staff. Your staff gets to ask real-world questions that apply to their unique development situations. To contact Cozzi, send an email to: bob at rpgworld.com

Bob also accepts your questions for use in future RPG Report articles or content for midrangeNews.com. Topics of interest include: RPG IV, Web development with RPG IV, APIs, C/C++ or anything else IBM i development related (except subfiles, data areas and RPGII/III because Bob doesn't care about that stuff) write your questions using the Feedback link on the midrangeNews.com website. 

You can subscribe to RPG Report (we call it "follow") by visiting the RPG Report page on midrangeNews.com and then click the FOLLOW link in the table of contents for that page. To unsubscribe, simply click that same link. You must be signed up and signed in to midrangeNews.com to start following RPG Report.

Follow Bob Cozzi on Twitter

Return to midrangenews.com home page.
Sort Ascend | Descend

COMMENTS