/* libpnm3.c - pnm utility library part 3
**
** Copyright (C) 1989, 1991 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.
*/

#include "pnm.h"

#include "ppm.h"
#include "libppm.h"

#include "pgm.h"
#include "libpgm.h"

#include "pbm.h"
#include "libpbm.h"

#if __STDC__
xel
pnm_backgroundxel( xel** xels, int cols, int rows, xelval maxval, int format )
#else /*__STDC__*/
xel
pnm_backgroundxel( xels, cols, rows, maxval, format )
    xel** xels;
    int cols, rows, format;
    xelval maxval;
#endif /*__STDC__*/
    {
    xel bgxel, ul, ur, ll, lr;

    /* Guess a good background value. */
    ul = xels[0][0];
    ur = xels[0][cols-1];
    ll = xels[rows-1][0];
    lr = xels[rows-1][cols-1];

    /* First check for three corners equal. */
    if ( PNM_EQUAL( ul, ur ) && PNM_EQUAL( ur, ll ) )
	bgxel = ul;
    else if ( PNM_EQUAL( ul, ur ) && PNM_EQUAL( ur, lr ) )
	bgxel = ul;
    else if ( PNM_EQUAL( ul, ll ) && PNM_EQUAL( ll, lr ) )
	bgxel = ul;
    else if ( PNM_EQUAL( ur, ll ) && PNM_EQUAL( ll, lr ) )
	bgxel = ur;
    /* Nope, check for two corners equal. */
    else if ( PNM_EQUAL( ul, ur ) || PNM_EQUAL( ul, ll ) ||
	      PNM_EQUAL( ul, lr ) )
	bgxel = ul;
    else if ( PNM_EQUAL( ur, ll ) || PNM_EQUAL( ur, lr ) )
	bgxel = ur;
    else if ( PNM_EQUAL( ll, lr ) )
	bgxel = ll;
    else
	{
	/* Nope, we have to average the four corners.  This breaks the
	** rules of pnm, but oh well.  Let's try to do it portably. */
	switch ( PNM_FORMAT_TYPE(format) )
	    {
	    case PPM_TYPE:
	    PPM_ASSIGN( bgxel,
		PPM_GETR(ul) + PPM_GETR(ur) + PPM_GETR(ll) + PPM_GETR(lr) / 4,
		PPM_GETG(ul) + PPM_GETG(ur) + PPM_GETG(ll) + PPM_GETG(lr) / 4,
		PPM_GETB(ul) + PPM_GETB(ur) + PPM_GETB(ll) + PPM_GETB(lr) / 4 );
	    break;

	    case PGM_TYPE:
	    {
	    gray gul, gur, gll, glr;
	    gul = (gray) PNM_GET1( ul );
	    gur = (gray) PNM_GET1( ur );
	    gll = (gray) PNM_GET1( ll );
	    glr = (gray) PNM_GET1( lr );
	    PNM_ASSIGN1( bgxel, ( ( gul + gur + gll + glr ) / 4 ) );
	    break;
	    }

	    case PBM_TYPE:
	    pm_error(
	    "pnm_backgroundxel: four bits no two of which equal each other??" );

	    default:
	    pm_error( "Invalid format passed to pnm_backgroundxel()");
	    }
	}

    return bgxel;
    }

#if __STDC__
xel
pnm_backgroundxelrow( xel* xelrow, int cols, xelval maxval, int format )
#else /*__STDC__*/
xel
pnm_backgroundxelrow( xelrow, cols, maxval, format )
    xel* xelrow;
    int cols, format;
    xelval maxval;
#endif /*__STDC__*/
    {
    xel bgxel, l, r;

    /* Guess a good background value. */
    l = xelrow[0];
    r = xelrow[cols-1];

    /* First check for both corners equal. */
    if ( PNM_EQUAL( l, r ) )
	bgxel = l;
    else
	{
	/* Nope, we have to average the two corners.  This breaks the
	** rules of pnm, but oh well.  Let's try to do it portably. */
	switch ( PNM_FORMAT_TYPE(format) )
	    {
	    case PPM_TYPE:
	    PPM_ASSIGN( bgxel, PPM_GETR(l) + PPM_GETR(r) / 2,
		PPM_GETG(l) + PPM_GETG(r) / 2, PPM_GETB(l) + PPM_GETB(r) / 2 );
	    break;

	    case PGM_TYPE:
	    {
	    gray gl, gr;
	    gl = (gray) PNM_GET1( l );
	    gr = (gray) PNM_GET1( r );
	    PNM_ASSIGN1( bgxel, ( ( gl + gr ) / 2 ) );
	    break;
	    }

	    case PBM_TYPE:
	    {
	    int col, blacks;

	    /* One black, one white.  Gotta count. */
	    for ( col = 0, blacks = 0; col < cols; ++col )
		{
		if ( PNM_GET1( xelrow[col] ) == 0 )
		    ++blacks;
		}
	    if ( blacks >= cols / 2 )
		PNM_ASSIGN1( bgxel, 0 );
	    else
		PNM_ASSIGN1( bgxel, maxval );
	    break;
	    }

	    default:
	    pm_error( "Invalid format passed to pnm_backgroundxelrow()");
	    }
	}

    return bgxel;
    }

#if __STDC__
xel
pnm_whitexel( xelval maxval, int format )
#else /*__STDC__*/
xel
pnm_whitexel( maxval, format )
    xelval maxval;
    int format;
#endif /*__STDC__*/
    {
    xel x;

    switch ( PNM_FORMAT_TYPE(format) )
	{
	case PPM_TYPE:
	PPM_ASSIGN( x, maxval, maxval, maxval );
	break;

	case PGM_TYPE:
	PNM_ASSIGN1( x, maxval );
	break;

	case PBM_TYPE:
	PNM_ASSIGN1( x, maxval );
	break;

	default:
	pm_error( "Invalid format passed to pnm_whitexel()");
	}

    return x;
    }

#if __STDC__
xel
pnm_blackxel( xelval maxval, int format )
#else /*__STDC__*/
xel
pnm_blackxel( maxval, format )
    xelval maxval;
    int format;
#endif /*__STDC__*/
    {
    xel x;

    switch ( PNM_FORMAT_TYPE(format) )
	{
	case PPM_TYPE:
	PPM_ASSIGN( x, 0, 0, 0 );
	break;

	case PGM_TYPE:
	PNM_ASSIGN1( x, (xelval) 0 );
	break;

	case PBM_TYPE:
	PNM_ASSIGN1( x, (xelval) 0 );
	break;

	default:
	pm_error( "Invalid format passed to pnm_blackxel(): %d", format);
	}

    return x;
    }

#if __STDC__
void
pnm_invertxel( xel* xP, xelval maxval, int format )
#else /*__STDC__*/
void
pnm_invertxel( xP, maxval, format )
    xel* xP;
    xelval maxval;
    int format;
#endif /*__STDC__*/
    {
    switch ( PNM_FORMAT_TYPE(format) )
	{
	case PPM_TYPE:
	PPM_ASSIGN(
	    *xP, maxval - PPM_GETR( *xP ),
	    maxval - PPM_GETG( *xP ), maxval - PPM_GETB( *xP ) );
	break;

	case PGM_TYPE:
	PNM_ASSIGN1( *xP, (gray) maxval - (gray) PNM_GET1( *xP ) );
	break;

	case PBM_TYPE:
        PNM_ASSIGN1( *xP, ( PNM_GET1( *xP ) == 0 ) ? maxval : 0 );
	break;

	default:
	pm_error( "Invalid format passed to pnm_invertxel()" );
	}
    }

#if __STDC__
void
pnm_promoteformat( xel** xels, int cols, int rows, xelval maxval, int format, xelval newmaxval, int newformat )
#else /*__STDC__*/
void
pnm_promoteformat( xels, cols, rows, maxval, format, newmaxval, newformat )
    xel** xels;
    xelval maxval, newmaxval;
    int cols, rows, format, newformat;
#endif /*__STDC__*/
    {
    int row;

    for ( row = 0; row < rows; ++row )
	pnm_promoteformatrow(
	    xels[row], cols, maxval, format, newmaxval, newformat );
    }

#if __STDC__
void
pnm_promoteformatrow( xel* xelrow, int cols, xelval maxval, int format, xelval newmaxval, int newformat )
#else /*__STDC__*/
void
pnm_promoteformatrow( xelrow, cols, maxval, format, newmaxval, newformat )
    xel* xelrow;
    xelval maxval, newmaxval;
    int cols, format, newformat;
#endif /*__STDC__*/
    {
    register int col;
    register xel* xP;

    if ( ( PNM_FORMAT_TYPE(format) == PPM_TYPE &&
	   ( PNM_FORMAT_TYPE(newformat) == PGM_TYPE ||
	     PNM_FORMAT_TYPE(newformat) == PBM_TYPE ) ) ||
	 ( PNM_FORMAT_TYPE(format) == PGM_TYPE &&
	   PNM_FORMAT_TYPE(newformat) == PBM_TYPE ) )
	pm_error( "pnm_promoteformatrow: can't promote downwards!" );

    /* Are we promoting to the same type? */
    if ( PNM_FORMAT_TYPE(format) == PNM_FORMAT_TYPE(newformat) )
	{
	if ( PNM_FORMAT_TYPE(format) == PBM_TYPE )
	    return;
	if ( newmaxval < maxval )
	    pm_error(
	   "pnm_promoteformatrow: can't decrease maxval - try using pnmdepth" );
	if ( newmaxval == maxval )
	    return;
	/* Increase maxval. */
	switch ( PNM_FORMAT_TYPE(format) )
	    {
	    case PGM_TYPE:
	    for ( col = 0, xP = xelrow; col < cols; ++col, ++xP )
		PNM_ASSIGN1(
		    *xP, (int) PNM_GET1(*xP) * newmaxval / maxval );
	    break;

	    case PPM_TYPE:
	    for ( col = 0, xP = xelrow; col < cols; ++col, ++xP )
		PPM_DEPTH( *xP, *xP, maxval, newmaxval );
	    break;

	    default:
	    pm_error( "Invalid old format passed to pnm_promoteformatrow()" );
	    }
	return;
	}

    /* We must be promoting to a higher type. */
    switch ( PNM_FORMAT_TYPE(format) )
	{
	case PBM_TYPE:
	switch ( PNM_FORMAT_TYPE(newformat) )
	    {
	    case PGM_TYPE:
	    for ( col = 0, xP = xelrow; col < cols; ++col, ++xP )
		if ( PNM_GET1(*xP) == 0 )
		    PNM_ASSIGN1( *xP, 0 );
		else
		    PNM_ASSIGN1( *xP, newmaxval );
	    break;

	    case PPM_TYPE:
	    for ( col = 0, xP = xelrow; col < cols; ++col, ++xP )
		if ( PNM_GET1(*xP) == 0 )
		    PPM_ASSIGN( *xP, 0, 0, 0 );
		else
		    PPM_ASSIGN( *xP, newmaxval, newmaxval, newmaxval );
	    break;

	    default:
	    pm_error( "Invalid new format passed to pnm_promoteformatrow()" );
	    }
	break;

	case PGM_TYPE:
	switch ( PNM_FORMAT_TYPE(newformat) )
	    {
	    case PPM_TYPE:
	    if ( newmaxval < maxval )
		pm_error(
	   "pnm_promoteformatrow: can't decrease maxval - try using pnmdepth" );
	    if ( newmaxval == maxval )
		{
		for ( col = 0, xP = xelrow; col < cols; ++col, ++xP )
		    PPM_ASSIGN(
			*xP, PNM_GET1(*xP), PNM_GET1(*xP), PNM_GET1(*xP) );
		}
	    else
		{ /* Increase maxval. */
		for ( col = 0, xP = xelrow; col < cols; ++col, ++xP )
		    PPM_ASSIGN(
			*xP, (int) PNM_GET1(*xP) * newmaxval / maxval,
			(int) PNM_GET1(*xP) * newmaxval / maxval,
			(int) PNM_GET1(*xP) * newmaxval / maxval );
		}
	    break;

	    default:
	    pm_error( "Invalid new format passed to pnm_promoteformatrow()" );
	    }
	break;

	default:
	    pm_error( "Invalid old format passed to pnm_promoteformatrow()" );
	}
    }


pixel
xeltopixel(xel const inputxel) {
    
    pixel outputpixel;

    PPM_ASSIGN(outputpixel, 
               PNM_GET1(inputxel), PNM_GET1(inputxel), PNM_GET1(inputxel));
    return outputpixel;
}
