Here is a way to print graphics to an EPOS receipt printer using APA(All Points Addressable) format. When printing to one of these little receipt printers, you could always use a windows driver which lets windows see it as standard GDI printer and therefore send graphics as a bitmap to the printer from any windows program. This works but its extremely slow. A 24dpi printer will take a long time to print a big bitmap image.
The ultimate way to send a bitmap to the printer is by using ESC/P commands and building vertical segment slices in code and sending these to the printer. It’s extremely fast. There is some work involved in understanding how this is done. The coding is not that complicated, however, if you don’t have a grasp of how these dot matrix printers handle bitmap image slices, you will have a tough time. So lets get started by showing you how these printers deal with graphics slices.
I was working on a project which uses either a “Ithaca Series 90 plus” or a “Cognitive TPG-A760” receipt printer to print a signature at the bottom of the receipts. I opened both programming books and found generally the same graphics format.
Ithaca APA graphics command format:
Cognitive TPG APA graphics command format:
Well sure, that makes perfect sense. It looks like I send [1B + 2A] to activate graphics mode then send  to select 24-dot double density then [n1 n2??????]. What? Nowhere is this explained very well. The bit layout at the bottom does a great job of explaining the layout of each slice, but what about n1..n2? I assume that d1..d2 is the data. so n1..n2 must be size of the data in some way. It is quite confusing and terribly written but if you study the formula given for 8 dot mode, it starts to make, well, no sense at all.
As it turns out, the best explanation I have ever read that explains this so well, which is also the inspiration for creating this Delphi Snippet, is
Nicholas Piasecki’s blog. (<- Nicholas took his site down so the link is dead)
And here is the snippet:
function BitmapToAPAGraphics(const bmp:TBitmap; const RowHeightEscapeStr:String; const SliceEscapeStr:String; const BitsPerSlice:byte = 8):String; const threshhold = 127; CR = #13; LF = #10; TYPE TBitArray = array of boolean; TRGBTripleArray = ARRAY[Word] of TRGBTriple; pRGBTripleArray = ^TRGBTripleArray; // Use a PByteArray for pf8bit color. var iCol,iRow,index, sliceIndex, bytePos, bitPos, offset,luminance: integer; line: pRGBTripleArray; Pixel: TRGBTriple; tmpFile: TextFile; dots: TBitArray; slice,bit,tmpBit,bytesPerSlice, hold: byte; bVal: Boolean; p:pRGBTripleArray; begin result := ''; if not Assigned(bmp) then exit; if SliceEscapeStr = '' then exit; try bmp.PixelFormat := pf24bit; SetLength(dots, (bmp.Height * bmp.Width)); index := 0; //1) Loop the bitmap scanlines and build a bitArray from each pixel for iRow := 0 to bmp.Height-1 do begin line := bmp.Scanline[iRow]; for iCol := 0 to bmp.Width-1 do begin Pixel := line[iCol]; luminance := Trunc((Pixel.rgbtRed * 0.3) + (Pixel.rgbtGreen * 0.59) + (Pixel.rgbtBlue * 0.11)); dots[index] := (luminance < threshhold); inc(index); end; end; offset := 0; //Convert BitsPerSlice to byte count in each slice bytesPerSlice := (BitsPerSlice div 8); //if there is a remainder, we need another byte added (9 bits is 2 bytes, etc...) if BitsPerSlice mod 8 > 0 then inc(bytesPerSlice); result := result + RowHeightEscapeStr; //Loop height steping down by offset while offset < bmp.Height do begin result := result + SliceEscapeStr; //Loop width of bitmap for iCol := 0 to bmp.Width-1 do begin //Loop each byte from current slice for sliceIndex := 0 to bytesPerSlice -1 do begin slice := 0; //Loop each bit from current byte from current slice for bit := 0 to 7 do begin bytePos := (((offset div 8) + sliceIndex) * 8) + bit; bitPos := (bytePos * bmp.Width) + iCol; bVal := FALSE; if bitPos < Length(dots) then bVal := dots[bitPos]; //Delphi has no ternary operator so we have to compensate if (bVal) then tmpBit := 1 else tmpBit := 0; slice := slice or (tmpBit shl (7 - bit)); end; result := result + chr(slice); end; end; //Step down to next slice inc(offset,BitsPerSlice); result := result + CR+LF; end; finally dots := nil; end; end;
function PrintBitmap(bitmapFileName:String):Boolean; var RowHeightEscapeStr,SliceEscapeStr, prnBuffer:String; myBmp:TBitmap; nL, nH: Byte; begin if FileExists(bitmapFileName) then begin myBMP := TBitmap.create; try myBMP.LoadFromFile(bitmapFileName); RowHeightEscapeStr := #27+#49; nH := Hi(myBMP.Width); nL := Lo(myBMP.Width); SliceEscapeStr := #27+#42+#50 + chr(nL)+ chr(nH); prnBuffer := prnBuffer + #13+ BitmapToAPAGraphics(myBMP,RowHeightEscapeStr,SliceEscapeStr,8); finally myBMP.Free; end; end; end;