/* pbm utility library
**
** Copyright (C) 1988 by Jef Poskanzer.
**
** Permission to use, copy, modify, and distribute this software and its
** documentation for any purpose and without fee is hereby granted, provided
** that the above copyright notice appear in all copies and that both that
** copyright notice and this permission notice appear in supporting
** documentation.  This software is provided "as is" without express or
** implied warranty.
*/

/* modified by Günther Röhrich */
/* WARNING: This file is not for use with normal pbm programs */

/* support functions for the ppm stuff */

#include <stdarg.h>
#include "ppm.h"
#include "ppm2AGA.h"
#include "libpbm.h"
#include "libppm.h"
#include "libpgm.h"

extern jmp_buf ErrorEnv;
static bit pbm_getbit(FILE *);
extern pixel **pixels;
extern int cols;
extern int rows;
extern int rowcnt;
extern pixel *pixrow;
extern pixval maxval;
extern int ppmformat;
extern int ColorRegMax;

extern long  *thisrerr;
extern long  *nextrerr;
extern long  *thisgerr;
extern long  *nextgerr;
extern long  *thisberr; 
extern long  *nextberr;     
extern int Convert4096; 
extern int fs_direction;
#define FS_SCALE 1024

void FSrow(pixel *pixelrow, int cols);

/* this is called when an error in ppm2ilbm() is found */
void pm_error(char *format, ...)
{
 va_list args;
 va_start(args, format);
 vprintf(format, args); /* modify this if we don't output to stdout any more */
 va_end(args);
 longjmp(ErrorEnv, 10);
}

/* this is defined in ppm2AGA.c
void pm_message(char *format, ...)
{
 va_list args;
 va_start(args, format);
 vprintf(format, args); 
 va_end(args);
}
*/

void ppm_readppminitrest(FILE *file, int *colsP, int *rowsP, pixval *maxvalP)
    {
    int maxval;

    /* Read size. */
    *colsP = pbm_getint( file );
    *rowsP = pbm_getint( file );

    /* Read maxval. */
    maxval = pbm_getint( file );
    if ( maxval > PPM_MAXMAXVAL )
	pm_error(
"maxval is too large - try reconfiguring with PGM_BIGGRAYS\n\
    or without PPM_PACKCOLORS\n" );
    *maxvalP = maxval;
    }

pixval ppm_pbmmaxval = 1;

void ppm_readppminit(FILE *file, int *colsP, int *rowsP, 
                     pixval *maxvalP, int *formatP)
    {
    /* Check magic number. */
    *formatP = pbm_readmagicnumber( file );
    switch ( PPM_FORMAT_TYPE(*formatP) )
	{
	case PPM_TYPE:
	ppm_readppminitrest( file, colsP, rowsP, maxvalP );
	break;

	case PGM_TYPE:
	pgm_readpgminitrest( file, colsP, rowsP, maxvalP );
	break;

	case PBM_TYPE:
	pbm_readpbminitrest( file, colsP, rowsP );
	*maxvalP = ppm_pbmmaxval;
	break;

	default:
	pm_error( "bad magic number - not a ppm, pgm, or pbm file\n" );
	}
    }


char pbm_getc(FILE *file)
    {
    register int ich;
    register char ch;

    ich = getc( file );
    if ( ich == EOF )
	pm_error( "EOF / read error\n" );
    ch = (char) ich;
    
    if ( ch == '#' )
	{
	do
	    {
	    ich = getc( file );
	    if ( ich == EOF )
		pm_error( "EOF / read error\n" );
	    ch = (char) ich;
	    }
	while ( ch != '\n' && ch != '\r' );
	}

    return ch;
    }

unsigned char pbm_getrawbyte(FILE *file)
    {
    register int iby;

    iby = getc( file );
    if ( iby == EOF )
	pm_error( "EOF / read error\n" );
    return (unsigned char) iby;
    }

int pbm_getint(FILE *file)
    {
    register char ch;
    register int i;

    do
	{
	ch = pbm_getc( file );
	}
    while ( ch == ' ' || ch == '\t' || ch == '\n' || ch == '\r' );

    if ( ch < '0' || ch > '9' )
	pm_error( "junk in file where an integer should be\n" );

    i = 0;
    do
	{
	i = i * 10 + ch - '0';
	ch = pbm_getc( file );
        }
    while ( ch >= '0' && ch <= '9' );

    return i;
    }

int pbm_readmagicnumber(FILE *file)
    {
    int ich1, ich2;

    ich1 = getc( file );
    if ( ich1 == EOF )
	pm_error( "EOF / read error reading magic number\n" );
    ich2 = getc( file );
    if ( ich2 == EOF )
	pm_error( "EOF / read error reading magic number\n" );
    return ich1 * 256 + ich2;
    }


void pbm_readpbminitrest(FILE *file, int *colsP, int *rowsP ) 
    {
    /* Read size. */
    *colsP = pbm_getint( file );
    *rowsP = pbm_getint( file );
    }

void pgm_readpgminitrest(FILE *file, int *colsP, int *rowsP, gray *maxvalP)
    {
    int maxval;

    /* Read size. */
    *colsP = pbm_getint( file );
    *rowsP = pbm_getint( file );

    /* Read maxval. */
    maxval = pbm_getint( file );
    if ( maxval > PGM_MAXMAXVAL )
	pm_error( "maxval is too large - try reconfiguring with PGM_BIGGRAYS\n" );
    *maxvalP = maxval;
    }

/* Variable-sized arrays. */

char*
pm_allocrow( cols, size )
    int cols;
    int size;
    {
    register char* itrow;

    itrow = (char*) malloc( cols * size );
    if ( itrow == (char*) 0 )
        pm_error( "out of memory allocating a row\n" );
    return itrow;
    }

void
pm_freerow( itrow )
    char* itrow;
    {
    free( itrow );
    }

char**
pm_allocarray( cols, rows, size )
    int cols, rows;
    int size;
    {
    char** its;
    int i;
    its = (char**) malloc( (rows + 1) * sizeof(char*) );
    if ( its == (char**) 0 )
        pm_error( "out of memory allocating an array\n" );

    /* initialize the array, modified by Günther Röhrich         */
    /* this is needed for using pm_freearray() even if the array */
    /* is not completely allocated                               */

    memset((void *)its, 0, (rows + 1) * sizeof(char*));

    its[rows] = its[0] = (char*) malloc( rows * cols * size );
    if ( its[0] != (char*) 0 )
        for ( i = 1; i < rows; ++i )
            its[i] = &(its[0][i * cols * size]);
    else
        for( i = 0; i < rows; ++i )
        {
           /* modified by Günther Röhrich */
           if(!(its[i] = (char*) malloc( cols * size )))
           {
             pm_freearray(its, rows);
             its = NULL;
             break;
            }
         }
    return its;
    }

void
pm_freearray( its, rows )
    char** its;
    int rows;
    {
    int i;
    if( its[rows] != (char*) 0 )
        free( its[rows] );
    else
        for( i = 0; i < rows; ++i )
            pm_freerow( its[i] );
    free( its );
    }

void ppm_readppmrow(FILE* file, pixel* pixelrow, int cols, pixval maxval, int format)
    {
    register int col;
    register pixel* pP;
    register pixval r, g, b;
    gray* grayrow;
    register gray* gP;
    bit* bitrow;
    register bit* bP;

    switch ( format )
	{
	case PPM_FORMAT:
      
            for ( col = 0, pP = pixelrow; col < cols; ++col, ++pP )
	    {
	    r = pbm_getint( file );
#ifdef DEBUG
	    if ( r > maxval )
		pm_error( "r value out of bounds (%u > %u)\n", r, maxval );
#endif /*DEBUG*/
	    g = pbm_getint( file );
#ifdef DEBUG
	    if ( g > maxval )
		pm_error( "g value out of bounds (%u > %u)\n", g, maxval );
#endif /*DEBUG*/
	    b = pbm_getint( file );
#ifdef DEBUG
	    if ( b > maxval )
		pm_error( "b value out of bounds (%u > %u)\n", b, maxval );
#endif /*DEBUG*/
	    PPM_ASSIGN( *pP, r, g, b );
	    }  
         if(Convert4096) FSrow(pixelrow, cols);    
         break;    

	case RPPM_FORMAT:
	for ( col = 0, pP = pixelrow; col < cols; ++col, ++pP )
	    {
	    r = pbm_getrawbyte( file );
#ifdef DEBUG
	    if ( r > maxval )
		pm_error( "r value out of bounds (%u > %u)\n", r, maxval );
#endif /*DEBUG*/
	    g = pbm_getrawbyte( file );
#ifdef DEBUG
	    if ( g > maxval )
		pm_error( "g value out of bounds (%u > %u)\n", g, maxval );
#endif /*DEBUG*/
	    b = pbm_getrawbyte( file );
#ifdef DEBUG
	    if ( b > maxval )
		pm_error( "b value out of bounds (%u > %u)\n", b, maxval );
#endif /*DEBUG*/
	    PPM_ASSIGN( *pP, r, g, b );
	    }
        if(Convert4096) FSrow(pixelrow, cols);
	break;

	case PGM_FORMAT:
	case RPGM_FORMAT:
	grayrow = pgm_allocrow( cols );
	pgm_readpgmrow( file, grayrow, cols, maxval, format );
	for ( col = 0, gP = grayrow, pP = pixelrow; col < cols; ++col, ++gP, ++pP )
	    {
	    r = *gP;
	    PPM_ASSIGN( *pP, r, r, r );
	    }
	pgm_freerow( grayrow );
	break;

	case PBM_FORMAT:
	case RPBM_FORMAT:
	bitrow = pbm_allocrow( cols );
	pbm_readpbmrow( file, bitrow, cols, format );
	for ( col = 0, bP = bitrow, pP = pixelrow; col < cols; ++col, ++bP, ++pP )
	    {
	    r = ( *bP == PBM_WHITE ) ? maxval : 0;
	    PPM_ASSIGN( *pP, r, r, r );
	    }
	pbm_freerow( bitrow );
	break;

	default:
	pm_error( "can't happen\n" );
	}
    }


/* converts one row from 16M colors to 4096 colors using FS dithering */

void FSrow(pixel *pixelrow, int cols)
         {

           /* if floyd is nonzero we will use Floyd-Steinberg dithering */
           register int col, limitcol;
           register long sg, sr, sb, err;
           long *temperr;
           register pixel *pP;
             
           for ( col = 0; col < cols + 2; ++col )
             nextrerr[col] = nextgerr[col] = nextberr[col] = 0;

           if ( fs_direction )
           {
             col = 0;
             limitcol = cols;
             /* pP = pixels[row]; */
             pP = pixelrow;
           }
           else
           {
            col = cols - 1;
            limitcol = -1;
            /* pP = &(pixels[row][col]); */
            pP = &pixelrow[col];
	   }

           do
           {
             /* Use Floyd-Steinberg errors to adjust actual color. */
             sr = PPM_GETR(*pP) + thisrerr[col + 1] / FS_SCALE;
             sg = PPM_GETG(*pP) + thisgerr[col + 1] / FS_SCALE;
             sb = PPM_GETB(*pP) + thisberr[col + 1] / FS_SCALE;
             if ( sr < 0 ) sr = 0;
             else if ( sr > maxval ) sr = maxval;
             if ( sg < 0 ) sg = 0;
             else if ( sg > maxval ) sg = maxval;
             if ( sb < 0 ) sb = 0;
             else if ( sb > maxval ) sb = maxval;
             /* PPM_ASSIGN( *pP, sr, sg, sb ); */
             PPM_ASSIGN(*pP, sr&0xF0, sg&0xF0, sb&0xF0);          
             
           
             /* Propagate Floyd-Steinberg error terms. */
             if ( fs_direction )
             {
               err = ( sr - (long) PPM_GETR(*pP) ) * FS_SCALE; 
               thisrerr[col + 2] += ( err * 7 ) / 16;
               nextrerr[col    ] += ( err * 3 ) / 16;
               nextrerr[col + 1] += ( err * 5 ) / 16;
               nextrerr[col + 2] += ( err     ) / 16;
               err = ( sg - (long) PPM_GETG(*pP) ) * FS_SCALE;
               thisgerr[col + 2] += ( err * 7 ) / 16;
               nextgerr[col    ] += ( err * 3 ) / 16;
               nextgerr[col + 1] += ( err * 5 ) / 16;
               nextgerr[col + 2] += ( err     ) / 16;
               err = ( sb - (long) PPM_GETB(*pP) ) * FS_SCALE;
               thisberr[col + 2] += ( err * 7 ) / 16;
               nextberr[col    ] += ( err * 3 ) / 16;
               nextberr[col + 1] += ( err * 5 ) / 16;
               nextberr[col + 2] += ( err     ) / 16;
             }
             else
             {
               err = ( sr - (long) PPM_GETR(*pP) ) * FS_SCALE;
               thisrerr[col    ] += ( err * 7 ) / 16;
               nextrerr[col + 2] += ( err * 3 ) / 16;
               nextrerr[col + 1] += ( err * 5 ) / 16;
               nextrerr[col    ] += ( err     ) / 16;
               err = ( sg - (long) PPM_GETG(*pP) ) * FS_SCALE;
               thisgerr[col    ] += ( err * 7 ) / 16;
               nextgerr[col + 2] += ( err * 3 ) / 16;
               nextgerr[col + 1] += ( err * 5 ) / 16;
               nextgerr[col    ] += ( err     ) / 16;
               err = ( sb - (long) PPM_GETB(*pP) ) * FS_SCALE;
               thisberr[col    ] += ( err * 7 ) / 16;
               nextberr[col + 2] += ( err * 3 ) / 16;
               nextberr[col + 1] += ( err * 5 ) / 16;
               nextberr[col    ] += ( err     ) / 16;
             }
           
             if ( fs_direction )
             {
               ++col;
               ++pP;
             }
             else
             {
               --col;
               --pP;
             }
           } while ( col != limitcol );      
         
           temperr = thisrerr;
           thisrerr = nextrerr;
           nextrerr = temperr;
           temperr = thisgerr;
           thisgerr = nextgerr;
           nextgerr = temperr;
           temperr = thisberr;
           thisberr = nextberr;
           nextberr = temperr;
           fs_direction = ! fs_direction;
         }                  




void pgm_readpgmrow(FILE* file, gray* grayrow, int cols, gray maxval, int format)
    {
    register int col;
    register gray* gP;
    bit* bitrow;
    register bit* bP;

    switch ( format )
	{
	case PGM_FORMAT:
	for ( col = 0, gP = grayrow; col < cols; ++col, ++gP )
	    {
	    *gP = pbm_getint( file );
#ifdef DEBUG
	    if ( *gP > maxval )
		pm_error( "value out of bounds (%u > %u)\n", *gP, maxval );
#endif /*DEBUG*/
	    }
	break;
	
	case RPGM_FORMAT:
	for ( col = 0, gP = grayrow; col < cols; ++col, ++gP )
	    {
	    *gP = pbm_getrawbyte( file );
#ifdef DEBUG
	    if ( *gP > maxval )
		pm_error( "value out of bounds (%u > %u)\n", *gP, maxval );
#endif /*DEBUG*/
	    }
	break;
	
	case PBM_FORMAT:
	case RPBM_FORMAT:
	bitrow = pbm_allocrow( cols );
	pbm_readpbmrow( file, bitrow, cols, format );
	for ( col = 0, gP = grayrow, bP = bitrow; col < cols; ++col, ++gP, ++bP )
	    *gP = ( *bP == PBM_WHITE ) ? maxval : 0;
	pbm_freerow( bitrow );
	break;

	default:
	pm_error( "can't happen\n" );
	}
    }

void pbm_readpbmrow(FILE *file, bit *bitrow, int cols, int format )
    {
    register int col, bitshift;
    register unsigned char item;
    register bit* bP;

    switch ( format )
	{
	case PBM_FORMAT:
	for ( col = 0, bP = bitrow; col < cols; ++col, ++bP )
	    *bP = pbm_getbit( file );
	break;

	case RPBM_FORMAT:
	bitshift = -1;
	for ( col = 0, bP = bitrow; col < cols; ++col, ++bP )
	    {
	    if ( bitshift == -1 )
		{
		item = pbm_getrawbyte( file );
		bitshift = 7;
		}
	    *bP = ( item >> bitshift ) & 1;
	    --bitshift;
	    }
	break;

	default:
	pm_error( "can't happen\n" );
	}
    }

static bit pbm_getbit(FILE *file )
    {
    register char ch;

    do
	{
	ch = pbm_getc( file );
	}
    while ( ch == ' ' || ch == '\t' || ch == '\n' || ch == '\r' );

    if ( ch != '0' && ch != '1' )
	pm_error( "junk in file where bits should be\n" );

    return ( ch == '1' ) ? 1 : 0;
    }


pixel *next_pixrow(FILE *fp, int row, int ColorShift)
{
  int i;
  pixel *pP;

    if( pixels )
        pixrow = pixels[row];
    else {
        if( row != rowcnt )
            pm_error("big mistake\n");
        rowcnt++;
        ppm_readppmrow(fp, pixrow, cols, maxval, ppmformat);
        if(maxval == 255)
        {
          for(i=0,pP=pixrow; i<cols; i++,pP++)
          {
            pP->r = pP->r >> ColorShift;
            pP->g = pP->g >> ColorShift;
            pP->b = pP->b >> ColorShift;
          }
        }
        else
        { 
          for(i=0,pP=pixrow; i<cols; i++,pP++) PPM_DEPTH(*pP, *pP, maxval, ColorRegMax);
        } 
    }
    return pixrow;
}
