/*****************************************************************************/
/*
 *      Gmosaic.c  -- Gray Scale Mosaic
 *
 *      Copyright (C) 1999 Jim Buzbee ( jbuzbee@nyx.net )
 *
 *      This program is free software; you can redistribute it and/or modify
 *      it under the terms of the GNU General Public License as published by
 *      the Free Software Foundation; either version 2 of the License, or
 *      (at your option) any later version.
 *
 *      This program is distributed in the hope that it will be useful,
 *      but WITHOUT ANY WARRANTY; without even the implied warranty of
 *      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *      GNU General Public License for more details.
 *
 *      For a copy of the GNU General Public License write to the Free Software
 *      Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
/*****************************************************************************/

#include <stdio.h>
#include <string.h>
#include <stdlib.h>     
#include <unistd.h>
#include <getopt.h>

/*****************************************************************************/

#define TRACE fprintf(stderr,"%s %d\n", __FILE__, __LINE__ )
#define MAX_FILENAME_LENGTH 256
#define MAX_LINE_LENGTH 256

/*****************************************************************************/

int SaveImage( char *filename, unsigned char* pixels, int width, int height );

void ConcatImageLtoR( unsigned char *s1,
                      int s1x,
                      unsigned char *s2,
                      int s2x,
                      int y,
                      unsigned char *d
                    );

void Usage( char *name );

void * Imagemalloc( int size );

int ScaleImage(
                unsigned char *InputImage,
                int InputWidth,
                int InputHeight,
                unsigned char *OutputImage,
                int OutputWidth,
                int OutputHeight
               );

int CreateAdjustedTile( float v,
                        unsigned char *pixels,
                        unsigned char *new_pixels,
                        int width,
                        int height,
                        float Adjust
                      );

/******************************************************************************/

int option_index = 0;
int debug = 0;

static struct option long_options[] =
{
   {"adjust", 1, 0, 'a'},
   {"tile", 1, 0, 0},
   {"debug", 0, 0, 'd'},
   {"output", 1, 0, 0},
   {0, 0, 0, 0}
};

/******************************************************************************/
main( int argc, char * argv[] )
{
   int ix, iy;
   int c;
   int TileWidth,TileHeight;
   int ImageWidth,ImageHeight;
   char *ImageFile;
   char TileFile[MAX_FILENAME_LENGTH];
   char OutputFile[MAX_FILENAME_LENGTH];
   unsigned char *Tile;
   unsigned char *Image;
   unsigned char *OutputImage;
   unsigned char *TileCopy;
   int Max = -999;
   unsigned char *Line;
   int TileSize;
   float Adjust;
   int DefaultTileSize = 14;

   Adjust = 1.0;
   TileFile[0]= '\0';
   OutputFile[0] = '\0';

   while (1)
   {
      int this_option_optind = optind ? optind : 1;

      /* I think I'm using this wrong, but it seems to work :-) */
      c = getopt_long (argc, argv, "s:a:t:ho:d", long_options, &option_index);

      if (c == -1)
      {
         break;
      }

      switch(c)
      {
         case 'a' :
            Adjust = atof( optarg );
         break;

         case 'd' :
            debug = 1;
         break;

         default :

         case 'h' :
            Usage( argv[0] );
            exit(1);
         break;

         case 't' :
            strcpy( TileFile, optarg );

            LoadImageFile( TileFile, &Tile, &TileWidth,  &TileHeight );
         break;

         case 'o' :
            strcpy( OutputFile, optarg );
         break;

         case 's' :
            DefaultTileSize = atoi( optarg );
         break;

      }
   }

   /* the input file name */
   ImageFile = argv[ optind ];

   /* if we don't have a file name */
   if ( ! ImageFile )
   {
      /* use standard input */
      ImageFile = "-";
   }

   /* load the image file */
   LoadImageFile( ImageFile, &Image, &ImageWidth,  &ImageHeight );

   /* if we don't have a tile file */
   if ( ! TileFile[0] )
   {
      unsigned char *ScaledImage;
      float N_Tiles_Wide;
      float N_Tiles_High;

      TileWidth  = DefaultTileSize ;
      TileHeight = DefaultTileSize ;

      N_Tiles_Wide = (float) ImageWidth  / (float) TileWidth;
      N_Tiles_High = (float) ImageHeight / (float) TileHeight ;

      if ( debug )
      {
         fprintf( stderr,"image  size = %d %d\n", ImageWidth ,ImageHeight  );
         fprintf( stderr,"tiles wide high = %f %f\n",N_Tiles_Wide,N_Tiles_High);
      }

      /* allocate space for a tile */
      Tile =  Imagemalloc( TileWidth * TileHeight );

      /* scale down the input image into tile size */
      ScaleImage(
                 Image,
                 ImageWidth,
                 ImageHeight,
                 Tile,
                 TileWidth,
                 TileHeight
                );

      /* allocate space for a scaled version of the input image  */
      ScaledImage =  Imagemalloc( N_Tiles_Wide * N_Tiles_High );

      /* scale down the input image */
      ScaleImage(
                 Image,
                 ImageWidth,
                 ImageHeight,
                 ScaledImage,
                 N_Tiles_Wide,
                 N_Tiles_High
                );

      /* we're done with this...*/
      free( Image );

      /* the input image we'll work off of now is the scaled image */
      Image = ScaledImage;

      ImageWidth =  N_Tiles_Wide;
      ImageHeight =  N_Tiles_High;
   }

   /* size of the image */
   TileSize =  TileHeight * TileWidth ;

   /* Line working buffer, initially the size of a single tile */
   Line     = Imagemalloc( TileSize  );

   /* this will hold a intensity adjusted copy of the tile */
   TileCopy =  Imagemalloc( TileSize  );

   /* this is where we build the output image */
   OutputImage =  Imagemalloc( TileSize * ImageWidth * ImageHeight  );

   if ( debug )
   {
      fprintf( stderr,"Tile = %d %d, Image = %d %d\n",
              TileWidth, 
              TileHeight, 
              ImageWidth, 
              ImageHeight
              );
   }

   /* if the output file has not been specified */
   if ( ! OutputFile[0] )
   {
      /* go to stdout */
      strcpy( OutputFile, "-" );
   }

   /* figure out the  maximum  intensity in the tile */
   for ( iy = 0; iy < TileHeight; iy++ )
   {
      for ( ix = 0; ix < TileWidth; ix++ )
      {
         if ( Tile[ iy * TileWidth + ix ] > Max )
         {
            Max =  Tile[ iy * TileWidth + ix ] ;
         }
      }
   }

   /* walk through each line */
   for ( iy = 0; iy < ImageHeight; iy++ )
   {
     if ( debug )
     {
        fprintf( stderr,"%d ", iy );
        fflush( stderr );
     }

     /* walk through each pixel in the line */
     for ( ix = 0; ix < ImageWidth; ix++ )
     {
        /* get a pixel in the source image */
        int ImagePixel = ( Image[ iy * ImageWidth + ix ] ) ;

        /* allocate existing size + room for new tile */
        unsigned char *Dest = Imagemalloc( TileSize * ( ix + 1 ) );
        unsigned char *tmp;

        /* make a copy of our image tile with intensity based on the pixel*/
        CreateAdjustedTile( ( float ) ImagePixel / ( float ) Max,
                            Tile, 
                            TileCopy, 
                            TileWidth, 
                            TileHeight,
                            Adjust 
                          ) ;

        /* add the adjusted tile to the current line */
        ConcatImageLtoR( Line,                     /* source 1 */
                         TileWidth * ix,           /* source 1 width */
                         TileCopy,                 /* source 2 */
                         TileWidth,                /* source 2 width */
                         TileHeight,               /* both's height */
                         Dest                      /* dest */
                         );

        /* save the old address */
        tmp = Line;

        /* keep track of the line we are building */
        Line = Dest;

        /* we are done with the old Line buffer */
        free( tmp );
     }

     /* copy this line into the final output buffer */
     memcpy( &OutputImage[ iy * ( TileHeight * TileWidth) * ImageWidth ],
              Line,
              TileSize * ImageWidth 
           );
   }

   if ( debug )
   {
      fprintf( stderr,"
               \n\nSaving %s...",
               (OutputFile[0] == '-' ? "to Standard ouput" : OutputFile ) 
             );

      fflush( stderr );
   }

   /* create the final output file */
   SaveImage( OutputFile, 
              OutputImage, 
              TileWidth * ImageWidth, 
              TileHeight * ImageHeight 
            );

   if ( debug )
   {
      fprintf( stderr,"\n\n");
   }
}
/******************************************************************************/
int SaveImage( char *filename, unsigned char* pixels, int width, int height )
{
   FILE *fp;
   int i;
   int p;

   if ( debug )
   {
      fprintf( stderr, "Image size %d %d\n", width, height );
   }

   /* if the filename indicates stdout */
   if ( filename[0] == '-' )
   {
      fp = stdout;
   }
   else
   {
      /* open up the specified file */
      fp = fopen(filename,"w");

      /* if we couldn't do it */
      if ( ! fp )
      {
         /* indicate why */
         perror(filename);
   
         /* bomb out */
         return(-1);
      }
   }

#ifdef ASCII

   fprintf(fp, "P2\n");

#else

   fprintf( fp, "P5\n");

#endif

   fprintf( fp, "# This was generated by mosaic\n");
   fprintf( fp, "%d %d\n255\n", width, height );

#ifdef ASCII

   for ( i = 0 ; i < width * height; i++ )
   {
      fprintf(fp,"%3d ", pixels[i]);

      if ( ! ( (i+1) %17 )  )
      {
         fprintf(fp,"\n");
      }
   }

   fprintf(fp,"\n\n");

#else
   fwrite( pixels, width * height, 1, fp );
#endif

   if ( fp != stdout )
   {
      fclose(fp);
   }

   return(0);
}
/******************************************************************************/
/*                                                                            */
/*    create a new tile based on the original tile scaled based on the image  */
/*                                                                            */
/******************************************************************************/
int CreateAdjustedTile( float ImagePixelRatio,
                        unsigned char *TilePixels,
                        unsigned char *NewPixels,
                        int Width,
                        int Height,
                        float Adjust
                      )
{
   int ix, iy;
   int Pixel;

   for ( iy = 0; iy < Height; iy++ )
   {
      for ( ix = 0; ix < Width; ix++ )
      {
         /* The new pixel is based on the tile pixel adjusted by the
            image pixel, further adjusted by an optional command line 
            parameter. Got that ?
         */ 

         Pixel = ( TilePixels[ iy * Width + ix ] * 
                   ImagePixelRatio 
                 ) * Adjust ;

         /* keep pixels legal */
         if ( Pixel > 255 )
         {
            Pixel = 255;
         }

         /* build the output image */
         NewPixels[ iy * Width + ix ] = Pixel ;
      }
   }
}
/******************************************************************************/
void Usage( char *name )
{
   fprintf( stderr,

"Usage : Gmosaic [-a value] [-t tile] [-s size] 
        [-o file] [-d] [-h] [file] 

Options :
	-a value	: Adjust the output intensity by this factor, i.e.
			  1.2, 0.5 etc.

	-d		: Debug. If this flag is specified, some
			  debug type messages will be written to stderr.

	-h		: Generate usage statement

	-o file		: Output file.  The file will be written in
			  PGM format.  If the output file is not specified
			  the image will be written to standard out.

	-s size		: Size of the tile to be used when Gmosaic 
			  generates a tile from the input image. The
			  value specifies the width of the tile and
			  should be a small number such as 20 or 22.

	-t tile		: A PGM format tile image that will be used to
			  compose the output image.  This should be a
			  fairly small image, i.e. 24x24, 15x15 etc. and
			  should be \"square\" or else the output image
			  will have an incorrect aspect ratio.

        file		: Input file.  The only supported input file
			  format is PGM in either raw or ASCII format.
			  If the input file is not specified, the image
			  will be read from standard input.
"
         );

   return;
}
/******************************************************************************/
void ConcatImageLtoR( unsigned char *s1,
                      int s1x,
                      unsigned char *s2,
                      int s2x,
                      int y,
                      unsigned char *d 
                    )
{
   int i;
   int FullWidth = s1x + s2x ;

   /* for all of the lines */
   for ( i = 0; i < y; i++ )
   {
      /* move the first image */
      memcpy( &d[ i * FullWidth       ], &s1[ i * s1x ], s1x );

      /* move the second image */
      memcpy( &d[ i * FullWidth + s1x ], &s2[ i * s2x ], s2x );
   }

   return;
}
/******************************************************************************/
int LoadImageFile( char *file, unsigned char **Tile, int *x, int *y )
{
   FILE *fp;
   char buffer[ MAX_LINE_LENGTH ];
   char *token;
   unsigned char *pixels;
   int size;
   int scale;
   int n;
   char type[2];

   /* if the file name indicates reading from standard input */
   if ( file[0] == '-' )
   {
      fp = stdin;
   }
   else
   {
      fp = fopen( file, "r" );
   }

   if ( !fp )
   {
      perror(file);
      exit(1);
   }
 
   /* get the first line of the (hopefully) pgm file */
   fgets(buffer, sizeof( buffer ), fp);  

   /* get the file type */
   type[0] = buffer[0] ;
   type[1] = buffer[1] ;

   /* if this is a pgm type file */
   if ( ( type[0] == 'P' ) && ( type[1] == '2' || type[1] == '5' ) )
   {
      fgets(buffer, sizeof( buffer ), fp);  

      /* while we are getting comments */
      while ( buffer[0] == '#' && ! feof(fp) )
      {
         /* throw them away */
         fgets(buffer, sizeof( buffer ), fp);  
      }

      /* get the image width and height */
      sscanf( buffer, "%d %d", x, y );
   
      /* size of the resultant image */
      size = *x * *y ;

      /* allocate it */
      *Tile =  Imagemalloc( size );

      /* make a copy for ease of use */
      pixels = *Tile;

      /* read the scale value */
      fgets( buffer, sizeof( buffer ), fp);  
      sscanf( buffer, "%d", &scale );

      /* if this is an ASCII pgm file */
      if ( type[1] == '2' )
      {
         n = 0;

         /* while we are not at the end of the file */
         while ( ! feof( fp ) )
         {
            /* read a line */
            fgets(buffer, sizeof( buffer ), fp);  

            /* strip off the carriage return */
            buffer[ strlen( buffer ) -1 ] = '\0';

            /* begin breaking up the line into tokens */
            token = strtok(buffer, " ");  

            /* while we are getting tokens */
            while ( token )
            {
               /* convert to binary */
               pixels[n++] = atoi( token );

               /* get the next token */
               token = strtok( 0, " " );  
            }
         }
      }
      /* else if this is an binary pgm file */
      else if ( type[1] == '5' )
      {
         /* read it all in one swell foop */
         fread( pixels , size , 1, fp );
      }
   }
   else
   {
      fprintf( stderr, 
              "Error, %s is not a pgm file.\npgm format images can be created"
              " using a tool such as xv, pbmplus, etc.\n", 
              ( file[0] == '-') ? "the input file" : file 
             );

      exit(1);
   }

   /* if we are not reading from standard input */
   if ( fp != stdin )
   {
      fclose(fp);
   }

   return;
}
/*********************************************************************/
int ScaleImage(
          unsigned char *InputImage,
          int InputWidth,
          int InputHeight,
          unsigned char *OutputImage,
          int OutputWidth,
          int OutputHeight
         )
{
   float iy;
   float ix;
   float yIncrement;
   float xIncrement;
   register int i = 0;
   register int x;
   register int y;
   int tmpIndex;

   /* NOTE : This is a crude image scaling routine that ony downsamples.
     better results could be accomplished if the algorithm averaged the
     source pixels before copying to the destination. This routine is only
     used when a tile file is not specified on the command line.  For best
     results, always generate a tile file manually before execution of this
     program.
   */

   /* this routine will only downscale :-(  */
   if ( ( InputWidth < OutputWidth ) || ( InputHeight < OutputHeight ) )
   {
      return;
   }

   /* x increment in the source image */
   xIncrement = InputWidth/OutputWidth;

   /* y increment in the source image */
   yIncrement = InputHeight/OutputHeight;

   /* for  all the lines in the source and the destination */
   for ( y = 0, iy = 0; y < OutputHeight; iy+= yIncrement, y++ )
   {
      /* the source line the following loop will  work on */
      tmpIndex = (int) (iy * InputWidth );

      /* for all the columns in the source and the destination */
      for ( x = 0, ix = tmpIndex; x < OutputWidth; ix+= xIncrement, x++ )
      {
         /* copy from source to destination */
         OutputImage[i++] = InputImage[ (int) ( ix ) ]; 
      }
   }

   return;
}
/*********************************************************************/
void * Imagemalloc( int size )
{
   void *tmp = malloc( size );

   /* if the allocation failed */
   if (  !tmp )
   {
      char message[80];

      sprintf(message, "Failed to allocate %d bytes ", size );

      /* indicate why ( as if we didn't know ) */
      perror(message);

      /* bail */
      exit(1);
   }

   return ( tmp );
}
/*********************************************************************/
