Subject: QL Hacker's Journal
From: Tim Swenson <[email-masked]>
To: [email-masked], [email-masked],
Date: Wed, 9 Sep 92 14:36:23 EDT

Q L  H A C K E R ' S  J O U R N A L
----------------------------------------------------------
   Number 10                               Sept 1992
----------------------------------------------------------

   The QL Hacker's Journal (QHJ) is an amateur publication
published by Tim Swenson strictly as a hobby and as a
service to the QL Community.
   The QHJ is copyright by Tim Swenson and respective
contributers, but may be freely copied and distributed
(please do) to all QL users.
   The QHJ is always interested in article submissions.
If interested, please send mail to the address below
(Snail Mail or E-Mail).
   Electronic copies of the QHJ are available on disk
or via e-mail from:

        QL Hacker's Journal
            c/o Tim Swenson
        4773 W. Braddock Rd. #3
        Alexandria, VA 22311
        (703) 820-6657
        [email-masked]
        [email-masked]

Editor's Forumn

   This issue is the second issue to take 3 months to come
out.  I had not planned on a quarterly schedule, but it has
come out that way.  There are two primary reasons for this
delay: 1)  My daughter is now crawling and requires more
supervison and this means I have to do my share.  2) I've
hit a writer's block as far as programming.  I have all
sorts of ideas, but most are longer programs.  With the time
that I have to sit at the computer, I really can only work
on short (but interesting) programs, and I've had a lack of
ideas for these type of programs.  If anyone has any ideas,
please send them my way.

   In the article below on Maus.sys.ql, there is some
discussion of putting QL executables on to FTP servers (Unix
boxes).  I've done some testing and have verified that when
a file is transfered to MS-DOS or Unix, the header
information is lost.  This means that all executables will
no longer execute.

   Some people have taken this to mean that only source
code and text files be put on FTP servers.  I don't agree
with that.  I've looked at a couple of QL data archivers
(HAR, Lhx, and ZOO) and find that only HAR will restore the
file header information with the file.  This means that
executable can be archived with HAR, put on an FTP server,
unarchived later, unarchived, and run.

  To test this, I took the archived YACC file, transfered
it to MS-DOS, and back from MS-DOS.  I then unarchived the
YACC file (using HAR) and made sure the Yacc executable
would execute.  In fact, in the HAR documentation it
mentions that it does retain all of the QL header
information.  HAR is Public Domain (but really Freeware).

   Using HAR as a compressor/archiver, I have uploaded QED,
EFORTH, YACC, and FLEX, to the QL FTP Server garbo.uwasa.fi.
The files were uploaded to /ql/incoming.

   As this issue is going to press,  I have just recieved a
copy of C68 3.0.  I have heard that it is a little on the
buggy side.  I will give it a try.  I have also heard that
Bob Dyl of the "International QL Report" has received C68
3.02, which is supposed to fix all the bugs of 3.0.  I am
sending disks to Bob to get version 3.02 and hope to have a
review for the next issue.  In the same package that I
received 3.0, received a copy of the Spectrum emulator for
the QL.  I have not had a chance to even look at the disk,
so I will have more to say on it next issue.

Programmer's Bookshelf
   By Tim Swenson

   Over the years I have picked up a number of computer
books.  Some I have purchased for classes, others I have
bought out of my own interest.  I have also scoured various
libraries to see what sort of selection of computer books
they have.

   Through all of this I have come across books that I feel
should be in every programmer's personal library.  Some are
for general programming, others are more language specific.
I don't believe my list is complete as I am always reading
books that I am happy to discover are "classics."

General Programming:

   "Elements of Programming Style" by Kernighan and Pike is
a classic.  It's second edition has been in print since
1978 and could stand to be updated.  It's examples are in
Fortran, a language now almost out-dated.  Some of thier
rules are aimed at Fortran and like languages.  But overall
it covers all aspects of writing good code. ( I got lucky
and found my copy at a library book sale for 50 cents :-) )

   "Software Tools" by Kernighan and Plauger.  This book
defined what is meant by software tools and how they are
used together to solve problems.  It covers the basic tools
used in UNIX and other operating systems, like COUNT (wc),
EDIT (ed), FORMAT (nroff like), etc.

   "Mythical Man-Month" - I've forgotten the author's name,
but this book has been around for years, so it should be
easy to find.  The author was involved with the development
of OS/360 for IBM.  He has taken what he learned from that
and other projects, and created a book that details the
process of software project management.  A must read if you
work with a number of programmers on a project.

   "Programming Pearls" and "More Programming Perls" by Jon
Bently.  Jon used to write a column called "Programming
Pearls" for "Communications of the ACM".  These columns have
been worked into two books.  They cover programming topics
in an easy to read manner with lots of sample code.  The
second book is the better of the two.  I found the chapter
dealing with Little Languages inspiring.

   "The Knuth Books" - I can't remember the names of the
three volume set, but this alias should be recognized by
all. Don Knuth has written three volumes of a projected 7
volume set.  The most popular of the three is the one on
sorting.  This book covers all aspects of sorting.  The are
two problems with these books: One, they are tough to read.
They are written for those familiar with college level math.
And, secondly, they are not cheap.  The current cost per
volume is about $50-60 (and this is a rough guess).

Language Books:

   "The C Programming Language" by Kernighan and Ritchie
(first and second edition).  This book is know simply as
K&R.  To distinquish between the editions most people say
K&R 2nd Ed. and K&R 1st Ed.  If you program in C, this is
the book to get. Personally, I have both editions.

   "The AWK Language" by Aho, Weinberger, and Kernighan.
This is THE book for AWK.  It is in the same style and
approach as K&R.  It's small, compact, and easy to read.

   "Postscript" - There is a series of books on Postscript
put out by Adobe that are very good.  If you need to learn
Postscript, these books cover it all.

   I don't know of any other books that are the definitive
for thier respective language.  BASIC is too diverse to have
one grand reference book.  I do have a T/S 1000 ROM
disassembly book that I will never part with.  At the very
least I can use it to study how a computer ROM is done.

Source Books:

   "PC-SIG Directory" - Even though I am mostly a QL user, I
do have an IBM laptop that I use.  The PC-SIG directory is a
source to hundreds of free/shareware disks.  Plus the PC-SIG
library is available via ANON-FTP.

   "C Users Group Library Volumes 1-3" - The C Users Group
(who publish the "C Users Journal") is a good source for
free C source code.  They carry C source from the CP/M
world, MS-DOS, OS-9, and UNIX.  For other computers they
will put it in thier library, but have an outside person
support copying it for them.  If there is enough support,
they might even list stuff for the QL.

Other Books:

   "Fire in the Valley" by Swaine and Freiburger.  This book
throughly covers the start of the personal computer industry
in Silicon Valley.  It does stray out of the valley to cover
MITS in New Mexico and some events on the east coast.  It
does not cover any computer development in Europe.  There is
one slight mention about Sinclair computers and one picture
of Sir Clive Sinclair.

   "Hackers" by Steve Levy.  This book is best for its
coverage of the hacker phenomonon at MIT in the early
and late sixties.  It really covers how computer programming
as a passion got started.  Does cover Steve Wozniac and
Sierra On-Line, the software company.

   "Cookoo's Egg" by Clifford Stoll.  This is a great book
to curl up with.  It covers the break-in and capture of some
German crackers that tried to break into some American
systems.  Very easy to read.  This book was used as the
basis for a NOVA (PBS) episode.

   "The Devouring Fungus" by Karla Jennings.  This book is a
collection of humorous computer stories, most taken from the
Internet.  Some of the stories I have heard before, others
were new to me.  Good light reading.

   This is just my list.  I'm interested in hearing other
opinions.  Let me know what other books I should add to the
list.

Maus.sys.ql
   By Tim Swenson

   In the European part of Usenet, there is a newsgroup
called Maus.sys.ql.  This newsgroup is for the discussion
of all things QL.  Due to the low numbers of American QLers
that read Usenet, a Usenet feed has not been established.

   I have established an e-mail feed from the newsgroup.
This means that all articles posted to maus.sys.ql will be
mailed to my e-mail account.  Others that would like this
service can contact [email-masked]
oldenburg.de.  I can also send all that I get to others.
Just send me a note.  To post messages from the Internet,
send a mail message to: [email-masked]
Oldenburg.de.

  For the European readers, there is a number of BBS's
(called QBOXnet) that carry the same articles.  Below is a
list of them:

   Fourth Dimension    +44-202600305   (England)
   Grizzilius Maximus  +44-772828975   (England)
   TF Services         +44-717062379   (England)
   Blanford BBS        +44-258455117   (England)
   Aspects             +44-617920260   (England)
   Quanta NE BBS       +44-914775472   (England)
   Jamten TCL          +46-64133330    (Sweden)
   Andromeda BBS       +39-6-3251114   (Italy)
   Lau's Place         +44-253780021   (England)
   SYNCNET             +31-35-237178   (Holland)
   QLAT                +31-30-962265   (Holland)
   KU-EL-TEL           +31-1650-37105  (Holland)

   Here are some of the more interesting tidbits coming out
of the newsgroup:

   From Dave Walker:
      I would like to know how the EJC libraries have been
made fully re-entrant and romable.  About 90% of library
routines refer to glogal variables such as 'errno' in real C
programs.  This makes it rather difficult to write properly
re-entrant code.  I agree that individual routines that make
this assumption CAN be made romable, but that does not solve
the generic problem - it just means that you can write
single routines that are romable.

   Peter Sulzer replies:
      I can tell you, I've written a reentrant, ROMable
startup module for Lattice some time ago (but was too lazy
to write a library - it was long before C68, and I'm afraid
the C68 libs must be rewritten for that startup module).

      You use option -b in the first compiler phase (OK you
know this).  Now comes the trick, your startup module
allocates the memory for all the static data, e.g. in a
"data-job".  Then you copy all static data (in fact one must
only copy the static initialised data) to the allocated
memory.  Of course your static data area is limited to max.
64 K - in fact I found no way to force the linker to use a
+32K offset enabling static data from -32K to +32K.  It
worked only with an offset of 0, so the max. static data is
0 to +32K.

      Of course one must also usse option -r in the second
phase, limiting the code size of the program to max. 32K
(perhaps less - at least with my startup module).

      I assume text87 plus4 does the same, cause you can
save memory when you have executed it, if you make it
non-reentrant so that the static data mustn't be copied.

   From Jan Bredenbeek:
      I have the runtime version of C68 v3.01 for download
or file request on my board.  File names are C68V301A.ZIP,
C68V301B.ZIP, C68V301C.ZIP for the contents of Disk 1, 2,
and 3 respectively.

      Beware though: The three files are together 800K in
size (300 for A and 250 for each of the other two) so if
you're calling from outside the Netherlands it's better to
use a 9600+ modem, unless you have access to a phone line
you can use on someone elses dime :-).
    (See SYNCNET in the above list)

   From Franz Herrmann:
      I can only repeat that neither any part of C68 itself
nor any compiled prog worked on an ExeQtor machine, Gold
Card v2.25.  We tried it with Minverva and MGG ROM.  No
difference.  We loaded Pointer Environment, Lighting, etc.,
removed the extensions piece by piece to find out which was
causing the crashes.  Even on the plain machine, nothing worked.

      Possibly GC v2.28 fixes that.  I don't know, I am just
reporting what I saw (Fortunately, I have no probs on my own QLs.)

   From Franz Herrmann:
      Dave,  Some notes on C68 v3.01, which is simply excellent,

      ..C68 does not report an error with such a definition:
         static char *test = "Hello World"
     (static char test[] = ... would be correct)

     Instead C68 runs into an endless loop!!!
     (C68 v2.x accepted the fault!)

   Peter Sulzer replies:
      >Right, I looked at K&R, both are legal.  They are not
completely different but exactly the same.
                     ^^^^^^^^^^^^^^^^^^^^^

      Sorry Franz, you think wrong :-)

      static char *test = "Hello World" defines a pointer (
one longword in C68) which points to the string "Hello
World".  The pointer may be at a totally different location
in memory than the string itself.

      static char test[] = "Hello World" defines a vector of
char, 12 bytes long, containing the characters 'H','e',...
'd','\0'.  The first construct needs 12+4=16 bytes, the
second 12 bytes.

   Erik Slagter replies:
      What Peter Sulzer says holds ONLY within a struct or
union.  Anywhere else it implies THE SAME level of
indirection namely one.  The only difference of *test and
test[] outside the struct/union is that test[x] may be
altered and *(test+x) may not be altered.  This is because
space is allocated for test[x] on the stack whilst *test
points to a location in program memory.

   From Dave Woodman:
      Further to my last message, the source files for the
QDOS port of cpp have now been uploaded to garbo.uwasa.fi
and may be found under the /ql/unsorted.  Please not that
the file is in zip format.  All those with anonymouse ftp
may snarf to their hearts content.

      Yes, Peter, you are right:- ascii files only on garbo!

PGM and PBM to QL
   By Herb Schaaf

   [ Editor's Note - There is a Freeware Unix package
called PBMPLUS that converts to/from a number of graphics
formats.  It does this by using three intermediate formats,
PBM, PGM, and PPM.  Herb Schaaf has written a C program
that will convert a PGM and PBM file to the QL.  Since the
conversion is going from a large number of colors to only
8, don't expect the results to be astonishing.

   I find it interesting to see Herb take the time to
figure out the PGM and PBM file formats and how to convert
them to the QL.  Both of us hope that some of you will find
it usefull.]

/*   pgmtoql8_c Sample C Program to read a
 *   file such as al125.pgm (pixel gray scale)
 *   and display on QL in 8 color mode
 *   May 22, 1992  9am
 */

#include <stdio_h>
#include <qlib_h>
#include <qdos_h>
#include <string.h>

#define MAX_I 130 /*   maximum image dimension  */
                  /*   based on 2x2 block size  */
#define LEFT_EDGE 126  /*   for image out screen   */

char _PROG_NAME[] = "pgmtoql8";

   struct QLRECT grawin, *grwin;
   FILE  *fp_grwn;  /* file pointer for graphic window */
   FILE  *fp_in;    /* file pointer for input images */
   unsigned char in_image[MAX_I*MAX_I];
    /* input image array */
   unsigned char  out_image[MAX_I*MAX_I];
    /* output image array */
   unsigned char  rid_one[1];
     /* get 'rid' of one byte */
   char  img_type[6];   /* image file type (P5 = gray) */
   char  filein[36];    /* input file name */
   char  grayvals[4];   /* keyboard input values */
   long  width, height; /* input image width, height */
   int   max_color, max_count; /* maximum color value */
   int   present;    /* number of color values found */
   int   pixels;     /* number of pels read in from file */
   long  colors[256]; /* histogram of density ? */
   long  maxcount;    /* highest count in colors[] */
   int   color;          /*   for colors[]   */
   int   levels;         /*   number of grey levels   */
   int   row, col, block;   /* loop control values */
   int   x, y, w, h;      /* QLRECT values */
   long  to_grwn, to_in;
   int count;

main()
{
   /*   get name of image file   */
   printf("name of image file ?\nsuch as flp2_AL125_PGM
    or\nflp2_MNDRL125_PGM\n");
   gets(filein);
   printf("%s\n",filein);
   /* open image file to read */
   fp_in = fopen(filein,"rb");   /* read binary file   */
   if (fp_in == NULL)
   {
      printf("Error Opening image file %s\n",filein);
      printf("Touch ENTER");
      getch();
      exit(1);
   }
   to_in = getchid(fileno(fp_in));
   printf("opened input file #%u located at %u\n",fileno
    (fp_in), fp_in);
   /* set mode, etc. */
   grwin = &grawin;
   mt_dmode("8","0");
   fp_grwn = freopen("scr_512x256a0x0","w",stdout);
   to_grwn = getchid(fileno(stdout));
   sd_bordr(to_grwn,-1,2,2);
   sd_setst(to_grwn,-1,6);
   sd_setpa(to_grwn,-1,6);
   sd_setsz(to_grwn,-1,1,0);
   sd_setin(to_grwn,-1,1);
   grwin->q_width = 256; w = grawin.q_width;
   grwin->q_height = 256; h = grawin.q_height;
   grwin->q_x = LEFT_EDGE; x = grawin.q_x;
   grwin->q_y = 0; y = grawin.q_y;
   sd_clear(to_grwn,-1);
   sd_clear(stderr,-1);

   /* read header information */
   printf("Reading Header information\n - - - \n\n");
   read_pgm_header(fp_in);
   printf("\nplease wait - - reading pixels\n");

   if((width >= MAX_I) || (height >= MAX_I)){
      printf("%d wide by %d high is oversize\n",width,
        height);
      printf("limit is %d x %d\n",MAX_I-1,MAX_I-1);
      printf("\n touch [space-bar] to exit");
      getch(); exit(1);
   }
   /* read image into 1 dimensional array */
   pixels = fread(in_image, sizeof(unsigned char),width*
     height,fp_in);
   fclose(fp_in);
   printf(" %d pixels\n\n",pixels);

   /*  'grey' shades   */

   printf("gray shades ? 2 to 255 ");
   gets(grayvals);
   printf(" %s\n",grayvals);
   levels = 256 / (atoi(grayvals));
   /*   Histogram ?   */
   sd_setin(to_grwn,-1,0);
   for (row = 0; row <= (pixels/width); row++)
      {
       for (col = 0; col <= width; col++)
         {
          color = in_image[col + (row*width)];
      colors[color] = colors[color] + 1;
     }
      }
   printf("touch ENTER\n");
   getch();

   /*   find maximum and missing values   */
      maxcount = 0;
      present = 256;
      printf("values missing for:\n");
      for (count = 0; count <= 255; count++)
      {

     if(maxcount < colors[count])
        {
        maxcount = colors[count];
        max_count = count;
        }
     if(colors[count] == 0)
        {
        printf("%4d",count);
        present--;
        }
      }
      printf("\n%d values found \n%d has the maximum
        count of %d\n", present, max_count, maxcount);

   printf("\ntouch ENTER\n");
   getch();
   sd_bordr(to_grwn,-1,0,0);
   sd_clear(to_grwn,-1);
   /*   draw lines    */
   sd_iscale(to_grwn,-1,256,0,0);
   for (count = 0; count <= 255; count++)
      {
      sd_iline(to_grwn,-1, 0,count, colors[count], count);
      }
   sd_pos(to_grwn,-1,20,22);
   printf("touch ENTER\n");
   getch();

   /* change by some function for new output file */
   /* let's try dividing into grey levels  */
   /* or the inversion idea 255 - value   */

   /* set size of block for pixelating */
   block = 2;
   grwin->q_width  = block; w = grawin.q_width;
   grwin->q_height = block; h = grawin.q_height;
   for (row = 0; row <= (pixels/width); row++)
   {
      grwin->q_y = 0+(block*row); y = grawin.q_y;
      for (col = 0; col <= width; col++)
      {
         grwin->q_x=LEFT_EDGE+(block*col); x = grawin.q_x;
         out_image[col + (row*width)] =  in_image[col +
       (row*width)] / levels;

         /* now we need a little plotting going on */
         sd_fill(to_grwn,-1,out_image[col + (row*width)]
       ,grwin);
      }

   }
   /* hang on until we see it */
   grwin->q_width = 512; w = grawin.q_width;
   grwin->q_height = 256; h = grawin.q_height;
   grwin->q_x = 0; x = grawin.q_x;
   grwin->q_y = 0; y = grawin.q_y;
   sd_setin(to_grwn,-1,0);
   getch();
   printf("ENTER to leave");
   getch();
   fclose(fp_grwn);
   exit(0);
}

int getint(fp) /*   from John Bradley's   */
FILE *fp;      /*   XV version 2.00       */
{              /*   as of 02/01/92        */
   int c, i;
   int garbage, numgot=0;
   /*   skip ahead   */
   c = getc(fp);
   while(1) {
      /*   comments ?   */
      if (c == '#'){    /*   found a comment   */
         while(c != '\n' && c != EOF) {
        c = getc(fp);
        printf("%c",c);
     }
      }
      if (c == EOF){
     return 0;
      }
      if ( c >='0' && c <= '9') break; /* found a number */
      if ( c != ' ' && c != '\t' && c != '\r' && c != '\n'
        && c != ',') garbage = 1;
      c = getc(fp);
   }

   /*   go until non-number   */
   i = 0;
   while(1){
      i = (i*10) + (c - '0');
      c = getc(fp);
      if ( c == EOF) return i;
      if ( c < '0' || c > '9') break;
   }
   numgot++;
   return i;
}

read_pgm_header(fp)
FILE *fp;

{
   int c, c1, file_type;
   c = getc(fp);
   c1 = getc(fp);
   if(c!='P' || c1<'5' || c1>'6') {
      printf("PGM error: unsupported format.\n");
      printf("Must be raw grayscale or color.\n");
      getch();      exit(1);
   }
   img_type[0]=c;   img_type[1]=c1;   img_type[3]='\0';
   printf("Image type: %s\n",img_type);
   width = getint(fp);
   printf("%d wide\n",width);
   height= getint(fp);
   printf("%d high\n",height);
   max_color = getint(fp);
   printf("%d colors\n",max_color);
   if ( max_color <= 0){
      printf("PGM error: Garbage in Header\n");
      getch();      exit(1);
   }
   return (c1-'0');
}

/* end of pgmtoql8_c May 22, 1992 noon  */

/* pbmtoql2C_c
 *  H. L. Schaaf
 *  Program to read a bit mapped black/white file
 *  such as ram2_out_pbm from RDSphere programs
 *  and display on QL in 2 color mode, block size == 1
 *  compensates for density by discounting guide boxes etc.
 *  compile with %300000  -bufp150K
 *  returns 96% and 20% memory usage
 *  June 3, 1992  11:15
 */

#include <stdio_h>
#include <qlib_h>
#include <qdos_h>
#include <string.h>
#include <ctype.h>
#include <limits.h>

#define MAX_W 512 /*   maximum image dimension   */
#define MAX_H 256 /*   based on 1x1 block size   */
#define LEFT_EDGE 0  /*   for image out screen   */
#define IMAGE_DOTS 86400 /*   assumes 360 x 240  */

char _PROG_NAME[] = "pbmtoql2C";

struct QLRECT grawin, *grwin;
FILE  *fp_grwn;   /* file pointer for graphic window */
FILE  *fp_in;     /* file pointer for input images */
unsigned char in_image[MAX_H*MAX_W];/* input image array */
char  img_type[6];      /* image file type (P4 = b&w) */
char  filein[36];         /*   input file name   */
int   width, height;      /* input image width, height */
int   img_bytes;        /*   number of bytes in image   */
int   row, col, block;   /* loop control values */
int   x, y, w, h;      /* QLRECT values */
long  to_grwn, to_in;   /*   QL channel id   */
int   bp, img_byte, count, ort;
unsigned char  bit_string[9];
int   byte_count, wite_count, hite_count;

main()
{
   /*   get name of image file   */
   sd_clear(fgetchid(stdout),-1);
   printf("name of image file ?\nsuch as flp1_SP1_PBM \n");
   gets(filein);
   printf("\n%s\n",filein);
   /* open image file to read */
   fp_in = fopen(filein,"rb");   /* read binary file   */
   if (fp_in == NULL)
   {
      printf("Error Opening image file %s\n",filein);
      printf("Touch [space-bar]");
      getch();      exit(1);
   }
   to_in = getchid(fileno(fp_in));
   printf("opened input file #%u located at %u\n",fileno
     (fp_in), fp_in);
   printf("\n touch [space-bar]\n");
   getch();
   /* set mode, etc. */
   grwin = &grawin;
   mt_dmode("4","0");
   fp_grwn = freopen("scr_512x256a0x0","w",stdout);
   to_grwn = getchid(fileno(stdout));
   sd_bordr(to_grwn,-1,0,0);
   sd_setst(to_grwn,-1,6);
   sd_setpa(to_grwn,-1,7);
   sd_setsz(to_grwn,-1,1,0);
   sd_setin(to_grwn,-1,0);
   grwin->q_width = 512; w = grawin.q_width;
   grwin->q_height = 256; h = grawin.q_height;
   grwin->q_x = LEFT_EDGE; x = grawin.q_x;
   grwin->q_y = 0; y = grawin.q_y;
   sd_clear(to_grwn,-1);
   sd_clear(stderr,-1);

   /* read header information */
   printf("\nReading Header information\n - - - \n\n");
   read_pbm_header(fp_in);
   printf("\nplease wait - - reading bytes\n");

   /* read image into 1 dimensional array */
   img_bytes = fread(in_image, sizeof(unsigned char),(width
     *height)/8,fp_in);
   fclose(fp_in);
   printf(" %d img_bytes\n\n",img_bytes);
   printf("touch [space-bar]\n");
   getch();
   sd_clear(to_grwn,-1);
   /*   density == bits set per image  */
   count = hite_count = wite_count= 0;
   /* set size of block for pixelating */

   block = 1;
   grwin->q_width  = block; w = grawin.q_width;
   grwin->q_height = block; h = grawin.q_height;

   for (byte_count=0; byte_count<img_bytes; byte_count++) {
      img_byte = in_image[byte_count];
      for ( bp = 0; bp < 8; bp++) {
         if(img_byte > 127)  {
            img_byte -= 128;
            grwin->q_x = LEFT_EDGE + wite_count;
        grwin->q_y = hite_count;
        x = grawin.q_x;  y = grawin.q_y;
        sd_fill(to_grwn,-1,0,grwin);
        count++;
     }
         img_byte <<= 1;
         wite_count++;
     if(wite_count == width) {
        wite_count = 0;
        hite_count++;
     }
      }
   }

   getch();

   /* hang on until we see it */
   grwin->q_width = 512; w = grawin.q_width;
   grwin->q_height = 256; h = grawin.q_height;
   grwin->q_x = 0; x = grawin.q_x;
   grwin->q_y = 0; y = grawin.q_y;
   sd_setin(to_grwn,-1,0);
   sd_tab(to_grwn,-1,48);
   count -= 96; /*   take out guide boxes   */
   printf("count = %d\n",count);
   sd_tab(to_grwn,-1,48);
   printf("last x = %d\n",wite_count);
   sd_tab(to_grwn,-1,48);
   printf("last y = %d\n",hite_count);
   sd_tab(to_grwn,-1,48);
   printf("width = %d\n",width);
   sd_tab(to_grwn,-1,48);
   printf("%d/%d = \n",(100*count),IMAGE_DOTS);
   sd_tab(to_grwn,-1,48);
   printf("%d with\n",(100*count)/IMAGE_DOTS);
   ort = (100*count) % IMAGE_DOTS;
   sd_tab(to_grwn,-1,48);
   printf("%d left over\n",ort);
   ort = ort*100;
   ort = ort/IMAGE_DOTS;
   getch();
   sd_tab(to_grwn,-1,48);
   sd_tab(to_grwn,-1,48);
   printf("density:\n");
   sd_tab(to_grwn,-1,48);
   sd_tab(to_grwn,-1,48);
   printf("%3d.%2d %%\n",((100*count)/(IMAGE_DOTS)),ort);
   printf("\n\n");
   sd_tab(to_grwn,-1,48);
   printf(" [space-bar]\n");
   sd_tab(to_grwn,-1,48);
   printf("  to leave\n");
   getch();
   fclose(fp_grwn);
   exit(0);
}

read_pbm_header(fp)
FILE *fp;
{
   int c, c1, file_type;
   c = getc(fp);
   c1 = getc(fp);
   if(c != 'P' || c1 != '4') {
      printf("PBM error: unsupported format.\n");
      printf("must be bitmapped black/white.\n");
      getch();      exit(1);
   }
   img_type[0]=c;   img_type[1]=c1;   img_type[3]='\0';
   printf("image type: %s\n",img_type);
   width = getint(fp);
   printf("%d wide\n",width);
   height= getint(fp);
   printf("%d high\n",height);
   return( c1 - '0');
}

int getint(fp) /*   from John Bradley's   */
FILE *fp;      /*   xv version 2.00       */
{              /*   as of 02/01/92        */
   int c, i;
   int garbage, numgot=0;
   /*   skip ahead   */
   c = getc(fp);
   while(1) {
      /*   comments ?   */
      if (c == '#'){    /*   found a comment   */
         printf("#");
     while(c != '\n' && c != EOF) {
        c = getc(fp);
        printf("%c",c);
     }
      }
      if (c == EOF){
     return 0;
      }
      if ( c >='0' && c <= '9') break; /* found a number */
      if ( c != ' ' && c != '\t' && c != '\r' && c != '\n'
         && c != ',') garbage = 1;
      c = getc(fp);
   }

   /*   go until non-number   */
   i = 0;
   while(1){
      i = (i*10) + (c - '0');
      c = getc(fp);
      if ( c == EOF) return i;
      if ( c < '0' || c > '9') break;
   }
   numgot++;
   return i;
}

itoab(n, s, b) /* convert integer to string in base b */
int n;   char *s; int b;
{
   char *ptr;
   int lowbit;
   ptr = s;
   b >>= 1;
   do {
      lowbit = n & 1;
      n = ( n >> 1 ) & INT_MAX;
      *ptr = ((n%b) << 1) + lowbit;
      if( *ptr <10)
         *ptr += '0';
      else *ptr += 55;
      ++ptr;
   }
   while (n /= b);
   *ptr = 0;
   reverse (s);
}

reverse(s)  /*   reverse string in place   */
char *s;
{
   char *j;
   int c;
   j=s+strlen(s) -1;
   while(s < j) {
      c = *s;
      *s++ = *j;
      *j-- = c;
   }
}

itoa(n,s)   /*   convert int n to string s   */
char *s; int n;
{
   int sign;
   char *ptr;
   ptr = s;
   if((sign=n)<0) n= -n;
   do{
      *ptr++ = n % 10 + '0';
   }  while (( n = n / 10) > 0);
   if ( sign <0) *ptr++ = '-';
   *ptr = '\0';
   reverse(s);
}

/* end of pbmtoql2C_c June 3, 1992 11:45  */