/* pnmhistmap.c -
* Draw a histogram for a PGM or PPM file
*
* Options: -verbose: the usual
* -max N: force scaling value to N
* -black: ignore all-black count
* -white: ignore all-white count
*
* - PGM histogram is a PBM file, PPM histogram is a PPM file
* - No conditional code - assumes all three: PBM, PGM, PPM
*
* Copyright (C) 1993 by Wilson H. Bent, Jr (whb@usc.edu)
*
* 2004-12-11 john h. dubois iii (john@armory.com)
* - Added options:
* -dots, -nmax, -red, -green, -blue, -width, -height, -lval, -rval
* - Deal properly with maxvals other than 256
*/
#include <assert.h>
#include <string.h>
#include "pm_c_util.h"
#include "pnm.h"
#include "shhopt.h"
#include "mallocvar.h"
static double const epsilon = .00001;
enum wantedColor {WANT_RED=0, WANT_GRN=1, WANT_BLU=2};
struct cmdlineInfo {
/* All the information the user supplied in the command line,
in a form easy for the program to use.
*/
const char *inputFilespec; /* Filespecs of input files */
unsigned int black;
unsigned int white;
unsigned int dots;
bool colorWanted[3];
/* subscript is enum wantedColor */
unsigned int verbose;
unsigned int nmaxSpec;
float nmax;
unsigned int lval;
unsigned int rval;
unsigned int widthSpec;
unsigned int width;
unsigned int height;
};
static void
parseCommandLine(int argc, const char ** argv,
struct cmdlineInfo *cmdlineP) {
/*----------------------------------------------------------------------------
Note that the file spec array we return is stored in the storage that
was passed to us as the argv array.
-----------------------------------------------------------------------------*/
optEntry *option_def;
/* Instructions to pm_optParseOptions3 on how to parse our options.
*/
optStruct3 opt;
unsigned int option_def_index;
unsigned int lvalSpec, rvalSpec, heightSpec;
unsigned int redSpec, greenSpec, blueSpec;
MALLOCARRAY_NOFAIL(option_def, 100);
option_def_index = 0; /* incremented by OPTENT3 */
OPTENT3(0, "black", OPT_FLAG, NULL, &cmdlineP->black, 0);
OPTENT3(0, "white", OPT_FLAG, NULL, &cmdlineP->white, 0);
OPTENT3(0, "dots", OPT_FLAG, NULL, &cmdlineP->dots, 0);
OPTENT3(0, "red", OPT_FLAG, NULL, &redSpec, 0);
OPTENT3(0, "green", OPT_FLAG, NULL, &greenSpec, 0);
OPTENT3(0, "blue", OPT_FLAG, NULL, &blueSpec, 0);
OPTENT3(0, "verbose", OPT_FLAG, NULL, &cmdlineP->verbose, 0);
OPTENT3(0, "nmax", OPT_FLOAT, &cmdlineP->nmax,
&cmdlineP->nmaxSpec, 0);
OPTENT3(0, "lval", OPT_UINT, &cmdlineP->lval,
&lvalSpec, 0);
OPTENT3(0, "rval", OPT_UINT, &cmdlineP->rval,
&rvalSpec, 0);
OPTENT3(0, "width", OPT_UINT, &cmdlineP->width,
&cmdlineP->widthSpec, 0);
OPTENT3(0, "height", OPT_UINT, &cmdlineP->height,
&heightSpec, 0);
opt.opt_table = option_def;
opt.short_allowed = FALSE; /* We have no short (old-fashioned) options */
opt.allowNegNum = FALSE; /* We may have parms that are negative numbers */
pm_optParseOptions3(&argc, (char **)argv, opt, sizeof(opt), 0);
/* Uses and sets argc, argv, and some of *cmdlineP and others. */
if (!lvalSpec)
cmdlineP->lval = 0;
if (!rvalSpec)
cmdlineP->rval = PNM_OVERALLMAXVAL;
if (!redSpec && !greenSpec && !blueSpec) {
cmdlineP->colorWanted[WANT_RED] = TRUE;
cmdlineP->colorWanted[WANT_GRN] = TRUE;
cmdlineP->colorWanted[WANT_BLU] = TRUE;
} else {
cmdlineP->colorWanted[WANT_RED] = redSpec;
cmdlineP->colorWanted[WANT_GRN] = greenSpec;
cmdlineP->colorWanted[WANT_BLU] = blueSpec;
}
if (!heightSpec)
cmdlineP->height = 200;
if (argc-1 == 0)
cmdlineP->inputFilespec = "-";
else if (argc-1 != 1)
pm_error("Program takes zero or one argument (filename). You "
"specified %u", argc-1);
else
cmdlineP->inputFilespec = argv[1];
}
static unsigned int
maxSlotCount(const unsigned int * const hist,
unsigned int const histWidth,
bool const no_white,
bool const no_black) {
/*----------------------------------------------------------------------------
Return the maximum count among all the slots in hist[], not counting
the first and last as suggested by 'no_white' and 'no_black'.
-----------------------------------------------------------------------------*/
unsigned int hmax;
unsigned int i;
unsigned int const start = (no_black ? 1 : 0);
unsigned int const finish = (no_white ? histWidth - 1 : histWidth);
for (hmax = 0, i = start; i < finish; ++i)
if (hmax < hist[i])
hmax = hist[i];
return hmax;
}
static void
clipHistogram(unsigned int * const hist,
unsigned int const histWidth,
unsigned int const hmax) {
unsigned int i;
for (i = 0; i < histWidth; ++i)
hist[i] = MIN(hmax, hist[i]);
}
static void
countComp(xelval const value,
xelval const startval,
xelval const endval,
unsigned int const histWidth,
unsigned int * const hist) {
double const hscale = (float)(histWidth-1) / (endval - startval - 1);
if (value >= startval && value < endval) {
unsigned int const bin = ROUNDU((value-startval) * hscale);
assert(bin < histWidth);
++hist[bin];
}
}
static void
pgmHist(FILE * const ifP,
int const cols,
int const rows,
xelval const maxval,
int const format,
bool const dots,
bool const no_white,
bool const no_black,
bool const verbose,
xelval const startval,
xelval const endval,
unsigned int const histWidth,
unsigned int const histHeight,
bool const clipSpec,
unsigned int const clipCount) {
gray * grayrow;
bit ** bits;
int i, j;
unsigned int * ghist;
double vscale;
unsigned int hmax;
MALLOCARRAY(ghist, histWidth);
if (ghist == NULL)
pm_error("Not enough memory for histogram array (%u bytes)",
histWidth * (unsigned)sizeof(int));
bits = pbm_allocarray(histWidth, histHeight);
if (bits == NULL)
pm_error("no space for output array (%u bits)",
histWidth * histHeight);
memset(ghist, 0, histWidth * sizeof(ghist[0]));
/* read the pixel values into the histogram arrays */
grayrow = pgm_allocrow(cols);
if (verbose)
pm_message("making histogram...");
for (i = rows; i > 0; --i) {
pgm_readpgmrow (ifP, grayrow, cols, maxval, format);
for (j = cols-1; j >= 0; --j)
countComp(grayrow[j], startval, endval, histWidth, ghist);
}
pgm_freerow(grayrow);
/* find the highest-valued slot and set the vertical scale value */
if (verbose)
pm_message("finding max. slot height...");
if (clipSpec)
hmax = clipCount;
else
hmax = maxSlotCount(ghist, histWidth, no_white, no_black);
assert(hmax > 0);
if (verbose)
pm_message("Done: height = %u", hmax);
clipHistogram(ghist, histWidth, hmax);
vscale = (double) histHeight / hmax;
for (i = 0; i < histWidth; ++i) {
int mark = histHeight - (int)(vscale * ghist[i]);
for (j = 0; j < mark; ++j)
bits[j][i] = PBM_BLACK;
if (j < histHeight)
bits[j++][i] = PBM_WHITE;
for ( ; j < histHeight; ++j)
bits[j][i] = dots ? PBM_BLACK : PBM_WHITE;
}
pbm_writepbm(stdout, bits, histWidth, histHeight, 0);
}
static unsigned int
maxSlotCountAll(unsigned int * const hist[3],
unsigned int const histWidth,
bool const no_white,
bool const no_black) {
/*----------------------------------------------------------------------------
Return the maximum count among all the slots in hist[x] not
counting the first and last as suggested by 'no_white' and
'no_black'. hist[x] may be NULL to indicate none.
-----------------------------------------------------------------------------*/
unsigned int hmax;
unsigned int color;
hmax = 0;
for (color = 0; color < 3; ++color)
if (hist[color])
hmax = MAX(hmax,
maxSlotCount(hist[color],
histWidth, no_white, no_black));
return hmax;
}
static void
createHist(bool const colorWanted[3],
unsigned int const histWidth,
unsigned int * (* const histP)[3]) {
/*----------------------------------------------------------------------------
Allocate the histogram arrays and set each slot count to zero.
-----------------------------------------------------------------------------*/
unsigned int color;
for (color = 0; color < 3; ++color) {
if (colorWanted[color]) {
unsigned int * hist;
unsigned int i;
MALLOCARRAY(hist, histWidth);
if (hist == NULL)
pm_error("Not enough memory for histogram arrays (%u bytes)",
histWidth * (unsigned)sizeof(hist[0]) * 3);
for (i = 0; i < histWidth; ++i)
hist[i] = 0;
(*histP)[color] = hist;
} else
(*histP)[color] = NULL;
}
}
static void
clipHistogramAll(unsigned int * const hist[3],
unsigned int const histWidth,
unsigned int const hmax) {
unsigned int color;
for (color = 0; color < 3; ++color)
if (hist[color])
clipHistogram(hist[color], histWidth, hmax);
}
static void
fillPpmBins(FILE * const ifP,
unsigned int const cols,
unsigned int const rows,
xelval const maxval,
int const format,
bool const colorWanted[3],
bool const verbose,
xelval const startval,
xelval const endval,
unsigned int const histWidth,
unsigned int ** const hist) {
/*----------------------------------------------------------------------------
For each wanted color component, given by colorWanted[], hist[color] is the
histogram. Each histogram as 'histWidth' bins; we ignore color component
values less than 'startval' and greater than or equal to 'endval' and
spread the rest evenly across the 'histWidth' bins.
We get the color component values from the PNM image on *ifP,
which is positioned to the raster, whose format is described
by 'cols', 'rows', 'maxval', and 'format'.
-----------------------------------------------------------------------------*/
pixel * pixrow;
unsigned int row;
pixrow = ppm_allocrow(cols);
if (verbose)
pm_message("making histogram...");
for (row = 0; row < rows; ++row) {
unsigned int col;
ppm_readppmrow(ifP, pixrow, cols, maxval, format);
for (col = 0; col < cols; ++col) {
if (colorWanted[WANT_RED])
countComp(PPM_GETR(pixrow[col]),
startval, endval, histWidth, hist[WANT_RED]);
if (colorWanted[WANT_GRN])
countComp(PPM_GETG(pixrow[col]),
startval, endval, histWidth, hist[WANT_GRN]);
if (colorWanted[WANT_BLU])
countComp(PPM_GETB(pixrow[col]),
startval, endval, histWidth, hist[WANT_BLU]);
}
}
ppm_freerow(pixrow);
}
static void
ppmHist(FILE * const ifP,
unsigned int const cols,
unsigned int const rows,
xelval const maxval,
int const format,
bool const dots,
bool const no_white,
bool const no_black,
bool const colorWanted[3],
bool const verbose,
xelval const startval,
xelval const endval,
unsigned int const histWidth,
unsigned int const histHeight,
bool const clipSpec,
unsigned int const clipCount) {
pixel ** pixels;
unsigned int i;
unsigned int * hist[3]; /* Subscript is enum wantedColor */
double vscale;
unsigned int hmax;
createHist(colorWanted, histWidth, &hist);
if ((pixels = ppm_allocarray (histWidth, histHeight)) == NULL)
pm_error("no space for output array (%u pixels)",
histWidth * histHeight);
for (i = 0; i < histHeight; ++i)
memset(pixels[i], 0, histWidth * sizeof(pixels[i][0]));
fillPpmBins(ifP, cols, rows, maxval, format, colorWanted, verbose,
startval, endval, histWidth, hist);
/* find the highest-valued slot and set the vertical scale value */
if (verbose)
pm_message("finding max. slot height...");
if (clipSpec)
hmax = clipCount;
else
hmax = maxSlotCountAll(hist, histWidth, no_white, no_black);
assert(hmax > 0);
clipHistogramAll(hist, histWidth, hmax);
vscale = (double) histHeight / hmax;
if (verbose && pm_have_float_format())
pm_message("Done: height = %u, vertical scale factor = %g",
hmax, vscale);
for (i = 0; i < histWidth; ++i) {
if (hist[WANT_RED]) {
unsigned int j;
bool plotted;
plotted = FALSE;
for (j = histHeight - (int)(vscale * hist[WANT_RED][i]);
j < histHeight && !plotted;
++j) {
PPM_PUTR(pixels[j][i], maxval);
plotted = dots;
}
}
if (hist[WANT_GRN]) {
unsigned int j;
bool plotted;
plotted = FALSE;
for (j = histHeight - (int)(vscale * hist[WANT_GRN][i]);
j < histHeight && !plotted;
++j) {
PPM_PUTG(pixels[j][i], maxval);
plotted = dots;
}
}
if (hist[WANT_BLU]) {
unsigned int j;
bool plotted;
plotted = FALSE;
for (j = histHeight - (int)(vscale * hist[WANT_BLU][i]);
j < histHeight && !plotted;
++j) {
PPM_PUTB(pixels[j][i], maxval);
plotted = dots;
}
}
}
ppm_writeppm(stdout, pixels, histWidth, histHeight, maxval, 0);
}
static void
reportScale(unsigned int const histWidth,
unsigned int const range,
bool const verbose) {
double const hscale = (float)(histWidth-1) / (range-1);
if (hscale - 1.0 < epsilon && verbose && pm_have_float_format())
pm_message("Horizontal scale factor: %g", hscale);
}
int
main(int argc, const char ** argv) {
struct cmdlineInfo cmdline;
FILE * ifP;
int cols, rows;
xelval maxval;
int format;
unsigned int histWidth;
unsigned int range;
unsigned int hmax;
xelval startval, endval;
pm_proginit(&argc, argv);
parseCommandLine(argc, argv, &cmdline);
ifP = pm_openr(cmdline.inputFilespec);
pnm_readpnminit(ifP, &cols, &rows, &maxval, &format);
startval = cmdline.lval;
endval = MIN(maxval, cmdline.rval) + 1;
range = endval - startval;
if (cmdline.widthSpec)
histWidth = cmdline.width;
else
histWidth = range;
reportScale(histWidth, range, cmdline.verbose);
if (cmdline.nmaxSpec)
hmax = cols * rows / histWidth * cmdline.nmax;
switch (PNM_FORMAT_TYPE(format)) {
case PPM_TYPE:
ppmHist(ifP, cols, rows, maxval, format,
cmdline.dots, cmdline.white, cmdline.black,
cmdline.colorWanted,
cmdline.verbose, startval, endval,
histWidth, cmdline.height, cmdline.nmaxSpec, hmax);
break;
case PGM_TYPE:
pgmHist(ifP, cols, rows, maxval, format,
cmdline.dots, cmdline.white, cmdline.black,
cmdline.verbose, startval, endval,
histWidth, cmdline.height, cmdline.nmaxSpec, hmax);
break;
case PBM_TYPE:
pm_error("Cannot do a histogram of a a PBM file");
break;
}
pm_close(ifP);
return 0;
}