|
Packit |
78deda |
/*=============================================================================
|
|
Packit |
78deda |
pamshaderelief
|
|
Packit |
78deda |
===============================================================================
|
|
Packit |
78deda |
Generate a shaded relief image of terrain, given a terrain map - a two
|
|
Packit |
78deda |
dimensional map of elevations. A shaded relief image is an image of
|
|
Packit |
78deda |
what terrain with the given elevations would look like illuminated by
|
|
Packit |
78deda |
oblique light.
|
|
Packit |
78deda |
|
|
Packit |
78deda |
The input array is a one-channel PAM image. The sample values are
|
|
Packit |
78deda |
elevations of terrain.
|
|
Packit |
78deda |
|
|
Packit |
78deda |
This is derived from John Walker's 'pgmcrater' which not only does this
|
|
Packit |
78deda |
shading, but first generates a terrain map of fractal craters on which to
|
|
Packit |
78deda |
run it.
|
|
Packit |
78deda |
|
|
Packit |
78deda |
|
|
Packit |
78deda |
The original program carried this attribution and license:
|
|
Packit |
78deda |
|
|
Packit |
78deda |
Designed and implemented in November of 1989 by:
|
|
Packit |
78deda |
|
|
Packit |
78deda |
John Walker
|
|
Packit |
78deda |
Autodesk SA
|
|
Packit |
78deda |
Avenue des Champs-Montants 14b
|
|
Packit |
78deda |
CH-2074 MARIN
|
|
Packit |
78deda |
Switzerland
|
|
Packit |
78deda |
Usenet: kelvin@Autodesk.com
|
|
Packit |
78deda |
Fax: 038/33 88 15
|
|
Packit |
78deda |
Voice: 038/33 76 33
|
|
Packit |
78deda |
|
|
Packit |
78deda |
Permission to use, copy, modify, and distribute this software and
|
|
Packit |
78deda |
its documentation for any purpose and without fee is hereby
|
|
Packit |
78deda |
granted, without any conditions or restrictions. This software is
|
|
Packit |
78deda |
provided "as is" without express or implied warranty.
|
|
Packit |
78deda |
|
|
Packit |
78deda |
=============================================================================*/
|
|
Packit |
78deda |
|
|
Packit |
78deda |
/* Modifications by Arjen Bax, 2001-06-21: Remove black vertical line at right
|
|
Packit |
78deda |
edge.
|
|
Packit |
78deda |
*/
|
|
Packit |
78deda |
|
|
Packit |
78deda |
#define _XOPEN_SOURCE 500 /* get M_PI in math.h */
|
|
Packit |
78deda |
|
|
Packit |
78deda |
#include <assert.h>
|
|
Packit |
78deda |
#include <math.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 "pam.h"
|
|
Packit |
78deda |
|
|
Packit |
78deda |
|
|
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;
|
|
Packit |
78deda |
float gamma;
|
|
Packit |
78deda |
};
|
|
Packit |
78deda |
|
|
Packit |
78deda |
|
|
Packit |
78deda |
|
|
Packit |
78deda |
static void
|
|
Packit |
78deda |
parseCommandLine(int argc, const char ** const argv,
|
|
Packit |
78deda |
struct CmdlineInfo * const cmdlineP) {
|
|
Packit |
78deda |
/*----------------------------------------------------------------------------
|
|
Packit |
78deda |
Note that the file spec array we return is stored in the storage that
|
|
Packit |
78deda |
was passed to us as the argv array.
|
|
Packit |
78deda |
-----------------------------------------------------------------------------*/
|
|
Packit |
78deda |
optEntry * option_def;
|
|
Packit |
78deda |
/* Instructions to OptParseOptions3 on how to parse our options.
|
|
Packit |
78deda |
*/
|
|
Packit |
78deda |
optStruct3 opt;
|
|
Packit |
78deda |
unsigned int option_def_index;
|
|
Packit |
78deda |
|
|
Packit |
78deda |
unsigned int gammaSpec;
|
|
Packit |
78deda |
|
|
Packit |
78deda |
MALLOCARRAY_NOFAIL(option_def, 100);
|
|
Packit |
78deda |
|
|
Packit |
78deda |
option_def_index = 0; /* incremented by OPTENT3 */
|
|
Packit |
78deda |
OPTENT3(0, "gamma", OPT_FLOAT, &cmdlineP->gamma,
|
|
Packit |
78deda |
&gammaSpec, 0);
|
|
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 may have 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 *cmdlineP and others. */
|
|
Packit |
78deda |
|
|
Packit |
78deda |
if (!gammaSpec)
|
|
Packit |
78deda |
cmdlineP->gamma = 1.0;
|
|
Packit |
78deda |
|
|
Packit |
78deda |
if (cmdlineP->gamma <= 0.0)
|
|
Packit |
78deda |
pm_error("gamma correction must be greater than 0");
|
|
Packit |
78deda |
|
|
Packit |
78deda |
if (argc-1 == 0)
|
|
Packit |
78deda |
cmdlineP->inputFileName = "-";
|
|
Packit |
78deda |
else if (argc-1 != 1)
|
|
Packit |
78deda |
pm_error("Program takes zero or one argument (filename). You "
|
|
Packit |
78deda |
"specified %u", argc-1);
|
|
Packit |
78deda |
else
|
|
Packit |
78deda |
cmdlineP->inputFileName = argv[1];
|
|
Packit |
78deda |
|
|
Packit |
78deda |
free(option_def);
|
|
Packit |
78deda |
}
|
|
Packit |
78deda |
|
|
Packit |
78deda |
|
|
Packit |
78deda |
|
|
Packit |
78deda |
/* Definitions for obtaining random numbers. */
|
|
Packit |
78deda |
|
|
Packit |
78deda |
/* Display parameters */
|
|
Packit |
78deda |
|
|
Packit |
78deda |
static double const ImageGamma = 0.5; /* Inherent gamma of mapped image */
|
|
Packit |
78deda |
static int const slopemin = -52;
|
|
Packit |
78deda |
static int const slopemax = 52;
|
|
Packit |
78deda |
|
|
Packit |
78deda |
|
|
Packit |
78deda |
|
|
Packit |
78deda |
static void
|
|
Packit |
78deda |
generateSlopeGrayMap(sample * const slopeGrayMap,
|
|
Packit |
78deda |
double const dgamma) {
|
|
Packit |
78deda |
/*----------------------------------------------------------------------------
|
|
Packit |
78deda |
Map each possible slope to the brightness that terrain with that
|
|
Packit |
78deda |
left-to-right slope should have in the shaded relief.
|
|
Packit |
78deda |
|
|
Packit |
78deda |
The brightness is what would result from light incident from the left
|
|
Packit |
78deda |
falling on the terrain.
|
|
Packit |
78deda |
-----------------------------------------------------------------------------*/
|
|
Packit |
78deda |
double const gamma = dgamma * ImageGamma;
|
|
Packit |
78deda |
|
|
Packit |
78deda |
int i;
|
|
Packit |
78deda |
|
|
Packit |
78deda |
for (i = slopemin; i <= 0; ++i) { /* Negative, downhill, dark */
|
|
Packit |
78deda |
slopeGrayMap[i - slopemin] =
|
|
Packit |
78deda |
128 - 127.0 * pow(sin((M_PI / 2) * i / slopemin), gamma);
|
|
Packit |
78deda |
}
|
|
Packit |
78deda |
for (i = 0; i <= slopemax; ++i) { /* Positive, uphill, bright */
|
|
Packit |
78deda |
slopeGrayMap[i - slopemin] =
|
|
Packit |
78deda |
128 + 127.0 * pow(sin((M_PI / 2) * i / slopemax), gamma);
|
|
Packit |
78deda |
}
|
|
Packit |
78deda |
|
|
Packit |
78deda |
/* Confused? OK, we're using the left-to-right slope to
|
|
Packit |
78deda |
calculate a shade based on the sine of the angle with
|
|
Packit |
78deda |
respect to the vertical (light incident from the left).
|
|
Packit |
78deda |
Then, with one exponentiation, we account for both the
|
|
Packit |
78deda |
inherent gamma of the image (ad-hoc), and the
|
|
Packit |
78deda |
user-specified display gamma, using the identity:
|
|
Packit |
78deda |
(x^y)^z = (x^(y*z))
|
|
Packit |
78deda |
*/
|
|
Packit |
78deda |
}
|
|
Packit |
78deda |
|
|
Packit |
78deda |
|
|
Packit |
78deda |
|
|
Packit |
78deda |
static gray
|
|
Packit |
78deda |
brightnessOfSlope(int const slope,
|
|
Packit |
78deda |
sample * const slopeGrayMap) {
|
|
Packit |
78deda |
|
|
Packit |
78deda |
return slopeGrayMap[MIN(MAX(slopemin, slope), slopemax) - slopemin];
|
|
Packit |
78deda |
}
|
|
Packit |
78deda |
|
|
Packit |
78deda |
|
|
Packit |
78deda |
|
|
Packit |
78deda |
static void
|
|
Packit |
78deda |
writeShadedRelief(struct pam * const terrainPamP,
|
|
Packit |
78deda |
tuple ** const terrain,
|
|
Packit |
78deda |
double const dgamma,
|
|
Packit |
78deda |
FILE * const ofP) {
|
|
Packit |
78deda |
|
|
Packit |
78deda |
unsigned int row;
|
|
Packit |
78deda |
tuple * outrow;
|
|
Packit |
78deda |
sample * slopeGrayMap; /* Slope to gray value map */
|
|
Packit |
78deda |
struct pam outpam;
|
|
Packit |
78deda |
|
|
Packit |
78deda |
outpam.size = sizeof(outpam);
|
|
Packit |
78deda |
outpam.len = PAM_STRUCT_SIZE(tuple_type);
|
|
Packit |
78deda |
outpam.file = ofP;
|
|
Packit |
78deda |
outpam.format = PAM_FORMAT;
|
|
Packit |
78deda |
outpam.height = terrainPamP->height;
|
|
Packit |
78deda |
outpam.width = terrainPamP->width;
|
|
Packit |
78deda |
outpam.depth = 1;
|
|
Packit |
78deda |
outpam.maxval = 255;
|
|
Packit |
78deda |
outpam.bytes_per_sample = 1;
|
|
Packit |
78deda |
STRSCPY(outpam.tuple_type, "GRAYSCALE");
|
|
Packit |
78deda |
|
|
Packit |
78deda |
outrow = pnm_allocpamrow(&outpam);
|
|
Packit |
78deda |
|
|
Packit |
78deda |
pnm_writepaminit(&outpam);
|
|
Packit |
78deda |
|
|
Packit |
78deda |
MALLOCARRAY(slopeGrayMap, slopemax - slopemin + 1);
|
|
Packit |
78deda |
|
|
Packit |
78deda |
generateSlopeGrayMap(slopeGrayMap, dgamma);
|
|
Packit |
78deda |
|
|
Packit |
78deda |
for (row = 0; row < terrainPamP->height; ++row) {
|
|
Packit |
78deda |
unsigned int col;
|
|
Packit |
78deda |
|
|
Packit |
78deda |
for (col = 0; col < terrainPamP->width - 1; ++col) {
|
|
Packit |
78deda |
int const slope = terrain[row][col+1][0] - terrain[row][col][0];
|
|
Packit |
78deda |
outrow[col][0] = brightnessOfSlope(slope, slopeGrayMap);
|
|
Packit |
78deda |
}
|
|
Packit |
78deda |
{
|
|
Packit |
78deda |
/* Wrap around to determine shade of pixel on right edge */
|
|
Packit |
78deda |
int const slope =
|
|
Packit |
78deda |
terrain[row][0][0] - terrain[row][outpam.width-1][0];
|
|
Packit |
78deda |
outrow[outpam.width - 1][0] =
|
|
Packit |
78deda |
brightnessOfSlope(slope, slopeGrayMap);
|
|
Packit |
78deda |
}
|
|
Packit |
78deda |
pnm_writepamrow(&outpam, outrow);
|
|
Packit |
78deda |
}
|
|
Packit |
78deda |
|
|
Packit |
78deda |
free(slopeGrayMap);
|
|
Packit |
78deda |
pnm_freepamrow(outrow);
|
|
Packit |
78deda |
}
|
|
Packit |
78deda |
|
|
Packit |
78deda |
|
|
Packit |
78deda |
|
|
Packit |
78deda |
static void
|
|
Packit |
78deda |
readTerrain(FILE * const ifP,
|
|
Packit |
78deda |
struct pam * const pamP,
|
|
Packit |
78deda |
tuple *** const tuplesP) {
|
|
Packit |
78deda |
|
|
Packit |
78deda |
*tuplesP = pnm_readpam(ifP, pamP, PAM_STRUCT_SIZE(tuple_type));
|
|
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 |
struct pam terrainPam;
|
|
Packit |
78deda |
tuple ** terrain;
|
|
Packit |
78deda |
/* Array of elevations */
|
|
Packit |
78deda |
|
|
Packit |
78deda |
pm_proginit(&argc, argv);
|
|
Packit |
78deda |
|
|
Packit |
78deda |
parseCommandLine(argc, argv, &cmdline);
|
|
Packit |
78deda |
|
|
Packit |
78deda |
ifP = pm_openr(cmdline.inputFileName);
|
|
Packit |
78deda |
|
|
Packit |
78deda |
readTerrain(ifP, &terrainPam, &terrain);
|
|
Packit |
78deda |
|
|
Packit |
78deda |
writeShadedRelief(&terrainPam, terrain, cmdline.gamma, stdout);
|
|
Packit |
78deda |
|
|
Packit |
78deda |
return 0;
|
|
Packit |
78deda |
}
|
|
Packit |
78deda |
|
|
Packit |
78deda |
|