From compuserve.com!72000.1642 Sat, 20 May 95 00:56:41 CEST
Received: by cloanto.it (9.7/wU1.10)
	  id <9u2l@cloanto.it>; Sat, 20 May 95 00:56:41 CEST
Received: from dub-img-2.compuserve.com by telnetwork.it with SMTP id AA20309
  (5.67b/IDA-1.5 for <mcb@cloanto.it>); Fri, 19 May 1995 18:30:29 +0200
Received: by dub-img-2.compuserve.com (8.6.10/5.950515)
	id MAA12301; Fri, 19 May 1995 12:30:01 -0400
Date: 19 May 95 12:27:46 EDT
Message-Id: <950519162746_72000.1642_FHG95-4@CompuServe.COM>
From: Diana Gruber <72000.1642@compuserve.com>
To: <mcb@cloanto.it>
Subject: Re: Distribution of GIF/LZW Article

Hi, Michael,

Check out files created by Steve Rimmer's GWSWIN. Especially, check out
the tEXT chunk. He embeds carriage returns in there, you need to watch
for that. (But only if you are displaying text).

In the next message, I will send you my version of "PNGCHECK" which
checks the chunks, etc.

Diana

From compuserve.com!72000.1642 Sat, 20 May 95 00:56:45 CEST
Received: by cloanto.it (9.7/wU1.10)
	  id <9u2m@cloanto.it>; Sat, 20 May 95 00:56:45 CEST
Received: from arl-img-3.compuserve.com by telnetwork.it with SMTP id AA20432
  (5.67b/IDA-1.5 for <mcb@cloanto.it>); Fri, 19 May 1995 18:37:43 +0200
Received: by arl-img-3.compuserve.com (8.6.10/5.950515)
	id MAA26573; Fri, 19 May 1995 12:37:16 -0400
Date: 19 May 95 12:33:41 EDT
Message-Id: <950519163341_72000.1642_FHG95-5@CompuServe.COM>
From: Diana Gruber <72000.1642@compuserve.com>
To: <mcb@cloanto.it>
Subject: Re: Distribution of GIF/LZW Article

 /*********************************************************************\
 *                                                                     *
 *  PNGCHECK.C -- read and display information about PNG files         *
 *  in it. This version by Diana Gruber (72000,1642@compuserve.com)    *
 *  based on code originally written by Alexander Lehmann              *         *
 *  <alex@hal.rhein-main.de> with contrubitions by Glenn               *
 *  Randers-Pehrson (and possibly others).                             *
 *                                                                     *
 *  This code is free for all commercial and and other use.            *
 *                                                                     *
 *  This code checks the validity of a PNG file, including the         *
 *  header "magic" bytes, the chunk id's, chunk sizes, and CRC's.      *
 *  Also, if specified, it writes the text chunk to the screen,        *
 *  and the IDAT chunk to a file.                                      *
 *                                                                     *
 *  Command line format is:                                            *
 *                                                                     *
 *   PNG <-v> <-t> <-i> filename                                       *
 *   -v: verbose listing                                               *
 *   -t: print tEXT block                                              *
 *   -i: print IDAT block info to file idatinfo.txt                    *
 *                                                                     *
 \*********************************************************************/
 
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
 
#define OK       1
#define ERROR    0
#define TRUE     1
#define FALSE    0
 
#define CRCCOMPL(c) ((c)^0xffffffffL)  /* for CRC */
#define CRCINIT (CRCCOMPL(0))
#define MIN(x,y) ((x) < (y)) ? (x) : (y)
 
 /*** function declarations ***/
 
int            main(int argc, char * *argv);
unsigned long  getlong(FILE *fp);
void           make_crc_table(void);
void           pngcheck(FILE *fp);
int            PNG_check_magic(unsigned char *magic);
int            PNG_check_chunk_name(char *chunk_name);
unsigned long  update_crc(unsigned long crc,unsigned char *buf,int len);
 
 /*** global variable declarations ***/
 
#define BUFSIZE 32767           /* size of read block for CRC calculation */
unsigned char buffer[BUFSIZE];  /* buffer for reading file data */
int verbose;                    /* if TRUE, print chunk info */
int printtext;                  /* if TRUE, print tEXt chunks */
int idat_info;                  /* if TRUE, print IDAT chunk to ASCII file */
char png_fname[13];             /* file name (from command line */
 
 /* table of crc's of all 8-bit messages */
unsigned long crc_table[256];
FILE *idat_stream;
 
 /*********************************************************************\
 *                                                                     *
 * function main -- accept command line parameters, process as         *
 * appropriate                                                         *
 *                                                                     *
 \*********************************************************************/
 
void main(int argc, char *argv[])
{
   FILE *fp;
   int i;
   int error;
 
   if (argc==1)
   {
      error = TRUE;
   } 
   else
   {
      /* get the command line options */
      verbose = FALSE;
      printtext = FALSE;
      idat_info = FALSE;
      for(i=1; i<argc; i++)
      {
        if (strcmpi(argv[i],"-v")==0)
           verbose=TRUE;
        if (strcmpi(argv[i],"-t")==0)
           printtext=TRUE;
        if (strcmpi(argv[i],"-i")==0)
           idat_info=TRUE;
      }
 
      /* try to open the PNG file */
      error = TRUE;
      for(i=1; i<argc; i++)
      {
         if((fp=fopen(argv[i],"rb"))!=NULL)
         {
            error = FALSE;
            strcpy(png_fname,argv[i]);
            break;
         }
      }
 
      /* Couldn't find it? Try again, this time appending ".png" 
         to the file name */
      if (error)
      {
         for(i=1; i<argc; i++)
         {
            if (strlen(argv[i]) <= 8)
            { 
               strcpy(png_fname,argv[i]);
               strcat(png_fname,".png");
               if((fp=fopen(png_fname,"rb"))!=NULL)
               {
                  error = FALSE;
                  break;
               }
            }
         }
      }
   }
 
   if (error)
   {
      printf ("format is: PNG <-v> <-t> <-i> filename\n");
      printf (" -v: verbose listing\n");
      printf (" -t: print tEXT block\n");
      printf (" -i: print IDAT block info to file idatinfo.txt\n");
   }
   else
   {
      if (idat_info)
      {
         idat_stream=fopen("idatinfo.txt","wt");
         fprintf(idat_stream,"PNG file name: %s\n",png_fname);
      }
      make_crc_table();
      pngcheck(fp);
      fclose(fp);
      fclose(idat_stream);
   }
   exit(0);
}
 
 /*********************************************************************\
 *                                                                     *
 * getlong -- read a 4-byte integer (e.g. specifies how many bytes     *
 * in a chunk). Converts big endian (PNG preferred) to little endian   *
 * (intel preferred) format.                                           *
 *                                                                     *
 \*********************************************************************/
 
unsigned long getlong(FILE *fp)
{
   unsigned long result;
   int c;
   int i;
 
   result = 0;
   for (i=0; i<4; i++) 
   {
      if ((c=fgetc(fp))==EOF) 
      {
         printf("%s: EOF while reading 4 bytes value\n", png_fname);
         return 0;
      }
 
      /* convert to little endian value */
      result<<=8;
      result|=c&0xff;
   }
   return(result);
}
 
 /*********************************************************************\
 *                                                                     *
 * make_crc_table -- make the table for a fast crc                     *
 *                                                                     *
 \*********************************************************************/
 
void make_crc_table(void)
{
   unsigned long c;
   int n, k;
 
   for (n = 0; n < 256; n++)
   {
      c = (unsigned long)n;
      for (k = 0; k < 8; k++)
         c = c & 1 ? 0xedb88320L ^ (c >> 1) : c >> 1;
      crc_table[n] = c;
   }
}
 
 /*********************************************************************\
 *                                                                     *
 * pngcheck -- check all the chunks in a png file, list verbose        *
 * information if requested.                                           *
 *                                                                     *
 \*********************************************************************/
 
void pngcheck(FILE *fp)
{
   long chunk_size,bytes_left;
   long save_pos;
   long print_index;
   long width, height;
   char bit_depth,compression_type;
   char filter_type, interlace_type;
   unsigned long crc, filecrc;
   unsigned char magic[8];
   char chunk_id[5];
   char *p;
   int first;
   int i,nchar;
   int to_read,nbytes;
   int c;
 
   /* string terminators (for printing out text ) */
   char termstring[6];
   termstring[0] = '\n';
   termstring[1] = '\r';
   termstring[3] = '';
   termstring[4] = 13;
   termstring[5] = 10;
   termstring[6] = 0;
 
   first = TRUE;
 
   /* check the 'magic' PNG header */
   if(fread(magic, 1, 8, fp)!=8)
   {
      printf("%s: Cannot read PNG header\n", png_fname);
      return;
   }
   if (!PNG_check_magic(magic)) return;
 
   /* read one character at a time, until end of file */
   while((c=fgetc(fp)) != EOF)
   {
      ungetc(c,fp);
      chunk_size = getlong(fp);
 
      /* read the chunk id */
      if (fread(chunk_id,1,4,fp) != 4)
      {
         printf("%s: EOF while reading chunk type\n", png_fname);
         return;
      }
      chunk_id[4]=0;
 
      /* check validity of chunk id */
      if (!PNG_check_chunk_name(chunk_id)) return;
 
      /* first chunk should be the header chunk */
 
      if (strcmp(chunk_id,"IHDR") == 0)
      {
         first = FALSE;
 
         /* keep track of the file position */
         save_pos = ftell(fp);
 
         /* read the header information */
         width = getlong(fp);
         height = getlong(fp);
         bit_depth = fgetc(fp);
         compression_type = fgetc(fp);
         filter_type = fgetc(fp);
         interlace_type = fgetc(fp);
 
         /* reset the file pointer so we don't mess up the crc */
         fseek (fp, save_pos, SEEK_SET);
 
         if (idat_info)
         {
             fprintf(idat_stream,"    width:  %ld\n",width);
             fprintf(idat_stream,"    height: %ld\n",height);
             fprintf(idat_stream,"    bit depth: %d\n",(int)bit_depth);
             fprintf(idat_stream,"    compression type: %d\n",(int)compression_type);
             fprintf(idat_stream,"    filter type: %d\n",(int)filter_type);
             fprintf(idat_stream,"    interlace type: %d\n",(int)interlace_type);
             fprintf(idat_stream,"   \nIDAT info:\n\n");
         }
      }
      else if (first)
      {
         printf("%s: file doesn't start with a IHDR chunk\n", png_fname);
      }
 
      /* print chunk data to screen */
      if (verbose)
      {
         printf("%s: chunk %s at %lXh length %lXh (%ld bytes)\n",
                 png_fname,chunk_id,ftell(fp)-4,chunk_size,chunk_size);
 
         /* if it's the header chunk, print more information */
         if (strcmp(chunk_id,"IHDR") == 0)
         {
             /* print the header information */
             printf("   width:  %ld\n",width);
             printf("   height: %ld\n",height);
             printf("   bit depth: %d\n",(int)bit_depth);
             printf("   compression type: %d\n",(int)compression_type);
             printf("   filter type: %d\n",(int)filter_type);
             printf("   interlace type: %d\n",(int)interlace_type);
         }
      }
 
      crc = update_crc(CRCINIT,(unsigned char *)chunk_id,4);
      print_index = 0;         /* used for text file formatting */
 
      /* read all the bytes in the chunk */
      bytes_left = chunk_size; /* keep track of how many bytes left in chunk */
      while (bytes_left > 0)
      {
         to_read = MIN(bytes_left,BUFSIZE);  /* read as many as possible */
         if ((nbytes = fread(buffer,1,to_read,fp)) < to_read)
         {
            printf("%s: EOF while reading chunk data (%s)\n", png_fname,chunk_id);
            return;
         }
 
         crc=update_crc(crc,buffer,nbytes);
         bytes_left-=nbytes;                 /* this buffer contains nbytes */
 
         /* print the text chunk */
         if(printtext && strcmp(chunk_id,"tEXt")==0)
         {
            i = 0;
 
            /* check here for string terminators (e.g. linefeeds, etc.) */
            if ((p = strpbrk(&buffer[i],termstring)) != NULL)
               *p = 0;
            nchar = strlen((char*)&buffer[i]);
 
            /* print out all the strings */
            while (i < to_read)
            {
               printf(&buffer[i]);
               printf("\n");
               i = i + nchar + 1;
               if ((p = strpbrk(&buffer[i],termstring)) != NULL)
                  *p = 0;
               nchar = strlen((char*)&buffer[i]);
            }
         }
 
         /* print the IDAT chunk to a text file*/
         if (idat_info && strcmp(chunk_id, "IDAT")==0)
         {
            for (i = 0; i < nbytes; i++)
            {
               print_bits(buffer[i]);
               /* format the text file - insert carriage returns */
               if (print_index%8 == 7)
                  fprintf(idat_stream,"\n");
               print_index++;   
            }
         }
      }
 
      /* check the crc info at the end of the chunk */
      filecrc=getlong(fp);
      if (filecrc!=CRCCOMPL(crc))
      {
         printf("%s: CRC error in chunk %s (actual %08lX, should be %08lX)\n",
            png_fname, chunk_id, CRCCOMPL(crc), filecrc);
         return;
      }
   }
 
   if(strcmp(chunk_id, "IEND")!=0)
   {
      printf("%s: file doesn't end with a IEND chunk\n", png_fname);
      return;
   }
   printf("%s: file appears to be OK\n", png_fname);
}
 
 /*********************************************************************\
 *                                                                     *
 *  PNG_check_chunk_name -- just make sure it has 4 alpha characters   *
 *  in it.                                                             *
 *                                                                     *
 \*********************************************************************/
 
int PNG_check_chunk_name(char *chunk_name)
{
   if(!isalpha(chunk_name[0]) || !isalpha(chunk_name[1]) ||
      !isalpha(chunk_name[2]) || !isalpha(chunk_name[3])) 
   {
      printf("chunk name %02X %02X %02X %02X doesn't comply to naming rules\n",
      chunk_name[0],chunk_name[1],chunk_name[2],chunk_name[3]);
      return(ERROR);
   }
   else
      return(OK);
}
 
 /*********************************************************************\
 *                                                                     *
 *  PNG_check_magic -- check the 'magic' png string for valid png      *
 *  file (by Alexander Lehmann).                                       *
 *                                                                     *
 \*********************************************************************/
 
int PNG_check_magic(unsigned char *magic)
{
   if (strncmp((char *)&magic[1],"PNG",3) != 0)
   {
       fprintf(stderr, "%s is not a PNG file\n",png_fname);
       return(ERROR);
   }
 
   if (magic[0] != 0x89 ||
       strncmp((char *)&magic[4],"\015\012\032\012",4) != 0)
   {
      fprintf(stderr, "PNG file is CORRUPTED.\n");
 
      /* this coding taken from Alexander Lehmanns checkpng code   */
      if (strncmp((char *)&magic[4],"\n\032",2) == 0)
         fprintf (stderr," It seems to have suffered DOS->unix conversion\n");
 
      else if(strncmp((char *)&magic[4],"\r\032",2) == 0)
         fprintf (stderr," It seems to have suffered DOS->Mac conversion\n");
 
      else if(strncmp((char *)&magic[4],"\r\r\032",3) == 0)
         fprintf (stderr," It seems to have suffered unix->Mac conversion\n");
 
      else if(strncmp((char *)&magic[4],"\n\n\032",3) == 0)
         fprintf (stderr," It seems to have suffered Mac-unix conversion\n");
 
      else if(strncmp((char *)&magic[4],"\r\n\032\r",4) == 0)
         fprintf (stderr," It seems to have suffered unix->DOS conversion\n");
 
      else if(strncmp((char *)&magic[4],"\r\r\n\032",4) == 0)
         fprintf (stderr," It seems to have suffered unix->DOS conversion\n");
 
      else if(strncmp((char *)&magic[4],"\r\n\032\n",4) != 0)
         fprintf (stderr," It seems to have suffered EOL conversion\n");
 
      if(magic[0]==9)
         fprintf (stderr," It was probably transmitted through a 7bit channel\n");
 
      else if (magic[0]!=0x89)
         fprintf (stderr,"  It was probably transmitted in text mode\n");
 
      return(ERROR);
   }
   return(OK);
}
 
 /*********************************************************************\
 *                                                                     *
 * print_bits -- print 8 bits per byte to file idat_stream             *                                   *
 *                                                                     *
 \*********************************************************************/
 
void print_bits (char c)
{
   register int i;
 
   for (i = 0; i < 8; i++)
   {
      if (c&0x80)
         fprintf(idat_stream,"1");
      else
         fprintf(idat_stream,"0");
      c<<=1;
   }
   fprintf(idat_stream," ");
}
 
 /*********************************************************************\
 *                                                                     *
 * update_crc -- update a running crc with the bytes buf[0..len-1]     *
 * the crc should be initialized to all 1's, and the transmitted value *
 * is the 1's complement of the final running crc.                     *
 *                                                                     *
 \*********************************************************************/
 
unsigned long update_crc(unsigned long crc, unsigned char *buf, int len)
{
   unsigned long c = crc;
   unsigned char *p = buf;
   int n = len;
 
   if (n > 0) do 
   {
      c = crc_table[(c ^ (*p++)) & 0xff] ^ (c >> 8);
   } while (--n);
 
   return(c);
}
