[alt.sources] pgmcolor - convert portable gray map to false-color

bert@let.rug.nl (Bert Bos) (11/22/90)

I needed a utility to convert numbers to colors, but I couldn't find
it among the PBM programs. Therefore I decided to make one myself.

pgmcolor reads a PGM file and outputs a PPM file, using the gray
values as indices into a palette which is itself a PPM file. pgmcolor
can be used to make false-color images. I use it to assign colors to
level sets of Mandelbrot and Julia fractals. It is also possible to
convert a gray image from a scanner to colors, if there where not too
maany colors in the original.

A Makefile and a manual are included. You need to have the pbmplus
package (specifically the libraries).

----- CUT HERE -----------------------------------------------------------
# This is a shell archive.  Remove anything before this line,
# then unpack it by saving it in a file and typing "sh file".
#
# Wrapped by Bert Bos <bert@gufal03> on Tue Nov 20 19:41:08 1990
#
# This archive contains:
#	README		pgmcolor.1	pgmcolor.c	Makefile	
#

LANG=""; export LANG
PATH=/bin:/usr/bin:$PATH; export PATH

echo x - README
cat >README <<'@EOF'
pgmcolor converts a portable gray map to a portable pixmap, using a
palette (which is itself a PPM file). pgmcolor can be used to make
false-color images. I use it to assign colors to level sets of
Mandelbrot and Julia fractals. It is also possible to convert a gray
image from a scanner to colors (but choosing convincing colors is not
a sinecure).

A Makefile and a manual are included. You need to have the pbmplus
package to compile this. The program is called with:

    pgmcolor [-v] [-a] palettefile [pgmfile]

Copyright by Bert Bos <bert@let.rug.nl>.
@EOF

chmod 644 README

echo x - pgmcolor.1
cat >pgmcolor.1 <<'@EOF'
.TH pgmcolor 1 "20 November 1990"
.SH NAME
pgmcolor - create false color image from a portable graymap
.SH SYNOPSIS
pgmcolor [-v] [-a] palette [pgmfile]
.SH DESCRIPTION
Reads a portable graymap as input and a portable pixmap as palette.
The different gray values are replaced by pixel values from the
palette, from darkest to lightest.  Normally, the darkest gray is
replaced by the first color in the palette file, the next gray by the
next color, etc. If there are more pixel values than actual gray
levels, the last pixels are discarded.  If there are insufficient
pixel values, they are applied cyclicly.

The -a (absolute) option forces the first pixel to be assigned to gray
level 0, the next to gray level 1, etc., regardless of the actual
existence of that gray level in the input.

The -v (verbose) flag reports the number of gray levels and the number
of pixel values in the input and the palette, respectively.

An example palette might look like this:

.nf
    P3
    # Palette for 16 yellowish colors
    16 1
    15
    0 0 0    1 1 0    3 2 0    5 3 0    7 4 0
    9 5 0    11 6 0   13 7 0   15 8 1   15 9 3
    15 10 5  15 11 7  15 12 9  15 13 11 15 14 13
    15 15 15
.fi

You can use pgmhist(1) to see how many gray levels a PGM file has.
.SH "SEE ALSO"
pgmhist(1), pgm(5), ppm(5)
.SH AUTHOR
Copyright (C) 1990 by Bert Bos <bert@let.rug.nl>.

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.
@EOF

chmod 644 pgmcolor.1

echo x - pgmcolor.c
cat >pgmcolor.c <<'@EOF'
/* 
  pgmcolor -- Make a false color image from a portable gray map
  Copyright: Bert Bos <bert@let.rug.nl>
  Date: 19 Nov 1990

  Usage: pgmcolor [-v] [-a] palette [pgmfile]

The palette file is a PPM file. It should contain a number of RGB
values.  Pgmcolor assigns the first pixel's color to the darkest gray
and the next pixels color to the next darkest gray. If there are more
grays than pixels in the palette file, the colors will be applied
cyclicly If there are more RGB values than gray values, the
superfluous RGB colors will be ignored.

The -v option (verbose) will display some statistics on this.

If the -a option is specified, the colors in the palette will be
assigned to graylevels 0, 1, 2, etc. regardless of the existence of
this levels in the PGM file.

*/

#include <stdio.h>
#include <pgm.h>
#include <ppm.h>

#define TRUE 1
#define FALSE 0
#define NULL 0

#define Bool int

static Bool verbose = FALSE;
static Bool absolute = FALSE;
static char *paletteName = (char *) NULL;
static char *infileName = (char *) NULL;
static char *progName = (char *) NULL;


/* usage -- Print usage message and exit */
void usage()
{
  fprintf(stderr, "Usage: %s [-v] [-a] palette [pgm-file]\n", progName);
  exit(1);
}


/* arguments -- interpret command line arguments */
void arguments(argc, argv)
     int argc;
     char **argv;
{
  char **p, *arg;
  int i = 0;

  p = argv;
  while (i < argc) {			/* Loop over arguments */
    arg = *p;
    if (*arg == '-') {			/* It's an option */
      arg++;				/* Skip '-' */
      while (*arg) {			/* Loop over all letters in option */
	switch (*arg) {
	case 'v':			/* -v verbose */
	  verbose = TRUE;
	  break;
	case 'a':			/* -a absolute */
	  absolute = TRUE;
	  break;
	default:
	  usage();			/* Unknown option */
	}
	arg++;				/* Next letter */
      }
    }
    else if (! progName) {		/* It's the program's own name */
      progName = arg;
    }
    else if (! paletteName) {		/* It's the palette's name */
      paletteName = arg;
    }
    else if (! infileName) {		/* It's the input file's name */
      infileName = arg;
    }
    else				/* Too many arguments */
      usage();
    p++;				/* Next argument */
    i++;
  }
  if (! paletteName)			/* Too few arguments */
    usage();
}


/* doAbsolute -- replace each gray level by corresponding pixel value */
void doAbsolute(infile, palette)
     FILE *infile, *palette;
{
  pixel **colors;			/* Holds palette */
  int colsP, rowsP;			/* For palette file */
  pixval maxvalP;			/* For palette file */
  int colsG, rowsG, formatG;		/* For PGM file */
  gray maxvalG;				/* For PGM file */
  gray *grayrow;			/* Holds successive rows of pgm file */
  pixel *newrow;			/* Holds successive rows of output */
  int row, col;				/* Temporary variables */

  /* Read colors and header of pgm input file, then write output header */
  colors = ppm_readppm(palette, &colsP, &rowsP, &maxvalP);
  fclose(palette);
  if (! colors) {
    fprintf(stderr, "%s: out of memory\n", progName);
    exit(-1);
  }
  pgm_readpgminit(infile, &colsG, &rowsG, &maxvalG, &formatG);
  ppm_writeppminit(stdout, colsG, rowsG, maxvalP);

  if (verbose) {
    fprintf(stderr, "%s is a %dx%d PGM file with %d gray levels.\n",
	    infileName?infileName:"standard input", colsG, rowsG, maxvalG);
    fprintf(stderr, "%s defines %d colors", paletteName, colsP * rowsP);
    if (maxvalG + 1 < colsP * rowsP)
      fprintf(stderr, ", last %d will be ignored.\n", colsP*rowsP-maxvalG-1);
    else if (maxvalG + 1 > colsP * rowsP)
      fprintf(stderr, ", colors will be used cyclicly\n");
    else
      fprintf(stderr, " exactly\n");
  }
    
  /* Loop over rows of PGM file */
  newrow = ppm_allocrow(colsG);
  for (row = 0; row < rowsG; row++) {
    pgm_readpgmrow(infile, grayrow, colsG, maxvalG, formatG);
    for (col = 0; col < colsG; col++)
      newrow[col] = (*colors)[(int) grayrow[col] % (colsP * rowsP)];
    ppm_writeppmrow(stdout, newrow, colsG, maxvalP);
  }

  /* Close input */
  fclose(infile);
}


/* doRelative -- replace only existing graylevels with pixel values */
void doRelative(infile, palette)
     FILE *infile, *palette;
{
  pixel **colors, *cmap;		/* Hold palette & color map */
  int colsP, rowsP;			/* For palette file */
  pixval maxvalP;			/* For palette file */
  gray **graymap;			/* Holds PGM file */
  int colsG, rowsG;			/* For PGM file */
  gray maxvalG;				/* For PGM file */
  pixel *newrow;			/* Holds successive rows of output */
  int *hist, count;			/* Histogram */
  int row, col, i, j;			/* Temporary variables */

  /* Read colors and gray map */
  colors = ppm_readppm(palette, &colsP, &rowsP, &maxvalP);
  fclose(palette);
  graymap = pgm_readpgm(infile, &colsG, &rowsG, &maxvalG);
  fclose(infile);
  if (! colors || ! graymap) {
    fprintf(stderr, "%s: out of memory\n", progName);
    exit(-1);
  }

  /* Build histogram of PGM file */
  hist = (int *) malloc((maxvalG + 1)*sizeof(int));
  cmap = ppm_allocrow(maxvalG);
  newrow = ppm_allocrow(colsG);
  if (! hist || ! cmap || ! newrow) {
    fprintf(stderr, "%s: out of memory\n", progName);
    exit(-1);
  }
  for (i = 0; i <= maxvalG; i++)
    hist[i] = 0;
  for (row = 0; row < rowsG; row++)
    for (col = 0; col < colsG; col++)
      hist[(int) graymap[row][col]]++;
  
  /* Now put a pixel value in cmap next to each nonzero hist[i] */
  count = 0;
  for (i = 0; i <= maxvalG; i++)
    if (hist[i]) {
      cmap[i] = (*colors)[count % (colsP * rowsP)];
      count++;
    }
  
  if (verbose) {
    if (infileName)
      fprintf(stderr, "%s is a %dx%d PGM file with %d gray levels out of %d\n",
	      infileName, colsG, rowsG, count, maxvalG);
    else
      fprintf(stderr, "standard input is a %dx%d PGM file with %d gray levels out of %d\n",
	      colsG, rowsG, count, maxvalG);
    fprintf(stderr, "%s defines %d colors", paletteName, colsP * rowsP);
    if (count < colsP * rowsP)
      fprintf(stderr, "; last %d will be ignored.\n", colsP * rowsP - count);
    else if (count > colsP * rowsP)
      fprintf(stderr, "; colors will be used cyclicly\n");
    else
      fprintf(stderr, " exactly\n");
  }

  /* Write out a PPM file of the same size as the PGM file */
  ppm_writeppminit(stdout, colsG, rowsG, maxvalP);
  for (row = 0; row < rowsG; row++) {
    for (col = 0; col <colsG; col++)
      newrow[col] = cmap[(int) graymap[row][col]];
    ppm_writeppmrow(stdout, newrow, colsG, maxvalP);
  }
}


/* main -- main routine of pgmcolor */
int main(argc, argv)
{
  FILE *infile, *palette;

  /* get command line arguments */
  arguments(argc, argv);

  /* Read palette and PGM input file */
  if (!(palette = fopen(paletteName, "r"))) {
    fprintf(stderr, "%s: cannot open %s\n", progName, paletteName);
    return 1;
  }
  if (! infileName)
    infile = stdin;
  else if (!(infile = fopen(infileName, "r"))) {
    fprintf(stderr, "%s: cannot open %s\n", progName, infileName);
    return 1;
  }

  /* Now call the appropriate routine */
  if (absolute)
    doAbsolute(infile, palette);
  else
    doRelative(infile, palette);

  /* Exit */
  return 0;
}
@EOF

chmod 644 pgmcolor.c

echo x - Makefile
cat >Makefile <<'@EOF'
#  Makefile for pgmcolor
#  Copyright Bert Bos, 1990

#  pgmcolor needs the three pbm libraries: libpbm, libpgm and libppm
#  The following three macros comtain the directories where they can be found:
PBMDIR=../pbm
PGMDIR=../pgm
PPMDIR=../ppm

#  The files pgm.h and ppm.h are also needed, if they are not in the same
#  directories as their respective libraries, the following macro needs to
#  be changed:
INC=-I $(PPMDIR) -I $(PGMDIR) -I $(PBMDIR)

#  Choose your compiler and compiler flags:
CC=gcc
CFLAGS=-O $(INC)

LDFLAGS=-L $(PPMDIR) -L $(PGMDIR) -L $(PBMDIR) -lppm -lpgm -lpbm

# The single rule to make pgmcolor:

pgmcolor:
@EOF

chmod 644 Makefile

exit 0
----- CUT HERE -----------------------------------------------------------
-- 
  "Always remember, however, that there's     Bert Bos (bert@let.rug.nl)
   usually a simpler and better way to do     Alfa-informatica
   something than the first way that pops     RijksUniversiteit Groningen
   into your head." (D.E. Knuth, TeXbook)     Groningen, The Netherlands