/* ppmscale.c - read a portable pixmap and scale it
**
** Copyright (C) 1989 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 <stdio.h>
#ifdef	SYSV
#include <string.h>
#else	SYSV
#include <strings.h>
#endif	SYSV
#include "ppm.h"

#define SCALE 4096
#define HALFSCALE 2048

#define max(a,b) ((a) > (b) ? (a) : (b))

main( argc, argv )
int argc;
char *argv[];
    {
    FILE *ifd;
    pixel **pixels, **temppixels, *newpixelrow;
    register pixel *pP, *npP;
    int argn, specxscale, specyscale, specxsize, specysize;
    int rows, cols, newrows, newcols;
    register int row, col, newrow, neednew;
    pixval maxval;
    float xscale, yscale;
    long sxscale, syscale;
    register long fractofill, fracleft;
    char *usage = "<s> [ppmfile]\n            -xsize|width|-ysize|-height <s> [ppmfile]\n            -xscale|-yscale <s> [ppmfile]\n            -xscale|-xsize|-width <s> -yscale|-ysize|-height <s> [ppmfile]";

    pm_progname = argv[0];

    argn = 1;
    specxscale = specyscale = specxsize = specysize = 0;

    while ( argn + 1 < argc && argv[argn][0] == '-' )
	{
	if ( strncmp(argv[argn],"-xscale",max(strlen(argv[argn]),4)) == 0 )
	    {
	    if ( specxsize )
		pm_error(
		    "only one of -xsize/-width and -xscale may be specified",
		    0,0,0,0,0 );
	    if ( sscanf( argv[argn+1], "%g", &xscale ) != 1 )
		pm_usage( usage );
	    if ( xscale <= 0.0 )
		pm_error( "x scale must be greater than 0", 0,0,0,0,0 );
	    specxscale = 1;
	    }
	else if ( strncmp(argv[argn],"-yscale",max(strlen(argv[argn]),4)) == 0 )
	    {
	    if ( specysize )
		pm_error(
		    "only one of -ysize/-height and -yscale may be specified",
		    0,0,0,0,0 );
	    if ( sscanf( argv[argn+1], "%g", &yscale ) != 1 )
		pm_usage( usage );
	    if ( yscale <= 0.0 )
		pm_error( "y scale must be greater than 0", 0,0,0,0,0 );
	    specyscale = 1;
	    }
	else if ( strncmp(argv[argn],"-xsize",max(strlen(argv[argn]),4)) == 0 ||
	          strncmp(argv[argn],"-width",max(strlen(argv[argn]),2)) == 0 )
	    {
	    if ( specxscale )
		pm_error(
		    "only one of -xscale and -xsize/-width may be specified",
		    0,0,0,0,0 );
	    if ( sscanf( argv[argn+1], "%d", &newcols ) != 1 )
		pm_usage( usage );
	    if ( newcols <= 0 )
		pm_error( "new width must be greater than 0", 0,0,0,0,0 );
	    specxsize = 1;
	    }
	else if ( strncmp(argv[argn],"-ysize",max(strlen(argv[argn]),4)) == 0 ||
	          strncmp(argv[argn],"-height",max(strlen(argv[argn]),2)) == 0 )
	    {
	    if ( specyscale )
		pm_error(
		    "only one of -yscale and -ysize/-height may be specified",
		    0,0,0,0,0 );
	    if ( sscanf( argv[argn+1], "%d", &newrows ) != 1 )
		pm_usage( usage );
	    if ( newrows <= 0 )
		pm_error( "new height must be greater than 0", 0,0,0,0,0 );
	    specysize = 1;
	    }
	else
	    pm_usage( usage );
	argn += 2;
	}

    if ( ! ( specxscale || specyscale || specxsize || specysize ) )
	{
	/* No flags specified, so a single scale factor is required. */
	if ( argn == argc )
	    pm_usage( usage );
	if ( sscanf( argv[argn], "%g", &xscale ) != 1 )
	    pm_usage( usage );
	if ( xscale <= 0.0 )
	    pm_error( "scale must be greater than 0", 0,0,0,0,0 );
	argn++;
	yscale = xscale;
	specxscale = specyscale = 1;
	}

    /* Now get input file. */
    if ( argn != argc )
	{
	ifd = pm_openr( argv[argn] );
	argn++;
	}
    else
	ifd = stdin;

    if ( argn != argc )
	pm_usage( usage );

    ppm_pbmmaxval = 255;	/* use larger value for better results */
    pixels = ppm_readppm( ifd, &cols, &rows, &maxval );

    pm_close( ifd );

    /* Compute all sizes and scales. */
    if ( specxsize )
	xscale = (float) newcols / (float) cols;
    else if ( specxscale )
	newcols = cols * xscale + 0.999;

    if ( specysize )
	yscale = (float) newrows / (float) rows;
    else if ( specyscale )
	newrows = rows * yscale + 0.999;
    else
	if ( specxsize )
	    {
	    yscale = xscale;
	    newrows = rows * yscale + 0.999;
	    }
	else
	    {
	    yscale = 1.0;
	    newrows = rows;
	    }
    
    if ( ! ( specxsize || specxscale ) )
	if ( specysize )
	    {
	    xscale = yscale;
	    newcols = cols * xscale + 0.999;
	    }
	else
	    {
	    xscale = 1.0;
	    newcols = cols;
	    }

    sxscale = xscale * SCALE;
    syscale = yscale * SCALE;

    /* First scale Y from pixels into temppixels. */
    if ( newrows == rows )	/* shortcut Y scaling if possible */
	temppixels = pixels;
    else
	{
	temppixels = ppm_allocarray( cols, newrows );
	for ( col = 0; col < cols; col++ )
	    {
	    register long r, g, b;

	    newrow = 0;
	    npP = &(temppixels[newrow][col]);
	    fractofill = SCALE;
	    r = g = b = HALFSCALE;
	    neednew = 0;
	    for ( row = 0; row < rows; row++ )
		{
		pP = &(pixels[row][col]);
		fracleft = syscale;
		while ( fracleft >= fractofill )
		    {
		    if ( neednew )
			{
			newrow++;
			npP = &(temppixels[newrow][col]);
			r = g = b = HALFSCALE;
			neednew = 0;
			}
		    r += fractofill * PPM_GETR( *pP );
		    g += fractofill * PPM_GETG( *pP );
		    b += fractofill * PPM_GETB( *pP );
		    r /= SCALE;
		    g /= SCALE;
		    b /= SCALE;
		    if ( r > maxval ) r = maxval;
		    if ( g > maxval ) g = maxval;
		    if ( b > maxval ) b = maxval;
		    PPM_ASSIGN( *npP, r, g, b );
		    fracleft -= fractofill;
		    fractofill = SCALE;
		    neednew = 1;
		    }
		if ( fracleft > 0 )
		    {
		    if ( neednew )
			{
			newrow++;
			npP = &(temppixels[newrow][col]);
			r = g = b = HALFSCALE;
			neednew = 0;
			}
		    r += fracleft * PPM_GETR( *pP );
		    g += fracleft * PPM_GETG( *pP );
		    b += fracleft * PPM_GETB( *pP );
		    fractofill -= fracleft;
		    }
		}
	    if ( fractofill > 0 )
		{
		pP = &(pixels[rows-1][col]);
		r += fractofill * PPM_GETR( *pP );
		g += fractofill * PPM_GETG( *pP );
		b += fractofill * PPM_GETB( *pP );
		}
	    if ( ! neednew )
		{
		r /= SCALE;
		g /= SCALE;
		b /= SCALE;
		if ( r > maxval ) r = maxval;
		if ( g > maxval ) g = maxval;
		if ( b > maxval ) b = maxval;
		PPM_ASSIGN( *npP, r, g, b );
		}
	    }
	ppm_freearray( pixels, rows );
	}

    /* Now scale X and write it out. */
    if ( newcols == cols )	/* shortcut X scaling if possible */
	ppm_writeppm( stdout, temppixels, newcols, newrows, maxval );
    else
	{
	ppm_writeppminit( stdout, newcols, newrows, maxval );
	newpixelrow = ppm_allocrow( newcols );
	for ( row = 0; row < newrows; row++ )
	    {
	    register long r, g, b;

	    npP = newpixelrow;
	    fractofill = SCALE;
	    r = g = b = HALFSCALE;
	    neednew = 0;
	    for ( col = 0, pP = temppixels[row]; col < cols; col++, pP++ )
		{
		fracleft = sxscale;
		while ( fracleft >= fractofill )
		    {
		    if ( neednew )
			{
			npP++;
			r = g = b = HALFSCALE;
			neednew = 0;
			}
		    r += fractofill * PPM_GETR( *pP );
		    g += fractofill * PPM_GETG( *pP );
		    b += fractofill * PPM_GETB( *pP );
		    r /= SCALE;
		    g /= SCALE;
		    b /= SCALE;
		    if ( r > maxval ) r = maxval;
		    if ( g > maxval ) g = maxval;
		    if ( b > maxval ) b = maxval;
		    PPM_ASSIGN( *npP, r, g, b );
		    fracleft -= fractofill;
		    fractofill = SCALE;
		    neednew = 1;
		    }
		if ( fracleft > 0 )
		    {
		    if ( neednew )
			{
			npP++;
			r = g = b = HALFSCALE;
			neednew = 0;
			}
		    r += fracleft * PPM_GETR( *pP );
		    g += fracleft * PPM_GETG( *pP );
		    b += fracleft * PPM_GETB( *pP );
		    fractofill -= fracleft;
		    }
		}
	    if ( fractofill > 0 )
		{
		pP--;
		r += fractofill * PPM_GETR( *pP );
		g += fractofill * PPM_GETG( *pP );
		b += fractofill * PPM_GETB( *pP );
		}
	    if ( ! neednew )
		{
		r /= SCALE;
		g /= SCALE;
		b /= SCALE;
		if ( r > maxval ) r = maxval;
		if ( g > maxval ) g = maxval;
		if ( b > maxval ) b = maxval;
		PPM_ASSIGN( *npP, r, g, b );
		}
	    ppm_writeppmrow( stdout, newpixelrow, newcols, maxval );
	    }
	}

    exit( 0 );
    }
