|
Packit |
78deda |
/******************************************************************************
|
|
Packit |
78deda |
Pnmnorm
|
|
Packit |
78deda |
*******************************************************************************
|
|
Packit |
78deda |
|
|
Packit |
78deda |
This program normalizes the contrast in a Netpbm image.
|
|
Packit |
78deda |
|
|
Packit |
78deda |
by Bryan Henderson bryanh@giraffe-data.com San Jose CA March 2002.
|
|
Packit |
78deda |
Adapted from Ppmnorm.
|
|
Packit |
78deda |
|
|
Packit |
78deda |
Ppmnorm is by Wilson H. Bent, Jr. (whb@usc.edu)
|
|
Packit |
78deda |
Extensively hacked from pgmnorm.c, which carries the following note:
|
|
Packit |
78deda |
|
|
Packit |
78deda |
Copyright (C) 1989, 1991 by Jef Poskanzer.
|
|
Packit |
78deda |
|
|
Packit |
78deda |
Permission to use, copy, modify, and distribute this software and its
|
|
Packit |
78deda |
documentation for any purpose and without fee is hereby granted, provided
|
|
Packit |
78deda |
that the above copyright notice appear in all copies and that both that
|
|
Packit |
78deda |
copyright notice and this permission notice appear in supporting
|
|
Packit |
78deda |
documentation. This software is provided "as is" without express or
|
|
Packit |
78deda |
implied warranty.
|
|
Packit |
78deda |
|
|
Packit |
78deda |
(End of note from pgmnorm.c)
|
|
Packit |
78deda |
|
|
Packit |
78deda |
Pgmnorm's man page also said:
|
|
Packit |
78deda |
|
|
Packit |
78deda |
Partially based on the fbnorm filter in Michael Mauldin's "Fuzzy Pixmap"
|
|
Packit |
78deda |
package.
|
|
Packit |
78deda |
*****************************************************************************/
|
|
Packit |
78deda |
|
|
Packit |
78deda |
#include <assert.h>
|
|
Packit |
78deda |
|
|
Packit |
78deda |
#include "pm_c_util.h"
|
|
Packit |
78deda |
#include "mallocvar.h"
|
|
Packit |
78deda |
#include "nstring.h"
|
|
Packit |
78deda |
#include "shhopt.h"
|
|
Packit |
78deda |
#include "matrix.h"
|
|
Packit |
78deda |
#include "pnm.h"
|
|
Packit |
78deda |
|
|
Packit |
78deda |
enum brightMethod {BRIGHT_LUMINOSITY, BRIGHT_COLORVALUE, BRIGHT_SATURATION};
|
|
Packit |
78deda |
|
|
Packit |
78deda |
struct cmdlineInfo {
|
|
Packit |
78deda |
/* All the information the user supplied in the command line,
|
|
Packit |
78deda |
in a form easy for the program to use.
|
|
Packit |
78deda |
*/
|
|
Packit |
78deda |
const char * inputFileName; /* Name of input file */
|
|
Packit |
78deda |
unsigned int bvalueSpec;
|
|
Packit |
78deda |
xelval bvalue;
|
|
Packit |
78deda |
unsigned int bpercentSpec;
|
|
Packit |
78deda |
float bpercent;
|
|
Packit |
78deda |
unsigned int wvalueSpec;
|
|
Packit |
78deda |
xelval wvalue;
|
|
Packit |
78deda |
unsigned int wpercentSpec;
|
|
Packit |
78deda |
float wpercent;
|
|
Packit |
78deda |
unsigned int bsingle;
|
|
Packit |
78deda |
unsigned int wsingle;
|
|
Packit |
78deda |
float middle;
|
|
Packit |
78deda |
unsigned int midvalueSpec;
|
|
Packit |
78deda |
xelval midvalue;
|
|
Packit |
78deda |
enum brightMethod brightMethod;
|
|
Packit |
78deda |
unsigned int keephues;
|
|
Packit |
78deda |
float maxExpansion;
|
|
Packit |
78deda |
/* The maximum allowed expansion factor for expansion specified
|
|
Packit |
78deda |
by per centile. This is a factor, not a per cent increase.
|
|
Packit |
78deda |
E.g. 50% increase means a factor of 1.50.
|
|
Packit |
78deda |
*/
|
|
Packit |
78deda |
unsigned int verbose;
|
|
Packit |
78deda |
};
|
|
Packit |
78deda |
|
|
Packit |
78deda |
|
|
Packit |
78deda |
|
|
Packit |
78deda |
static void
|
|
Packit |
78deda |
parseCommandLine(int argc, const char ** argv,
|
|
Packit |
78deda |
struct cmdlineInfo * const cmdlineP) {
|
|
Packit |
78deda |
/*----------------------------------------------------------------------------
|
|
Packit |
78deda |
parse program command line described in Unix standard form by argc
|
|
Packit |
78deda |
and argv. Return the information in the options as *cmdlineP.
|
|
Packit |
78deda |
|
|
Packit |
78deda |
If command line is internally inconsistent (invalid options, etc.),
|
|
Packit |
78deda |
issue error message to stderr and abort program.
|
|
Packit |
78deda |
|
|
Packit |
78deda |
Note that the strings we return are stored in the storage that
|
|
Packit |
78deda |
was passed to us as the argv array. We also trash *argv.
|
|
Packit |
78deda |
-----------------------------------------------------------------------------*/
|
|
Packit |
78deda |
optEntry *option_def;
|
|
Packit |
78deda |
/* Instructions to pm_optParseOptions3 on how to parse our options.
|
|
Packit |
78deda |
*/
|
|
Packit |
78deda |
optStruct3 opt;
|
|
Packit |
78deda |
|
|
Packit |
78deda |
unsigned int luminosity, colorvalue, saturation;
|
|
Packit |
78deda |
unsigned int middleSpec, maxexpandSpec;
|
|
Packit |
78deda |
float maxexpand;
|
|
Packit |
78deda |
|
|
Packit |
78deda |
unsigned int option_def_index;
|
|
Packit |
78deda |
|
|
Packit |
78deda |
MALLOCARRAY_NOFAIL(option_def, 100);
|
|
Packit |
78deda |
|
|
Packit |
78deda |
option_def_index = 0; /* incremented by OPTENT3 */
|
|
Packit |
78deda |
OPTENT3(0, "bpercent", OPT_FLOAT,
|
|
Packit |
78deda |
&cmdlineP->bpercent, &cmdlineP->bpercentSpec, 0);
|
|
Packit |
78deda |
OPTENT3(0, "wpercent", OPT_FLOAT,
|
|
Packit |
78deda |
&cmdlineP->wpercent, &cmdlineP->wpercentSpec, 0);
|
|
Packit |
78deda |
OPTENT3(0, "bvalue", OPT_UINT,
|
|
Packit |
78deda |
&cmdlineP->bvalue, &cmdlineP->bvalueSpec, 0);
|
|
Packit |
78deda |
OPTENT3(0, "wvalue", OPT_UINT,
|
|
Packit |
78deda |
&cmdlineP->wvalue, &cmdlineP->wvalueSpec, 0);
|
|
Packit |
78deda |
OPTENT3(0, "bsingle", OPT_FLAG,
|
|
Packit |
78deda |
NULL, &cmdlineP->bsingle, 0);
|
|
Packit |
78deda |
OPTENT3(0, "wsingle", OPT_FLAG,
|
|
Packit |
78deda |
NULL, &cmdlineP->wsingle, 0);
|
|
Packit |
78deda |
OPTENT3(0, "middle", OPT_FLOAT,
|
|
Packit |
78deda |
&cmdlineP->middle, &middleSpec, 0);
|
|
Packit |
78deda |
OPTENT3(0, "midvalue", OPT_UINT,
|
|
Packit |
78deda |
&cmdlineP->midvalue, &cmdlineP->midvalueSpec, 0);
|
|
Packit |
78deda |
OPTENT3(0, "maxexpand", OPT_FLOAT,
|
|
Packit |
78deda |
&maxexpand, &maxexpandSpec, 0);
|
|
Packit |
78deda |
OPTENT3(0, "keephues", OPT_FLAG,
|
|
Packit |
78deda |
NULL, &cmdlineP->keephues, 0);
|
|
Packit |
78deda |
OPTENT3(0, "luminosity", OPT_FLAG,
|
|
Packit |
78deda |
NULL, &luminosity, 0);
|
|
Packit |
78deda |
OPTENT3(0, "colorvalue", OPT_FLAG,
|
|
Packit |
78deda |
NULL, &colorvalue, 0);
|
|
Packit |
78deda |
OPTENT3(0, "saturation", OPT_FLAG,
|
|
Packit |
78deda |
NULL, &saturation, 0);
|
|
Packit |
78deda |
OPTENT3(0, "brightmax", OPT_FLAG,
|
|
Packit |
78deda |
NULL, &colorvalue, 0);
|
|
Packit |
78deda |
OPTENT3(0, "verbose", OPT_FLAG,
|
|
Packit |
78deda |
NULL, &cmdlineP->verbose, 0);
|
|
Packit |
78deda |
|
|
Packit |
78deda |
/* Note: -brightmax was documented and accepted long before it was
|
|
Packit |
78deda |
actually implemented. By the time we implemented it, we
|
|
Packit |
78deda |
decided -colorvalue was a better name for it.
|
|
Packit |
78deda |
*/
|
|
Packit |
78deda |
|
|
Packit |
78deda |
opt.opt_table = option_def;
|
|
Packit |
78deda |
opt.short_allowed = FALSE; /* We have no short (old-fashioned) options */
|
|
Packit |
78deda |
opt.allowNegNum = FALSE; /* We have no parms that are negative numbers */
|
|
Packit |
78deda |
|
|
Packit |
78deda |
pm_optParseOptions3(&argc, (char **)argv, opt, sizeof(opt), 0);
|
|
Packit |
78deda |
/* Uses and sets argc, argv, and some of *cmdline_p and others. */
|
|
Packit |
78deda |
|
|
Packit |
78deda |
if (!cmdlineP->wpercentSpec)
|
|
Packit |
78deda |
cmdlineP->wpercent = 1.0;
|
|
Packit |
78deda |
if (!cmdlineP->bpercentSpec)
|
|
Packit |
78deda |
cmdlineP->bpercent = 2.0;
|
|
Packit |
78deda |
|
|
Packit |
78deda |
if (cmdlineP->wpercent < 0.0)
|
|
Packit |
78deda |
pm_error("You specified a negative value for wpercent: %f",
|
|
Packit |
78deda |
cmdlineP->wpercent);
|
|
Packit |
78deda |
if (cmdlineP->bpercent < 0.0)
|
|
Packit |
78deda |
pm_error("You specified a negative value for bpercent: %f",
|
|
Packit |
78deda |
cmdlineP->bpercent);
|
|
Packit |
78deda |
if (cmdlineP->wpercent > 100.0)
|
|
Packit |
78deda |
pm_error("You specified a per centage > 100 for wpercent: %f",
|
|
Packit |
78deda |
cmdlineP->wpercent);
|
|
Packit |
78deda |
if (cmdlineP->bpercent > 100.0)
|
|
Packit |
78deda |
pm_error("You specified a per centage > 100 for bpercent: %f",
|
|
Packit |
78deda |
cmdlineP->bpercent);
|
|
Packit |
78deda |
|
|
Packit |
78deda |
if (cmdlineP->bsingle && (cmdlineP->bpercentSpec || cmdlineP->bvalueSpec))
|
|
Packit |
78deda |
pm_error("You cannot specify both -bsingle and -bpercent or -bvalue");
|
|
Packit |
78deda |
|
|
Packit |
78deda |
if (cmdlineP->wsingle && (cmdlineP->wpercentSpec || cmdlineP->wvalueSpec))
|
|
Packit |
78deda |
pm_error("You cannot specify both -wsingle and -wpercent or -wvalue");
|
|
Packit |
78deda |
|
|
Packit |
78deda |
if (middleSpec) {
|
|
Packit |
78deda |
if (cmdlineP->middle < 0.0 || cmdlineP->middle > 1.0)
|
|
Packit |
78deda |
pm_error("-middle is a normalized brightness value; it "
|
|
Packit |
78deda |
"must be in the range 0..1. You specified %f",
|
|
Packit |
78deda |
cmdlineP->middle);
|
|
Packit |
78deda |
} else
|
|
Packit |
78deda |
cmdlineP->middle = 0.5;
|
|
Packit |
78deda |
|
|
Packit |
78deda |
if (luminosity + colorvalue + saturation > 1)
|
|
Packit |
78deda |
pm_error("You can specify only one of "
|
|
Packit |
78deda |
"-luminosity, -colorvalue, and -saturation");
|
|
Packit |
78deda |
else {
|
|
Packit |
78deda |
if (colorvalue)
|
|
Packit |
78deda |
cmdlineP->brightMethod = BRIGHT_COLORVALUE;
|
|
Packit |
78deda |
else if (saturation)
|
|
Packit |
78deda |
cmdlineP->brightMethod = BRIGHT_SATURATION;
|
|
Packit |
78deda |
else
|
|
Packit |
78deda |
cmdlineP->brightMethod = BRIGHT_LUMINOSITY;
|
|
Packit |
78deda |
}
|
|
Packit |
78deda |
if (maxexpandSpec) {
|
|
Packit |
78deda |
if (maxexpand < 0)
|
|
Packit |
78deda |
pm_error("-maxexpand must be positive. You specified %f",
|
|
Packit |
78deda |
maxexpand);
|
|
Packit |
78deda |
else
|
|
Packit |
78deda |
cmdlineP->maxExpansion = 1 + (float)maxexpand/100;
|
|
Packit |
78deda |
} else
|
|
Packit |
78deda |
cmdlineP->maxExpansion = 1e6; /* essentially infinite */
|
|
Packit |
78deda |
|
|
Packit |
78deda |
if (argc-1 > 1)
|
|
Packit |
78deda |
pm_error("Program takes at most one argument: the input file "
|
|
Packit |
78deda |
"specification. "
|
|
Packit |
78deda |
"You specified %d arguments.", argc-1);
|
|
Packit |
78deda |
if (argc-1 < 1)
|
|
Packit |
78deda |
cmdlineP->inputFileName = "-";
|
|
Packit |
78deda |
else
|
|
Packit |
78deda |
cmdlineP->inputFileName = argv[1];
|
|
Packit |
78deda |
}
|
|
Packit |
78deda |
|
|
Packit |
78deda |
|
|
Packit |
78deda |
|
|
Packit |
78deda |
static void
|
|
Packit |
78deda |
buildHistogram(FILE * const ifp,
|
|
Packit |
78deda |
int const cols,
|
|
Packit |
78deda |
int const rows,
|
|
Packit |
78deda |
xelval const maxval,
|
|
Packit |
78deda |
int const format,
|
|
Packit |
78deda |
unsigned int hist[],
|
|
Packit |
78deda |
enum brightMethod const brightMethod) {
|
|
Packit |
78deda |
/*----------------------------------------------------------------------------
|
|
Packit |
78deda |
Build the histogram of brightness values for the image that is in file
|
|
Packit |
78deda |
'ifp', which is positioned just after the header (at the raster).
|
|
Packit |
78deda |
|
|
Packit |
78deda |
The histogram is the array hist[] such that hist[x] is the number
|
|
Packit |
78deda |
of xels in the image that have brightness x. That brightness is
|
|
Packit |
78deda |
either the color value (intensity of most intense component) of the
|
|
Packit |
78deda |
xel or it is the luminosity of the xel, depending on
|
|
Packit |
78deda |
'brightMethod'. In either case, it is based on the same maxval as
|
|
Packit |
78deda |
the image, which is 'maxval'. The image is 'cols' columns wide by
|
|
Packit |
78deda |
'rows' rows high.
|
|
Packit |
78deda |
|
|
Packit |
78deda |
Leave the file positioned arbitrarily.
|
|
Packit |
78deda |
-----------------------------------------------------------------------------*/
|
|
Packit |
78deda |
int row;
|
|
Packit |
78deda |
xel * xelrow;
|
|
Packit |
78deda |
|
|
Packit |
78deda |
xelrow = pnm_allocrow(cols);
|
|
Packit |
78deda |
|
|
Packit |
78deda |
{
|
|
Packit |
78deda |
unsigned int i;
|
|
Packit |
78deda |
for (i = 0; i <= maxval; ++i)
|
|
Packit |
78deda |
hist[i] = 0;
|
|
Packit |
78deda |
}
|
|
Packit |
78deda |
for (row = 0; row < rows; ++row) {
|
|
Packit |
78deda |
int col;
|
|
Packit |
78deda |
pnm_readpnmrow(ifp, xelrow, cols, maxval, format);
|
|
Packit |
78deda |
for (col = 0; col < cols; ++col) {
|
|
Packit |
78deda |
xelval brightness;
|
|
Packit |
78deda |
xel const p = xelrow[col];
|
|
Packit |
78deda |
if (PNM_FORMAT_TYPE(format) == PPM_TYPE) {
|
|
Packit |
78deda |
switch(brightMethod) {
|
|
Packit |
78deda |
case BRIGHT_LUMINOSITY:
|
|
Packit |
78deda |
brightness = ppm_luminosity(p);
|
|
Packit |
78deda |
break;
|
|
Packit |
78deda |
case BRIGHT_COLORVALUE:
|
|
Packit |
78deda |
brightness = ppm_colorvalue(p);
|
|
Packit |
78deda |
break;
|
|
Packit |
78deda |
case BRIGHT_SATURATION:
|
|
Packit |
78deda |
brightness = ppm_saturation(p, maxval);
|
|
Packit |
78deda |
break;
|
|
Packit |
78deda |
}
|
|
Packit |
78deda |
} else
|
|
Packit |
78deda |
brightness = PNM_GET1(p);
|
|
Packit |
78deda |
++hist[brightness];
|
|
Packit |
78deda |
}
|
|
Packit |
78deda |
}
|
|
Packit |
78deda |
pnm_freerow(xelrow);
|
|
Packit |
78deda |
}
|
|
Packit |
78deda |
|
|
Packit |
78deda |
|
|
Packit |
78deda |
|
|
Packit |
78deda |
static xelval
|
|
Packit |
78deda |
minimumValue(const unsigned int * const hist,
|
|
Packit |
78deda |
unsigned int const highest) {
|
|
Packit |
78deda |
|
|
Packit |
78deda |
xelval i;
|
|
Packit |
78deda |
bool foundOne;
|
|
Packit |
78deda |
|
|
Packit |
78deda |
for (i = 0, foundOne = false; !foundOne; ) {
|
|
Packit |
78deda |
if (hist[i] > 0)
|
|
Packit |
78deda |
foundOne = true;
|
|
Packit |
78deda |
else {
|
|
Packit |
78deda |
if (i == highest)
|
|
Packit |
78deda |
pm_error("INTERNAL ERROR in '%s'. No pixels", __FUNCTION__);
|
|
Packit |
78deda |
else
|
|
Packit |
78deda |
++i;
|
|
Packit |
78deda |
}
|
|
Packit |
78deda |
}
|
|
Packit |
78deda |
return i;
|
|
Packit |
78deda |
}
|
|
Packit |
78deda |
|
|
Packit |
78deda |
|
|
Packit |
78deda |
|
|
Packit |
78deda |
static xelval
|
|
Packit |
78deda |
maximumValue(const unsigned int * const hist,
|
|
Packit |
78deda |
unsigned int const highest) {
|
|
Packit |
78deda |
|
|
Packit |
78deda |
xelval i;
|
|
Packit |
78deda |
bool foundOne;
|
|
Packit |
78deda |
|
|
Packit |
78deda |
for (i = highest, foundOne = false; !foundOne; ) {
|
|
Packit |
78deda |
if (hist[i] > 0)
|
|
Packit |
78deda |
foundOne = true;
|
|
Packit |
78deda |
else {
|
|
Packit |
78deda |
if (i == 0)
|
|
Packit |
78deda |
pm_error("INTERNAL ERROR in '%s'. No pixels", __FUNCTION__);
|
|
Packit |
78deda |
else
|
|
Packit |
78deda |
--i;
|
|
Packit |
78deda |
}
|
|
Packit |
78deda |
}
|
|
Packit |
78deda |
return i;
|
|
Packit |
78deda |
}
|
|
Packit |
78deda |
|
|
Packit |
78deda |
|
|
Packit |
78deda |
|
|
Packit |
78deda |
static void
|
|
Packit |
78deda |
computeBottomPercentile(unsigned int hist[],
|
|
Packit |
78deda |
unsigned int const highest,
|
|
Packit |
78deda |
unsigned int const total,
|
|
Packit |
78deda |
float const percent,
|
|
Packit |
78deda |
unsigned int * const percentileP) {
|
|
Packit |
78deda |
/*----------------------------------------------------------------------------
|
|
Packit |
78deda |
Compute the lowest index of hist[] such that the sum of the hist[]
|
|
Packit |
78deda |
values with that index and lower represent at least 'percent' per cent of
|
|
Packit |
78deda |
'total' (which is assumed to be the sum of all the values in hist[],
|
|
Packit |
78deda |
given to us to save us the time of computing it).
|
|
Packit |
78deda |
-----------------------------------------------------------------------------*/
|
|
Packit |
78deda |
unsigned int cutoff = total * percent / 100.0;
|
|
Packit |
78deda |
unsigned int count;
|
|
Packit |
78deda |
unsigned int percentile;
|
|
Packit |
78deda |
|
|
Packit |
78deda |
percentile = 0; /* initial value */
|
|
Packit |
78deda |
count = hist[0]; /* initial value */
|
|
Packit |
78deda |
|
|
Packit |
78deda |
while (count < cutoff) {
|
|
Packit |
78deda |
if (percentile == highest)
|
|
Packit |
78deda |
pm_error("Internal error: computeBottomPercentile() received"
|
|
Packit |
78deda |
"a 'total' value greater than the sum of the hist[]"
|
|
Packit |
78deda |
"values");
|
|
Packit |
78deda |
++percentile;
|
|
Packit |
78deda |
count += hist[percentile];
|
|
Packit |
78deda |
}
|
|
Packit |
78deda |
*percentileP = percentile;
|
|
Packit |
78deda |
}
|
|
Packit |
78deda |
|
|
Packit |
78deda |
|
|
Packit |
78deda |
|
|
Packit |
78deda |
static void
|
|
Packit |
78deda |
computeTopPercentile(unsigned int hist[],
|
|
Packit |
78deda |
unsigned int const highest,
|
|
Packit |
78deda |
unsigned int const total,
|
|
Packit |
78deda |
float const percent,
|
|
Packit |
78deda |
unsigned int * const percentileP) {
|
|
Packit |
78deda |
/*----------------------------------------------------------------------------
|
|
Packit |
78deda |
Compute the highest index of hist[] such that the sum of the hist[]
|
|
Packit |
78deda |
values with that index and higher represent 'percent' per cent of
|
|
Packit |
78deda |
'total' (which is assumed to be the sum of all the values in hist[],
|
|
Packit |
78deda |
given to us to save us the time of computing it).
|
|
Packit |
78deda |
-----------------------------------------------------------------------------*/
|
|
Packit |
78deda |
unsigned int cutoff = total * percent / 100.0;
|
|
Packit |
78deda |
unsigned int count;
|
|
Packit |
78deda |
unsigned int percentile;
|
|
Packit |
78deda |
|
|
Packit |
78deda |
percentile = highest; /* initial value */
|
|
Packit |
78deda |
count = hist[highest];
|
|
Packit |
78deda |
|
|
Packit |
78deda |
while (count < cutoff) {
|
|
Packit |
78deda |
--percentile;
|
|
Packit |
78deda |
count += hist[percentile];
|
|
Packit |
78deda |
}
|
|
Packit |
78deda |
*percentileP = percentile;
|
|
Packit |
78deda |
}
|
|
Packit |
78deda |
|
|
Packit |
78deda |
|
|
Packit |
78deda |
|
|
Packit |
78deda |
static void
|
|
Packit |
78deda |
computeAdjustmentForExpansionLimit(xelval const maxval,
|
|
Packit |
78deda |
xelval const unlBvalue,
|
|
Packit |
78deda |
xelval const unlWvalue,
|
|
Packit |
78deda |
float const maxExpansion,
|
|
Packit |
78deda |
xelval * const bLowerP,
|
|
Packit |
78deda |
xelval * const wRaiseP) {
|
|
Packit |
78deda |
/*----------------------------------------------------------------------------
|
|
Packit |
78deda |
Assuming 'unlBvalue' and 'unlWvalue' are the appropriate bvalue and
|
|
Packit |
78deda |
wvalue to normalize the image to 0 .. maxval, compute the amount
|
|
Packit |
78deda |
by which the bvalue must be raised and the wvalue lowered from that
|
|
Packit |
78deda |
in order to cap the expansion factor at 'maxExpansion'.
|
|
Packit |
78deda |
|
|
Packit |
78deda |
E.g. if 'maxval' is 100, 'unlBvalue' is 20 and 'unlWvalue' is 70, that
|
|
Packit |
78deda |
implies an expansion factor of 100/50 (because the range goes from
|
|
Packit |
78deda |
70-20, which is 50, to 100 - 0, which is 100). If 'maxEpansion' is
|
|
Packit |
78deda |
1.333, these values are unacceptable. To get down to the desired 1.333
|
|
Packit |
78deda |
factor, we need the span of bvalue to wvalue to be 75, not 50. So
|
|
Packit |
78deda |
we need to raise the bvalue and lower the wvalue by a total of 25.
|
|
Packit |
78deda |
We apportion that adjustment to bvalue and wvalue in proportion to
|
|
Packit |
78deda |
how close each is already to it's end (which we call the margin).
|
|
Packit |
78deda |
'unlBvalue' is 20 from its end, while 'unlWvalue' is 30 from its end,
|
|
Packit |
78deda |
so we want to lower the bvalue by 10 and raise the wvalue by 15.
|
|
Packit |
78deda |
Ergo we return *bLowerP = 10 and *wRaise = 15.
|
|
Packit |
78deda |
-----------------------------------------------------------------------------*/
|
|
Packit |
78deda |
unsigned int const newRange = maxval - 0;
|
|
Packit |
78deda |
/* The range of sample values after normalization, if we used
|
|
Packit |
78deda |
the unlimited bvalue and wvalue
|
|
Packit |
78deda |
*/
|
|
Packit |
78deda |
unsigned int const oldRange = unlWvalue - unlBvalue;
|
|
Packit |
78deda |
/* The range of sample values in the original image that normalize
|
|
Packit |
78deda |
to 0 .. maxval, if we used the unlimited bvalue and wvalue
|
|
Packit |
78deda |
*/
|
|
Packit |
78deda |
float const unlExpansion = (float)newRange/oldRange;
|
|
Packit |
78deda |
|
|
Packit |
78deda |
if (unlExpansion <= maxExpansion) {
|
|
Packit |
78deda |
/* No capping is necessary. Unlimited values are already within
|
|
Packit |
78deda |
range.
|
|
Packit |
78deda |
*/
|
|
Packit |
78deda |
*bLowerP = 0;
|
|
Packit |
78deda |
*wRaiseP = 0;
|
|
Packit |
78deda |
} else {
|
|
Packit |
78deda |
unsigned int const totalWidening = newRange/maxExpansion - oldRange;
|
|
Packit |
78deda |
/* Amount by which the (bvalue, wvalue) range must be widened
|
|
Packit |
78deda |
to limit expansion to 'maxExpansion'
|
|
Packit |
78deda |
*/
|
|
Packit |
78deda |
unsigned int const bMargin = unlBvalue - 0;
|
|
Packit |
78deda |
unsigned int const wMargin = maxval - unlWvalue;
|
|
Packit |
78deda |
|
|
Packit |
78deda |
/* Apportion 'totalWidening' between the black and and the
|
|
Packit |
78deda |
white end
|
|
Packit |
78deda |
*/
|
|
Packit |
78deda |
*bLowerP =
|
|
Packit |
78deda |
ROUNDU((float)bMargin / (bMargin + wMargin) * totalWidening);
|
|
Packit |
78deda |
*wRaiseP =
|
|
Packit |
78deda |
ROUNDU((float)wMargin / (bMargin + wMargin) * totalWidening);
|
|
Packit |
78deda |
|
|
Packit |
78deda |
pm_message("limiting expansion of %.1f%% to %.1f%%",
|
|
Packit |
78deda |
(unlExpansion - 1) * 100, (maxExpansion -1) * 100);
|
|
Packit |
78deda |
}
|
|
Packit |
78deda |
}
|
|
Packit |
78deda |
|
|
Packit |
78deda |
|
|
Packit |
78deda |
|
|
Packit |
78deda |
static void
|
|
Packit |
78deda |
disOverlap(xelval const reqBvalue,
|
|
Packit |
78deda |
xelval const reqWvalue,
|
|
Packit |
78deda |
bool const bIsFixed,
|
|
Packit |
78deda |
bool const wIsFixed,
|
|
Packit |
78deda |
xelval const maxval,
|
|
Packit |
78deda |
xelval * const nonOlapBvalueP,
|
|
Packit |
78deda |
xelval * const nonOlapWvalueP) {
|
|
Packit |
78deda |
/*----------------------------------------------------------------------------
|
|
Packit |
78deda |
Compute black and white end values that don't overlap, i.e. the
|
|
Packit |
78deda |
black value is darker than the white, from an initial attempt that
|
|
Packit |
78deda |
might overlap.
|
|
Packit |
78deda |
|
|
Packit |
78deda |
'req{B|W}value' is that initial attempt. We return the
|
|
Packit |
78deda |
nonoverlapping version as *nonOlap{B|W}valueP.
|
|
Packit |
78deda |
|
|
Packit |
78deda |
'{b|w}IsFixed' means we cannot change that endpoint.
|
|
Packit |
78deda |
|
|
Packit |
78deda |
If both ends are fixed 'reqBvalue' and 'reqWvalue' overlap, we just
|
|
Packit |
78deda |
fail the program -- the user asked for the impossible.
|
|
Packit |
78deda |
|
|
Packit |
78deda |
Where one end is fixed and the other is not, we move the unfixed end
|
|
Packit |
78deda |
to be one unit above or below the fixed end, as appropriate.
|
|
Packit |
78deda |
|
|
Packit |
78deda |
Where both ends are free, we move them to the point halfway between them,
|
|
Packit |
78deda |
the white end being one more than the black end.
|
|
Packit |
78deda |
-----------------------------------------------------------------------------*/
|
|
Packit |
78deda |
assert(maxval > 0);
|
|
Packit |
78deda |
|
|
Packit |
78deda |
if (reqBvalue < reqWvalue) {
|
|
Packit |
78deda |
/* No overlap; initial attempt is fine. */
|
|
Packit |
78deda |
*nonOlapBvalueP = reqBvalue;
|
|
Packit |
78deda |
*nonOlapWvalueP = reqWvalue;
|
|
Packit |
78deda |
} else {
|
|
Packit |
78deda |
if (bIsFixed && wIsFixed)
|
|
Packit |
78deda |
pm_error("The colors which become black (value <= %u) "
|
|
Packit |
78deda |
"would overlap the "
|
|
Packit |
78deda |
"colors which become white (value >= %u).",
|
|
Packit |
78deda |
reqBvalue, reqWvalue);
|
|
Packit |
78deda |
else if (bIsFixed) {
|
|
Packit |
78deda |
if (reqBvalue >= maxval)
|
|
Packit |
78deda |
pm_error("The black value must be less than the maxval");
|
|
Packit |
78deda |
else {
|
|
Packit |
78deda |
*nonOlapBvalueP = reqBvalue;
|
|
Packit |
78deda |
*nonOlapWvalueP = reqBvalue + 1;
|
|
Packit |
78deda |
}
|
|
Packit |
78deda |
} else if (wIsFixed) {
|
|
Packit |
78deda |
if (reqWvalue == 0)
|
|
Packit |
78deda |
pm_error("The white value must be greater than 0");
|
|
Packit |
78deda |
else {
|
|
Packit |
78deda |
*nonOlapBvalueP = reqWvalue - 1;
|
|
Packit |
78deda |
*nonOlapWvalueP = reqWvalue;
|
|
Packit |
78deda |
}
|
|
Packit |
78deda |
} else {
|
|
Packit |
78deda |
/* Both ends are free; use the point halfway between them. */
|
|
Packit |
78deda |
xelval const midPoint = (reqWvalue + reqBvalue + maxval/2)/2;
|
|
Packit |
78deda |
*nonOlapBvalueP = MIN(midPoint, maxval-1);
|
|
Packit |
78deda |
*nonOlapWvalueP = *nonOlapBvalueP + 1;
|
|
Packit |
78deda |
}
|
|
Packit |
78deda |
}
|
|
Packit |
78deda |
}
|
|
Packit |
78deda |
|
|
Packit |
78deda |
|
|
Packit |
78deda |
|
|
Packit |
78deda |
static void
|
|
Packit |
78deda |
resolvePercentParams(FILE * const ifP,
|
|
Packit |
78deda |
unsigned int const cols,
|
|
Packit |
78deda |
unsigned int const rows,
|
|
Packit |
78deda |
xelval const maxval,
|
|
Packit |
78deda |
int const format,
|
|
Packit |
78deda |
struct cmdlineInfo const cmdline,
|
|
Packit |
78deda |
xelval * const bvalueP,
|
|
Packit |
78deda |
xelval * const wvalueP) {
|
|
Packit |
78deda |
/*----------------------------------------------------------------------------
|
|
Packit |
78deda |
Figure out the endpoint of the stretch (the value that is to be stretched
|
|
Packit |
78deda |
to black and the one that is to be stretched to white) as requested
|
|
Packit |
78deda |
by the -{b,w}{value,percent,single} options.
|
|
Packit |
78deda |
|
|
Packit |
78deda |
These values may be invalid because of overlapping, and they may exceed
|
|
Packit |
78deda |
the maximum allowed stretch; Caller must deal with that.
|
|
Packit |
78deda |
-----------------------------------------------------------------------------*/
|
|
Packit |
78deda |
unsigned int * hist; /* malloc'ed */
|
|
Packit |
78deda |
|
|
Packit |
78deda |
MALLOCARRAY(hist, PNM_OVERALLMAXVAL+1);
|
|
Packit |
78deda |
|
|
Packit |
78deda |
if (hist == NULL)
|
|
Packit |
78deda |
pm_error("Unable to allocate storage for intensity histogram.");
|
|
Packit |
78deda |
else {
|
|
Packit |
78deda |
buildHistogram(ifP, cols, rows, maxval, format, hist,
|
|
Packit |
78deda |
cmdline.brightMethod);
|
|
Packit |
78deda |
|
|
Packit |
78deda |
if (cmdline.bsingle)
|
|
Packit |
78deda |
*bvalueP = minimumValue(hist, maxval);
|
|
Packit |
78deda |
else if (cmdline.bvalueSpec && !cmdline.bpercentSpec) {
|
|
Packit |
78deda |
*bvalueP = cmdline.bvalue;
|
|
Packit |
78deda |
} else {
|
|
Packit |
78deda |
xelval percentBvalue;
|
|
Packit |
78deda |
computeBottomPercentile(hist, maxval, cols*rows, cmdline.bpercent,
|
|
Packit |
78deda |
&percentBvalue);
|
|
Packit |
78deda |
if (cmdline.bvalueSpec)
|
|
Packit |
78deda |
*bvalueP = MIN(percentBvalue, cmdline.bvalue);
|
|
Packit |
78deda |
else
|
|
Packit |
78deda |
*bvalueP = percentBvalue;
|
|
Packit |
78deda |
}
|
|
Packit |
78deda |
|
|
Packit |
78deda |
if (cmdline.wsingle)
|
|
Packit |
78deda |
*wvalueP = maximumValue(hist, maxval);
|
|
Packit |
78deda |
else if (cmdline.wvalueSpec && !cmdline.wpercentSpec) {
|
|
Packit |
78deda |
*wvalueP = cmdline.wvalue;
|
|
Packit |
78deda |
} else {
|
|
Packit |
78deda |
xelval percentWvalue;
|
|
Packit |
78deda |
computeTopPercentile(hist, maxval, cols*rows, cmdline.wpercent,
|
|
Packit |
78deda |
&percentWvalue);
|
|
Packit |
78deda |
if (cmdline.wvalueSpec)
|
|
Packit |
78deda |
*wvalueP = MAX(percentWvalue, cmdline.wvalue);
|
|
Packit |
78deda |
else
|
|
Packit |
78deda |
*wvalueP = percentWvalue;
|
|
Packit |
78deda |
}
|
|
Packit |
78deda |
free(hist);
|
|
Packit |
78deda |
}
|
|
Packit |
78deda |
}
|
|
Packit |
78deda |
|
|
Packit |
78deda |
|
|
Packit |
78deda |
|
|
Packit |
78deda |
static void
|
|
Packit |
78deda |
computeEndValues(FILE * const ifP,
|
|
Packit |
78deda |
int const cols,
|
|
Packit |
78deda |
int const rows,
|
|
Packit |
78deda |
xelval const maxval,
|
|
Packit |
78deda |
int const format,
|
|
Packit |
78deda |
struct cmdlineInfo const cmdline,
|
|
Packit |
78deda |
xelval * const bvalueP,
|
|
Packit |
78deda |
xelval * const wvalueP,
|
|
Packit |
78deda |
bool * const quadraticP,
|
|
Packit |
78deda |
xelval * const midvalueP) {
|
|
Packit |
78deda |
/*----------------------------------------------------------------------------
|
|
Packit |
78deda |
Figure out what original values will be translated to full bright and full
|
|
Packit |
78deda |
dark and, if user requested, a middle brightness -- thus defining to what
|
|
Packit |
78deda |
all the other values get translated.
|
|
Packit |
78deda |
|
|
Packit |
78deda |
This may involve looking at the image. The image is in the file 'ifP',
|
|
Packit |
78deda |
which is positioned just past the header (at the raster). Leave it
|
|
Packit |
78deda |
positioned arbitrarily.
|
|
Packit |
78deda |
|
|
Packit |
78deda |
We return *quadraticP == true iff the normalization is to be via a
|
|
Packit |
78deda |
quadratic transfer function fixed at 3 points - full bright, full dark, and
|
|
Packit |
78deda |
something in between. In that case, the original brightnesses of those
|
|
Packit |
78deda |
three points are *bvalueP, *midvalueP, and *wvalueP. We return *quadraticP
|
|
Packit |
78deda |
== false iff the normalization is to be via a linear function fixed at 2
|
|
Packit |
78deda |
points - full bright and full dark. In that case, *midvalueP is
|
|
Packit |
78deda |
meaningless.
|
|
Packit |
78deda |
-----------------------------------------------------------------------------*/
|
|
Packit |
78deda |
xelval reqBvalue, reqWvalue, nonOlapBvalue, nonOlapWvalue;
|
|
Packit |
78deda |
unsigned int bLower, wRaise;
|
|
Packit |
78deda |
|
|
Packit |
78deda |
resolvePercentParams(ifP, cols, rows, maxval, format, cmdline,
|
|
Packit |
78deda |
&reqBvalue, &reqWvalue);
|
|
Packit |
78deda |
|
|
Packit |
78deda |
disOverlap(reqBvalue, reqWvalue,
|
|
Packit |
78deda |
cmdline.bvalueSpec, cmdline.wvalueSpec, maxval,
|
|
Packit |
78deda |
&nonOlapBvalue, &nonOlapWvalue);
|
|
Packit |
78deda |
|
|
Packit |
78deda |
computeAdjustmentForExpansionLimit(
|
|
Packit |
78deda |
maxval, nonOlapBvalue, nonOlapWvalue, cmdline.maxExpansion,
|
|
Packit |
78deda |
&bLower, &wRaise);
|
|
Packit |
78deda |
|
|
Packit |
78deda |
*bvalueP = nonOlapBvalue - bLower;
|
|
Packit |
78deda |
*wvalueP = nonOlapWvalue + wRaise;
|
|
Packit |
78deda |
|
|
Packit |
78deda |
if (cmdline.midvalueSpec) {
|
|
Packit |
78deda |
if (cmdline.midvalue > *bvalueP && cmdline.midvalue < *wvalueP) {
|
|
Packit |
78deda |
*quadraticP = true;
|
|
Packit |
78deda |
*midvalueP = cmdline.midvalue;
|
|
Packit |
78deda |
} else
|
|
Packit |
78deda |
*quadraticP = false;
|
|
Packit |
78deda |
} else
|
|
Packit |
78deda |
*quadraticP = false;
|
|
Packit |
78deda |
}
|
|
Packit |
78deda |
|
|
Packit |
78deda |
|
|
Packit |
78deda |
|
|
Packit |
78deda |
static void
|
|
Packit |
78deda |
computeLinearTransfer(xelval const bvalue,
|
|
Packit |
78deda |
xelval const wvalue,
|
|
Packit |
78deda |
xelval const maxval,
|
|
Packit |
78deda |
xelval * const newBrightness) {
|
|
Packit |
78deda |
/*----------------------------------------------------------------------------
|
|
Packit |
78deda |
Map the middle brightnesses (the ones that don't get clipped to full dark
|
|
Packit |
78deda |
or full bright, i.e. from 'bvalue' to 'wvalue') linearly onto 0..maxval.
|
|
Packit |
78deda |
Set this mapping in newBrightness[].
|
|
Packit |
78deda |
-----------------------------------------------------------------------------*/
|
|
Packit |
78deda |
unsigned int const range = wvalue - bvalue;
|
|
Packit |
78deda |
|
|
Packit |
78deda |
xelval i;
|
|
Packit |
78deda |
unsigned int val;
|
|
Packit |
78deda |
/* The following for structure is a hand optimization of this one:
|
|
Packit |
78deda |
for (i = bvalue; i <= wvalue; ++i)
|
|
Packit |
78deda |
newBrightness[i] = (i-bvalue)*maxval/range);
|
|
Packit |
78deda |
(with proper rounding)
|
|
Packit |
78deda |
*/
|
|
Packit |
78deda |
for (i = bvalue, val = range/2;
|
|
Packit |
78deda |
i <= wvalue;
|
|
Packit |
78deda |
++i, val += maxval)
|
|
Packit |
78deda |
newBrightness[i] = MIN(val / range, maxval);
|
|
Packit |
78deda |
|
|
Packit |
78deda |
assert(newBrightness[bvalue] == 0);
|
|
Packit |
78deda |
assert(newBrightness[wvalue] == maxval);
|
|
Packit |
78deda |
}
|
|
Packit |
78deda |
|
|
Packit |
78deda |
|
|
Packit |
78deda |
|
|
Packit |
78deda |
typedef struct {
|
|
Packit |
78deda |
/*----------------------------------------------------------------------------
|
|
Packit |
78deda |
A quadratic polynomial function
|
|
Packit |
78deda |
-----------------------------------------------------------------------------*/
|
|
Packit |
78deda |
double a; /* x^2 coefficient */
|
|
Packit |
78deda |
double b; /* x^1 coefficient */
|
|
Packit |
78deda |
double c; /* x^0 coefficient */
|
|
Packit |
78deda |
} Quadfn;
|
|
Packit |
78deda |
|
|
Packit |
78deda |
|
|
Packit |
78deda |
|
|
Packit |
78deda |
static void
|
|
Packit |
78deda |
computeQuadraticFunction(xelval const bvalue,
|
|
Packit |
78deda |
xelval const midvalue,
|
|
Packit |
78deda |
xelval const wvalue,
|
|
Packit |
78deda |
xelval const middle,
|
|
Packit |
78deda |
xelval const maxval,
|
|
Packit |
78deda |
Quadfn * const functionP) {
|
|
Packit |
78deda |
|
|
Packit |
78deda |
/* The matrix equation we solve (for varMatrix) is
|
|
Packit |
78deda |
|
|
Packit |
78deda |
a * x = c
|
|
Packit |
78deda |
*/
|
|
Packit |
78deda |
double ** a;
|
|
Packit |
78deda |
double c[3];
|
|
Packit |
78deda |
double x[3];
|
|
Packit |
78deda |
const char * error;
|
|
Packit |
78deda |
|
|
Packit |
78deda |
MALLOCARRAY2_NOFAIL(a, 3, 3);
|
|
Packit |
78deda |
|
|
Packit |
78deda |
a[0][0] = SQR(bvalue); a[0][1] = bvalue; a[0][2] = 1.0;
|
|
Packit |
78deda |
a[1][0] = SQR(midvalue); a[1][1] = midvalue; a[1][2] = 1.0;
|
|
Packit |
78deda |
a[2][0] = SQR(wvalue); a[2][1] = wvalue; a[2][2] = 1.0;
|
|
Packit |
78deda |
|
|
Packit |
78deda |
c[0] = 0.0;
|
|
Packit |
78deda |
c[1] = middle;
|
|
Packit |
78deda |
c[2] = maxval;
|
|
Packit |
78deda |
|
|
Packit |
78deda |
pm_solvelineareq(a, x, c, 3, &error);
|
|
Packit |
78deda |
|
|
Packit |
78deda |
if (error) {
|
|
Packit |
78deda |
pm_error("Cannot fit a quadratic function to the points "
|
|
Packit |
78deda |
"(%u, %u), (%u, %u), and (%u, %u). %s",
|
|
Packit |
78deda |
bvalue, 0, midvalue, middle, wvalue, maxval, error);
|
|
Packit |
78deda |
pm_strfree(error);
|
|
Packit |
78deda |
} else {
|
|
Packit |
78deda |
functionP->a = x[0];
|
|
Packit |
78deda |
functionP->b = x[1];
|
|
Packit |
78deda |
functionP->c = x[2];
|
|
Packit |
78deda |
}
|
|
Packit |
78deda |
|
|
Packit |
78deda |
pm_freearray2((void **)a);
|
|
Packit |
78deda |
}
|
|
Packit |
78deda |
|
|
Packit |
78deda |
|
|
Packit |
78deda |
|
|
Packit |
78deda |
static void
|
|
Packit |
78deda |
computeQuadraticTransfer(xelval const bvalue,
|
|
Packit |
78deda |
xelval const midvalue,
|
|
Packit |
78deda |
xelval const wvalue,
|
|
Packit |
78deda |
float const middleNorm,
|
|
Packit |
78deda |
xelval const maxval,
|
|
Packit |
78deda |
bool const verbose,
|
|
Packit |
78deda |
xelval * const newBrightness) {
|
|
Packit |
78deda |
/*----------------------------------------------------------------------------
|
|
Packit |
78deda |
Map the middle brightnesses (the ones that don't get clipped to full dark
|
|
Packit |
78deda |
or full bright, i.e. from 'bvalue' to 'wvalue') quadratically onto
|
|
Packit |
78deda |
0..maxval, such that 'bvalue' maps to 0, 'wvalue' maps to 'maxval, and
|
|
Packit |
78deda |
'midvalue' maps to the normalized value 'middleNorm' (i.e. the actual
|
|
Packit |
78deda |
xelval middleNorm * maxval).
|
|
Packit |
78deda |
|
|
Packit |
78deda |
Set this mapping in newBrightness[].
|
|
Packit |
78deda |
-----------------------------------------------------------------------------*/
|
|
Packit |
78deda |
xelval const middle = ROUNDU(middleNorm * maxval);
|
|
Packit |
78deda |
|
|
Packit |
78deda |
/* Computing this function is just the task of finding a parabola that
|
|
Packit |
78deda |
passes through 3 given points:
|
|
Packit |
78deda |
|
|
Packit |
78deda |
(bvalue, 0)
|
|
Packit |
78deda |
(midvalue, middle)
|
|
Packit |
78deda |
(wvalue, maxval)
|
|
Packit |
78deda |
|
|
Packit |
78deda |
We do that by solving the system of three linear equations in
|
|
Packit |
78deda |
in 3 variables. The 3 variables are the coefficients of the
|
|
Packit |
78deda |
quadratic function we're looking for -- A, B, and C in this:
|
|
Packit |
78deda |
|
|
Packit |
78deda |
NEWVAL = A * OLDVAL^2 + B * OLDVAL + C
|
|
Packit |
78deda |
|
|
Packit |
78deda |
The three equations of the system are:
|
|
Packit |
78deda |
|
|
Packit |
78deda |
0 = A * bvalue^2 + B * bvalue + C
|
|
Packit |
78deda |
middle = A * midvalue^2 + B * midvalue + C
|
|
Packit |
78deda |
maxval = A * wvalue^2 + B * wvalue + C
|
|
Packit |
78deda |
|
|
Packit |
78deda |
Expressed in matrix form:
|
|
Packit |
78deda |
|
|
Packit |
78deda |
[ bvalue^2 bvalue 1 ] [ A ] [ 0 ]
|
|
Packit |
78deda |
[ midvalue^2 midvalue 1 ] * [ B ] = [ middle ]
|
|
Packit |
78deda |
[ wvalue^2 wvalue 1 ] [ C ] [ maxval ]
|
|
Packit |
78deda |
|
|
Packit |
78deda |
So we solve that for A, B, and C.
|
|
Packit |
78deda |
|
|
Packit |
78deda |
With those coefficients, we have the quadratic function, and we
|
|
Packit |
78deda |
simple apply it to every old sample value I in the range to get the
|
|
Packit |
78deda |
new:
|
|
Packit |
78deda |
|
|
Packit |
78deda |
newBrightness[I] = A * I^2 + B * I + C
|
|
Packit |
78deda |
*/
|
|
Packit |
78deda |
|
|
Packit |
78deda |
Quadfn xfer;
|
|
Packit |
78deda |
|
|
Packit |
78deda |
computeQuadraticFunction(bvalue, midvalue, wvalue, middle, maxval,
|
|
Packit |
78deda |
&xfer);
|
|
Packit |
78deda |
|
|
Packit |
78deda |
|
|
Packit |
78deda |
if (verbose)
|
|
Packit |
78deda |
pm_message("Transfer function is %f * s^2 + %f * s + %f",
|
|
Packit |
78deda |
xfer.a, xfer.b, xfer.c);
|
|
Packit |
78deda |
|
|
Packit |
78deda |
{
|
|
Packit |
78deda |
xelval i;
|
|
Packit |
78deda |
for (i = bvalue; i <= wvalue; ++i)
|
|
Packit |
78deda |
newBrightness[i] =
|
|
Packit |
78deda |
MIN(ROUNDU(xfer.a * SQR(i) + xfer.b * i + xfer.c), maxval);
|
|
Packit |
78deda |
}
|
|
Packit |
78deda |
}
|
|
Packit |
78deda |
|
|
Packit |
78deda |
|
|
Packit |
78deda |
|
|
Packit |
78deda |
static void
|
|
Packit |
78deda |
computeTransferFunction(bool const quadratic,
|
|
Packit |
78deda |
xelval const bvalue,
|
|
Packit |
78deda |
xelval const midvalue,
|
|
Packit |
78deda |
xelval const wvalue,
|
|
Packit |
78deda |
float const middle,
|
|
Packit |
78deda |
xelval const maxval,
|
|
Packit |
78deda |
bool const verbose,
|
|
Packit |
78deda |
xelval ** const newBrightnessP) {
|
|
Packit |
78deda |
/*----------------------------------------------------------------------------
|
|
Packit |
78deda |
Compute the transfer function, i.e. the array *newBrightnessP such that
|
|
Packit |
78deda |
(*newBrightnessP)[x] is the brightness of the xel that should replace a
|
|
Packit |
78deda |
xel with brightness x. Brightness in this case means either luminosity
|
|
Packit |
78deda |
or color value (and it doesn't matter to us which).
|
|
Packit |
78deda |
|
|
Packit |
78deda |
'bvalue' is the highest brightness that should map to zero brightness;
|
|
Packit |
78deda |
'wvalue' is the lowest brightness that should map to full brightness.
|
|
Packit |
78deda |
|
|
Packit |
78deda |
If 'quadratic' is false, brightnesses in between should be stretched
|
|
Packit |
78deda |
linearly. Otherwise, brightness 'midvalue' should map to brightness
|
|
Packit |
78deda |
'middle' (which is expressed on a 0..1 normalized scale) and brightnesses
|
|
Packit |
78deda |
should be stretched according to a quadratic polynomial that includes those
|
|
Packit |
78deda |
3 points.
|
|
Packit |
78deda |
|
|
Packit |
78deda |
This stretching could conceivably result in more brightnesses mapping to
|
|
Packit |
78deda |
zero and full brightness that 'bvalue' and 'wvalue' demand, because of
|
|
Packit |
78deda |
rounding.
|
|
Packit |
78deda |
|
|
Packit |
78deda |
Define function only for values 0..maxval.
|
|
Packit |
78deda |
-----------------------------------------------------------------------------*/
|
|
Packit |
78deda |
xelval * newBrightness;
|
|
Packit |
78deda |
xelval i;
|
|
Packit |
78deda |
|
|
Packit |
78deda |
MALLOCARRAY(newBrightness, maxval+1);
|
|
Packit |
78deda |
|
|
Packit |
78deda |
if (newBrightness == NULL)
|
|
Packit |
78deda |
pm_error("Unable to allocate memory for transfer function.");
|
|
Packit |
78deda |
|
|
Packit |
78deda |
/* Clip the lowest brightnesses to zero */
|
|
Packit |
78deda |
if (bvalue > 0)
|
|
Packit |
78deda |
for (i = 0; i < bvalue; ++i)
|
|
Packit |
78deda |
newBrightness[i] = 0;
|
|
Packit |
78deda |
|
|
Packit |
78deda |
/* Map the middle brightnesses onto 0..maxval */
|
|
Packit |
78deda |
|
|
Packit |
78deda |
if (quadratic)
|
|
Packit |
78deda |
computeQuadraticTransfer(bvalue, midvalue, wvalue, middle, maxval,
|
|
Packit |
78deda |
verbose, newBrightness);
|
|
Packit |
78deda |
else
|
|
Packit |
78deda |
computeLinearTransfer(bvalue, wvalue, maxval, newBrightness);
|
|
Packit |
78deda |
|
|
Packit |
78deda |
/* Clip the highest brightnesses to maxval */
|
|
Packit |
78deda |
for (i = wvalue+1; i <= maxval; ++i)
|
|
Packit |
78deda |
newBrightness[i] = maxval;
|
|
Packit |
78deda |
|
|
Packit |
78deda |
*newBrightnessP = newBrightness;
|
|
Packit |
78deda |
}
|
|
Packit |
78deda |
|
|
Packit |
78deda |
|
|
Packit |
78deda |
|
|
Packit |
78deda |
static float
|
|
Packit |
78deda |
brightScaler(xel const p,
|
|
Packit |
78deda |
pixval const maxval,
|
|
Packit |
78deda |
xelval const newBrightness[],
|
|
Packit |
78deda |
enum brightMethod const brightMethod) {
|
|
Packit |
78deda |
/*----------------------------------------------------------------------------
|
|
Packit |
78deda |
Return the multiple by which the brightness pixel of color 'p' (based
|
|
Packit |
78deda |
on maxval 'maxval') should be changed according to the transfer
|
|
Packit |
78deda |
function newBrightness[], using the 'brightMethod' measure of
|
|
Packit |
78deda |
brightness.
|
|
Packit |
78deda |
|
|
Packit |
78deda |
For example, if 'brightMethod' is BRIGHT_LUMINOSITY, p is has
|
|
Packit |
78deda |
luminosity 50, and newBrightness[50] is 75, we would return 1.5.
|
|
Packit |
78deda |
-----------------------------------------------------------------------------*/
|
|
Packit |
78deda |
xelval oldBrightness;
|
|
Packit |
78deda |
float scaler;
|
|
Packit |
78deda |
|
|
Packit |
78deda |
switch (brightMethod) {
|
|
Packit |
78deda |
case BRIGHT_LUMINOSITY:
|
|
Packit |
78deda |
oldBrightness = ppm_luminosity(p);
|
|
Packit |
78deda |
break;
|
|
Packit |
78deda |
case BRIGHT_COLORVALUE:
|
|
Packit |
78deda |
oldBrightness = ppm_colorvalue(p);
|
|
Packit |
78deda |
break;
|
|
Packit |
78deda |
case BRIGHT_SATURATION:
|
|
Packit |
78deda |
oldBrightness = ppm_saturation(p, maxval);
|
|
Packit |
78deda |
break;
|
|
Packit |
78deda |
}
|
|
Packit |
78deda |
if (oldBrightness == 0) {
|
|
Packit |
78deda |
assert(newBrightness[oldBrightness] == 0);
|
|
Packit |
78deda |
/* Doesn't matter what we scale by. zero times anything is zero. */
|
|
Packit |
78deda |
scaler = 1.0;
|
|
Packit |
78deda |
} else
|
|
Packit |
78deda |
scaler = (float)newBrightness[oldBrightness]/oldBrightness;
|
|
Packit |
78deda |
|
|
Packit |
78deda |
return scaler;
|
|
Packit |
78deda |
}
|
|
Packit |
78deda |
|
|
Packit |
78deda |
|
|
Packit |
78deda |
|
|
Packit |
78deda |
static void
|
|
Packit |
78deda |
writeRowNormalized(xel * const xelrow,
|
|
Packit |
78deda |
int const cols,
|
|
Packit |
78deda |
xelval const maxval,
|
|
Packit |
78deda |
int const format,
|
|
Packit |
78deda |
enum brightMethod const brightMethod,
|
|
Packit |
78deda |
bool const keephues,
|
|
Packit |
78deda |
xelval const newBrightness[],
|
|
Packit |
78deda |
xel * const rowbuf) {
|
|
Packit |
78deda |
/*----------------------------------------------------------------------------
|
|
Packit |
78deda |
Write to Standard Output a normalized version of the xel row
|
|
Packit |
78deda |
'xelrow'. Normalize it via the transfer function newBrightness[].
|
|
Packit |
78deda |
|
|
Packit |
78deda |
Use 'rowbuf' as a work buffer. It is at least 'cols' columns wide.
|
|
Packit |
78deda |
-----------------------------------------------------------------------------*/
|
|
Packit |
78deda |
xel * const outrow = rowbuf;
|
|
Packit |
78deda |
|
|
Packit |
78deda |
unsigned int col;
|
|
Packit |
78deda |
for (col = 0; col < cols; ++col) {
|
|
Packit |
78deda |
xel const p = xelrow[col];
|
|
Packit |
78deda |
|
|
Packit |
78deda |
if (PPM_FORMAT_TYPE(format) == PPM_TYPE) {
|
|
Packit |
78deda |
if (keephues) {
|
|
Packit |
78deda |
float const scaler =
|
|
Packit |
78deda |
brightScaler(p, maxval, newBrightness, brightMethod);
|
|
Packit |
78deda |
|
|
Packit |
78deda |
xelval const r = MIN(ROUNDU(PPM_GETR(p)*scaler), maxval);
|
|
Packit |
78deda |
xelval const g = MIN(ROUNDU(PPM_GETG(p)*scaler), maxval);
|
|
Packit |
78deda |
xelval const b = MIN(ROUNDU(PPM_GETB(p)*scaler), maxval);
|
|
Packit |
78deda |
PNM_ASSIGN(outrow[col], r, g, b);
|
|
Packit |
78deda |
} else
|
|
Packit |
78deda |
PNM_ASSIGN(outrow[col],
|
|
Packit |
78deda |
newBrightness[PPM_GETR(p)],
|
|
Packit |
78deda |
newBrightness[PPM_GETG(p)],
|
|
Packit |
78deda |
newBrightness[PPM_GETB(p)]);
|
|
Packit |
78deda |
} else
|
|
Packit |
78deda |
PNM_ASSIGN1(outrow[col], newBrightness[PNM_GET1(p)]);
|
|
Packit |
78deda |
}
|
|
Packit |
78deda |
pnm_writepnmrow(stdout, outrow, cols, maxval, format, 0);
|
|
Packit |
78deda |
}
|
|
Packit |
78deda |
|
|
Packit |
78deda |
|
|
Packit |
78deda |
|
|
Packit |
78deda |
static void
|
|
Packit |
78deda |
reportTransferParm(bool const quadratic,
|
|
Packit |
78deda |
xelval const bvalue,
|
|
Packit |
78deda |
xelval const midvalue,
|
|
Packit |
78deda |
xelval const wvalue,
|
|
Packit |
78deda |
xelval const maxval,
|
|
Packit |
78deda |
float const middle) {
|
|
Packit |
78deda |
|
|
Packit |
78deda |
if (quadratic)
|
|
Packit |
78deda |
pm_message("remapping %u..%u..%u to %u..%u..%u",
|
|
Packit |
78deda |
bvalue, midvalue, wvalue,
|
|
Packit |
78deda |
0, ROUNDU(maxval*middle), maxval);
|
|
Packit |
78deda |
else
|
|
Packit |
78deda |
pm_message("remapping %u..%u to %u..%u",
|
|
Packit |
78deda |
bvalue, wvalue, 0, maxval);
|
|
Packit |
78deda |
}
|
|
Packit |
78deda |
|
|
Packit |
78deda |
|
|
Packit |
78deda |
|
|
Packit |
78deda |
int
|
|
Packit |
78deda |
main(int argc, const char *argv[]) {
|
|
Packit |
78deda |
|
|
Packit |
78deda |
struct cmdlineInfo cmdline;
|
|
Packit |
78deda |
FILE *ifP;
|
|
Packit |
78deda |
pm_filepos imagePos;
|
|
Packit |
78deda |
xelval maxval;
|
|
Packit |
78deda |
int rows, cols, format;
|
|
Packit |
78deda |
bool quadratic;
|
|
Packit |
78deda |
xelval bvalue, midvalue, wvalue;
|
|
Packit |
78deda |
|
|
Packit |
78deda |
pm_proginit(&argc, argv);
|
|
Packit |
78deda |
|
|
Packit |
78deda |
parseCommandLine(argc, argv, &cmdline);
|
|
Packit |
78deda |
|
|
Packit |
78deda |
ifP = pm_openr_seekable(cmdline.inputFileName);
|
|
Packit |
78deda |
|
|
Packit |
78deda |
/* Rescale so that bvalue maps to 0, wvalue maps to maxval. */
|
|
Packit |
78deda |
pnm_readpnminit(ifP, &cols, &rows, &maxval, &format);
|
|
Packit |
78deda |
pm_tell2(ifP, &imagePos, sizeof(imagePos));
|
|
Packit |
78deda |
|
|
Packit |
78deda |
computeEndValues(ifP, cols, rows, maxval, format, cmdline,
|
|
Packit |
78deda |
&bvalue, &wvalue, &quadratic, &midvalue);
|
|
Packit |
78deda |
{
|
|
Packit |
78deda |
xelval * newBrightness;
|
|
Packit |
78deda |
int row;
|
|
Packit |
78deda |
xel * xelrow;
|
|
Packit |
78deda |
xel * rowbuf;
|
|
Packit |
78deda |
|
|
Packit |
78deda |
assert(wvalue > bvalue);
|
|
Packit |
78deda |
|
|
Packit |
78deda |
xelrow = pnm_allocrow(cols);
|
|
Packit |
78deda |
|
|
Packit |
78deda |
reportTransferParm(quadratic, bvalue, midvalue, wvalue, maxval,
|
|
Packit |
78deda |
cmdline.middle);
|
|
Packit |
78deda |
|
|
Packit |
78deda |
|
|
Packit |
78deda |
computeTransferFunction(quadratic, bvalue, midvalue, wvalue,
|
|
Packit |
78deda |
cmdline.middle, maxval, cmdline.verbose,
|
|
Packit |
78deda |
&newBrightness);
|
|
Packit |
78deda |
|
|
Packit |
78deda |
pm_seek2(ifP, &imagePos, sizeof(imagePos));
|
|
Packit |
78deda |
pnm_writepnminit(stdout, cols, rows, maxval, format, 0);
|
|
Packit |
78deda |
|
|
Packit |
78deda |
rowbuf = pnm_allocrow(cols);
|
|
Packit |
78deda |
|
|
Packit |
78deda |
for (row = 0; row < rows; ++row) {
|
|
Packit |
78deda |
pnm_readpnmrow(ifP, xelrow, cols, maxval, format);
|
|
Packit |
78deda |
writeRowNormalized(xelrow, cols, maxval, format,
|
|
Packit |
78deda |
cmdline.brightMethod, cmdline.keephues,
|
|
Packit |
78deda |
newBrightness, rowbuf);
|
|
Packit |
78deda |
}
|
|
Packit |
78deda |
free(newBrightness);
|
|
Packit |
78deda |
pnm_freerow(rowbuf);
|
|
Packit |
78deda |
pnm_freerow(xelrow);
|
|
Packit |
78deda |
}
|
|
Packit |
78deda |
pm_close(ifP);
|
|
Packit |
78deda |
return 0;
|
|
Packit |
78deda |
}
|
|
Packit |
78deda |
|
|
Packit |
78deda |
|
|
Packit |
78deda |
|