/* * Copyright 2003 Roberto Ragusa <r.ragusa@libero.it> * * This program converts a 600dpi bitmap representation of an A4 sized * page (4768x6796 pbm file with *exactly* 13 bytes of header) into a * binary stream of data. * If this data is sent to a Epson EPL-5700L, EPL-5800L or EPL-5900L * laser printer the original image will be printed. * * This code is currently highly experimental and tested only on an * EPL-5900L. * All the parameters (dpi, paper size are hard coded into the * binary header). * * The purpose of this program is to build the fundamentals of an * Epson EPL5x00L printer driver. * * History: * * 2003-01-01 Roberto Ragusa <r.ragusa@libero.it> * * * First version, the code is not commented and the * protocol is not described at all. * Preliminar tests successful! * Running speed and compression ratio are very good. * * 2003-01-02 Roberto Ragusa <r.ragusa@libero.it> * * * Added copy-2 and copy-3 commands. * * Little fixes and polishing. * * Added huge comment text describing protocol. * The program is now understandable by humans. * * 2003-01-06 Roberto Ragusa <r.ragusa@libero.it> * * * General reorganization of the code. * * Added debug features (stderr). * * Improved C89 compliance. * * 2003-01-07 Roberto Ragusa <r.ragusa@libero.it> * * * Memory allocations. * * Eliminated some hard coded values, * better cropping of the bitmap. * * Tests on error conditions. * * 2003-01-08 Roberto Ragusa <r.ragusa@libero.it> * * * More tests on error conditions. * * 2003-02-02 Roberto Ragusa <r.ragusa@libero.it> * * * 4-bit code now implemented! Added * explanation in the protocol description. * * ToDo: * * Many things. * */ /* * --- Explanation of how it works (the though part) --- * * The Epson EPL-5700L, EPL-5800L and EPL-5900L printers are a low cost * version of the equivalent "not-L" printers. The difference is that L * versions have less memory and CPU power on-board and some of the * firmware functionalities are moved to the host sending the file to * print. There is no Postscript support and no support for widely known * bitmap printing protocols such as PCL or ESC/P2. * The printers want the bitmap in a specific binary format and not in * a simple raw format. The format is designed to give a good * compression of typical images so to have a smaller data file going * from host to printer. * * No documentation on the format is publicly available, so a lot of * guess work had to be done to understand the meaning of spool files * generated by official drivers (that is, closed-source drivers). * * There are some job-header, page-header and page-footer byte sequences * which will not be discussed here. They contain information about * papersize, resolution and other options. Some documentation about * the headers can be found rather easily elsewhere. * The bitmap is divided in horizontal stripes, each composed by 64 * rows. * Every stripe is encoded independently from the others. * The stripe description starts with a stripe header with information * about the length of the following binary data. * * Now, the actual thing: how the stripe data is encoded. * The binary data is a set of 16bit words. The bit stream starts with * the less significant bit and after the most significant bit we pass * to the next 16bit word, again LSB first. * Example: * a binary data like this (letters are just marks to help you tracking * the bits) * 01110001 10101111 00001000 01001110 ... * a b c de f g h i * * has 2 16 bit words before the dots. * The first word is to be considered first. But not starting from the * MSB, instead from LSB. * So the real bit stream should be interpreted as * 11110101100011100111001000010000... * ed cb ai hg f * * Looking at things this way is not very comfortable because pixel * patterns and binary counters encoding needs to be bit-reversed (that * is, a 8bit value of 128 is not 1000000 but 00000001). * * So, we face the situation differently. We imagine the bit stream grows * in the left direction (not right). So the data is for us * ... 00001000 01001110 01110001 10101111 * f g h i a b c de * Note that the dots are now on the right and if we read from right * to left we have a correct bit order (we encounter marks "edcbaihgf"). * * We start with an empty 32bit temp variable and populate the LSB bit * first and than the others. When we have more than 16 bits ready * we copy bit 15-8 and 7-0 (LSB is bit 0) in the first two bytes of the * output buffer and shift the temp word 16 bit right, ready to populate * other bits. * * There is a last thing to be careful about. Bits are not inserted * into the temp word one by one, but on groups of variable length. * These groups are represented by numeric constants just ready for * insertion and so are to be interpreted from right to left. * * The bit stream above could have been generated by an insertion * of 101111 and then 0110 and then 011100 and then 10 and then 0011 and * so on. * So if I say we emit a 001 code followed by a 101 code I'm * speaking of this: * ...101001 * and if I say we emit a 111 code followed by the 8 bit representation * of the number 64 I'm speaking of this * ...01000000111 * and if I say we emit a 110 code followed by a bitmap bbwwbbbb * (b=black bit=1, w=white bit=0) I'm speaking of this * ...00110000110 * Note that we can simply paste the 64 and the bitmap in the stream * this way. * * The above description is fundamental to quickly understand the * following. * * We are going to encode the stripe. * we assume we have the bitmap data in the simple bit by bit * left-to-right order, so 3 white pixels followed by 6 black, 2 white * and 5 black is * 00011111 10011111 (0x1f,0x9f) * Note that I delimited byte boundaries. * This is the format commonly used in PBM format files. * * The stripe is formed by 64 rows. * The description of one row can use data present at the row just above * the current one. If we are in the first row (row 0) we *can* use * an hypothetical row -1 which is assumed completely white. * * Now the description of a row. It is all byte based. * There are two types of possible commands: literal and copy. * A literal command is followed by the bitmap data, while a * copy command tells that we can copy one of the bytes already sent * (the specific copy command tells where it is, as there are * more than one copy command). * After a copy command there is *always* a repetition command * which indicates how many times the copy has to be done. * Literal commands are *not* repeatable. * * Here is the full table: * * LITERAL * * 10 xxxxxxxx The bitmap data is the byte xxxxxxxx * 00 xxxx (this is not completely understood and is not used) * (UPDATE: this 4-bit code is now understood, * explanation at the end of this section) * * COPY * * 01 Copy the byte from the row above * 011 Copy the byte from our left (last byte sent) * 0111 Copy the second byte from our left (current-2) * 1111 Copy the third byte from our left (current-3) * * REPETITIONS (only after copy) * 0 Copy only one time (no repetition) * 01 Copy 2 times * 0011 Copy 3 times * 1011 Copy 4 times * 01111 Copy 5 times * 011111 Copy 6 times * 111111 Copy 7 times * 0111 yyyyyyy Copy more than 7 times. yyyyyyy is a 7 bit * value containing the number of repetitions. * This number can be: * - 0 special case: copy until end of row * - 1..7 not accepted (crash on 5900L), * you have to use the short form * - 8..126 repeat said times * - 127 repeat 127 times, and immediately * read another 7 bit yyyyyyy value * again. This way long repetitions * can be specified 127 by 127 and * finally the rest. * * That's all, folks. * * Note that 0111 yyyyyyy means emit 0111 and then yyyyyyy so the * bit stream is * ...yyyyyyy0111 * as explained above. * * When a full stripe has been encoded a certain number of 0 bits * is emitted to complete the last 16bit word. * * EXAMPLES: * * Suppose we want to encode a white row and assume the row is * 260 byte long (260*8 pixels). * * We can do: * 10 00000000 10 00000000 10 00000000 10 00000000 ... 260 times * that is * literal 0x00 260 times (awful!) * * Or we can do: * 10 00000000 011 0111 1111111 1111111 0000101 * that is * literal 0x00, copy last byte repeating copy 127+127+5 times * * Or just: * 10 00000000 011 0111 0000000 * that is * literal 0x00, copy last byte until the end of the row * * Or just (if this is the first row of the stripe or the row * above is completely white): * 01 0111 0000000 * that is * copy from above repeating until row end. * * For a totally black row (without using the row above): * 10 11111111 011 0111 0000000 * that is * literal 0xff, copy last byte until row end * * If we have a totally white stripe we can do: * 01 0111 0000000 ... 64 times * that is * 64 commands "copy from above until row end" * * A more complex example. * We want to code a white stripe with a black box inside; * 2 white rows above the box and 10 below the box (which * is 52 rows tall); 15 white pixels on the left of the * box (which is 6 pixels wide) and white on the right * of it. * The bitmap in hexadecimal is: * 0x00 0x00 0x00 0x00 0x00 0x00 0x00 ... * 0x00 0x00 0x00 0x00 0x00 0x00 0x00 ... * 0x00 0x01 0xf8 0x00 0x00 0x00 0x00 ... 52 times * 0x00 0x00 0x00 0x00 0x00 0x00 0x00 ... 10 times * A good coding is: * 01 0111 0000000 * 01 0111 0000000 * 01 0 10 00000001 10 11111000 1111 0 01 0111 0000000 * 01 0111 0000000 51 times * 01 0 011 0111 0000000 * 01 0111 0000000 9 times * that is * copy above (white) row * copy above row * copy 1 byte from above (don't repeat), literal 0x1, * literal 0xf8, copy from current-3 (don't repeat), * copy from above until row end * copy above row (51 times) * copy 1 byte from above (don't repeat), copy last * until row end * copy above row (9 times) * Note that the current-3 copy is a waste because without * it we should have copied from above the white byte * we needed. * A final detail. * When a copy is repeating both the source and destination * pointers move forward. So a * 01 0111 0000000 * at the beginning of a row copies all the row above, * it doesn't fill the row with a repetition of the first * byte of the row above. * If we had used "copy current-3 repeating until end" * in the last example we wouldn't have had a white * sequence but a repeated pattern of 0x00 0x01 0xf8. * * * --- UPDATE: 4 bit code not a mistery anymore. --- * * After much experimentation (including high-res scanning * of printings from manually generated spool files), the * meaning of the 4 bit code has been discovered. * * It's a cache. * * The printer has 16 bytes of cache and a register with * 4 bits which we will call "lri". * When a stripe begins, the cache is initialized to * 0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0x0a, * 0x0b,0x0c,0x0d,0x0e,0x0f and the lri is set to 0. * If we use the 4-bit code number i (0<=i<=15) while encoding * a stripe, we will print the byte at cache position i. * Until now, it's a simple lookup table. * But the table is updated every time we use an 8-bit literal; * the byte is placed at cache position lri and lri is * incremented by one (modulo 16). * * So, this a cache with a LRI replace policy; yes, not LRU * (Least Recently Used), but LRI (Least Recently Inserted). * This is why we call the additional register "lri". * * While the implementation of this algorithm on the printer * is really simple, the driver has to check the cache * content every time a literal byte is going out. * An efficient implementation can be obtained by means of * an additional lookup-table (reversed cache). * * The data size reduction is moderate for text pages (about 15%) * and minimal for complex graphics; the encoding process * is a little slower because of the additional work. * * But, after so much work to discovery the logic (the * replacement policy in particular way), this can't * be left out of the driver. :-) * * * Roberto Ragusa * */ /* INCLUDE */ #include <stdio.h> #include <stdarg.h> #include <stdlib.h> #include <string.h> /* DEBUG */ #define GLOBALDEBUG #ifdef GLOBALDEBUG void debug(int flag,...){ va_list args; const char *fmt; const char *name; name=""; if(flag){ va_start(args,flag); fmt=va_arg(args,const char *); vfprintf(stderr,fmt,args); } } #else void debug(int flag,...){ /* nothing!!! */ } #endif #define DGENERIC 1 #define DSTREAM 1 #define DFILEIO 1 /* STREAM HANDLING */ unsigned char *stream_ptr; int stream_temp,stream_bits; inline void stream_begin(unsigned char *outbuffer){ stream_ptr=outbuffer; stream_temp=0; stream_bits=0; } inline void stream_emit_nocheck(int d, int l){ stream_temp|=(d)<<stream_bits; stream_bits+=(l); } inline void stream_check(){ if(stream_bits>=16){ *(stream_ptr++)=(stream_temp>>8)&0xff; *(stream_ptr++)=stream_temp&0xff; stream_temp>>=16; stream_bits-=16; } } inline void stream_emit(int d, int l){ stream_emit_nocheck(d,l); stream_check(); } inline unsigned char *stream_end(){ stream_emit(0,(16-stream_bits)%16); return stream_ptr; } /* RUN LENGTH */ inline int run_length_find(unsigned char *b1, unsigned char *b2, int maxl){ int run_length_table_d[]={/**/0,/*0*/0,/*01*/1,/*0011*/3,/*1011*/0xb,/*01111*/0xf,/*011111*/0x1f,/*111111*/0x3f}; int run_length_table_l[]={ 0, 1, 2, 4, 4, 5, 6, 6}; int l; int returned_l; for(l=1;l<maxl&&b1[l]==b2[l];l++); returned_l=l; if(l==maxl){ stream_emit(/*0000000 0111*/0x007,4+7); debug(DSTREAM,"REP2END[%x(%i)] ",l,l); } else if(l>=8){ stream_emit(/*0111*/0x7,4); debug(DSTREAM,"REPBIG[%x(%i)] ",l,l); while(l>=127){ stream_emit(127,7); debug(DSTREAM,"%x(%i)+",127,127); l-=127; } stream_emit(l,7); debug(DSTREAM,"%x(%i) ",l,l); } else{ stream_emit(run_length_table_d[l],run_length_table_l[l]); debug(DSTREAM,"REPSMALL%x(%i) ",l,l); } return returned_l; } /* STRIPE PROCESSING */ int process_stripe(unsigned char *inbuffer, unsigned char *outbuffer, int s, int wbytes, int stripe_size){ char cache[16]; char byte2cache[256]; int lri; /* least recently inserted */ unsigned char *whiterow; unsigned char *thisrow, *rowabove; int x,y; int len; int i; whiterow=(unsigned char *)malloc(wbytes); if(whiterow==NULL) return -1; for(i=0;i<wbytes;i++) whiterow[i]=0; /* cache initialization */ for(i=0;i<16;i++){ cache[i]=i; byte2cache[i]=i; } for(i=16;i<256;i++){ byte2cache[i]=-1; } lri=0; rowabove=&whiterow[0]; /* special case */ thisrow=&inbuffer[0]; debug(DSTREAM,"s=%05i\n",s); stream_begin(outbuffer); for(y=0;y<stripe_size;y++){ debug(DSTREAM,"y=%05i: ",y); for(x=0;x<wbytes;){ if(thisrow[x]==rowabove[x]){ stream_emit(/*01*/0x1,2); debug(DSTREAM,"CABOVE "); x+=run_length_find(&thisrow[x],&rowabove[x],wbytes-x); } else if(x-1>=0&&thisrow[x]==thisrow[x-1]){ stream_emit(/*011*/0x3,3); debug(DSTREAM,"C-1 "); x+=run_length_find(&thisrow[x],&thisrow[x-1],wbytes-x); } else if(x-2>=0&&thisrow[x]==thisrow[x-2]){ stream_emit(/*0111*/0x7,4); debug(DSTREAM,"C-2 "); x+=run_length_find(&thisrow[x],&thisrow[x-2],wbytes-x); } else if(x-3>=0&&thisrow[x]==thisrow[x-3]){ stream_emit(/*1111*/0xf,4); debug(DSTREAM,"C-3 "); x+=run_length_find(&thisrow[x],&thisrow[x-3],wbytes-x); } else if(byte2cache[thisrow[x]]>=0){ /* cache hit */ stream_emit(/*00*/0x0,2); debug(DSTREAM,"LIT4CACHE"); stream_emit(byte2cache[thisrow[x]],4); debug(DSTREAM,"pos_%01x %02x ",byte2cache[thisrow[x]],thisrow[x]); x++; } else{ stream_emit(/*10*/0x2,2); debug(DSTREAM,"LIT8 "); stream_emit(thisrow[x],8); debug(DSTREAM,"%02x ",thisrow[x]); debug(DSTREAM,"to_pos_%01x ",lri); byte2cache[(unsigned char)cache[lri]]=-1; cache[lri]=thisrow[x]; byte2cache[thisrow[x]]=lri; lri=(lri+1)&0xf; x++; } } stream_emit(1,2); stream_emit(7,11); debug(DSTREAM,"\n",y); rowabove=thisrow; thisrow+=wbytes; } len=stream_end()-outbuffer; debug(DSTREAM,"\n",y); free(whiterow); return len; } /* MAIN */ int main(void){ int dpi_x,dpi_y; int wpix,hpix; int stripe_size; int wbytes,hstripes; int pbm_wpix,pbm_hpix; int pbm_wbytes; unsigned char *bitmap; unsigned char *outbuffer; char temp_string[256]; /* temp string buffer */ char *ts; int s; int y; int slen; int e; unsigned char dpi_code1,dpi_code2; dpi_x=600; dpi_y=600; /* A4 paper 210x297mm */ wpix=(210-4-4)/25.4*dpi_x; /* 4771 @ 600 dpi */ hpix=(297-4-4)/25.4*dpi_y; /* 6826 @ 600 dpi */ debug(DGENERIC,"wpix=%i,hpix=%i (calculated)",wpix,hpix); /* manual override */wpix=4768;hpix=6796; stripe_size=64; pbm_wpix=4768; pbm_hpix=6796; wbytes=wpix/8;wbytes=(wbytes+4-1)/4*4; hstripes=(hpix+stripe_size-1)/stripe_size; pbm_wbytes=(pbm_wpix+8-1)/8; bitmap=(unsigned char *)malloc(wbytes*stripe_size*hstripes); if(bitmap==NULL) return 10; outbuffer=(unsigned char *)malloc(wbytes*stripe_size+wbytes*stripe_size/2); /* 50% safety margin */ if(outbuffer==NULL) return 10; /* start job */ ts=temp_string; ts+=sprintf(ts,"\x01b\x001"); ts+=sprintf(ts,"@EJL \x00a"); ts+=sprintf(ts,"@EJL STARTJOB MACHINE=\"experiment\" USER=\"epl5x00l_driver\"\x00a"); ts+=sprintf(ts,"@EJL EN LA=ESC/PAGE\x00a"); ts+=sprintf(ts,"\x01d"); ts+=sprintf(ts,"4eps{I"); ts+=sprintf(ts,"%c%c%c%c",0x00,0x00,0x00,0x00); ts+=sprintf(ts,"\x01d"); ts+=sprintf(ts,"9eps{I"); if(dpi_x==300&&dpi_y==300){ dpi_code1=0;dpi_code2=0; } else if(dpi_x==600&&dpi_y==300){ dpi_code1=0;dpi_code2=1; } else if(dpi_x==600&&dpi_y==600){ dpi_code1=1;dpi_code2=0; } else if(dpi_x==1200&&dpi_y==600){ dpi_code1=1;dpi_code2=1; } else{ return 10; } ts+=sprintf(ts,"%c%c%c%c%c%c%c%c%c", 0x02, 0x00, dpi_code1,dpi_code2, 0x01, /* RITech */ 0x00, /* Toner save */ 0x00, /* Paper type? */ 0x03, /* density */ 0x00 ); ts+=sprintf(ts,"\x01d"); e=fwrite(temp_string,1,ts-temp_string,stdout); if(e!=ts-temp_string) return 10; /* start page */ ts=temp_string; ts+=sprintf(ts,"26eps{I"); ts+=sprintf(ts,"%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c", 0x04,0x00, 0x0e, /* paper code */ stripe_size/*0x40*//*0x20*/, /* stripe height */ ((wbytes)>>8)&0xff/*0x02*/,(wbytes)&0xff/*0x54*/, /* bytes per row (multiple of 4) */ 0x00, 0x00, 0x00, 0x00, (hpix>>8)&0xff/*0x1a*/,hpix&0xff/*0x8c*/, /* vertical pixels */ (wpix>>8)&0xff/*0x12*/,wpix&0xff/*0x84*//*0xa0*/, /* horizontal pixels */ (hstripes>>8)&0xff/*0x00*/,hstripes&0xff/*0x6b*//*0xd5*/, /* stripe per page */ 0x00, /* tray */ 0x00, 0x01, /* copies */ 0xff, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x01); e=fwrite(temp_string,1,ts-temp_string,stdout); if(e!=ts-temp_string) return 10; memset(bitmap,0,wbytes*stripe_size*hstripes); e=fread(bitmap,1,0xd,stdin); /* skip 13 bytes (horror!)*/ if(e!=0xd) return 10; for(y=0;y<pbm_hpix;y++){ debug(DFILEIO,"%i\n",y); e=fread(bitmap+wbytes*y,1,pbm_wbytes,stdin); if(e!=pbm_wbytes) return 10; } for(s=0;s<hstripes;s++){ slen=process_stripe(bitmap+wbytes*stripe_size*s,outbuffer,s,wbytes,stripe_size); if(slen==-1) return 10; ts=temp_string; ts+=sprintf(ts,"\x1d%deps{I%c%c%c%c%c%c%c",slen+7,6,0,1,(slen>>24)&0xff,(slen>>16)&0xff,(slen>>8)&0xff,slen&0xff); e=fwrite(temp_string,1,ts-temp_string,stdout); if(e!=ts-temp_string) return 10; e=fwrite(outbuffer,1,slen,stdout); if(e!=slen) return 10; } /* end page */ ts=temp_string; ts+=sprintf(ts,"\x01d"); ts+=sprintf(ts,"2eps{I"); ts+=sprintf(ts,"%c%c", 0x05, 0x00 ); e=fwrite(temp_string,1,ts-temp_string,stdout); if(e!=ts-temp_string) return 10; /* end job */ ts=temp_string; ts+=sprintf(ts,"\x01d"); ts+=sprintf(ts,"2eps{I"); ts+=sprintf(ts,"%c%c", 0x03, 0x00 ); ts+=sprintf(ts,"\x01d"); ts+=sprintf(ts,"2eps{I"); ts+=sprintf(ts,"%c%c", 0x01, /* 1=last page 2=next page??? maybe not */ 0x00 ); ts+=sprintf(ts,"\x01b\x001"); ts+=sprintf(ts,"@EJL \x00a"); ts+=sprintf(ts,"@EJL EJ \x00a"); ts+=sprintf(ts,"\x01b\x001"); ts+=sprintf(ts,"@EJL \x00a"); e=fwrite(temp_string,1,ts-temp_string,stdout); if(e!=ts-temp_string) return 10; free(outbuffer); free(bitmap); return 0; }