diff options
author | giraffedata <giraffedata@9d0c8265-081b-0410-96cb-a4ca84ce46f8> | 2006-08-19 03:12:28 +0000 |
---|---|---|
committer | giraffedata <giraffedata@9d0c8265-081b-0410-96cb-a4ca84ce46f8> | 2006-08-19 03:12:28 +0000 |
commit | 1fd361a1ea06e44286c213ca1f814f49306fdc43 (patch) | |
tree | 64c8c96cf54d8718847339a403e5e67b922e8c3f /generator | |
download | netpbm-mirror-1fd361a1ea06e44286c213ca1f814f49306fdc43.tar.gz netpbm-mirror-1fd361a1ea06e44286c213ca1f814f49306fdc43.tar.xz netpbm-mirror-1fd361a1ea06e44286c213ca1f814f49306fdc43.zip |
Create Subversion repository
git-svn-id: http://svn.code.sf.net/p/netpbm/code/trunk@1 9d0c8265-081b-0410-96cb-a4ca84ce46f8
Diffstat (limited to 'generator')
-rw-r--r-- | generator/Makefile | 40 | ||||
-rw-r--r-- | generator/pamgauss.c | 207 | ||||
-rw-r--r-- | generator/pamgradient.c | 215 | ||||
-rw-r--r-- | generator/pamseq.c | 202 | ||||
-rw-r--r-- | generator/pamstereogram.c | 885 | ||||
-rw-r--r-- | generator/pamstereogram.test | 70 | ||||
-rw-r--r-- | generator/pbmmake.c | 184 | ||||
-rw-r--r-- | generator/pbmmake.test | 9 | ||||
-rw-r--r-- | generator/pbmpage.c | 291 | ||||
-rw-r--r-- | generator/pbmtext.c | 732 | ||||
-rw-r--r-- | generator/pbmtextps.c | 377 | ||||
-rw-r--r-- | generator/pbmupc.c | 544 | ||||
-rw-r--r-- | generator/pgmcrater.c | 383 | ||||
-rw-r--r-- | generator/pgmkernel.c | 91 | ||||
-rw-r--r-- | generator/pgmmake.c | 111 | ||||
-rw-r--r-- | generator/pgmnoise.c | 77 | ||||
-rw-r--r-- | generator/pgmramp.c | 170 | ||||
-rw-r--r-- | generator/ppmcie.c | 1201 | ||||
-rw-r--r-- | generator/ppmcolors.c | 87 | ||||
-rw-r--r-- | generator/ppmforge.c | 1182 | ||||
-rw-r--r-- | generator/ppmmake.c | 117 | ||||
-rw-r--r-- | generator/ppmpat.c | 1060 | ||||
-rwxr-xr-x | generator/ppmrainbow | 74 | ||||
-rw-r--r-- | generator/ppmrough.c | 292 | ||||
-rw-r--r-- | generator/ppmwheel.c | 157 |
25 files changed, 8758 insertions, 0 deletions
diff --git a/generator/Makefile b/generator/Makefile new file mode 100644 index 00000000..52de9b10 --- /dev/null +++ b/generator/Makefile @@ -0,0 +1,40 @@ +ifeq ($(SRCDIR)x,x) + SRCDIR = $(CURDIR)/.. + BUILDDIR = $(SRCDIR) +endif +SUBDIR = generator +VPATH=.:$(SRCDIR)/$(SUBDIR) + +include $(BUILDDIR)/Makefile.config + +# We tend to separate out the build targets so that we don't have +# any more dependencies for a given target than it really needs. +# That way, if there is a problem with a dependency, we can still +# successfully build all the stuff that doesn't depend upon it. +# This package is so big, it's useful even when some parts won't +# build. + +PORTBINARIES = pamgauss pamgradient pamseq pamstereogram \ + pbmpage pbmmake pbmtext pbmtextps pbmupc \ + pgmcrater pgmkernel pgmmake pgmnoise pgmramp \ + ppmcie ppmcolors ppmforge ppmmake ppmpat ppmrough ppmwheel \ + +# We don't include programs that have special library dependencies in the +# merge scheme, because we don't want those dependencies to prevent us +# from building all the other programs. + +NOMERGEBINARIES = +MERGEBINARIES = $(PORTBINARIES) + + +BINARIES = $(MERGEBINARIES) $(NOMERGEBINARIES) +SCRIPTS = ppmrainbow + +OBJECTS = $(BINARIES:%=%.o) + +MERGE_OBJECTS = $(MERGEBINARIES:%=%.o2) + +.PHONY: all +all: $(BINARIES) + +include $(SRCDIR)/Makefile.common diff --git a/generator/pamgauss.c b/generator/pamgauss.c new file mode 100644 index 00000000..eb5fa3fe --- /dev/null +++ b/generator/pamgauss.c @@ -0,0 +1,207 @@ +#include <string.h> +#include <unistd.h> +#include <stdlib.h> +#include <stdio.h> +#include <math.h> + +#include "pm_c_util.h" +#include "shhopt.h" +#include "mallocvar.h" +#include "pam.h" + +#define true (1) +#define false (0) + + +struct cmdlineInfo { + /* All the information the user supplied in the command line, + in a form easy for the program to use. + */ + unsigned int width; + unsigned int height; + sample maxval; + float sigma; + const char * tupletype; +}; + + + +static void +parseCommandLine(int argc, char ** argv, + struct cmdlineInfo * const cmdlineP) { +/*---------------------------------------------------------------------------- + Convert program invocation arguments (argc,argv) into a format the + program can use easily, struct cmdlineInfo. Validate arguments along + the way and exit program with message if invalid. + + Note that some string information we return as *cmdlineP is in the storage + argv[] points to. +-----------------------------------------------------------------------------*/ + optEntry *option_def; + /* Instructions to OptParseOptions2 on how to parse our options. + */ + optStruct3 opt; + + unsigned int tupletypeSpec, maxvalSpec, sigmaSpec; + unsigned int option_def_index; + + MALLOCARRAY_NOFAIL(option_def, 100); + + option_def_index = 0; /* incremented by OPTENTRY */ + OPTENT3(0, "tupletype", OPT_STRING, &cmdlineP->tupletype, + &tupletypeSpec, 0); + OPTENT3(0, "maxval", OPT_UINT, &cmdlineP->maxval, + &maxvalSpec, 0); + OPTENT3(0, "sigma", OPT_FLOAT, &cmdlineP->sigma, + &sigmaSpec, 0); + + opt.opt_table = option_def; + opt.short_allowed = FALSE; /* We have no short (old-fashioned) options */ + opt.allowNegNum = FALSE; /* We have no parms that are negative numbers */ + + optParseOptions3(&argc, argv, opt, sizeof(opt), 0); + /* Uses and sets argc, argv, and some of *cmdlineP and others. */ + + if (!tupletypeSpec) + cmdlineP->tupletype = ""; + else { + struct pam pam; + if (strlen(cmdlineP->tupletype)+1 > sizeof(pam.tuple_type)) + pm_error("The tuple type you specified is too long. " + "Maximum %d characters.", sizeof(pam.tuple_type)-1); + } + + if (!sigmaSpec) + pm_error("You must specify the -sigma option."); + else if (cmdlineP->sigma <= 0.0) + pm_error("-sigma must be positive. You specified %f", + cmdlineP->sigma); + + if (!maxvalSpec) + cmdlineP->maxval = PNM_MAXMAXVAL; + else { + if (cmdlineP->maxval > PNM_OVERALLMAXVAL) + pm_error("The maxval you specified (%u) is too big. " + "Maximum is %u", (unsigned int) cmdlineP->maxval, + PNM_OVERALLMAXVAL); + if (cmdlineP->maxval < 1) + pm_error("-maxval must be at least 1"); + } + + if (argc-1 < 2) + pm_error("Need two arguments: width and height."); + else if (argc-1 > 2) + pm_error("Only two argumeents allowed: with and height. " + "You specified %d", argc-1); + else { + cmdlineP->width = atoi(argv[1]); + cmdlineP->height = atoi(argv[2]); + if (cmdlineP->width <= 0) + pm_error("width argument must be a positive number. You " + "specified '%s'", argv[1]); + if (cmdlineP->height <= 0) + pm_error("height argument must be a positive number. You " + "specified '%s'", argv[2]); + } +} + + + +static double +distFromCenter(struct pam * const pamP, + int const col, + int const row) { + + return sqrt(SQR(col - pamP->width/2) + SQR(row - pamP->height/2)); +} + + + +static double +gauss(double const arg, + double const sigma) { +/*---------------------------------------------------------------------------- + Compute the value of the gaussian function with sigma parameter 'sigma' + and mu parameter zero of argument 'arg'. +-----------------------------------------------------------------------------*/ + double const pi = 3.14159; + double const coefficient = 1 / (sigma * sqrt(2*pi)); + double const exponent = - SQR(arg-0) / (2 * SQR(sigma)); + + return coefficient * exp(exponent); +} + + + +static double +imageNormalizer(struct pam * const pamP, + double const sigma) { +/*---------------------------------------------------------------------------- + Compute the value that has to be multiplied by the value of the + one-dimensional gaussian function of the distance from center in + order to get the value for a normalized two-dimensional gaussian + function. Normalized here means that the volume under the whole + curve is 1, just as the area under a whole one-dimensional gaussian + function is 1. +-----------------------------------------------------------------------------*/ + double volume; + + unsigned int row; + + volume = 0.0; /* initial value */ + + for (row = 0; row < pamP->height; ++row) { + unsigned int col; + for (col = 0; col < pamP->width; ++col) + volume += gauss(distFromCenter(pamP, col, row), sigma); + } + return 1.0 / volume; +} + + + +int +main(int argc, char **argv) { + + struct cmdlineInfo cmdline; + struct pam pam; + int row; + double normalizer; + tuplen * tuplerown; + + pnm_init(&argc, argv); + + parseCommandLine(argc, argv, &cmdline); + + pam.size = sizeof(pam); + pam.len = PAM_STRUCT_SIZE(tuple_type); + pam.file = stdout; + pam.format = PAM_FORMAT; + pam.plainformat = 0; + pam.width = cmdline.width; + pam.height = cmdline.height; + pam.depth = 1; + pam.maxval = cmdline.maxval; + strcpy(pam.tuple_type, cmdline.tupletype); + + normalizer = imageNormalizer(&pam, cmdline.sigma); + + pnm_writepaminit(&pam); + + tuplerown = pnm_allocpamrown(&pam); + + for (row = 0; row < pam.height; ++row) { + int col; + for (col = 0; col < pam.width; ++col) { + double const gauss1 = gauss(distFromCenter(&pam, col, row), + cmdline.sigma); + + tuplerown[col][0] = gauss1 * normalizer; + } + pnm_writepamrown(&pam, tuplerown); + } + + pnm_freepamrown(tuplerown); + + return 0; +} diff --git a/generator/pamgradient.c b/generator/pamgradient.c new file mode 100644 index 00000000..7717bf4b --- /dev/null +++ b/generator/pamgradient.c @@ -0,0 +1,215 @@ +#include <string.h> + +#include "pam.h" +#include "shhopt.h" +#include "mallocvar.h" + + + +struct cmdlineInfo { + tuple colorTopLeft; + tuple colorTopRight; + tuple colorBottomLeft; + tuple colorBottomRight; + unsigned depth; + unsigned int cols; + unsigned int rows; + unsigned int maxval; +}; + +static void +parseCommandLine(int argc, char **argv, + struct cmdlineInfo * const cmdlineP) { +/*---------------------------------------------------------------------------- + Convert program invocation arguments (argc,argv) into a format the + program can use easily, struct cmdlineInfo. Validate arguments along + the way and exit program with message if invalid. + + Note that some string information we return as *cmdlineP is in the storage + argv[] points to. +-----------------------------------------------------------------------------*/ + optEntry * option_def; + /* Instructions to OptParseOptions3 on how to parse our options. + */ + optStruct3 opt; + + unsigned int maxvalSpec; + unsigned int option_def_index; + + MALLOCARRAY_NOFAIL(option_def, 100); + + option_def_index = 0; + OPTENT3(0, "maxval", OPT_UINT, &cmdlineP->maxval, &maxvalSpec, 0); + + opt.opt_table = option_def; + opt.short_allowed = FALSE; /* We have no short (old-fashioned) options */ + opt.allowNegNum = FALSE; /* We have no parms that are negative numbers */ + + optParseOptions3(&argc, argv, opt, sizeof(opt), 0); + /* Uses and sets argc, argv, and some of *cmdlineP and others. */ + + if (!maxvalSpec) + cmdlineP->maxval = 255; + else { + if (cmdlineP->maxval > PAM_OVERALL_MAXVAL) + pm_error("The value you specified for -maxval (%u) is too big. " + "Max allowed is %u", cmdlineP->maxval, + PAM_OVERALL_MAXVAL); + + if (cmdlineP->maxval < 1) + pm_error("You cannot specify 0 for -maxval"); + } + + if (argc-1 != 6) { + pm_error("Need 6 arguments: colorTopLeft, colorTopRight, " + "colorBottomLeft, colorBottomRight, width, height"); + } else { + cmdlineP->colorTopLeft = pnm_parsecolor(argv[1], cmdlineP->maxval); + cmdlineP->colorTopRight = pnm_parsecolor(argv[2], cmdlineP->maxval); + cmdlineP->colorBottomLeft = pnm_parsecolor(argv[3], cmdlineP->maxval); + cmdlineP->colorBottomRight = pnm_parsecolor(argv[4], cmdlineP->maxval); + cmdlineP->cols = atoi(argv[5]); + cmdlineP->rows = atoi(argv[6]); + if (cmdlineP->cols <= 0) + pm_error("width argument must be a positive number. You " + "specified '%s'", argv[5]); + if (cmdlineP->rows <= 0) + pm_error("height argument must be a positive number. You " + "specified '%s'", argv[6]); + } +} + + + +static void +freeCmdline(struct cmdlineInfo const cmdline) { + + pnm_freepamtuple(cmdline.colorTopLeft); + pnm_freepamtuple(cmdline.colorTopRight); + pnm_freepamtuple(cmdline.colorBottomLeft); + pnm_freepamtuple(cmdline.colorBottomRight); +} + + + +static void +interpolate(struct pam * const pamP, + tuple * const tuplerow, + tuple const first, + tuple const last) { + + unsigned int plane; + + for (plane = 0; plane < pamP->depth; ++plane) { + int const spread = last[plane] - first[plane]; + + int col; + + if (INT_MAX / pamP->width < abs(spread)) + pm_error("Arithmetic overflow. You must reduce the width of " + "the image (now %u) or the range of color values " + "(%u in plane %u) so that their " + "product is less than %d", + pamP->width, abs(spread), plane, INT_MAX); + + for (col = 0; col < pamP->width; ++col) + tuplerow[col][plane] = + first[plane] + (spread * col / (int)pamP->width); + } +} + + + +static int +isgray(struct pam * const pamP, + tuple const color) { + + return (pamP->depth == 1) + || ((pamP->depth == 3) + && (color[PAM_RED_PLANE] == color[PAM_GRN_PLANE]) + && (color[PAM_RED_PLANE] == color[PAM_BLU_PLANE])); +} + + + +static tuple * +createEdge(const struct pam * const pamP, + tuple const topColor, + tuple const bottomColor) { +/*---------------------------------------------------------------------------- + Create a left or right edge, interpolating from top to bottom. +-----------------------------------------------------------------------------*/ + struct pam interpPam; + tuple * tupleRow; + + interpPam = *pamP; /* initial value */ + interpPam.width = pamP->height; + interpPam.height = 1; + + tupleRow = pnm_allocpamrow(&interpPam); + + interpolate(&interpPam, tupleRow, topColor, bottomColor); + + return tupleRow; +} + + + +int +main(int argc, char *argv[]) { + + struct cmdlineInfo cmdline; + struct pam pam; + tuple * tupleRow; + tuple * leftEdge; + tuple * rightEdge; + unsigned int row; + + pnm_init(&argc, argv); + + parseCommandLine(argc, argv, &cmdline); + + pam.size = sizeof pam; + pam.len = PAM_STRUCT_SIZE(tuple_type); + pam.file = stdout; + pam.plainformat = 0; + pam.width = cmdline.cols; + pam.height = cmdline.rows; + pam.maxval = cmdline.maxval; + pam.bytes_per_sample = pnm_bytespersample(pam.maxval); + pam.format = PAM_FORMAT; + if (isgray(&pam, cmdline.colorTopLeft) + && isgray(&pam, cmdline.colorTopRight) + && isgray(&pam, cmdline.colorBottomLeft) + && isgray(&pam, cmdline.colorBottomRight)) { + pam.depth = 1; + strcpy(pam.tuple_type, PAM_PGM_TUPLETYPE); + } else { + pam.depth = 3; + strcpy(pam.tuple_type, PAM_PPM_TUPLETYPE); + } + + pnm_writepaminit(&pam); + + tupleRow = pnm_allocpamrow(&pam); + + leftEdge = createEdge(&pam, + cmdline.colorTopLeft, cmdline.colorBottomLeft); + rightEdge = createEdge(&pam, + cmdline.colorTopRight, cmdline.colorBottomRight); + + /* interpolate each row between the left edge and the right edge */ + for (row = 0; row < pam.height; ++row) { + interpolate(&pam, tupleRow, leftEdge[row], rightEdge[row]); + pnm_writepamrow(&pam, tupleRow); + } + + pm_close(stdout); + pnm_freepamrow(rightEdge); + pnm_freepamrow(leftEdge); + pnm_freepamrow(tupleRow); + + freeCmdline(cmdline); + + return 0; +} diff --git a/generator/pamseq.c b/generator/pamseq.c new file mode 100644 index 00000000..58419d12 --- /dev/null +++ b/generator/pamseq.c @@ -0,0 +1,202 @@ +#include <string.h> +#include <unistd.h> +#include <stdlib.h> +#include <stdio.h> + +#include "pam.h" +#include "shhopt.h" + +#define true (1) +#define false (0) + + +struct cmdlineInfo { + /* All the information the user supplied in the command line, + in a form easy for the program to use. + */ + unsigned int depth; + sample maxval; + const char * tupletype; +}; + + + +static void +parseCommandLine(int argc, char ** argv, + struct cmdlineInfo * const cmdlineP) { +/*---------------------------------------------------------------------------- + Convert program invocation arguments (argc,argv) into a format the + program can use easily, struct cmdlineInfo. Validate arguments along + the way and exit program with message if invalid. + + Note that some string information we return as *cmdlineP is in the storage + argv[] points to. +-----------------------------------------------------------------------------*/ + optEntry *option_def = malloc(100*sizeof(optEntry)); + /* Instructions to OptParseOptions2 on how to parse our options. + */ + optStruct3 opt; + + unsigned int tupletypeSpec; + unsigned int option_def_index; + + option_def_index = 0; /* incremented by OPTENT3 */ + OPTENT3(0, "tupletype", OPT_STRING, &cmdlineP->tupletype, + &tupletypeSpec, 0); + + + opt.opt_table = option_def; + opt.short_allowed = FALSE; /* We have no short (old-fashioned) options */ + opt.allowNegNum = FALSE; /* We have no parms that are negative numbers */ + + optParseOptions3(&argc, argv, opt, sizeof(opt), 0); + /* Uses and sets argc, argv, and some of *cmdlineP and others. */ + + if (!tupletypeSpec) + cmdlineP->tupletype = ""; + else { + struct pam pam; + if (strlen(cmdlineP->tupletype)+1 > sizeof(pam.tuple_type)) + pm_error("The tuple type you specified is too long. " + "Maximum %d characters.", sizeof(pam.tuple_type)-1); + } + + if (argc-1 < 2) + pm_error("Need two arguments: depth and maxval."); + else if (argc-1 > 2) + pm_error("Only two argumeents allowed: depth and maxval. " + "You specified %d", argc-1); + else { + cmdlineP->depth = atoi(argv[1]); + cmdlineP->maxval = atoi(argv[2]); + if (cmdlineP->depth <= 0) + pm_error("depth argument must be a positive number. You " + "specified '%s'", argv[1]); + if (cmdlineP->maxval <= 0) + pm_error("maxval argument must be a positive number. You " + "specified '%s'", argv[2]); + if (cmdlineP->maxval > PNM_OVERALLMAXVAL) + pm_error("The maxval you specified (%u) is too big. " + "Maximum is %u", (unsigned int) cmdlineP->maxval, + PNM_OVERALLMAXVAL); + if (pm_maxvaltobits(cmdlineP->maxval) + + pm_maxvaltobits(cmdlineP->depth-1) > sizeof(unsigned int)*8) + pm_error("The maxval (%u) and depth (%u) you specified result " + "in a larger number of tuples than this program can " + "handle (roughly %u)", + (unsigned int) cmdlineP->maxval, cmdlineP->depth, + (unsigned int) -1); + } +} + + + +static unsigned int +powint(unsigned int base, unsigned int exponent) { +/*---------------------------------------------------------------------------- + This is standard pow(), but for integers and optimized for small + exponents. +-----------------------------------------------------------------------------*/ + unsigned int result; + unsigned int i; + + result = 1; /* initial value */ + for (i = 0; i < exponent; ++i) + result *= base; + + return(result); +} + + +static void +permuteHigherPlanes(struct pam const pam, int const nextplane, + tuple * const tuplerow, int * const colP, + tuple const lowerPlanes) { +/*---------------------------------------------------------------------------- + Create all the possible permutations of tuples whose lower-numbered planes + contain the values from 'lowerPlanes'. I.e. vary the higher-numbered + planes between zero and maxval. + + Write them sequentially into *tuplerow, starting at *colP. Adjust + *colP to next the column after the ones we write. + + lower-numbered means with plane numbers less than 'nextplane'. + + We modify 'lowerPlanes' in the higher planes to undefined values. +-----------------------------------------------------------------------------*/ + if (nextplane == pam.depth - 1) { + sample value; + for (value = 0; value <= pam.maxval; ++value) { + unsigned int plane; + for (plane = 0; plane < nextplane; ++plane) + tuplerow[*colP][plane] = lowerPlanes[plane]; + tuplerow[*colP][nextplane] = value; + ++(*colP); + } + } else { + sample value; + + for (value = 0; value <= pam.maxval; ++value) { + /* We do something sleazy here and use Caller's lowerPlanes[] + variable as a local variable, modifying it in the higher + plane positions. That's just for speed. + */ + lowerPlanes[nextplane] = value; + + permuteHigherPlanes(pam, nextplane+1, tuplerow, colP, lowerPlanes); + } + } +} + + + +int +main(int argc, char **argv) { + + struct cmdlineInfo cmdline; + struct pam pam; + int col; + tuple lowerPlanes; + /* This is working storage passed to permuteHigherPlanes(), + which we call. Note that because we always pass zero as the + "planes" argument to permuteHigherPlanes(), none of the + "lower planes" value is defined as an input to + permuteHigherPlanes(). + */ + tuple * tuplerow; + + pnm_init(&argc, argv); + + parseCommandLine(argc, argv, &cmdline); + + pam.size = sizeof(pam); + pam.len = PAM_STRUCT_SIZE(tuple_type); + pam.file = stdout; + pam.format = PAM_FORMAT; + pam.plainformat = 0; + pam.width = powint(cmdline.maxval+1, cmdline.depth); + pam.height = 1; + pam.depth = cmdline.depth; + pam.maxval = cmdline.maxval; + strcpy(pam.tuple_type, cmdline.tupletype); + + pnm_writepaminit(&pam); + + tuplerow = pnm_allocpamrow(&pam); + + lowerPlanes = pnm_allocpamtuple(&pam); + + col = 0; + + permuteHigherPlanes(pam, 0, tuplerow, &col, lowerPlanes); + + if (col != pam.width) + pm_error("INTERNAL ERROR: Wrote %d columns; should have written %d.", + col, pam.width); + + pnm_writepamrow(&pam, tuplerow); + + pnm_freepamrow(tuplerow); + + return 0; +} diff --git a/generator/pamstereogram.c b/generator/pamstereogram.c new file mode 100644 index 00000000..e9e58677 --- /dev/null +++ b/generator/pamstereogram.c @@ -0,0 +1,885 @@ +/* ---------------------------------------------------------------------- + * + * Create a single image stereogram from a height map. + * by Scott Pakin <scott+pbm@pakin.org> + * Adapted to Netbpm conventions by Bryan Henderson. + * Revised by Scott Pakin. + * + * The core of this program is a simple adaptation of the code in + * "Displaying 3D Images: Algorithms for Single Image Random Dot + * Stereograms" by Harold W. Thimbleby, Stuart Inglis, and Ian + * H. Witten in IEEE Computer, 27(10):38-48, October 1994. See that + * paper for a thorough explanation of what's going on here. + * + * ---------------------------------------------------------------------- + * + * Copyright (C) 2006 Scott Pakin <scott+pbm@pakin.org> + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * ---------------------------------------------------------------------- + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> +#include <assert.h> + +#include "pam.h" +#include "shhopt.h" +#include "mallocvar.h" + +/* Define a few helper macros. */ +#define round2int(X) ((int)((X)+0.5)) /* Nonnegative numbers only */ + +enum outputType {OUTPUT_BW, OUTPUT_GRAYSCALE, OUTPUT_COLOR}; + +/* ---------------------------------------------------------------------- */ + +struct cmdlineInfo { + /* All the information the user supplied in the command line, + in a form easy for the program to use. + */ + const char * inputFilespec; /* '-' if stdin */ + unsigned int verbose; /* -verbose option */ + unsigned int crosseyed; /* -crosseyed option */ + unsigned int makemask; /* -makemask option */ + unsigned int dpi; /* -dpi option */ + float eyesep; /* -eyesep option */ + float depth; /* -depth option */ + unsigned int maxvalSpec; /* -maxval option count */ + unsigned int maxval; /* -maxval option value x*/ + int guidesize; /* -guidesize option */ + unsigned int magnifypat; /* -magnifypat option */ + unsigned int xshift; /* -xshift option */ + unsigned int yshift; /* -yshift option */ + const char * patFilespec; /* -patfile option. Null if none */ + unsigned int randomseed; /* -randomseed option */ + enum outputType outputType; /* Type of output file */ +}; + + + +static void +parseCommandLine(int argc, + char ** argv, + struct cmdlineInfo *cmdlineP ) { +/*---------------------------------------------------------------------------- + Parse program command line described in Unix standard form by argc + and argv. Return the information in the options as *cmdlineP. + + If command line is internally inconsistent (invalid options, etc.), + issue error message to stderr and abort program. + + Note that the strings we return are stored in the storage that + was passed to us as the argv array. We also trash *argv. +-----------------------------------------------------------------------------*/ + optEntry *option_def; + /* Instructions to optParseOptions3 on how to parse our options. + */ + optStruct3 opt; + + unsigned int option_def_index; + + unsigned int patfileSpec, dpiSpec, eyesepSpec, depthSpec, + guidesizeSpec, magnifypatSpec, xshiftSpec, yshiftSpec, randomseedSpec; + + unsigned int blackandwhite, grayscale, color; + + + MALLOCARRAY_NOFAIL(option_def, 100); + + option_def_index = 0; /* incremented by OPTENT3 */ + OPTENT3(0, "verbose", OPT_FLAG, NULL, + (unsigned int *)&cmdlineP->verbose, 0); + OPTENT3(0, "crosseyed", OPT_FLAG, NULL, + &cmdlineP->crosseyed, 0); + OPTENT3(0, "makemask", OPT_FLAG, NULL, + &cmdlineP->makemask, 0); + OPTENT3(0, "blackandwhite", OPT_FLAG, NULL, + &blackandwhite, 0); + OPTENT3(0, "grayscale", OPT_FLAG, NULL, + &grayscale, 0); + OPTENT3(0, "color", OPT_FLAG, NULL, + &color, 0); + OPTENT3(0, "dpi", OPT_UINT, &cmdlineP->dpi, + &dpiSpec, 0); + OPTENT3(0, "eyesep", OPT_FLOAT, &cmdlineP->eyesep, + &eyesepSpec, 0); + OPTENT3(0, "depth", OPT_FLOAT, &cmdlineP->depth, + &depthSpec, 0); + OPTENT3(0, "maxval", OPT_UINT, &cmdlineP->maxval, + &cmdlineP->maxvalSpec, 0); + OPTENT3(0, "guidesize", OPT_INT, &cmdlineP->guidesize, + &guidesizeSpec, 0); + OPTENT3(0, "magnifypat", OPT_UINT, &cmdlineP->magnifypat, + &magnifypatSpec, 0); + OPTENT3(0, "xshift", OPT_UINT, &cmdlineP->xshift, + &xshiftSpec, 0); + OPTENT3(0, "yshift", OPT_UINT, &cmdlineP->yshift, + &yshiftSpec, 0); + OPTENT3(0, "patfile", OPT_STRING, &cmdlineP->patFilespec, + &patfileSpec, 0); + OPTENT3(0, "randomseed", OPT_UINT, &cmdlineP->randomseed, + &randomseedSpec, 0); + + opt.opt_table = option_def; + opt.short_allowed = FALSE; /* We have no short (old-fashioned) options */ + opt.allowNegNum = FALSE; /* We have no parms that are negative numbers */ + + optParseOptions3( &argc, argv, opt, sizeof(opt), 0); + /* Uses and sets argc, argv, and some of *cmdlineP and others. */ + + + if (blackandwhite + grayscale + color == 0) + cmdlineP->outputType = OUTPUT_BW; + else if (blackandwhite + grayscale + color > 1) + pm_error("You may specify only one of -blackandwhite, -grayscale, " + "and -color"); + else { + if (blackandwhite) + cmdlineP->outputType = OUTPUT_BW; + else if (grayscale) + cmdlineP->outputType = OUTPUT_GRAYSCALE; + else { + assert(color); + cmdlineP->outputType = OUTPUT_COLOR; + } + } + if (!patfileSpec) + cmdlineP->patFilespec = NULL; + + if (!dpiSpec) + cmdlineP->dpi = 96; + else if (cmdlineP->dpi < 1) + pm_error("The argument to -dpi must be a positive integer"); + + if (!eyesepSpec) + cmdlineP->eyesep = 2.5; + else if (cmdlineP->eyesep <= 0.0) + pm_error("The argument to -eyesep must be a positive number"); + + if (!depthSpec) + cmdlineP->depth = (1.0/3.0); + else if (cmdlineP->depth < 0.0 || cmdlineP->depth > 1.0) + pm_error("The argument to -depth must be a number from 0.0 to 1.0"); + + if (cmdlineP->maxvalSpec) { + if (cmdlineP->maxval < 1) + pm_error("-maxval must be at least 1"); + else if (cmdlineP->maxval > PNM_OVERALLMAXVAL) + pm_error("-maxval must be at most %u. You specified %u", + PNM_OVERALLMAXVAL, cmdlineP->maxval); + } + + if (!guidesizeSpec) + cmdlineP->guidesize = 0; + + if (!magnifypatSpec) + cmdlineP->magnifypat = 1; + else if (cmdlineP->magnifypat < 1) + pm_error("The argument to -magnifypat must be a positive integer"); + + if (!xshiftSpec) + cmdlineP->xshift = 0; + + if (!yshiftSpec) + cmdlineP->yshift = 0; + + if (!randomseedSpec) + cmdlineP->randomseed = time(NULL); + + if (xshiftSpec && !cmdlineP->patFilespec) + pm_error("-xshift is valid only with -patfile"); + if (yshiftSpec && !cmdlineP->patFilespec) + pm_error("-yshift is valid only with -patfile"); + + if (cmdlineP->makemask && cmdlineP->patFilespec) + pm_error("You may not specify both -makemask and -patfile"); + + if (cmdlineP->patFilespec && blackandwhite) + pm_error("-blackandwhite is not valid with -patfile"); + if (cmdlineP->patFilespec && grayscale) + pm_error("-grayscale is not valid with -patfile"); + if (cmdlineP->patFilespec && color) + pm_error("-color is not valid with -patfile"); + if (cmdlineP->patFilespec && cmdlineP->maxvalSpec) + pm_error("-maxval is not valid with -patfile"); + + + if (argc-1 < 1) + cmdlineP->inputFilespec = "-"; + else if (argc-1 == 1) + cmdlineP->inputFilespec = argv[1]; + else + pm_error("Too many non-option arguments: %d. Only argument is " + "input file name", argc-1); +} + + + +static int +separation(double const dist, + double const eyesep, + unsigned int const dpi, + double const dof /* depth of field */ + ) { +/*---------------------------------------------------------------------------- + Return a separation in pixels which corresponds to a 3-D distance + between the viewer's eyes and a point on an object. +-----------------------------------------------------------------------------*/ + int const pixelEyesep = round2int(eyesep * dpi); + + return round2int((1.0 - dof * dist) * pixelEyesep / (2.0 - dof * dist)); +} + + + +static void +reportImageParameters(const char * const fileDesc, + const struct pam * const pamP) { + + pm_message("%s: tuple type '%s', %d wide x %d high x %d deep, maxval %lu", + fileDesc, pamP->tuple_type, pamP->width, pamP->height, + pamP->depth, pamP->maxval); +} + + + +/*---------------------------------------------------------------------------- + Background generators +-----------------------------------------------------------------------------*/ + +struct outGenerator; + +typedef tuple coord2Color(struct outGenerator *, int, int); + /* A type to use for functions that map a 2-D coordinate to a color. */ +typedef void outGenStateTerm(struct outGenerator *); + + +typedef struct outGenerator { + struct pam pam; + coord2Color * getTuple; + /* Map from a height-map (x,y) coordinate to a tuple */ + outGenStateTerm * terminateState; + void * stateP; +} outGenerator; + + + +struct randomState { + /* The state of a randomColor generator. */ + unsigned int magnifypat; + tuple * currentRow; + unsigned int prevy; +}; + + +static coord2Color randomColor; + +static tuple +randomColor(outGenerator * const outGenP, + int const x, + int const y) { +/*---------------------------------------------------------------------------- + Return a random RGB value. +-----------------------------------------------------------------------------*/ + struct randomState * const stateP = outGenP->stateP; + + /* Every time we start a new row, we select a new sequence of random + colors. + */ + if (y/stateP->magnifypat != stateP->prevy/stateP->magnifypat) { + unsigned int const modulus = outGenP->pam.maxval + 1; + int col; + + for (col = 0; col < outGenP->pam.width; ++col) { + tuple const thisTuple = stateP->currentRow[col]; + + unsigned int plane; + + for (plane = 0; plane < outGenP->pam.depth; ++plane) { + unsigned int const randval = rand(); + thisTuple[plane] = randval % modulus; + } + } + } + + /* Return the appropriate column from the pregenerated color row. */ + stateP->prevy = y; + return stateP->currentRow[x/stateP->magnifypat]; +} + + + +static outGenStateTerm termRandomColor; + +static void +termRandomColor(outGenerator * const outGenP) { + + struct randomState * const stateP = outGenP->stateP; + + pnm_freepamrow(stateP->currentRow); +} + + + +static void +initRandomColor(outGenerator * const outGenP, + const struct pam * const inPamP, + struct cmdlineInfo const cmdline) { + + struct randomState * stateP; + + outGenP->pam.format = PAM_FORMAT; + outGenP->pam.plainformat = 0; + + switch (cmdline.outputType) { + case OUTPUT_BW: + strcpy(outGenP->pam.tuple_type, PAM_PBM_TUPLETYPE); + outGenP->pam.maxval = 1; + outGenP->pam.depth = 1; + break; + case OUTPUT_GRAYSCALE: + strcpy(outGenP->pam.tuple_type, PAM_PGM_TUPLETYPE); + outGenP->pam.maxval = + cmdline.maxvalSpec ? cmdline.maxval : inPamP->maxval; + outGenP->pam.depth = 1; + break; + case OUTPUT_COLOR: + strcpy(outGenP->pam.tuple_type, PAM_PPM_TUPLETYPE); + outGenP->pam.maxval = + cmdline.maxvalSpec ? cmdline.maxval : inPamP->maxval; + outGenP->pam.depth = 3; + break; + } + + MALLOCVAR_NOFAIL(stateP); + + stateP->currentRow = pnm_allocpamrow(&outGenP->pam); + stateP->magnifypat = cmdline.magnifypat; + stateP->prevy = (unsigned int)(-cmdline.magnifypat); + + outGenP->stateP = stateP; + outGenP->getTuple = &randomColor; + outGenP->terminateState = &termRandomColor; +} + + + +struct patternPixelState { + /* This is the state of a patternPixel generator.*/ + struct pam patPam; /* Descriptor of pattern image */ + tuple ** patTuples; /* Entire image read from the pattern file */ + unsigned int xshift; + unsigned int yshift; + unsigned int magnifypat; +}; + + + +static coord2Color patternPixel; + +static tuple +patternPixel(outGenerator * const outGenP, + int const x, + int const y) { +/*---------------------------------------------------------------------------- + Return a pixel from the pattern file. +-----------------------------------------------------------------------------*/ + struct patternPixelState * const stateP = outGenP->stateP; + struct pam * const patPamP = &stateP->patPam; + + int patx, paty; + + paty = ((y - stateP->yshift) / stateP->magnifypat) % patPamP->height; + + if (paty < 0) + paty += patPamP->height; + + patx = ((x - stateP->xshift) / stateP->magnifypat) % patPamP->width; + + if (patx < 0) + patx += patPamP->width; + + return stateP->patTuples[paty][patx]; +} + + + +static outGenStateTerm termPatternPixel; + +static void +termPatternPixel(outGenerator * const outGenP) { + + struct patternPixelState * const stateP = outGenP->stateP; + + pnm_freepamarray(stateP->patTuples, &stateP->patPam); +} + + + +static void +initPatternPixel(outGenerator * const outGenP, + struct cmdlineInfo const cmdline) { + + struct patternPixelState * stateP; + FILE * patternFileP; + + MALLOCVAR_NOFAIL(stateP); + + patternFileP = pm_openr(cmdline.patFilespec); + + stateP->patTuples = + pnm_readpam(patternFileP, + &stateP->patPam, PAM_STRUCT_SIZE(tuple_type)); + + pm_close(patternFileP); + + stateP->xshift = cmdline.xshift; + stateP->yshift = cmdline.yshift; + stateP->magnifypat = cmdline.magnifypat; + + outGenP->stateP = stateP; + outGenP->getTuple = &patternPixel; + outGenP->terminateState = &termPatternPixel; + outGenP->pam.format = stateP->patPam.format; + outGenP->pam.plainformat = stateP->patPam.plainformat; + outGenP->pam.depth = stateP->patPam.depth; + outGenP->pam.maxval = stateP->patPam.maxval; + + if (cmdline.verbose) + reportImageParameters("Pattern file", &stateP->patPam); +} + + + +static void +createoutputGenerator(struct cmdlineInfo const cmdline, + const struct pam * const inPamP, + outGenerator ** const outputGeneratorPP) { + + outGenerator * outGenP; + + MALLOCVAR_NOFAIL(outGenP); + + outGenP->pam.size = sizeof(struct pam); + outGenP->pam.len = PAM_STRUCT_SIZE(tuple_type); + outGenP->pam.bytes_per_sample = pnm_bytespersample(outGenP->pam.maxval); + outGenP->pam.file = stdout; + outGenP->pam.height = inPamP->height + 3 * abs(cmdline.guidesize); + /* Allow room for guides. */ + outGenP->pam.width = inPamP->width; + + if (cmdline.patFilespec) { + /* Background pixels should come from the pattern file. */ + + initPatternPixel(outGenP, cmdline); + } else { + /* Background pixels should be generated randomly */ + + initRandomColor(outGenP, inPamP, cmdline); + } + + *outputGeneratorPP = outGenP; +} + + + +static void +destroyoutputGenerator(outGenerator * const outputGeneratorP) { + + outputGeneratorP->terminateState(outputGeneratorP); + free(outputGeneratorP); +} + + +/* End of background generators */ + +/* ---------------------------------------------------------------------- */ + + +static void +makeWhiteRow(const struct pam * const pamP, + tuple * const tuplerow) { + + int col; + + for (col = 0; col < pamP->width; ++col) { + unsigned int plane; + for (plane = 0; plane < pamP->depth; ++plane) + tuplerow[col][plane] = pamP->maxval; + } +} + + + +static void +writeRowCopies(const struct pam * const outPamP, + const tuple * const outrow, + unsigned int const copyCount) { + + unsigned int i; + for (i = 0; i < copyCount; ++i) + pnm_writepamrow(outPamP, outrow); +} + + + +/* Draw a pair of guide boxes. */ +static void +drawguides(int const guidesize, + const struct pam * const outPamP, + double const eyesep, + unsigned int const dpi, + double const depthOfField) { + + int const far = separation(0, eyesep, dpi, depthOfField); + /* Space between the two guide boxes. */ + int const width = outPamP->width; /* Width of the output image */ + + tuple *outrow; /* One row of output data */ + tuple blackTuple; + int col; + + pnm_createBlackTuple(outPamP, &blackTuple); + + outrow = pnm_allocpamrow(outPamP); + + /* Leave some blank rows before the guides. */ + makeWhiteRow(outPamP, outrow); + writeRowCopies(outPamP, outrow, guidesize); + + /* Draw the guides. */ + if ((width - far + guidesize)/2 < 0 || + (width + far - guidesize)/2 >= width) + pm_message("warning: the guide boxes are completely out of bounds " + "at %d DPI", dpi); + else if ((width - far - guidesize)/2 < 0 || + (width + far + guidesize)/2 >= width) + pm_message("warning: the guide boxes are partially out of bounds " + "at %d DPI", dpi); + + for (col = (width - far - guidesize)/2; + col < (width - far + guidesize)/2; + ++col) + if (col >= 0 && col < width) + pnm_assigntuple(outPamP, outrow[col], blackTuple); + + for (col = (width + far - guidesize)/2; + col < (width + far + guidesize)/2; + ++col) + if (col >= 0 && col < width) + pnm_assigntuple(outPamP, outrow[col], blackTuple); + + writeRowCopies(outPamP,outrow, guidesize); + + /* Leave some blank rows after the guides. */ + makeWhiteRow(outPamP, outrow); + writeRowCopies(outPamP, outrow, guidesize); + + pnm_freerow(outrow); +} + + + +/* Do the bulk of the work. See the paper cited above for code + * comments. All I (Scott) did was transcribe the code and make + * minimal changes for Netpbm. And some style changes by Bryan to + * match Netpbm style. + */ +static void +makeStereoRow(const struct pam * const inPamP, + tuple * const inRow, + int * const same, + double const depthOfField, + double const eyesep, + unsigned int const dpi) { + +#define Z(X) (1.0-inRow[X][0]/(double)inPamP->maxval) + + int const width = inPamP->width; + int const pixelEyesep = round2int(eyesep * dpi); + /* Separation in pixels between the viewer's eyes */ + + int col; + + for (col = 0; col < width; ++col) + same[col] = col; + + for (col = 0; col < width; ++col) { + int const s = separation(Z(col), eyesep, dpi, depthOfField); + int left, right; + + left = col - s/2; /* initial value */ + right = left + s; /* initial value */ + + if (0 <= left && right < width) { + int visible; + int t; + double zt; + + t = 1; /* initial value */ + + do { + double const dof = depthOfField; + zt = Z(col) + 2.0*(2.0 - dof*Z(col))*t/(dof*pixelEyesep); + visible = Z(col-t) < zt && Z(col+t) < zt; + ++t; + } while (visible && zt < 1); + if (visible) { + int l; + + l = same[left]; + while (l != left && l != right) { + if (l < right) { + left = l; + l = same[left]; + } else { + same[left] = right; + left = right; + l = same[left]; + right = l; + } + } + same[left] = right; + } + } + } +} + + + +static void +makeMaskRow(const struct pam * const outPamP, + const int * const same, + const tuple * const outRow) { + int col; + + for (col = outPamP->width-1; col >= 0; --col) { + bool const duplicate = (same[col] != col); + unsigned int plane; + + for (plane = 0; plane < outPamP->depth; ++plane) + outRow[col][plane] = duplicate ? outPamP->maxval : 0; + } +} + + + +static void +makeImageRow(outGenerator * const outGenP, + int const row, + const int * const same, + const tuple * const outRow) { +/*---------------------------------------------------------------------------- + same[N] is one of two things: + + same[N] == N means to generate a value for Column N independent of + other columns in the row. + + same[N] > N means Column N should be identical to Column same[N]. + + same[N] < N is not allowed. +-----------------------------------------------------------------------------*/ + int col; + for (col = outGenP->pam.width-1; col >= 0; --col) { + bool const duplicate = (same[col] != col); + + tuple newtuple; + unsigned int plane; + + if (duplicate) { + assert(same[col] > col); + assert(same[col] < outGenP->pam.width); + + newtuple = outRow[same[col]]; + } else + newtuple = outGenP->getTuple(outGenP, col, row); + + for (plane = 0; plane < outGenP->pam.depth; ++plane) + outRow[col][plane] = newtuple[plane]; + } +} + + + +static void +invertHeightRow(const struct pam * const heightPamP, + tuple * const tupleRow) { + + int col; + + for (col = 0; col < heightPamP->width; ++col) + tupleRow[col][0] = heightPamP->maxval - tupleRow[col][0]; +} + + + +static void +makeImageRows(const struct pam * const inPamP, + outGenerator * const outputGeneratorP, + double const depthOfField, + double const eyesep, + unsigned int const dpi, + bool const crossEyed, + bool const makeMask) { + + tuple * inRow; /* One row of pixels read from the height-map file */ + tuple * outRow; /* One row of pixels to write to the height-map file */ + int * same; + /* Array: same[N] is the column number of a pixel to the right forced + to have the same color as the one in column N + */ + int row; /* Current row in the input and output files */ + + inRow = pnm_allocpamrow(inPamP); + outRow = pnm_allocpamrow(&outputGeneratorP->pam); + MALLOCARRAY(same, inPamP->width); + if (same == NULL) + pm_error("Unable to allocate space for \"same\" array."); + + /* See the paper cited above for code comments. All I (Scott) did was + * transcribe the code and make minimal changes for Netpbm. And some + * style changes by Bryan to match Netpbm style. + */ + for (row = 0; row < inPamP->height; ++row) { + pnm_readpamrow(inPamP, inRow); + if (crossEyed) + /* Invert heights for cross-eyed (as opposed to wall-eyed) + people. + */ + invertHeightRow(inPamP, inRow); + + /* Determine color constraints. */ + makeStereoRow(inPamP, inRow, same, depthOfField, eyesep, dpi); + + if (makeMask) + makeMaskRow(&outputGeneratorP->pam, same, outRow); + else + makeImageRow(outputGeneratorP, row, same, outRow); + + /* Write the resulting row. */ + pnm_writepamrow(&outputGeneratorP->pam, outRow); + } + free(same); + pnm_freepamrow(outRow); + pnm_freepamrow(inRow); +} + + + +static void +produceStereogram(FILE * const ifP, + struct cmdlineInfo const cmdline) { + + struct pam inPam; /* PAM information for the height-map file */ + outGenerator * outputGeneratorP; + /* Handle of an object that generates background pixels */ + + pnm_readpaminit(ifP, &inPam, PAM_STRUCT_SIZE(tuple_type)); + + createoutputGenerator(cmdline, &inPam, &outputGeneratorP); + + if (cmdline.verbose) { + reportImageParameters("Input (height map) file", &inPam); + if (inPam.depth > 1) + pm_message("Ignoring all but the first plane of input."); + reportImageParameters("Output (stereogram) file", + &outputGeneratorP->pam); + } + + pnm_writepaminit(&outputGeneratorP->pam); + + /* Draw guide boxes at the top, if desired. */ + if (cmdline.guidesize < 0) + drawguides(-cmdline.guidesize, &outputGeneratorP->pam, + cmdline.eyesep, + cmdline.dpi, cmdline.depth); + + makeImageRows(&inPam, outputGeneratorP, + cmdline.depth, cmdline.eyesep, cmdline.dpi, + cmdline.crosseyed, cmdline.makemask); + + /* Draw guide boxes at the bottom, if desired. */ + if (cmdline.guidesize > 0) + drawguides(cmdline.guidesize, &outputGeneratorP->pam, + cmdline.eyesep, cmdline.dpi, cmdline.depth); + + destroyoutputGenerator(outputGeneratorP); +} + + + +static void +reportParameters(struct cmdlineInfo const cmdline) { + + unsigned int const pixelEyesep = round2int(cmdline.eyesep * cmdline.dpi); + + pm_message("Eye separation: %.4g inch * %d DPI = %u pixels", + cmdline.eyesep, cmdline.dpi, pixelEyesep); + + if (cmdline.magnifypat > 1) + pm_message("Background magnification: %uX * %uX", + cmdline.magnifypat, cmdline.magnifypat); + pm_message("\"Optimal\" pattern width: %u / (%u * 2) = %u pixels", + pixelEyesep, cmdline.magnifypat, + pixelEyesep/(cmdline.magnifypat * 2)); + pm_message("Unique 3-D depth levels possible: %u", + separation(0, cmdline.eyesep, cmdline.dpi, cmdline.depth) - + separation(1, cmdline.eyesep, cmdline.dpi, cmdline.depth) + 1); + if (cmdline.patFilespec && (cmdline.xshift || cmdline.yshift)) + pm_message("Pattern shift: (%u, %u)", cmdline.xshift, cmdline.yshift); +} + + + +int +main(int argc, char *argv[]) { + + struct cmdlineInfo cmdline; /* Parsed command line */ + FILE * ifP; + + /* Parse the command line. */ + pnm_init(&argc, argv); + + parseCommandLine(argc, argv, &cmdline); + + if (cmdline.verbose) + reportParameters(cmdline); + + srand(cmdline.randomseed); + + ifP = pm_openr(cmdline.inputFilespec); + + /* Produce a stereogram. */ + produceStereogram(ifP, cmdline); + + pm_close(ifP); + + return 0; +} diff --git a/generator/pamstereogram.test b/generator/pamstereogram.test new file mode 100644 index 00000000..7eb01fff --- /dev/null +++ b/generator/pamstereogram.test @@ -0,0 +1,70 @@ +# Make some input files +pamdepth -quiet 255 ../testgrid.pbm >/tmp/testgrid.pgm + + +# Random pattern + +echo Test 01. Should print 610673698 293: +./pamstereogram -randomseed=1 ../testgrid.pbm | cksum +echo Test 02. Should print 610673698 293: +./pamstereogram -randomseed=1 -blackandwhite ../testgrid.pbm | cksum +echo Test 03. Should print 3439084201 170: +pamseq -tupletype=GRAYSCALE 1 100 | ./pamstereogram -randomseed=1 | cksum +echo Test 04. Should print 2484923390 1070: +pamgauss 100 10 -maxval=10000 -sigma 20 | pamfunc -multiplier=500 | \ + ./pamstereogram -randomseed=1 -dpi=10 | cksum + +# Makemask + +echo Test 10. Should print 1266273778 293: +./pamstereogram -randomseed=1 -makemask ../testgrid.pbm | cksum + +echo Test 11. Should print 3034751595 1070: +./pamgauss 100 10 -maxval=10000 -sigma 20 | pamfunc -multiplier=500 | \ + ./pamstereogram -randomseed=1 -dpi=10 -makemask | cksum + +# Grayscale + +echo Test 20. Should print 2468969328 289: +./pamstereogram -randomseed=1 -grayscale ../testgrid.pbm | cksum +echo Test 21. Should print 1946982115 4068: +./pamseq 1 100 | pnmtile 200 20 | \ + ./pamstereogram -randomseed=1 -dpi=10 -grayscale | \ + cksum +echo Test 22. Should print 2078013430 4068: +./pamseq 1 100 | pnmtile 200 20 | \ + ./pamstereogram -randomseed=1 -dpi=10 -grayscale -maxval 255 | \ + cksum + +# Color + +echo Test 30. Should print 1319392622 731: +./pamstereogram -randomseed=1 -color ../testgrid.pbm | cksum +echo Test 31. Should print 389886159 12062: +./pamseq 1 100 | pnmtile 200 20 | \ + ./pamstereogram -randomseed=1 -dpi=10 -color | \ + cksum + +# Pattern file + +echo Test 40. Should print 1834916830 660: +pamgradient black gray50 white gray50 100 50 | \ + ./pamstereogram -patfile ../testgrid.pbm -eyesep=.1 -crosseyed | cksum + +echo Test 41. Should print 4016818756 5014: +pamgradient black gray50 white gray50 100 50 | \ + ./pamstereogram -patfile /tmp/testgrid.pgm -eyesep=.1 -crosseyed | cksum + +# drawguides + +echo Test 51. Should print 2365956562 11071: +pamgradient black gray50 white gray50 100 50 | \ + ./pamstereogram -randomseed=1 -dpi 10 -guidesize=20 | cksum + +echo Test 51. Should print 3502025270 1441: +pamgradient black gray50 white gray50 100 50 | \ + ./pamstereogram -patfile=../testgrid.pbm -dpi 10 -guidesize=20 | cksum + + +# Clean up files we created +rm /tmp/testgrid.pgm diff --git a/generator/pbmmake.c b/generator/pbmmake.c new file mode 100644 index 00000000..afe1dac3 --- /dev/null +++ b/generator/pbmmake.c @@ -0,0 +1,184 @@ +/* pbmmake.c - create a blank bitmap of a specified size + * + * Akira Urushibata ("Douso") wrote some of the core code that went + * into the Netpbm 10.23 (July 2004) version of this program and licenses + * that code to the public under GPL. + * + * Bryan Henderson wrote the rest of that version and contributed his + * work to the public domain. + * + * See doc/HISTORY for a full history of this program. +** +*/ + +#include "shhopt.h" +#include "mallocvar.h" +#include "pbm.h" + +enum color {COLOR_BLACK, COLOR_WHITE, COLOR_GRAY}; + +struct cmdlineInfo { + /* All the information the user supplied in the command line, + in a form easy for the program to use. + */ + unsigned int width; + unsigned int height; + enum color color; +}; + + + +static void +parseCommandLine(int argc, 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 optParseOptions3 on how to parse our options. + */ + optStruct3 opt; + + unsigned int option_def_index; + unsigned int blackOpt, whiteOpt, grayOpt; + + MALLOCARRAY_NOFAIL(option_def, 100); + + option_def_index = 0; /* incremented by OPTENTRY */ + OPTENT3(0, "black", OPT_FLAG, NULL, &blackOpt, 0); + OPTENT3(0, "white", OPT_FLAG, NULL, &whiteOpt, 0); + OPTENT3(0, "gray", OPT_FLAG, NULL, &grayOpt, 0); + OPTENT3(0, "grey", OPT_FLAG, NULL, &grayOpt, 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 */ + + optParseOptions3(&argc, argv, opt, sizeof(opt), 0); + /* Uses and sets argc, argv, and some of *cmdlineP and others. */ + + if (blackOpt + whiteOpt + grayOpt > 1) + pm_error("You can specify only one of -black, -white, and -gray"); + + if (blackOpt) + cmdlineP->color = COLOR_BLACK; + else if (whiteOpt) + cmdlineP->color = COLOR_WHITE; + else if (grayOpt) + cmdlineP->color = COLOR_GRAY; + else + cmdlineP->color = COLOR_WHITE; + + if (argc-1 != 2) + pm_error("Wrong number of arguments (%d). There are two " + "non-option arguments: width and height in pixels", + argc-1); + else { + cmdlineP->width = atoi(argv[1]); + cmdlineP->height = atoi(argv[2]); + + if (cmdlineP->width < 1) + pm_error("Width must be positive. You specified %d.", + cmdlineP->width); + + if (cmdlineP->height < 1) + pm_error("Height must be positive. You specified %d.", + cmdlineP->height); + } +} + + + +static void +writeGrayRaster(unsigned int const cols, + unsigned int const rows, + FILE * const ofP) { + + unsigned int const lastCol = (cols-1)/8; + + unsigned char * bitrow0, * bitrow1; + unsigned int i; + + bitrow0 = pbm_allocrow_packed(cols); + bitrow1 = pbm_allocrow_packed(cols); + + for (i=0; i <= lastCol; ++i) { + bitrow0[i] = (PBM_WHITE*0xaa) | (PBM_BLACK*0x55); + bitrow1[i] = (PBM_WHITE*0x55) | (PBM_BLACK*0xaa); + /* 0xaa = 10101010 ; 0x55 = 01010101 */ + } + if (cols % 8 > 0) { + bitrow0[lastCol] >>= 8 - cols % 8; + bitrow0[lastCol] <<= 8 - cols % 8; + bitrow1[lastCol] >>= 8 - cols % 8; + bitrow1[lastCol] <<= 8 - cols % 8; + } + if (rows > 1) { + unsigned int row; + for (row = 1; row < rows; row += 2) { + pbm_writepbmrow_packed(ofP, bitrow0, cols, 0); + pbm_writepbmrow_packed(ofP, bitrow1, cols, 0); + } + } + if (rows % 2 == 1) + pbm_writepbmrow_packed(ofP, bitrow0, cols, 0); + + pbm_freerow(bitrow0); + pbm_freerow(bitrow1); +} + + + +static void +writeSingleColorRaster(unsigned int const cols, + unsigned int const rows, + bit const color, + FILE * const ofP) { + + unsigned int const lastCol = (cols-1)/8; + + unsigned char * bitrow0; + unsigned int i; + + bitrow0 = pbm_allocrow_packed(cols); + + for (i = 0; i <= lastCol; ++i) + bitrow0[i] = color*0xff; + + if (cols % 8 > 0) { + bitrow0[lastCol] >>= 8 - cols % 8; + bitrow0[lastCol] <<= 8 - cols % 8; + /* row end trimming, really not necessary with white */ + } + { + unsigned int row; + for (row = 0; row < rows; ++row) + pbm_writepbmrow_packed(ofP, bitrow0, cols, 0); + } + pbm_freerow(bitrow0); +} + + + +int +main(int argc, char * argv[]) { + + struct cmdlineInfo cmdline; + + pbm_init(&argc, argv); + + parseCommandLine(argc, argv, &cmdline); + + pbm_writepbminit(stdout, cmdline.width, cmdline.height, 0); + + if (cmdline.color == COLOR_GRAY) + writeGrayRaster(cmdline.width, cmdline.height, stdout); + else { + bit const color = cmdline.color == COLOR_WHITE ? PBM_WHITE : PBM_BLACK; + writeSingleColorRaster(cmdline.width, cmdline.height, color, stdout); + } + pm_close(stdout); + + return 0; +} diff --git a/generator/pbmmake.test b/generator/pbmmake.test new file mode 100644 index 00000000..0fd99ccd --- /dev/null +++ b/generator/pbmmake.test @@ -0,0 +1,9 @@ +echo Test 1. Should print 3892756435 12 +./pbmmake -white 16 2 | cksum +echo Test 2. Should print 1576602925 8 +./pbmmake -black 1 1 | cksum +echo Test 3. Should print 4272952448 14 +./pbmmake -gray 7 7 | cksum +echo Test 4. Should print 1634688086 15 +./pbmmake -grey 8 8 | cksum +echo Tests done. diff --git a/generator/pbmpage.c b/generator/pbmpage.c new file mode 100644 index 00000000..e10ee6d6 --- /dev/null +++ b/generator/pbmpage.c @@ -0,0 +1,291 @@ +/*************************************************************************** + pbmpage + + This program produces a printed page test pattern in PBM format. + + This was adapted from Tim Norman's 'pbmtpg' program, part of his + 'pbm2ppa' package, by Bryan Henderson on 2000.05.01. The only + change was to make it use the Netpbm libraries to generate the + output. + + For copyright and licensing information, see the pbmtoppa program, + which was also derived from the same package. +****************************************************************************/ + +#include <stdlib.h> +#include <string.h> +#include <math.h> +#include <stdio.h> + +#include "pbm.h" + +/* US is 8.5 in by 11 in */ + +#define USWIDTH (5100) +#define USHEIGHT (6600) + +/* A4 is 210 mm by 297 mm == 8.27 in by 11.69 in */ + +#define A4WIDTH (4960) +#define A4HEIGHT (7016) + + +struct bitmap { + int Width; /* width and height in 600ths of an inch */ + int Height; + int Pwidth; /* width in bytes */ + char *bitmap; +}; + +static struct bitmap bitmap; + + + +static void +setpixel(int const x, + int const y, + int const c) { + + int const charidx = y * bitmap.Pwidth + x/8; + char const bitmask = 128 >> (x % 8); + + if (x < 0 || x >= bitmap.Width) + return; + if (y < 0 || y >= bitmap.Height) + return; + + if (c) + bitmap.bitmap[charidx] |= bitmask; + else + bitmap.bitmap[charidx] &= ~bitmask; +} + + + +static void +setplus(int x,int y,int s) +/*---------------------------------------------------------------------------- + Draw a black plus sign centered at (x,y) with arms 's' pixels long. + Leave the exact center of the plus white. +-----------------------------------------------------------------------------*/ +{ + int i; + + for(i=0; i<s; i++) + { + setpixel(x+i,y,1); + setpixel(x-i,y,1); + setpixel(x,y+i,1); + setpixel(x,y-i,1); + } +} + + + +static void +setblock(int x,int y,int s) +{ + int i,j; + + for(i=0; i<s; i++) + for(j=0; j<s; j++) + setpixel(x+i,y+j,1); +} + + + +static void +setchar(int x,int y,char c) +{ + int xo,yo; + static char charmap[10][5]= { { 0x3e, 0x41, 0x41, 0x41, 0x3e }, + { 0x00, 0x42, 0x7f, 0x40, 0x00 }, + { 0x42, 0x61, 0x51, 0x49, 0x46 }, + { 0x22, 0x41, 0x49, 0x49, 0x36 }, + { 0x18, 0x14, 0x12, 0x7f, 0x10 }, + { 0x27, 0x45, 0x45, 0x45, 0x39 }, + { 0x3e, 0x49, 0x49, 0x49, 0x32 }, + { 0x01, 0x01, 0x61, 0x19, 0x07 }, + { 0x36, 0x49, 0x49, 0x49, 0x36 }, + { 0x26, 0x49, 0x49, 0x49, 0x3e } }; + + if(c<='9' && c>='0') + for(xo=0; xo<5; xo++) + for(yo=0; yo<8; yo++) + if((charmap[c-'0'][xo]>>yo)&1) + setblock(x+xo*3,y+yo*3,3); +} + + + +static void +setstring(int x,int y,char* s) +{ + char* p; + int xo; + + for(xo=0, p=s; *p; xo+=21, p++) + setchar(x+xo,y,*p); +} + + + +static void +setCG(int x,int y) +{ + int xo,yo,zo; + + for(xo=0; xo<=50; xo++) + { + yo=sqrt(50.0*50.0-xo*xo); + setpixel(x+xo,y+yo,1); + setpixel(x+yo,y+xo,1); + setpixel(x-1-xo,y-1-yo,1); + setpixel(x-1-yo,y-1-xo,1); + setpixel(x+xo,y-1-yo,1); + setpixel(x-1-xo,y+yo,1); + for(zo=0; zo<yo; zo++) + { + setpixel(x+xo,y-1-zo,1); + setpixel(x-1-xo,y+zo,1); + } + } +} + + + +static void +outputPbm(FILE * const file, + struct bitmap const bitmap) { +/*---------------------------------------------------------------------------- + Create a pbm file containing the image from the global variable bitmap[]. +-----------------------------------------------------------------------------*/ + int const forceplain = 0; + bit *pbmrow; + int row; + int bitmap_cursor; + + pbm_writepbminit(file, bitmap.Width, bitmap.Height, forceplain); + + /* We round the allocated row space up to a multiple of 8 so the ugly + fast code below can work. + */ + pbmrow = pbm_allocrow(((bitmap.Width+7)/8)*8); + + bitmap_cursor = 0; + for (row = 0; row < bitmap.Height; row++) { + int col; + for (col = 0; col < bitmap.Width;) { + /* A little ugliness makes a big speed difference here. */ + pbmrow[col++] = bitmap.bitmap[bitmap_cursor] & (1<<7); + pbmrow[col++] = bitmap.bitmap[bitmap_cursor] & (1<<6); + pbmrow[col++] = bitmap.bitmap[bitmap_cursor] & (1<<5); + pbmrow[col++] = bitmap.bitmap[bitmap_cursor] & (1<<4); + pbmrow[col++] = bitmap.bitmap[bitmap_cursor] & (1<<3); + pbmrow[col++] = bitmap.bitmap[bitmap_cursor] & (1<<2); + pbmrow[col++] = bitmap.bitmap[bitmap_cursor] & (1<<1); + pbmrow[col++] = bitmap.bitmap[bitmap_cursor] & (1<<0); + + bitmap_cursor++; + } + pbm_writepbmrow(file, pbmrow, bitmap.Width, forceplain); + } + pbm_freerow(pbmrow); + pm_close(file); +} + + +static void +framePerimeter(unsigned int const Width, + unsigned int const Height) { + + unsigned int x,y; + + /* Top edge */ + for (x = 0; x < Width; ++x) + setpixel(x, 0, 1); + + /* Bottom edge */ + for (x = 0; x < Width; ++x) + setpixel(x, Height-1, 1); + + /* Left edge */ + for (y = 0; y < Height; ++y) + setpixel(0, y, 1); + + /* Right edge */ + for (y = 0; y < Height; ++y) + setpixel(Width-1, y, 1); +} + + + +int +main(int argc,char** argv) { + + int TP=1; + int x,y; + char buf[128]; + int Width; /* width and height in 600ths of an inch */ + int Height; + + pbm_init(&argc, argv); + + if (argc > 1 && strcmp(argv[1], "-a4") == 0) { + Width = A4WIDTH; + Height = A4HEIGHT; + argc--; + argv++; + } else { + Width = USWIDTH; + Height = USHEIGHT; + } + + bitmap.Width = Width; + bitmap.Height = Height; + bitmap.Pwidth = (Width + 7) / 8; + bitmap.bitmap = malloc(bitmap.Pwidth * bitmap.Height); + + if (argc>1) + TP = atoi(argv[1]); + + switch (TP) { + case 1: + framePerimeter(Width, Height); + for (x = 0; x < Width; x += 100) + for(y = 0; y < Height; y += 100) + setplus(x, y, 4); + for(x = 0; x < Width; x += 100) { + sprintf(buf,"%d", x); + setstring(x + 3, (Height/200) * 100 + 3, buf); + } + for (y=0; y < Height; y += 100) { + sprintf(buf, "%d", y); + setstring((Width/200) * 100 + 3, y + 3, buf); + } + for (x = 0; x < Width; x += 10) + for (y = 0; y < Height; y += 100) + setplus(x, y, ((x%100) == 50) ? 2 : 1); + for (x=0; x < Width; x += 100) + for (y = 0; y < Height; y += 10) + setplus(x, y, ((y%100) == 50) ? 2 : 1); + setCG(Width/2, Height/2); + break; + case 2: + for (y = 0; y < 300; ++y) + setpixel(Width/2, Height/2-y, 1); + break; + case 3: + for (y = 0; y < 300; ++y) { + setpixel(y, y, 1); + setpixel(Width-1-y, Height-1-y, 1); + } + break; + default: + pm_error("unknown test pattern (%d)", TP); + } + + outputPbm(stdout, bitmap); + + return 0; +} diff --git a/generator/pbmtext.c b/generator/pbmtext.c new file mode 100644 index 00000000..e1d132d0 --- /dev/null +++ b/generator/pbmtext.c @@ -0,0 +1,732 @@ +/* pbmtext.c - render text into a bitmap +** +** Copyright (C) 1991 by Jef Poskanzer. +** +** 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. +*/ + +#define _BSD_SOURCE 1 /* Make sure strdup() is in string.h */ +#define _XOPEN_SOURCE 500 /* Make sure strdup() is in string.h */ + +#include <string.h> + +#include "pbm.h" +#include "pbmfont.h" +#include "shhopt.h" +#include "mallocvar.h" + +struct cmdline_info { + /* All the information the user supplied in the command line, + in a form easy for the program to use. + */ + const char *text; /* text from command line or NULL if none */ + const char *font; /* -font option value or NULL if none */ + const char *builtin; /* -builtin option value or NULL if none */ + unsigned int dump; + /* undocumented dump option for installing a new built-in font */ + float space; /* -space option value or default */ + unsigned int width; /* -width option value or zero */ + int lspace; /* lspace option value or default */ + unsigned int nomargins; /* -nomargins */ +}; + + + + +static void +parse_command_line(int argc, char ** argv, + struct cmdline_info *cmdline_p) { +/*---------------------------------------------------------------------------- + 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 = malloc(100*sizeof(optEntry)); + /* Instructions to OptParseOptions3 on how to parse our options. + */ + optStruct3 opt; + + unsigned int option_def_index; + + option_def_index = 0; /* incremented by OPTENTRY */ + OPTENT3(0, "font", OPT_STRING, &cmdline_p->font, NULL, 0); + OPTENT3(0, "builtin", OPT_STRING, &cmdline_p->builtin, NULL, 0); + OPTENT3(0, "dump", OPT_FLAG, NULL, &cmdline_p->dump, 0); + OPTENT3(0, "space", OPT_FLOAT, &cmdline_p->space, NULL, 0); + OPTENT3(0, "width", OPT_UINT, &cmdline_p->width, NULL, 0); + OPTENT3(0, "lspace", OPT_INT, &cmdline_p->lspace, NULL, 0); + OPTENT3(0, "nomargins", OPT_FLAG, NULL, &cmdline_p->nomargins, 0); + + /* Set the defaults */ + cmdline_p->font = NULL; + cmdline_p->builtin = NULL; + cmdline_p->space = 0.0; + cmdline_p->width = 0; + cmdline_p->lspace = 0; + + opt.opt_table = option_def; + opt.short_allowed = FALSE; /* We have no short (old-fashioned) options */ + opt.allowNegNum = FALSE; /* We have no parms that are negative numbers */ + + optParseOptions3(&argc, argv, opt, sizeof(opt), 0); + /* Uses and sets argc, argv, and some of *cmdline_p and others. */ + + if (argc-1 == 0) + cmdline_p->text = NULL; + else { + char *text; + int i; + int totaltextsize; + + totaltextsize = 1; /* initial value */ + + text = malloc(totaltextsize); /* initial allocation */ + text[0] = '\0'; + + for (i = 1; i < argc; i++) { + if (i > 1) { + totaltextsize += 1; + text = realloc(text, totaltextsize); + if (text == NULL) + pm_error("out of memory allocating space for input text"); + strcat(text, " "); + } + totaltextsize += strlen(argv[i]); + text = realloc(text, totaltextsize); + if (text == NULL) + pm_error("out of memory allocating space for input text"); + strcat(text, argv[i]); + } + cmdline_p->text = text; + } +} + + +struct text { + char ** textArray; /* malloc'ed */ + unsigned int allocatedLineCount; + unsigned int lineCount; +}; + + +static void +allocTextArray(struct text * const textP, + unsigned int const maxLineCount, + unsigned int const maxColumnCount) { + + unsigned int line; + + textP->allocatedLineCount = maxLineCount; + + MALLOCARRAY_NOFAIL(textP->textArray, maxLineCount); + + for (line = 0; line < maxLineCount; ++line) + MALLOCARRAY_NOFAIL(textP->textArray[line], maxColumnCount+1); + + textP->lineCount = 0; +} + + +static void +freeTextArray(struct text const text) { + + unsigned int line; + + for (line = 0; line < text.allocatedLineCount; ++line) + free((char **)text.textArray[line]); + + free(text.textArray); +} + + + +static void +fix_control_chars(char * const buf, struct font * const fn) { + + int i; + + /* chop off terminating newline */ + if (strlen(buf) >= 1 && buf[strlen(buf)-1] == '\n') + buf[strlen(buf)-1] = '\0'; + + for ( i = 0; buf[i] != '\0'; ++i ) { + if ( buf[i] == '\t' ) { + /* Turn tabs into the right number of spaces. */ + int j, n, l; + n = ( i + 8 ) / 8 * 8; + l = strlen( buf ); + for ( j = l; j > i; --j ) + buf[j + n - i - 1] = buf[j]; + for ( ; i < n; ++i ) + buf[i] = ' '; + --i; + } + else if ( !fn->glyph[(unsigned char)buf[i]] ) + /* Turn unknown chars into a single space. */ + buf[i] = ' '; + } +} + + + +static void +fill_rect(bit** const bits, + int const row0, + int const col0, + int const height, + int const width, + bit const color) { + + int row; + + for (row = row0; row < row0 + height; ++row) { + int col; + for (col = col0; col < col0 + width; ++col) + bits[row][col] = color; + } +} + + + +static void +get_line_dimensions(const char line[], const struct font * const font_p, + const float intercharacter_space, + int * const bwid_p, int * const backup_space_needed_p) { +/*---------------------------------------------------------------------------- + Determine the width in pixels of the line of text line[] in the font + *font_p, and return it as *bwid_p. Also determine how much of this + width goes to the left of the nominal starting point of the line because + the first character in the line has a "backup" distance. Return that + as *backup_space_needed_p. +-----------------------------------------------------------------------------*/ + int cursor; /* cursor into the line of text */ + float accumulated_ics; + /* accumulated intercharacter space so far in the line we are + stepping through. Because the intercharacter space might not be + an integer, we accumulate it here and realize full pixels whenever + we have more than one pixel. + */ + + int no_chars_yet; + /* logical: we haven't seen any renderable characters yet in + the line. + */ + struct glyph * lastGlyphP; + /* Glyph of last character processed so far. Undefined if + 'no_chars_yet'. + */ + + no_chars_yet = TRUE; /* initial value */ + *bwid_p = 0; /* initial value */ + accumulated_ics = 0.0; /* initial value */ + + for (cursor = 0; line[cursor] != '\0'; cursor++) { + struct glyph * const glyphP = + font_p->glyph[(unsigned char)line[cursor]]; + + if (glyphP) { + if (no_chars_yet) { + no_chars_yet = FALSE; + if (glyphP->x < 0) + *backup_space_needed_p = -glyphP->x; + else { + *backup_space_needed_p = 0; + *bwid_p += glyphP->x; + } + } else { + /* handle extra intercharacter space (-space option) */ + int full_pixels; /* integer part of accumulated_ics */ + accumulated_ics += intercharacter_space; + full_pixels = (int) accumulated_ics; + if (full_pixels > 0) { + *bwid_p += full_pixels; + accumulated_ics -= full_pixels; + } + lastGlyphP = glyphP; + } + *bwid_p += glyphP->xadd; + } + } + if (no_chars_yet) + /* Line has no renderable characters */ + *backup_space_needed_p = 0; + else { + /* Line has at least one renderable character. + Recalculate width of last character in line so it ends + right at the right edge of the glyph (no extra space to + anticipate another character). + */ + *bwid_p -= lastGlyphP->xadd; + *bwid_p += lastGlyphP->width + lastGlyphP->x; + } +} + + + +static void +insert_character(const struct glyph * const glyph, + int const toprow, + int const leftcol, + bit ** const bits) { +/*---------------------------------------------------------------------------- + Insert one character (whose glyph is 'glyph') into the image bits[]. + Its top left corner shall be row 'toprow', column 'leftcol'. +-----------------------------------------------------------------------------*/ + + int glyph_y, glyph_x; /* position within the glyph */ + + for (glyph_y = 0; glyph_y < glyph->height; glyph_y++) { + for (glyph_x = 0; glyph_x < glyph->width; glyph_x++) { + if (glyph->bmap[glyph_y * glyph->width + glyph_x]) + bits[toprow+glyph_y][leftcol+glyph->x+glyph_x] = + PBM_BLACK; + } + } +} + + + +static void +insert_characters(bit ** const bits, + struct text const lp, + struct font * const fontP, + int const topmargin, + int const leftmargin, + float const intercharacter_space, + int const lspace) { +/*---------------------------------------------------------------------------- + Render the text 'lp' into the image 'bits' using font *fontP and + putting 'intercharacter_space' pixels between characters and + 'lspace' pixels between the lines. +-----------------------------------------------------------------------------*/ + int line; /* Line number in input text */ + + for (line = 0; line < lp.lineCount; ++line) { + int row; /* row in image of top of current typeline */ + int leftcol; /* Column in image of left edge of current glyph */ + int cursor; /* cursor into a line of input text */ + float accumulated_ics; + /* accumulated intercharacter space so far in the line we + are building. Because the intercharacter space might + not be an integer, we accumulate it here and realize + full pixels whenever we have more than one pixel. + */ + + row = topmargin + line * (fontP->maxheight + lspace); + leftcol = leftmargin; + accumulated_ics = 0.0; /* initial value */ + + for (cursor = 0; lp.textArray[line][cursor] != '\0'; ++cursor) { + unsigned int const glyphIndex = + (unsigned char)lp.textArray[line][cursor]; + struct glyph* glyph; /* the glyph for this character */ + + glyph = fontP->glyph[glyphIndex]; + if (glyph != NULL) { + const int toprow = row + fontP->maxheight + fontP->y + - glyph->height - glyph->y; + /* row number in image of top row in glyph */ + + insert_character(glyph, toprow, leftcol, bits); + + leftcol += glyph->xadd; + { + /* handle extra intercharacter space (-space option) */ + int full_pixels; /* integer part of accumulated_ics */ + accumulated_ics += intercharacter_space; + full_pixels = (int) accumulated_ics; + if (full_pixels > 0) { + leftcol += full_pixels; + accumulated_ics -= full_pixels; + } + } + } + } + } +} + + +struct outputTextCursor { + struct text text; + /* The output text. The lineCount field of this represents + the number of lines we have completed. The line after that + is the one we are currently filling. + */ + unsigned int maxWidth; + /* A line of output can't be wider than this many pixels */ + float intercharacterSpace; + /* The amount of extra space, in characters, that should be added + between every two characters (Pbmtext -space option) + */ + unsigned int columnNo; + /* The column Number (starting at 0) in the current line that we are + filling where the next character goes. + */ + bool noCharsYet; + /* We haven't put any renderable characters yet in the + output line. + */ + unsigned int widthSoFar; + /* The accumulated width, in pixels, of all the characters now + in the current output line + */ + float accumulatedIcs; + /* accumulated intercharacter space so far in the line we + are stepping through. Because the intercharacter space + might not be an integer, we accumulate it here and + realize full pixels whenever we have more than one + pixel. + */ +}; + + + +static void +initializeFlowedOutputLine(struct outputTextCursor * const cursorP) { + + cursorP->columnNo = 0; + cursorP->noCharsYet = TRUE; + cursorP->widthSoFar = 0.0; + cursorP->accumulatedIcs = 0.0; +} + + + +static void +initializeFlowedOutput(struct outputTextCursor * const cursorP, + unsigned int const maxLines, + unsigned int const maxWidth, + float const intercharacterSpace) { + + allocTextArray(&cursorP->text, maxLines, maxWidth); + cursorP->maxWidth = maxWidth; + cursorP->intercharacterSpace = intercharacterSpace; + initializeFlowedOutputLine(cursorP); +} + + + +static void +finishOutputLine(struct outputTextCursor * const cursorP) { + + if (cursorP->text.lineCount < cursorP->text.allocatedLineCount) { + char * const currentLine = + cursorP->text.textArray[cursorP->text.lineCount]; + currentLine[cursorP->columnNo++] = '\0'; + ++cursorP->text.lineCount; + } +} + + + +static void +placeCharacterInOutput(char const lastch, + struct font * const fontP, + struct outputTextCursor * const cursorP) { +/*---------------------------------------------------------------------------- + Place a character of text in the text array at the position indicated + by *cursorP, keeping track of what space this character will occupy + when this text array is ultimately rendered using font *fontP. + + Note that while we compute how much space the character will take when + rendered, we don't render it. +-----------------------------------------------------------------------------*/ + if (cursorP->text.lineCount < cursorP->text.allocatedLineCount) { + unsigned int const glyphIndex = (unsigned char)lastch; + if (fontP->glyph[glyphIndex]) { + if (cursorP->noCharsYet) { + cursorP->noCharsYet = FALSE; + if (fontP->glyph[glyphIndex]->x > 0) + cursorP->widthSoFar += fontP->glyph[glyphIndex]->x; + } else { + /* handle extra intercharacter space (-space option) */ + unsigned int fullPixels; /* integer part of accumulatedIcs */ + cursorP->accumulatedIcs += cursorP->intercharacterSpace; + fullPixels = (int) cursorP->accumulatedIcs; + if (fullPixels > 0) { + cursorP->widthSoFar += fullPixels; + cursorP->accumulatedIcs -= fullPixels; + } + } + cursorP->widthSoFar += fontP->glyph[glyphIndex]->xadd; + } + if (cursorP->widthSoFar < cursorP->maxWidth) { + char * const currentLine = + cursorP->text.textArray[cursorP->text.lineCount]; + currentLine[cursorP->columnNo++] = lastch; + } else { + /* Line is full; finish it off, start the next one, and + place the character there. + */ + /* TODO: We really should back up to the previous white space + character and move the rest of the line to the next line + */ + finishOutputLine(cursorP); + initializeFlowedOutputLine(cursorP); + placeCharacterInOutput(lastch, fontP, cursorP); + } + } +} + + + +static void +flowText(struct text const inputText, + int const width, + struct font * const fontP, + float const intercharacterSpace, + struct text * const outputTextP) { + + unsigned int const maxLineCount = 50; + + unsigned int inputLine; + /* Input line number on which we are currently working */ + struct outputTextCursor outputCursor; + + for (inputLine = 0; inputLine < inputText.lineCount; ++inputLine) { + unsigned int incursor; /* cursor into the line we are reading */ + + initializeFlowedOutput(&outputCursor, maxLineCount, + width, intercharacterSpace); + + for (incursor = 0; + inputText.textArray[inputLine][incursor] != '\0'; + ++incursor) + placeCharacterInOutput(inputText.textArray[inputLine][incursor], + fontP, &outputCursor); + finishOutputLine(&outputCursor); + } + *outputTextP = outputCursor.text; +} + + + +static void +truncateText(struct text const inputText, + unsigned int const width, + struct font * const fontP, + float const intercharacterSpace, + struct text * const outputTextP) { + + struct text truncatedText; + int line; /* Line number on which we are currently working */ + + allocTextArray(&truncatedText, inputText.lineCount, width); + + for (line = 0; line < inputText.lineCount; ++line){ + int cursor; /* cursor into the line of text */ + unsigned char lastch; /* line[cursor] */ + int widthSoFar; + /* How long the line we've built, in pixels, is so far */ + float accumulatedIcs; + /* accumulated intercharacter space so far in the line we are + stepping through. Because the intercharacter space might not be + an integer, we accumulate it here and realize full pixels whenever + we have more than one pixel. + */ + + int noCharsYet; + /* logical: we haven't seen any renderable characters yet in + the line. + */ + noCharsYet = TRUE; /* initial value */ + widthSoFar = 0; /* initial value */ + accumulatedIcs = 0.0; /* initial value */ + + truncatedText.textArray[line][0] = '\0'; /* Start with empty line */ + + for (cursor = 0; + inputText.textArray[line][cursor] != '\0' && widthSoFar < width; + cursor++) { + lastch = inputText.textArray[line][cursor]; + if (fontP->glyph[(unsigned char)lastch]) { + if (noCharsYet) { + noCharsYet = FALSE; + if (fontP->glyph[lastch]->x > 0) + widthSoFar += fontP->glyph[lastch]->x; + } else { + /* handle extra intercharacter space (-space option) */ + int fullPixels; /* integer part of accumulatedIcs */ + accumulatedIcs += intercharacterSpace; + fullPixels = (int) intercharacterSpace; + if (fullPixels > 0) { + widthSoFar += fullPixels; + accumulatedIcs -= fullPixels; + } + } + widthSoFar += fontP->glyph[lastch]->xadd; + } + if (widthSoFar < width) { + truncatedText.textArray[line][cursor] = + inputText.textArray[line][cursor]; + truncatedText.textArray[line][cursor+1] = '\0'; + } + } + } + truncatedText.lineCount = inputText.lineCount; + *outputTextP = truncatedText; +} + + + +static void +getText(const char cmdline_text[], + struct font * const fn, + struct text * const input_textP) { + + struct text input_text; + + if (cmdline_text) { + allocTextArray(&input_text, 1, strlen(cmdline_text)); + strcpy(input_text.textArray[0], cmdline_text); + fix_control_chars(input_text.textArray[0], fn); + input_text.lineCount = 1; + } else { + /* Read text from stdin. */ + + unsigned int maxlines; + /* Maximum number of lines for which we presently have space + in the text array + */ + char buf[5000]; + char ** text_array; + unsigned int lineCount; + + maxlines = 50; /* initial value */ + MALLOCARRAY_NOFAIL(text_array, maxlines); + + lineCount = 0; /* initial value */ + while (fgets(buf, sizeof(buf), stdin) != NULL) { + fix_control_chars(buf, fn); + if (lineCount >= maxlines) { + maxlines *= 2; + text_array = (char**) realloc((char*) text_array, + maxlines * sizeof(char*)); + if (text_array == NULL) + pm_error("out of memory"); + } + text_array[lineCount] = strdup(buf); + if (text_array[lineCount] == NULL) + pm_error("out of memory"); + ++lineCount; + } + input_text.textArray = text_array; + input_text.lineCount = lineCount; + input_text.allocatedLineCount = lineCount; + } + *input_textP = input_text; +} + + + +static void +compute_image_width(struct text const lp, + const struct font * const fn, + float const intercharacter_space, + int * const maxwidthP, + int * const maxleftbP) { + int line; + + *maxwidthP = 0; /* initial value */ + *maxleftbP = 0; /* initial value */ + for (line = 0; line < lp.lineCount; ++line) { + int bwid, backup_space_needed; + + get_line_dimensions(lp.textArray[line], fn, intercharacter_space, + &bwid, &backup_space_needed); + + *maxwidthP = MAX(*maxwidthP, bwid); + *maxleftbP = MAX(*maxleftbP, backup_space_needed); + } +} + + + +int +main(int argc, char *argv[]) { + + struct cmdline_info cmdline; + bit** bits; + int rows, cols; + struct font* fontP; + int vmargin, hmargin; + struct text inputText; + struct text formattedText; + int maxwidth; + /* width in pixels of the longest line of text in the image */ + int maxleftb; + + pbm_init( &argc, argv ); + + parse_command_line(argc, argv, &cmdline); + + if (cmdline.font) + fontP = pbm_loadfont(cmdline.font); + else { + if (cmdline.builtin) + fontP = pbm_defaultfont(cmdline.builtin); + else + fontP = pbm_defaultfont("bdf"); + } + + if (cmdline.dump) { + pbm_dumpfont(fontP); + exit(0); + } + + getText(cmdline.text, fontP, &inputText); + + if (cmdline.nomargins) { + vmargin = 0; + hmargin = 0; + } else { + if (inputText.lineCount == 1) { + vmargin = fontP->maxheight / 2; + hmargin = fontP->maxwidth; + } else { + vmargin = fontP->maxheight; + hmargin = 2 * fontP->maxwidth; + } + } + + if (cmdline.width > 0) { + /* Flow or truncate lines to meet user's width request */ + if (inputText.lineCount == 1) + flowText(inputText, cmdline.width, fontP, cmdline.space, + &formattedText); + else + truncateText(inputText, cmdline.width, fontP, cmdline.space, + &formattedText); + freeTextArray(inputText); + } else + formattedText = inputText; + + rows = 2 * vmargin + + formattedText.lineCount * fontP->maxheight + + (formattedText.lineCount-1) * cmdline.lspace; + + compute_image_width(formattedText, fontP, cmdline.space, + &maxwidth, &maxleftb); + + cols = 2 * hmargin + maxwidth; + bits = pbm_allocarray(cols, rows); + + /* Fill background with white */ + fill_rect(bits, 0, 0, rows, cols, PBM_WHITE); + + /* Put the text in */ + insert_characters(bits, formattedText, fontP, vmargin, hmargin + maxleftb, + cmdline.space, cmdline.lspace); + + /* All done. */ + pbm_writepbm(stdout, bits, cols, rows, 0); + + freeTextArray(formattedText); + pm_close(stdout); + + return 0; +} diff --git a/generator/pbmtextps.c b/generator/pbmtextps.c new file mode 100644 index 00000000..ef8ae008 --- /dev/null +++ b/generator/pbmtextps.c @@ -0,0 +1,377 @@ +/* + * pbmtextps.c - render text into a bitmap using a postscript interpreter + * + * Copyright (C) 2002 by James McCann. + * + * 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. + * + * PostScript is a registered trademark of Adobe Systems International. + * + * Additions by Bryan Henderson contributed to public domain by author. + * + */ +#define _XOPEN_SOURCE /* Make sure popen() is in stdio.h */ +#define _BSD_SOURCE /* Make sure stdrup() is in string.h */ +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include "pbm.h" +#include "nstring.h" +#include "shhopt.h" + + +#define BUFFER_SIZE 2048 + +static const char *gs_exe_path = +#ifdef GHOSTSCRIPT_EXECUTABLE_PATH +GHOSTSCRIPT_EXECUTABLE_PATH; +#else +0; +#endif + +static const char *pnmcrop_exe_path = +#ifdef PNMCROP_EXECUTABLE_PATH +PNMCROP_EXECUTABLE_PATH; +#else +0; +#endif + +struct cmdlineInfo { + /* All the information the user supplied in the command line, + in a form easy for the program to use. + */ + int res; /* resolution, DPI */ + int fontsize; /* Size of font in points */ + const char * font; /* Name of postscript font */ + float stroke; + /* Width of stroke in points (only for outline font) */ + unsigned int verbose; + const char * text; +}; + + + +static void +parseCommandLine(int argc, 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 = malloc(100*sizeof(optEntry)); + /* Instructions to OptParseOptions2 on how to parse our options. + */ + optStruct3 opt; + + unsigned int option_def_index; + int i; + char * text; + int totaltextsize = 0; + + option_def_index = 0; /* incremented by OPTENTRY */ + OPTENT3(0, "resolution", OPT_INT, &cmdlineP->res, NULL, 0); + OPTENT3(0, "font", OPT_STRING, &cmdlineP->font, NULL, 0); + OPTENT3(0, "fontsize", OPT_INT, &cmdlineP->fontsize, NULL, 0); + OPTENT3(0, "stroke", OPT_FLOAT, &cmdlineP->stroke, NULL, 0); + OPTENT3(0, "verbose", OPT_FLAG, NULL, &cmdlineP->verbose, 0); + + /* Set the defaults */ + cmdlineP->res = 150; + cmdlineP->fontsize = 24; + cmdlineP->font = "Times-Roman"; + cmdlineP->stroke = -1; + + opt.opt_table = option_def; + opt.short_allowed = FALSE; /* We have no short (old-fashioned) options */ + opt.allowNegNum = FALSE; + + optParseOptions3(&argc, argv, opt, sizeof(opt), 0); + + text = NULL; + + for (i = 1; i < argc; i++) { + if (i > 1) { + totaltextsize += 1; + text = realloc(text, totaltextsize); + if (text == NULL) + pm_error("out of memory"); + strcat(text, " "); + } + totaltextsize += strlen(argv[i]); + text = realloc(text, totaltextsize); + if (text == NULL) + pm_error("out of memory"); + strcat(text, argv[i]); + } + cmdlineP->text = text; +} + + + +static const char * +construct_postscript(struct cmdlineInfo const cmdl) { + + const char * retval; + const char * template; + + if (cmdl.stroke <= 0) + template = "/%s findfont\n%d scalefont\nsetfont\n12 36 moveto\n" + "(%s) show\nshowpage\n"; + else + template = "/%s findfont\n%d scalefont\nsetfont\n12 36 moveto\n" + "%f setlinewidth\n0 setgray\n" + "(%s) true charpath\nstroke\nshowpage\n"; + + if (cmdl.stroke < 0) + asprintfN(&retval, template, cmdl.font, cmdl.fontsize, + cmdl.text); + else + asprintfN(&retval, template, cmdl.font, cmdl.fontsize, + cmdl.stroke, cmdl.text); + + return retval; +} + + + +static const char * +gs_executable_name() +{ + static char buffer[BUFFER_SIZE]; + if(! gs_exe_path) { + const char * const which = "which gs"; + FILE *f; + memset(buffer, 0, BUFFER_SIZE); + if(!(f = popen(which, "r"))) + pm_error("Can't find ghostscript"); + fread(buffer, 1, BUFFER_SIZE, f); + if(buffer[strlen(buffer) - 1] == '\n') + buffer[strlen(buffer) - 1] = 0; + pclose(f); + if(buffer[0] != '/' && buffer[0] != '.') + pm_error("Can't find ghostscript"); + } + else + strcpy(buffer, gs_exe_path); + + return buffer; +} + + + +static const char * +crop_executable_name() +{ + static char buffer[BUFFER_SIZE]; + if(! pnmcrop_exe_path) { + const char * const which = "which pnmcrop"; + FILE *f; + memset(buffer, 0, BUFFER_SIZE); + if(!(f = popen(which, "r"))) { + return 0; + } + + fread(buffer, 1, BUFFER_SIZE, f); + if(buffer[strlen(buffer) - 1] == '\n') + buffer[strlen(buffer) - 1] = 0; + pclose(f); + if(buffer[0] != '/' && buffer[0] != '.') { + buffer[0] = 0; + pm_message("Can't find pnmcrop"); + } + } + else + strcpy(buffer, pnmcrop_exe_path); + + return buffer; +} + + + +static const char * +gsCommand(const char * const psFname, + const char * const outputFilename, + struct cmdlineInfo const cmdline) { + + const char * retval; + int const x = cmdline.res * 11; + int const y = cmdline.res * (cmdline.fontsize * 2 + 72) / 72.; + asprintfN(&retval, "%s -g%dx%d -r%d -sDEVICE=pbm " + "-sOutputFile=%s -q -dBATCH -dNOPAUSE %s </dev/null >/dev/null", + gs_executable_name(), x, y, cmdline.res, + outputFilename, psFname); + return retval; +} + + + +static const char * +cropCommand(const char * const inputFileName) { + + const char * retval; + + if (crop_executable_name()) { + asprintfN(&retval, "%s -top -right %s", + crop_executable_name(), inputFileName); + if (retval == NULL) + pm_error("Unable to allocate memory"); + } else + retval = NULL; + + return retval; +} + + + +static void +writeProgram(const char * const psFname, + struct cmdlineInfo const cmdline) { + + const char *ps; + FILE * psfile; + + psfile = fopen(psFname, "w"); + if (psfile == NULL) + pm_error("Can't open temp file '%s'. Errno=%d (%s)", + psFname, errno, strerror(errno)); + + ps = construct_postscript(cmdline); + + if (cmdline.verbose) + pm_message("Postscript program = '%s'", ps); + + if (fwrite(ps, 1, strlen(ps), psfile) != strlen(ps)) + pm_error("Can't write postscript to temp file"); + + fclose(psfile); + + strfree(ps); +} + + + +static void +executeProgram(const char * const psFname, + const char * const outputFname, + struct cmdlineInfo const cmdline) { + + const char * com; + int rc; + + com = gsCommand(psFname, outputFname, cmdline); + if (com == NULL) + pm_error("Can't allocate memory for a 'ghostscript' command"); + + if (cmdline.verbose) + pm_message("Running Postscript interpreter '%s'", com); + + rc = system(com); + if (rc != 0) + pm_error("Failed to run Ghostscript process. rc=%d", rc); + + strfree(com); +} + + + +static void +cropToStdout(const char * const inputFileName, + bool const verbose) { + + const char * com; + + com = cropCommand(inputFileName); + if (com == NULL) { + /* No pnmcrop. So don't crop. */ + pm_message("Can't find pnmcrop command, image will be large"); + asprintfN(&com, "cat %s", inputFileName); + if (com == NULL) + pm_error("Unable to allocate memory."); + } else { + FILE *pnmcrop; + + if (verbose) + pm_message("Running crop command '%s'", com); + + pnmcrop = popen(com, "r"); + if (pnmcrop == NULL) + pm_error("Can't run pnmcrop process"); + else { + char buf[2048]; + bool eof; + + eof = FALSE; + + while (!eof) { + int bytesRead; + + bytesRead = fread(buf, 1, sizeof(buf), pnmcrop); + if (bytesRead > 0) { + int rc; + rc = fwrite(buf, 1, bytesRead, stdout); + if (rc != bytesRead) + pm_error("Can't write to stdout"); + } else if (bytesRead == 0) + eof = TRUE; + else + pm_error("Failed to read output of Pnmcrop process. " + "Errno=%d (%s)", errno, strerror(errno)); + } + fclose(pnmcrop); + } + } + strfree(com); +} + + + +static void +createOutputFile(struct cmdlineInfo const cmdline) { + + const char * const template = "./pstextpbm.%d.tmp.%s"; + + const char * psFname; + const char * uncroppedPbmFname; + + asprintfN(&psFname, template, getpid(), "ps"); + if (psFname == NULL) + pm_error("Unable to allocate memory"); + + writeProgram(psFname, cmdline); + + asprintfN(&uncroppedPbmFname, template, getpid(), "pbm"); + if (uncroppedPbmFname == NULL) + pm_error("Unable to allocate memory"); + + executeProgram(psFname, uncroppedPbmFname, cmdline); + + unlink(psFname); + strfree(psFname); + + cropToStdout(uncroppedPbmFname, cmdline.verbose); + + unlink(uncroppedPbmFname); + strfree(uncroppedPbmFname); +} + + + +int +main(int argc, char *argv[]) { + + struct cmdlineInfo cmdline; + + pbm_init(&argc, argv); + + parseCommandLine(argc, argv, &cmdline); + + createOutputFile(cmdline); + + return 0; +} diff --git a/generator/pbmupc.c b/generator/pbmupc.c new file mode 100644 index 00000000..5f694a00 --- /dev/null +++ b/generator/pbmupc.c @@ -0,0 +1,544 @@ +/* pbmupc.c - create a Universal Product Code bitmap +** +** Copyright (C) 1988 by Jef Poskanzer. +** +** 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. +*/ + +#include <string.h> + +#include "pbm.h" + +#define MARGIN 20 +#define DIGIT_WIDTH 14 +#define DIGIT_HEIGHT 23 +#define LINE1_WIDTH 2 + +#define LINE2_WIDTH ( 2 * LINE1_WIDTH ) +#define LINE3_WIDTH ( 3 * LINE1_WIDTH ) +#define LINE4_WIDTH ( 4 * LINE1_WIDTH ) +#define LINES_WIDTH ( 7 * LINE1_WIDTH ) +#define SHORT_HEIGHT ( 8 * LINES_WIDTH ) +#define TALL_HEIGHT ( SHORT_HEIGHT + DIGIT_HEIGHT / 2 ) + +static int alldig ARGS(( char* cp )); +static void putdigit ARGS(( int d, bit** bits, int row0, int col0 )); +static int addlines ARGS(( int d, bit** bits, int row0, int col0, int height, bit color )); +static int rect ARGS(( bit** bits, int row0, int col0, int height, int width, bit color )); + +int +main( argc, argv ) + int argc; + char* argv[]; + { + register bit** bits; + int argn, style, rows, cols, row, digrow, col, digcolofs; + char* typecode; + char* manufcode; + char* prodcode; + int sum, p, lc0, lc1, lc2, lc3, lc4, rc0, rc1, rc2, rc3, rc4; + const char* const usage = "[-s1|-s2] <type> <manufac> <product>"; + + + pbm_init( &argc, argv ); + + argn = 1; + style = 1; + + /* Check for flags. */ + while ( argn < argc && argv[argn][0] == '-' && argv[argn][1] != '\0' ) + { + if ( pm_keymatch( argv[argn], "-s1", 3 ) ) + style = 1; + else if ( pm_keymatch( argv[argn], "-s2", 3 ) ) + style = 2; + else + pm_usage( usage ); + argn++; + } + + if ( argn + 3 < argc ) + pm_usage( usage ); + typecode = argv[argn]; + manufcode = argv[argn + 1]; + prodcode = argv[argn + 2]; + argn += 3; + + if ( argn != argc ) + pm_usage( usage ); + + if ( strlen( typecode ) != 1 || ( ! alldig( typecode ) ) || + strlen( manufcode ) != 5 || ( ! alldig ( manufcode ) ) || + strlen( prodcode ) != 5 || ( ! alldig ( prodcode ) ) ) + pm_error( + "type code must be one digit, and\n manufacturer and product codes must be five digits" ); + p = typecode[0] - '0'; + lc0 = manufcode[0] - '0'; + lc1 = manufcode[1] - '0'; + lc2 = manufcode[2] - '0'; + lc3 = manufcode[3] - '0'; + lc4 = manufcode[4] - '0'; + rc0 = prodcode[0] - '0'; + rc1 = prodcode[1] - '0'; + rc2 = prodcode[2] - '0'; + rc3 = prodcode[3] - '0'; + rc4 = prodcode[4] - '0'; + sum = ( 10 - ( ( ( p + lc1 + lc3 + rc0 + rc2 + rc4 ) * 3 + lc0 + lc2 + lc4 + rc1 + rc3 ) % 10 ) ) % 10; + + rows = 2 * MARGIN + SHORT_HEIGHT + DIGIT_HEIGHT; + cols = 2 * MARGIN + 12 * LINES_WIDTH + 11 * LINE1_WIDTH; + bits = pbm_allocarray( cols, rows ); + + (void) rect( bits, 0, 0, rows, cols, PBM_WHITE ); + + row = MARGIN; + digrow = row + SHORT_HEIGHT; + col = MARGIN; + digcolofs = ( LINES_WIDTH - DIGIT_WIDTH ) / 2; + + if ( style == 1 ) + putdigit( p, bits, digrow, col - DIGIT_WIDTH - LINE1_WIDTH ); + else if ( style == 2 ) + putdigit( + p, bits, row + SHORT_HEIGHT / 2, col - DIGIT_WIDTH - LINE1_WIDTH ); + col = rect( bits, row, col, TALL_HEIGHT, LINE1_WIDTH, PBM_BLACK ); + col = rect( bits, row, col, TALL_HEIGHT, LINE1_WIDTH, PBM_WHITE ); + col = rect( bits, row, col, TALL_HEIGHT, LINE1_WIDTH, PBM_BLACK ); + col = addlines( p, bits, row, col, TALL_HEIGHT, PBM_WHITE ); + putdigit( lc0, bits, digrow, col + digcolofs ); + col = addlines( lc0, bits, row, col, SHORT_HEIGHT, PBM_WHITE ); + putdigit( lc1, bits, digrow, col + digcolofs ); + col = addlines( lc1, bits, row, col, SHORT_HEIGHT, PBM_WHITE ); + putdigit( lc2, bits, digrow, col + digcolofs ); + col = addlines( lc2, bits, row, col, SHORT_HEIGHT, PBM_WHITE ); + putdigit( lc3, bits, digrow, col + digcolofs ); + col = addlines( lc3, bits, row, col, SHORT_HEIGHT, PBM_WHITE ); + putdigit( lc4, bits, digrow, col + digcolofs ); + col = addlines( lc4, bits, row, col, SHORT_HEIGHT, PBM_WHITE ); + col = rect( bits, row, col, TALL_HEIGHT, LINE1_WIDTH, PBM_WHITE ); + col = rect( bits, row, col, TALL_HEIGHT, LINE1_WIDTH, PBM_BLACK ); + col = rect( bits, row, col, TALL_HEIGHT, LINE1_WIDTH, PBM_WHITE ); + col = rect( bits, row, col, TALL_HEIGHT, LINE1_WIDTH, PBM_BLACK ); + col = rect( bits, row, col, TALL_HEIGHT, LINE1_WIDTH, PBM_WHITE ); + putdigit( rc0, bits, digrow, col + digcolofs ); + col = addlines( rc0, bits, row, col, SHORT_HEIGHT, PBM_BLACK ); + putdigit( rc1, bits, digrow, col + digcolofs ); + col = addlines( rc1, bits, row, col, SHORT_HEIGHT, PBM_BLACK ); + putdigit( rc2, bits, digrow, col + digcolofs ); + col = addlines( rc2, bits, row, col, SHORT_HEIGHT, PBM_BLACK ); + putdigit( rc3, bits, digrow, col + digcolofs ); + col = addlines( rc3, bits, row, col, SHORT_HEIGHT, PBM_BLACK ); + putdigit( rc4, bits, digrow, col + digcolofs ); + col = addlines( rc4, bits, row, col, SHORT_HEIGHT, PBM_BLACK ); + col = addlines( sum, bits, row, col, TALL_HEIGHT, PBM_BLACK ); + col = rect( bits, row, col, TALL_HEIGHT, LINE1_WIDTH, PBM_BLACK ); + col = rect( bits, row, col, TALL_HEIGHT, LINE1_WIDTH, PBM_WHITE ); + col = rect( bits, row, col, TALL_HEIGHT, LINE1_WIDTH, PBM_BLACK ); + if ( style == 1 ) + putdigit( sum, bits, digrow, col + LINE1_WIDTH ); + + pbm_writepbm( stdout, bits, cols, rows, 0 ); + pm_close( stdout ); + + exit( 0 ); + } + +static int +alldig( cp ) + char* cp; + { + for ( ; *cp != '\0'; cp++ ) + if ( *cp < '0' || *cp > '9' ) + return 0; + return 1; + } + +static void +putdigit( d, bits, row0, col0 ) + int d; + bit** bits; + int row0, col0; + { + int row, col; + static bit digits[10][DIGIT_HEIGHT][DIGIT_WIDTH] = { + /* 0 */ + { + {0,0,0,0,0,0,0,0,0,0,0,0,0,0}, + {0,0,0,0,0,0,0,0,0,0,0,0,0,0}, + {0,0,0,0,1,1,1,1,1,1,0,0,0,0}, + {0,0,0,1,1,1,1,1,1,1,1,0,0,0}, + {0,0,1,1,1,0,0,0,0,1,1,1,0,0}, + {0,0,1,1,0,0,0,0,0,0,1,1,0,0}, + {0,1,1,1,0,0,0,0,0,0,1,1,1,0}, + {0,1,1,0,0,0,0,0,0,0,0,1,1,0}, + {0,1,1,0,0,0,0,0,0,0,0,1,1,0}, + {0,1,1,0,0,0,0,0,0,0,0,1,1,0}, + {0,1,1,0,0,0,0,0,0,0,0,1,1,0}, + {0,1,1,0,0,0,0,0,0,0,0,1,1,0}, + {0,1,1,0,0,0,0,0,0,0,0,1,1,0}, + {0,1,1,0,0,0,0,0,0,0,0,1,1,0}, + {0,1,1,0,0,0,0,0,0,0,0,1,1,0}, + {0,1,1,0,0,0,0,0,0,0,0,1,1,0}, + {0,1,1,1,0,0,0,0,0,0,1,1,1,0}, + {0,0,1,1,0,0,0,0,0,0,1,1,0,0}, + {0,0,1,1,1,0,0,0,0,1,1,1,0,0}, + {0,0,0,1,1,1,1,1,1,1,1,0,0,0}, + {0,0,0,0,1,1,1,1,1,1,0,0,0,0}, + {0,0,0,0,0,0,0,0,0,0,0,0,0,0}, + {0,0,0,0,0,0,0,0,0,0,0,0,0,0} + }, + /* 1 */ + { + {0,0,0,0,0,0,0,0,0,0,0,0,0,0}, + {0,0,0,0,0,0,0,0,0,0,0,0,0,0}, + {0,0,0,0,0,0,1,1,0,0,0,0,0,0}, + {0,0,0,0,0,1,1,1,0,0,0,0,0,0}, + {0,0,0,0,1,1,1,1,0,0,0,0,0,0}, + {0,0,0,1,1,1,1,1,0,0,0,0,0,0}, + {0,0,1,1,1,0,1,1,0,0,0,0,0,0}, + {0,0,1,1,0,0,1,1,0,0,0,0,0,0}, + {0,0,0,0,0,0,1,1,0,0,0,0,0,0}, + {0,0,0,0,0,0,1,1,0,0,0,0,0,0}, + {0,0,0,0,0,0,1,1,0,0,0,0,0,0}, + {0,0,0,0,0,0,1,1,0,0,0,0,0,0}, + {0,0,0,0,0,0,1,1,0,0,0,0,0,0}, + {0,0,0,0,0,0,1,1,0,0,0,0,0,0}, + {0,0,0,0,0,0,1,1,0,0,0,0,0,0}, + {0,0,0,0,0,0,1,1,0,0,0,0,0,0}, + {0,0,0,0,0,0,1,1,0,0,0,0,0,0}, + {0,0,0,0,0,0,1,1,0,0,0,0,0,0}, + {0,0,0,0,0,0,1,1,0,0,0,0,0,0}, + {0,0,0,0,0,0,1,1,0,0,0,0,0,0}, + {0,0,0,0,0,0,1,1,0,0,0,0,0,0}, + {0,0,0,0,0,0,0,0,0,0,0,0,0,0}, + {0,0,0,0,0,0,0,0,0,0,0,0,0,0} + }, + /* 2 */ + { + {0,0,0,0,0,0,0,0,0,0,0,0,0,0}, + {0,0,0,0,0,0,0,0,0,0,0,0,0,0}, + {0,0,0,0,0,1,1,1,1,0,0,0,0,0}, + {0,0,0,1,1,1,1,1,1,1,1,0,0,0}, + {0,0,1,1,1,1,0,0,1,1,1,1,0,0}, + {0,1,1,1,0,0,0,0,0,0,1,1,0,0}, + {0,1,1,0,0,0,0,0,0,0,1,1,1,0}, + {0,0,0,0,0,0,0,0,0,0,0,1,1,0}, + {0,0,0,0,0,0,0,0,0,0,0,1,1,0}, + {0,0,0,0,0,0,0,0,0,0,1,1,1,0}, + {0,0,0,0,0,0,0,0,0,1,1,1,0,0}, + {0,0,0,0,0,0,0,0,1,1,1,0,0,0}, + {0,0,0,0,0,0,0,1,1,1,0,0,0,0}, + {0,0,0,0,0,0,1,1,1,0,0,0,0,0}, + {0,0,0,0,0,1,1,1,0,0,0,0,0,0}, + {0,0,0,0,1,1,1,0,0,0,0,0,0,0}, + {0,0,0,1,1,1,0,0,0,0,0,0,0,0}, + {0,0,1,1,1,0,0,0,0,0,0,0,0,0}, + {0,1,1,1,0,0,0,0,0,0,0,0,0,0}, + {0,1,1,1,1,1,1,1,1,1,1,1,1,0}, + {0,1,1,1,1,1,1,1,1,1,1,1,1,0}, + {0,0,0,0,0,0,0,0,0,0,0,0,0,0}, + {0,0,0,0,0,0,0,0,0,0,0,0,0,0} + }, + /* 3 */ + { + {0,0,0,0,0,0,0,0,0,0,0,0,0,0}, + {0,0,0,0,0,0,0,0,0,0,0,0,0,0}, + {0,1,1,1,1,1,1,1,1,1,1,1,1,0}, + {0,1,1,1,1,1,1,1,1,1,1,1,1,0}, + {0,0,0,0,0,0,0,0,0,0,1,1,1,0}, + {0,0,0,0,0,0,0,0,0,1,1,1,0,0}, + {0,0,0,0,0,0,0,0,1,1,1,0,0,0}, + {0,0,0,0,0,0,0,1,1,1,0,0,0,0}, + {0,0,0,0,0,0,1,1,1,0,0,0,0,0}, + {0,0,0,0,0,1,1,1,1,0,0,0,0,0}, + {0,0,0,0,0,1,1,1,1,1,1,0,0,0}, + {0,0,0,0,0,0,0,0,1,1,1,1,0,0}, + {0,0,0,0,0,0,0,0,0,0,1,1,0,0}, + {0,0,0,0,0,0,0,0,0,0,1,1,1,0}, + {0,0,0,0,0,0,0,0,0,0,0,1,1,0}, + {0,0,0,0,0,0,0,0,0,0,0,1,1,0}, + {0,1,1,0,0,0,0,0,0,0,1,1,1,0}, + {0,1,1,1,0,0,0,0,0,0,1,1,0,0}, + {0,0,1,1,1,1,0,0,1,1,1,1,0,0}, + {0,0,0,1,1,1,1,1,1,1,1,0,0,0}, + {0,0,0,0,0,1,1,1,1,0,0,0,0,0}, + {0,0,0,0,0,0,0,0,0,0,0,0,0,0}, + {0,0,0,0,0,0,0,0,0,0,0,0,0,0} + }, + /* 4 */ + { + {0,0,0,0,0,0,0,0,0,0,0,0,0,0}, + {0,0,0,0,0,0,0,0,0,0,0,0,0,0}, + {0,0,0,0,0,0,1,1,0,0,0,0,0,0}, + {0,0,0,0,0,0,1,1,0,0,0,0,0,0}, + {0,0,0,0,0,1,1,1,0,0,0,0,0,0}, + {0,0,0,0,0,1,1,0,0,0,0,0,0,0}, + {0,0,0,0,1,1,1,0,0,0,0,0,0,0}, + {0,0,0,0,1,1,0,0,0,0,0,0,0,0}, + {0,0,0,1,1,1,0,0,0,0,0,0,0,0}, + {0,0,0,1,1,0,0,0,1,1,0,0,0,0}, + {0,0,1,1,1,0,0,0,1,1,0,0,0,0}, + {0,0,1,1,0,0,0,0,1,1,0,0,0,0}, + {0,1,1,1,0,0,0,0,1,1,0,0,0,0}, + {0,1,1,1,1,1,1,1,1,1,1,1,1,0}, + {0,1,1,1,1,1,1,1,1,1,1,1,1,0}, + {0,0,0,0,0,0,0,0,1,1,0,0,0,0}, + {0,0,0,0,0,0,0,0,1,1,0,0,0,0}, + {0,0,0,0,0,0,0,0,1,1,0,0,0,0}, + {0,0,0,0,0,0,0,0,1,1,0,0,0,0}, + {0,0,0,0,0,0,0,0,1,1,0,0,0,0}, + {0,0,0,0,0,0,0,0,1,1,0,0,0,0}, + {0,0,0,0,0,0,0,0,0,0,0,0,0,0}, + {0,0,0,0,0,0,0,0,0,0,0,0,0,0} + }, + /* 5 */ + { + {0,0,0,0,0,0,0,0,0,0,0,0,0,0}, + {0,0,0,0,0,0,0,0,0,0,0,0,0,0}, + {0,1,1,1,1,1,1,1,1,1,1,1,1,0}, + {0,1,1,1,1,1,1,1,1,1,1,1,1,0}, + {0,1,1,0,0,0,0,0,0,0,0,0,0,0}, + {0,1,1,0,0,0,0,0,0,0,0,0,0,0}, + {0,1,1,0,0,0,0,0,0,0,0,0,0,0}, + {0,1,1,0,0,0,0,0,0,0,0,0,0,0}, + {0,1,1,0,0,0,0,0,0,0,0,0,0,0}, + {0,1,1,1,1,1,1,1,1,0,0,0,0,0}, + {0,1,1,1,1,1,1,1,1,1,1,0,0,0}, + {0,0,0,0,0,0,0,0,1,1,1,1,0,0}, + {0,0,0,0,0,0,0,0,0,0,1,1,0,0}, + {0,0,0,0,0,0,0,0,0,0,1,1,1,0}, + {0,0,0,0,0,0,0,0,0,0,0,1,1,0}, + {0,0,0,0,0,0,0,0,0,0,0,1,1,0}, + {0,1,1,0,0,0,0,0,0,0,1,1,1,0}, + {0,1,1,1,0,0,0,0,0,0,1,1,0,0}, + {0,0,1,1,1,1,0,0,1,1,1,1,0,0}, + {0,0,0,1,1,1,1,1,1,1,1,0,0,0}, + {0,0,0,0,0,1,1,1,1,0,0,0,0,0}, + {0,0,0,0,0,0,0,0,0,0,0,0,0,0}, + {0,0,0,0,0,0,0,0,0,0,0,0,0,0} + }, + /* 6 */ + { + {0,0,0,0,0,0,0,0,0,0,0,0,0,0}, + {0,0,0,0,0,0,0,0,0,0,0,0,0,0}, + {0,0,0,0,0,0,0,1,1,0,0,0,0,0}, + {0,0,0,0,0,0,1,1,1,0,0,0,0,0}, + {0,0,0,0,0,1,1,1,0,0,0,0,0,0}, + {0,0,0,0,1,1,1,0,0,0,0,0,0,0}, + {0,0,0,1,1,1,0,0,0,0,0,0,0,0}, + {0,0,0,1,1,0,0,0,0,0,0,0,0,0}, + {0,0,1,1,1,0,0,0,0,0,0,0,0,0}, + {0,0,1,1,0,1,1,1,1,0,0,0,0,0}, + {0,0,1,1,1,1,1,1,1,1,1,0,0,0}, + {0,1,1,1,1,1,0,0,1,1,1,1,0,0}, + {0,1,1,1,0,0,0,0,0,0,1,1,0,0}, + {0,1,1,1,0,0,0,0,0,0,1,1,1,0}, + {0,1,1,0,0,0,0,0,0,0,0,1,1,0}, + {0,1,1,0,0,0,0,0,0,0,0,1,1,0}, + {0,1,1,1,0,0,0,0,0,0,1,1,1,0}, + {0,0,1,1,0,0,0,0,0,0,1,1,0,0}, + {0,0,1,1,1,1,0,0,1,1,1,1,0,0}, + {0,0,0,1,1,1,1,1,1,1,1,0,0,0}, + {0,0,0,0,0,1,1,1,1,0,0,0,0,0}, + {0,0,0,0,0,0,0,0,0,0,0,0,0,0}, + {0,0,0,0,0,0,0,0,0,0,0,0,0,0} + }, + /* 7 */ + { + {0,0,0,0,0,0,0,0,0,0,0,0,0,0}, + {0,0,0,0,0,0,0,0,0,0,0,0,0,0}, + {0,1,1,1,1,1,1,1,1,1,1,1,1,0}, + {0,1,1,1,1,1,1,1,1,1,1,1,1,0}, + {0,0,0,0,0,0,0,0,0,0,1,1,1,0}, + {0,0,0,0,0,0,0,0,0,0,1,1,0,0}, + {0,0,0,0,0,0,0,0,0,1,1,1,0,0}, + {0,0,0,0,0,0,0,0,0,1,1,0,0,0}, + {0,0,0,0,0,0,0,0,1,1,1,0,0,0}, + {0,0,0,0,0,0,0,0,1,1,0,0,0,0}, + {0,0,0,0,0,0,0,1,1,1,0,0,0,0}, + {0,0,0,0,0,0,0,1,1,0,0,0,0,0}, + {0,0,0,0,0,0,1,1,1,0,0,0,0,0}, + {0,0,0,0,0,0,1,1,0,0,0,0,0,0}, + {0,0,0,0,0,1,1,1,0,0,0,0,0,0}, + {0,0,0,0,0,1,1,0,0,0,0,0,0,0}, + {0,0,0,0,0,1,1,0,0,0,0,0,0,0}, + {0,0,0,0,1,1,1,0,0,0,0,0,0,0}, + {0,0,0,0,1,1,0,0,0,0,0,0,0,0}, + {0,0,0,0,1,1,0,0,0,0,0,0,0,0}, + {0,0,0,0,1,1,0,0,0,0,0,0,0,0}, + {0,0,0,0,0,0,0,0,0,0,0,0,0,0}, + {0,0,0,0,0,0,0,0,0,0,0,0,0,0} + }, + /* 8 */ + { + {0,0,0,0,0,0,0,0,0,0,0,0,0,0}, + {0,0,0,0,0,0,0,0,0,0,0,0,0,0}, + {0,0,0,1,1,1,1,1,1,1,1,0,0,0}, + {0,0,1,1,1,1,1,1,1,1,1,1,0,0}, + {0,1,1,1,0,0,0,0,0,0,1,1,1,0}, + {0,1,1,0,0,0,0,0,0,0,0,1,1,0}, + {0,1,1,0,0,0,0,0,0,0,0,1,1,0}, + {0,1,1,1,0,0,0,0,0,0,1,1,1,0}, + {0,0,1,1,1,0,0,0,0,1,1,1,0,0}, + {0,0,0,1,1,1,0,0,1,1,1,0,0,0}, + {0,0,0,0,1,1,1,1,1,1,0,0,0,0}, + {0,0,0,0,1,1,1,1,1,1,0,0,0,0}, + {0,0,0,1,1,1,0,0,1,1,1,0,0,0}, + {0,0,1,1,1,0,0,0,0,1,1,1,0,0}, + {0,1,1,1,0,0,0,0,0,0,1,1,1,0}, + {0,1,1,0,0,0,0,0,0,0,0,1,1,0}, + {0,1,1,0,0,0,0,0,0,0,0,1,1,0}, + {0,1,1,0,0,0,0,0,0,0,0,1,1,0}, + {0,1,1,1,0,0,0,0,0,0,1,1,1,0}, + {0,0,1,1,1,1,1,1,1,1,1,1,0,0}, + {0,0,0,1,1,1,1,1,1,1,1,0,0,0}, + {0,0,0,0,0,0,0,0,0,0,0,0,0,0}, + {0,0,0,0,0,0,0,0,0,0,0,0,0,0} + }, + /* 9 */ + { + {0,0,0,0,0,0,0,0,0,0,0,0,0,0}, + {0,0,0,0,0,0,0,0,0,0,0,0,0,0}, + {0,0,0,0,0,1,1,1,1,0,0,0,0,0}, + {0,0,0,1,1,1,1,1,1,1,1,0,0,0}, + {0,0,1,1,1,1,0,0,1,1,1,1,0,0}, + {0,0,1,1,0,0,0,0,0,0,1,1,0,0}, + {0,1,1,1,0,0,0,0,0,0,1,1,1,0}, + {0,1,1,0,0,0,0,0,0,0,0,1,1,0}, + {0,1,1,0,0,0,0,0,0,0,0,1,1,0}, + {0,1,1,1,0,0,0,0,0,0,1,1,1,0}, + {0,0,1,1,0,0,0,0,0,0,1,1,1,0}, + {0,0,1,1,1,1,0,0,1,1,1,1,1,0}, + {0,0,0,1,1,1,1,1,1,1,1,1,0,0}, + {0,0,0,0,0,1,1,1,1,0,1,1,0,0}, + {0,0,0,0,0,0,0,0,0,1,1,1,0,0}, + {0,0,0,0,0,0,0,0,0,1,1,0,0,0}, + {0,0,0,0,0,0,0,0,1,1,1,0,0,0}, + {0,0,0,0,0,0,0,1,1,1,0,0,0,0}, + {0,0,0,0,0,0,1,1,1,0,0,0,0,0}, + {0,0,0,0,0,1,1,1,0,0,0,0,0,0}, + {0,0,0,0,0,1,1,0,0,0,0,0,0,0}, + {0,0,0,0,0,0,0,0,0,0,0,0,0,0}, + {0,0,0,0,0,0,0,0,0,0,0,0,0,0} + } + }; + + for ( row = 0; row < DIGIT_HEIGHT; row++ ) + for ( col = 0; col < DIGIT_WIDTH; col++ ) + bits[row0 + row][col0 + col] = digits[d][row][col]; + } + +#if __STDC__ +static int +addlines( int d, bit** bits, int row0, int col0, int height, bit color ) +#else /*__STDC__*/ +static int +addlines( d, bits, row0, col0, height, color ) + int d; + bit** bits; + int row0, col0, height; + bit color; +#endif /*__STDC__*/ + { + switch ( d ) + { + case 0: + col0 = rect( bits, row0, col0, height, LINE3_WIDTH, color ); + col0 = rect( bits, row0, col0, height, LINE2_WIDTH, 1 - color ); + col0 = rect( bits, row0, col0, height, LINE1_WIDTH, color ); + col0 = rect( bits, row0, col0, height, LINE1_WIDTH, 1 - color ); + break; + + case 1: + col0 = rect( bits, row0, col0, height, LINE2_WIDTH, color ); + col0 = rect( bits, row0, col0, height, LINE2_WIDTH, 1 - color ); + col0 = rect( bits, row0, col0, height, LINE2_WIDTH, color ); + col0 = rect( bits, row0, col0, height, LINE1_WIDTH, 1 - color ); + break; + + case 2: + col0 = rect( bits, row0, col0, height, LINE2_WIDTH, color ); + col0 = rect( bits, row0, col0, height, LINE1_WIDTH, 1 - color ); + col0 = rect( bits, row0, col0, height, LINE2_WIDTH, color ); + col0 = rect( bits, row0, col0, height, LINE2_WIDTH, 1 - color ); + break; + + case 3: + col0 = rect( bits, row0, col0, height, LINE1_WIDTH, color ); + col0 = rect( bits, row0, col0, height, LINE4_WIDTH, 1 - color ); + col0 = rect( bits, row0, col0, height, LINE1_WIDTH, color ); + col0 = rect( bits, row0, col0, height, LINE1_WIDTH, 1 - color ); + break; + + case 4: + col0 = rect( bits, row0, col0, height, LINE1_WIDTH, color ); + col0 = rect( bits, row0, col0, height, LINE1_WIDTH, 1 - color ); + col0 = rect( bits, row0, col0, height, LINE3_WIDTH, color ); + col0 = rect( bits, row0, col0, height, LINE2_WIDTH, 1 - color ); + break; + + case 5: + col0 = rect( bits, row0, col0, height, LINE1_WIDTH, color ); + col0 = rect( bits, row0, col0, height, LINE2_WIDTH, 1 - color ); + col0 = rect( bits, row0, col0, height, LINE3_WIDTH, color ); + col0 = rect( bits, row0, col0, height, LINE1_WIDTH, 1 - color ); + break; + + case 6: + col0 = rect( bits, row0, col0, height, LINE1_WIDTH, color ); + col0 = rect( bits, row0, col0, height, LINE1_WIDTH, 1 - color ); + col0 = rect( bits, row0, col0, height, LINE1_WIDTH, color ); + col0 = rect( bits, row0, col0, height, LINE4_WIDTH, 1 - color ); + break; + + case 7: + col0 = rect( bits, row0, col0, height, LINE1_WIDTH, color ); + col0 = rect( bits, row0, col0, height, LINE3_WIDTH, 1 - color ); + col0 = rect( bits, row0, col0, height, LINE1_WIDTH, color ); + col0 = rect( bits, row0, col0, height, LINE2_WIDTH, 1 - color ); + break; + + case 8: + col0 = rect( bits, row0, col0, height, LINE1_WIDTH, color ); + col0 = rect( bits, row0, col0, height, LINE2_WIDTH, 1 - color ); + col0 = rect( bits, row0, col0, height, LINE1_WIDTH, color ); + col0 = rect( bits, row0, col0, height, LINE3_WIDTH, 1 - color ); + break; + + case 9: + col0 = rect( bits, row0, col0, height, LINE3_WIDTH, color ); + col0 = rect( bits, row0, col0, height, LINE1_WIDTH, 1 - color ); + col0 = rect( bits, row0, col0, height, LINE1_WIDTH, color ); + col0 = rect( bits, row0, col0, height, LINE2_WIDTH, 1 - color ); + break; + + default: + pm_error( "can't happen" ); + } + + return col0; + } + +#if __STDC__ +static int +rect( bit** bits, int row0, int col0, int height, int width, bit color ) +#else /*__STDC__*/ +static int +rect( bits, row0, col0, height, width, color ) + bit** bits; + int row0, col0, height, width; + bit color; +#endif /*__STDC__*/ + { + int row, col; + + for ( row = row0; row < row0 + height; row++ ) + for ( col = col0; col < col0 + width; col++ ) + bits[row][col] = color; + return col0 + width; + } diff --git a/generator/pgmcrater.c b/generator/pgmcrater.c new file mode 100644 index 00000000..1833e604 --- /dev/null +++ b/generator/pgmcrater.c @@ -0,0 +1,383 @@ +/* + + Fractal cratering + + Designed and implemented in November of 1989 by: + + John Walker + Autodesk SA + Avenue des Champs-Montants 14b + CH-2074 MARIN + Switzerland + Usenet: kelvin@Autodesk.com + Fax: 038/33 88 15 + Voice: 038/33 76 33 + + The algorithm used to determine crater size is as described on + pages 31 and 32 of: + + Peitgen, H.-O., and Saupe, D. eds., The Science Of Fractal + Images, New York: Springer Verlag, 1988. + + The mathematical technique used to calculate crater radii that + obey the proper area law distribution from a uniformly distributed + pseudorandom sequence was developed by Rudy Rucker. + + Permission to use, copy, modify, and distribute this software and + its documentation for any purpose and without fee is hereby + granted, without any conditions or restrictions. This software is + provided "as is" without express or implied warranty. + + PLUGWARE! + + If you like this kind of stuff, you may also enjoy "James Gleick's + Chaos--The Software" for MS-DOS, available for $59.95 from your + local software store or directly from Autodesk, Inc., Attn: Science + Series, 2320 Marinship Way, Sausalito, CA 94965, USA. Telephone: + (800) 688-2344 toll-free or, outside the U.S. (415) 332-2344 Ext + 4886. Fax: (415) 289-4718. "Chaos--The Software" includes a more + comprehensive fractal forgery generator which creates + three-dimensional landscapes as well as clouds and planets, plus + five more modules which explore other aspects of Chaos. The user + guide of more than 200 pages includes an introduction by James + Gleick and detailed explanations by Rudy Rucker of the mathematics + and algorithms used by each program. + +*/ + +/* Modifications by Arjen Bax, 2001-06-21: Remove black vertical line at right + * edge. Make craters wrap around the image (enables tiling of image). + */ + +#define _XOPEN_SOURCE /* get M_PI in math.h */ + +#include <assert.h> +#include <math.h> + +#include "pm_c_util.h" +#include "pgm.h" +#include "mallocvar.h" + +static void gencraters ARGS((void)); +static void initseed ARGS((void)); + +/* Definitions for obtaining random numbers. */ + +#define Cast(low, high) ((low)+((high)-(low)) * ((rand() & 0x7FFF) / arand)) + +/* Data types */ + +typedef int Boolean; +#define FALSE 0 +#define TRUE 1 + +#define V (void) + +/* Display parameters */ + +#define SCRX screenxsize /* Screen width */ +#define SCRY screenysize /* Screen height */ +#define SCRGAMMA 1.0 /* Display gamma */ + +/* Local variables */ + +#define ImageGamma 0.5 /* Inherent gamma of mapped image */ + +static int screenxsize = 256; /* Screen X size */ +static int screenysize = 256; /* Screen Y size */ +static double dgamma = SCRGAMMA; /* Display gamma */ +static double arand = 32767.0; /* Random number parameters */ +static long ncraters = 50000L; /* Number of craters to generate */ +static double CdepthPower = 1.5; /* Crater depth power factor */ +static double DepthBias = 0.707107; /* Depth bias */ + +static int modulo(int t, int n) +{ + int m; + assert(n>0); + m = t % n; + while (m<0) { + m+=n; + } + return m; +} + +/* INITSEED -- Generate initial random seed, if needed. */ + +static void initseed() +{ + int i; + + i = time(NULL) * 0xF37C; + srand(i); + for (i = 0; i < 7; i++) + V rand(); +} + +/* GENCRATERS -- Generate cratered terrain. */ + +static void gencraters() +{ + int i, j, x, y; + long l; + unsigned short *aux; + int slopemin = -52, slopemax = 52; +#define RGBQuant 255 + unsigned char *slopemap; /* Slope to pixel map */ + gray *pixels; /* Pixel vector */ + +#define Auxadr(x, y) &aux[modulo(y, SCRY)*SCRX+modulo(x, SCRX)] + + /* Acquire the elevation array and initialize it to mean + surface elevation. */ + + MALLOCARRAY(aux, SCRX * SCRY); + if (aux == NULL) + pm_error("out of memory allocating elevation array"); + + /* Acquire the elevation buffer and initialize to mean + initial elevation. */ + + for (i = 0; i < SCRY; i++) { + unsigned short *zax = aux + (((long) SCRX) * i); + + for (j = 0; j < SCRX; j++) { + *zax++ = 32767; + } + } + + /* Every time we go around this loop we plop another crater + on the surface. */ + + for (l = 0; l < ncraters; l++) { + double g; + int cx = Cast(0.0, ((double) SCRX - 1)), + cy = Cast(0.0, ((double) SCRY - 1)), + gx, gy, x, y; + unsigned long amptot = 0, axelev; + unsigned int npatch = 0; + + + /* Phase 1. Compute the mean elevation of the impact + area. We assume the impact area is a + fraction of the total crater size. */ + + /* Thanks, Rudy, for this equation that maps the uniformly + distributed numbers from Cast into an area-law + distribution as observed on cratered bodies. */ + + g = sqrt(1 / (M_PI * (1 - Cast(0, 0.9999)))); + + /* If the crater is tiny, handle it specially. */ + +#if 0 + fprintf(stderr, "crater=%lu ", (unsigned long)l); + fprintf(stderr, "cx=%d ", cx); + fprintf(stderr, "cy=%d ", cy); + fprintf(stderr, "size=%g\n", g); +#endif + + if (g < 3) { + + /* Set pixel to the average of its Moore neighbourhood. */ + + for (y = cy - 1; y <= cy + 1; y++) { + for (x = cx - 1; x <= cx + 1; x++) { + amptot += *Auxadr(x, y); + npatch++; + } + } + axelev = amptot / npatch; + + /* Perturb the mean elevation by a small random factor. */ + + x = (g >= 1) ? ((rand() >> 8) & 3) - 1 : 0; + *Auxadr(cx, cy) = axelev + x; + + /* Jam repaint sizes to correct patch. */ + + gx = 1; + gy = 0; + + } else { + + /* Regular crater. Generate an impact feature of the + correct size and shape. */ + + /* Determine mean elevation around the impact area. */ + + gx = MAX(2, (g / 3)); + gy = MAX(2, g / 3); + + for (y = cy - gy; y <= cy + gy; y++) { + for (x = cx-gx; x <= cx + gx; x++) { + amptot += *Auxadr(x,y); + npatch++; + } + } + axelev = amptot / npatch; + + gy = MAX(2, g); + g = gy; + gx = MAX(2, g); + + for (y = cy - gy; y <= cy + gy; y++) { + double dy = (cy - y) / (double) gy, + dysq = dy * dy; + + for (x = cx - gx; x <= cx + gx; x++) { + double dx = ((cx - x) / (double) gx), + cd = (dx * dx) + dysq, + cd2 = cd * 2.25, + tcz = DepthBias - sqrt(fabs(1 - cd2)), + cz = MAX((cd2 > 1) ? 0.0 : -10, tcz), + roll, iroll; + unsigned short av; + + cz *= pow(g, CdepthPower); + if (dy == 0 && dx == 0 && ((int) cz) == 0) { + cz = cz < 0 ? -1 : 1; + } + +#define rollmin 0.9 + roll = (((1 / (1 - MIN(rollmin, cd))) / + (1 / (1 - rollmin))) - (1 - rollmin)) / rollmin; + iroll = 1 - roll; + + av = (axelev + cz) * iroll + (*Auxadr(x,y) + cz) * roll; + av = MAX(1000, MIN(64000, av)); + *Auxadr(x,y) = av; + } + } + } + if ((l % 5000) == 4999) { + pm_message( "%ld craters generated of %ld (%ld%% done)", + l + 1, ncraters, ((l + 1) * 100) / ncraters); + } + } + + i = MAX((slopemax - slopemin) + 1, 1); + MALLOCARRAY(slopemap, i); + if (slopemap == NULL) + pm_error("out of memory allocating slope map"); + + for (i = slopemin; i <= slopemax; i++) { + + /* Confused? OK, we're using the left-to-right slope to + calculate a shade based on the sine of the angle with + respect to the vertical (light incident from the left). + Then, with one exponentiation, we account for both the + inherent gamma of the image (ad-hoc), and the + user-specified display gamma, using the identity: + + (x^y)^z = (x^(y*z)) */ + + slopemap[i - slopemin] = i > 0 ? + (128 + 127.0 * + pow(sin((M_PI / 2) * i / slopemax), + dgamma * ImageGamma)) : + (128 - 127.0 * + pow(sin((M_PI / 2) * i / slopemin), + dgamma * ImageGamma)); + } + + /* Generate the screen image. */ + + pgm_writepgminit(stdout, SCRX, SCRY, RGBQuant, FALSE); + pixels = pgm_allocrow(SCRX); + + for (y = 0; y < SCRY; y++) { + gray *pix = pixels; + + for (x = 0; x < SCRX; x++) { + int j = *Auxadr(x+1, y) - *Auxadr(x, y); + j = MIN(MAX(slopemin, j), slopemax); + *pix++ = slopemap[j - slopemin]; + } + pgm_writepgmrow(stdout, pixels, SCRX, RGBQuant, FALSE); + } + pm_close(stdout); + pgm_freerow(pixels); + +#undef Auxadr +#undef Scradr + free((char *) slopemap); + free((char *) aux); +} + +/* MAIN -- Main program. */ + +int main(argc, argv) + int argc; + char *argv[]; +{ + int i; + Boolean gammaspec = FALSE, numspec = FALSE, + widspec = FALSE, hgtspec = FALSE; + const char * const usage = "[-number <n>] [-width|-xsize <w>]\n\ + [-height|-ysize <h>] [-gamma <f>]"; + + DepthBias = sqrt(0.5); /* Get exact value for depth bias */ + + + pgm_init(&argc, argv); + + i = 1; + while ((i < argc) && (argv[i][0] == '-') && (argv[i][1] != '\0')) { + if (pm_keymatch(argv[i], "-gamma", 2)) { + if (gammaspec) { + pm_error("already specified gamma correction"); + } + i++; + if ((i == argc) || (sscanf(argv[i], "%lf", &dgamma) != 1)) + pm_usage(usage); + if (dgamma <= 0.0) { + pm_error("gamma correction must be greater than 0"); + } + gammaspec = TRUE; + } else if (pm_keymatch(argv[i], "-number", 2)) { + if (numspec) { + pm_error("already specified number of craters"); + } + i++; + if ((i == argc) || (sscanf(argv[i], "%ld", &ncraters) != 1)) + pm_usage(usage); + if (ncraters <= 0) { + pm_error("number of craters must be greater than 0!"); + } + numspec = TRUE; + } else if (pm_keymatch(argv[i], "-xsize", 2) || + pm_keymatch(argv[i], "-width", 2)) { + if (widspec) { + pm_error("already specified a width/xsize"); + } + i++; + if ((i == argc) || (sscanf(argv[i], "%d", &screenxsize) != 1)) + pm_usage(usage); + if (screenxsize <= 0) { + pm_error("screen width must be greater than 0"); + } + widspec = TRUE; + } else if (pm_keymatch(argv[i], "-ysize", 2) || + pm_keymatch(argv[i], "-height", 2)) { + if (hgtspec) { + pm_error("already specified a height/ysize"); + } + i++; + if ((i == argc) || (sscanf(argv[i], "%d", &screenysize) != 1)) + pm_usage(usage); + if (screenxsize <= 0) { + pm_error("screen height must be greater than 0"); + } + hgtspec = TRUE; + } else { + pm_usage(usage); + } + i++; + } + + initseed(); + gencraters(); + + exit(0); +} diff --git a/generator/pgmkernel.c b/generator/pgmkernel.c new file mode 100644 index 00000000..b741d596 --- /dev/null +++ b/generator/pgmkernel.c @@ -0,0 +1,91 @@ +/* pgmkernel.c - generate a portable graymap convolution kernel +** +** Creates a Portable Graymap file containing a convolution filter +** with max value = 255 and minimum value > 127 that can be used as a +** smoothing kernel for pnmconvol. +** +** Copyright (C) 1992 by Alberto Accomazzi, Smithsonian Astrophysical +** Observatory. +** +** 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. +*/ + +#include <math.h> +#include "pgm.h" +#include "mallocvar.h" + +int +main ( argc, argv ) + int argc; + char *argv[]; +{ + register int i, j; + int argn = 1, ixsize, iysize, maxval = 255; + double fxsize = 0.0, fysize = 0.0, w = 6.0, kxcenter, kycenter, + tmax = 0, *fkernel; + const char *usage = "[-weight f] width [height]"; + + pgm_init( &argc, argv ); + + while ( argn < argc && argv[argn][0] == '-' && argv[argn][1] != '\0' ) + { + if ( pm_keymatch( argv[argn], "-weight", 2 )) { + if (++argn >= argc) + pm_usage( usage ); + else if (sscanf(argv[argn], "%lf", &w) != 1) + pm_usage( usage ); + } + else + pm_usage( usage ); + argn++; + } + + if (argn == argc) + pm_usage( usage ); + + if (sscanf(argv[argn], "%lf", &fxsize) != 1) + pm_error( "error reading input kernel x size, (%s)\n", argv[argn]); + + ++argn; + if (argn == argc - 1) { + if (sscanf(argv[argn], "%lf", &fysize) != 1) + pm_error( "error reading input kernel y size, (%s)\n", argv[argn]); + } + else if (argn == argc) + fysize = fxsize; + else + pm_usage( usage ); + + if (fxsize <= 1 || fysize <= 1) + pm_usage( usage ); + + kxcenter = (fxsize - 1) / 2.0; + kycenter = (fysize - 1) / 2.0; + ixsize = fxsize + 0.999; + iysize = fysize + 0.999; + MALLOCARRAY(fkernel, ixsize * iysize); + for (i = 0; i < iysize; i++) + for (j = 0; j < ixsize; j++) { + fkernel[i*ixsize+j] = 1.0 / (1.0 + w * sqrt((double) + (i-kycenter)*(i-kycenter)+ + (j-kxcenter)*(j-kxcenter))); + if (tmax < fkernel[i*ixsize+j]) + tmax = fkernel[i*ixsize+j]; + } + + /* output PGM header + data (ASCII format only) */ + printf("P2\n%d %d\n%d\n", ixsize, iysize, maxval); + + for (i = 0; i < iysize; i++, printf("\n")) + for (j = 0; j < ixsize; j++) + printf(" %3d", (int)(maxval * (fkernel[i*ixsize+j] / + (2*tmax) + 0.5))); + + exit(0); +} + diff --git a/generator/pgmmake.c b/generator/pgmmake.c new file mode 100644 index 00000000..42d96581 --- /dev/null +++ b/generator/pgmmake.c @@ -0,0 +1,111 @@ +#include "pm_c_util.h" +#include "mallocvar.h" +#include "shhopt.h" +#include "pgm.h" + +struct cmdlineInfo { + /* All the information the user supplied in the command line, + in a form easy for the program to use. + */ + gray grayLevel; + unsigned int cols; + unsigned int rows; + gray maxval; +}; + + + +static void +parseCommandLine(int argc, char ** argv, + struct cmdlineInfo * const cmdlineP) { +/*---------------------------------------------------------------------------- + Convert program invocation arguments (argc,argv) into a format the + program can use easily, struct cmdlineInfo. Validate arguments along + the way and exit program with message if invalid. + + Note that some string information we return as *cmdlineP is in the storage + argv[] points to. +-----------------------------------------------------------------------------*/ + optEntry * option_def; + /* Instructions to OptParseOptions3 on how to parse our options. + */ + optStruct3 opt; + + unsigned int maxvalSpec; + unsigned int option_def_index; + + MALLOCARRAY(option_def, 100); + + option_def_index = 0; /* incremented by OPTENTRY */ + OPTENT3(0, "maxval", OPT_UINT, &cmdlineP->maxval, &maxvalSpec, 0); + + opt.opt_table = option_def; + opt.short_allowed = false; /* We have no short (old-fashioned) options */ + opt.allowNegNum = false; /* We have no parms that are negative numbers */ + + optParseOptions3(&argc, argv, opt, sizeof(opt), 0); + /* Uses and sets argc, argv, and some of *cmdlineP and others. */ + + if (!maxvalSpec) + cmdlineP->maxval = PGM_MAXMAXVAL; + else { + if (cmdlineP->maxval > PGM_OVERALLMAXVAL) + pm_error("The value you specified for -maxval (%u) is too big. " + "Max allowed is %u", cmdlineP->maxval, PGM_OVERALLMAXVAL); + + if (cmdlineP->maxval < 1) + pm_error("You cannot specify 0 for -maxval"); + } + + if (argc-1 < 3) + pm_error("Need 3 arguments: gray level, width, height."); + else if (argc-1 > 3) + pm_error("Only 3 arguments allowed: gray level, width, height. " + "You specified %d", argc-1); + else { + double const grayLevel = atof(argv[1]); + if (grayLevel < 0.0) + pm_error("You can't have a negative gray level (%f)", grayLevel); + if (grayLevel > 1.0) + pm_error("Gray level must be in the range [0.0, 1.0]. " + "You specified %f", grayLevel); + cmdlineP->grayLevel = ROUNDU(grayLevel * cmdlineP->maxval); + cmdlineP->cols = atoi(argv[2]); + cmdlineP->rows = atoi(argv[3]); + if (cmdlineP->cols <= 0) + pm_error("width argument must be a positive number. You " + "specified '%s'", argv[2]); + if (cmdlineP->rows <= 0) + pm_error("height argument must be a positive number. You " + "specified '%s'", argv[3]); + } +} + + + +int +main(int argc, char *argv[]) { + + struct cmdlineInfo cmdline; + gray * grayrow; + unsigned int row; + + pgm_init(&argc, argv); + + parseCommandLine(argc, argv, &cmdline); + + pgm_writepgminit(stdout, cmdline.cols, cmdline.rows, cmdline.maxval, 0); + grayrow = pgm_allocrow(cmdline.cols); + + for (row = 0; row < cmdline.rows; ++row) { + unsigned int col; + for (col = 0; col < cmdline.cols; ++col) + grayrow[col] = cmdline.grayLevel; + pgm_writepgmrow(stdout, grayrow, cmdline.cols, cmdline.maxval, 0); + } + + pgm_freerow(grayrow); + pm_close(stdout); + + return 0; +} diff --git a/generator/pgmnoise.c b/generator/pgmnoise.c new file mode 100644 index 00000000..3929759b --- /dev/null +++ b/generator/pgmnoise.c @@ -0,0 +1,77 @@ + +/*********************************************************************/ +/* pgmnoise - create a portable graymap with white noise */ +/* Frank Neumann, October 1993 */ +/* V1.1 16.11.1993 */ +/* */ +/* version history: */ +/* V1.0 12.10.1993 first version */ +/* V1.1 16.11.1993 Rewritten to be NetPBM.programming conforming */ +/*********************************************************************/ + +#include "pgm.h" + +/* global variables */ +#ifdef AMIGA +static char *version = "$VER: pgmnoise 1.1 (16.11.93)"; /* Amiga version identification */ +#endif + +/**************************/ +/* start of main function */ +/**************************/ +int main(argc, argv) +int argc; +char *argv[]; +{ + int argn, rows, cols, i, j; + gray *destrow; + const char * const usage = "width height\n width and height are picture dimensions in pixels\n"; + time_t timenow; + + /* parse in 'default' parameters */ + pgm_init(&argc, argv); + + argn = 1; + + /* parse in dim factor */ + if (argn == argc) + pm_usage(usage); + if (sscanf(argv[argn], "%d", &cols) != 1) + pm_usage(usage); + argn++; + if (argn == argc) + pm_usage(usage); + if (sscanf(argv[argn], "%d", &rows) != 1) + pm_usage(usage); + + if (cols <= 0 || rows <= 0) + pm_error("picture dimensions should be positive numbers"); + ++argn; + + if (argn != argc) + pm_usage(usage); + + /* no error checking required here, ppmlib does it all for us */ + destrow = pgm_allocrow(cols); + + pgm_writepgminit(stdout, cols, rows, PGM_MAXMAXVAL, 0); + + /* get time of day to feed the random number generator */ + timenow = time(NULL); + srand(timenow); + + /* create the (gray) noise */ + for (i = 0; i < rows; i++) + { + for (j = 0; j < cols; j++) + destrow[j] = rand() % PGM_MAXMAXVAL; + + /* write out one line of graphic data */ + pgm_writepgmrow(stdout, destrow, cols, PGM_MAXMAXVAL, 0); + } + + pgm_freerow(destrow); + + exit(0); +} + diff --git a/generator/pgmramp.c b/generator/pgmramp.c new file mode 100644 index 00000000..b84ed345 --- /dev/null +++ b/generator/pgmramp.c @@ -0,0 +1,170 @@ +/* pgmramp.c - generate a grayscale ramp +** +** Copyright (C) 1989 by Jef Poskanzer. +** +** 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. +*/ + +#include <math.h> +#include "pgm.h" +#include "shhopt.h" + +enum ramptype {RT_LR, RT_TB, RT_RECT, RT_ELLIP}; + + +struct cmdlineInfo { + /* All the information the user supplied in the command line, + in a form easy for the program to use. + */ + enum ramptype ramptype; + unsigned int cols; + unsigned int rows; + gray maxval; +}; + + + +static void +parseCommandLine(int argc, char ** argv, + struct cmdlineInfo * const cmdlineP) { +/*---------------------------------------------------------------------------- + Convert program invocation arguments (argc,argv) into a format the + program can use easily, struct cmdlineInfo. Validate arguments along + the way and exit program with message if invalid. + + Note that some string information we return as *cmdlineP is in the storage + argv[] points to. +-----------------------------------------------------------------------------*/ + optEntry *option_def = malloc(100*sizeof(optEntry)); + /* Instructions to OptParseOptions2 on how to parse our options. + */ + optStruct3 opt; + + unsigned int lrSpec, tbSpec, rectangleSpec, ellipseSpec; + unsigned int maxvalSpec; + unsigned int option_def_index; + + option_def_index = 0; /* incremented by OPTENTRY */ + OPTENT3(0, "lr", OPT_FLAG, NULL, &lrSpec, 0); + OPTENT3(0, "tb", OPT_FLAG, NULL, &tbSpec, 0); + OPTENT3(0, "rectangle", OPT_FLAG, NULL, &rectangleSpec, 0); + OPTENT3(0, "ellipse", OPT_FLAG, NULL, &ellipseSpec, 0); + OPTENT3(0, "maxval", OPT_UINT, &cmdlineP->maxval, &maxvalSpec, 0); + + opt.opt_table = option_def; + opt.short_allowed = FALSE; /* We have no short (old-fashioned) options */ + opt.allowNegNum = FALSE; /* We have no parms that are negative numbers */ + + optParseOptions3(&argc, argv, opt, sizeof(opt), 0); + /* Uses and sets argc, argv, and some of *cmdlineP and others. */ + + if (lrSpec + tbSpec + rectangleSpec + ellipseSpec == 0) + pm_error("You must specify one of -lr, -tb, -rectangle, or -ellipse"); + if (lrSpec + tbSpec + rectangleSpec + ellipseSpec > 1) + pm_error("You may specify at most one of " + "-lr, -tb, -rectangle, or -ellipse"); + if (lrSpec) + cmdlineP->ramptype = RT_LR; + else if (tbSpec) + cmdlineP->ramptype = RT_TB; + else if (rectangleSpec) + cmdlineP->ramptype = RT_RECT; + else if (ellipseSpec) + cmdlineP->ramptype = RT_ELLIP; + else + pm_error("INTERNAL ERROR - no ramp type option found"); + + if (!maxvalSpec) + cmdlineP->maxval = PGM_MAXMAXVAL; + else { + if (cmdlineP->maxval > PGM_OVERALLMAXVAL) + pm_error("The value you specified for -maxval (%u) is too big. " + "Max allowed is %u", cmdlineP->maxval, PGM_OVERALLMAXVAL); + + if (cmdlineP->maxval < 1) + pm_error("You cannot specify 0 for -maxval"); + } + + if (argc-1 < 2) + pm_error("Need two arguments: width and height."); + else if (argc-1 > 2) + pm_error("Only two arguments allowed: width and height. " + "You specified %d", argc-1); + else { + cmdlineP->cols = atoi(argv[1]); + cmdlineP->rows = atoi(argv[2]); + if (cmdlineP->cols <= 0) + pm_error("width argument must be a positive number. You " + "specified '%s'", argv[1]); + if (cmdlineP->rows <= 0) + pm_error("height argument must be a positive number. You " + "specified '%s'", argv[2]); + } +} + + + +int +main(int argc, char *argv[]) { + + struct cmdlineInfo cmdline; + gray *grayrow; + int rowso2, colso2; + unsigned int row; + + pgm_init( &argc, argv ); + + parseCommandLine(argc, argv, &cmdline); + + colso2 = MAX(1, cmdline.cols / 2); + rowso2 = MAX(1, cmdline.rows / 2); + + pgm_writepgminit(stdout, cmdline.cols, cmdline.rows, cmdline.maxval, 0); + grayrow = pgm_allocrow(cmdline.cols); + + for (row = 0; row < cmdline.rows; ++row) { + unsigned int col; + for (col = 0; col < cmdline.cols; ++col) { + switch (cmdline.ramptype) { + case RT_LR: + grayrow[col] = + col * cmdline.maxval / MAX(cmdline.cols-1, 1); + break; + case RT_TB: + grayrow[col] = + row * cmdline.maxval / MAX(cmdline.rows-1, 1); + break; + + case RT_RECT: { + float const r = fabs((int)(rowso2 - row)) / rowso2; + float const c = fabs((int)(colso2 - col)) / colso2; + grayrow[col] = + cmdline.maxval - (r + c) / 2.0 * cmdline.maxval; + } + break; + + case RT_ELLIP: { + float const r = fabs((int)(rowso2 - row)) / rowso2; + float const c = fabs((int)(colso2 - col)) / colso2; + float v; + + v = r * r + c * c; + if ( v < 0.0 ) v = 0.0; + else if ( v > 1.0 ) v = 1.0; + grayrow[col] = cmdline.maxval - v * cmdline.maxval; + } + break; + } + } + pgm_writepgmrow(stdout, grayrow, cmdline.cols, cmdline.maxval, 0); + } + + pgm_freerow(grayrow); + pm_close(stdout); + return 0; +} diff --git a/generator/ppmcie.c b/generator/ppmcie.c new file mode 100644 index 00000000..fda0ab7c --- /dev/null +++ b/generator/ppmcie.c @@ -0,0 +1,1201 @@ +/* + + Generate a PPM file representing a CIE color gamut chart + + by John Walker -- kelvin@@fourmilab.ch + WWW home page: http://www.fourmilab.ch/ + + Permission to use, copy, modify, and distribute this software and + its documentation for any purpose and without fee is hereby + granted, without any conditions or restrictions. This software is + provided "as is" without express or implied warranty. + + This program was called cietoppm in Walker's original work. + Because "cie" is not a graphics format, Bryan changed the name + when he integrated it into the Netpbm package in March 2000. +*/ + +/* + Modified by + Andrew J. S. Hamilton 21 May 1999 + Andrew.Hamilton@Colorado.EDU + http://casa.colorado.edu/~ajsh/ + + Corrected XYZ -> RGB transform. + Introduced gamma correction. + Introduced option to plot 1976 u' v' chromaticities. +*/ + +#include <math.h> + +#include "pm_c_util.h" +#include "ppm.h" +#include "ppmdraw.h" +#include "nstring.h" + +#define CLAMP(v, l, h) ((v) < (l) ? (l) : (v) > (h) ? (h) : (v)) +#define TRUE 1 +#define FALSE 0 + +#define Maxval 255 /* Maxval to use in generated pixmaps */ + +/* A color system is defined by the CIE x and y coordinates of its + three primary illuminants and the x and y coordinates of the white + point. */ + +struct colorSystem { + const char *name; /* Color system name */ + double xRed, yRed, /* Red primary illuminant */ + xGreen, yGreen, /* Green primary illuminant */ + xBlue, yBlue, /* Blue primary illuminant */ + xWhite, yWhite, /* White point */ + gamma; /* gamma of nonlinear correction */ +}; + +/* The following table gives the CIE color matching functions + \bar{x}(\lambda), \bar{y}(\lambda), and \bar{z}(\lambda), for + wavelengths \lambda at 5 nanometre increments from 380 nm through + 780 nm. This table is used in conjunction with Planck's law for + the energy spectrum of a black body at a given temperature to plot + the black body curve on the CIE chart. */ + +static double cie_color_match[][3] = { + { 0.0014, 0.0000, 0.0065 }, /* 380 nm */ + { 0.0022, 0.0001, 0.0105 }, + { 0.0042, 0.0001, 0.0201 }, + { 0.0076, 0.0002, 0.0362 }, + { 0.0143, 0.0004, 0.0679 }, + { 0.0232, 0.0006, 0.1102 }, + { 0.0435, 0.0012, 0.2074 }, + { 0.0776, 0.0022, 0.3713 }, + { 0.1344, 0.0040, 0.6456 }, + { 0.2148, 0.0073, 1.0391 }, + { 0.2839, 0.0116, 1.3856 }, + { 0.3285, 0.0168, 1.6230 }, + { 0.3483, 0.0230, 1.7471 }, + { 0.3481, 0.0298, 1.7826 }, + { 0.3362, 0.0380, 1.7721 }, + { 0.3187, 0.0480, 1.7441 }, + { 0.2908, 0.0600, 1.6692 }, + { 0.2511, 0.0739, 1.5281 }, + { 0.1954, 0.0910, 1.2876 }, + { 0.1421, 0.1126, 1.0419 }, + { 0.0956, 0.1390, 0.8130 }, + { 0.0580, 0.1693, 0.6162 }, + { 0.0320, 0.2080, 0.4652 }, + { 0.0147, 0.2586, 0.3533 }, + { 0.0049, 0.3230, 0.2720 }, + { 0.0024, 0.4073, 0.2123 }, + { 0.0093, 0.5030, 0.1582 }, + { 0.0291, 0.6082, 0.1117 }, + { 0.0633, 0.7100, 0.0782 }, + { 0.1096, 0.7932, 0.0573 }, + { 0.1655, 0.8620, 0.0422 }, + { 0.2257, 0.9149, 0.0298 }, + { 0.2904, 0.9540, 0.0203 }, + { 0.3597, 0.9803, 0.0134 }, + { 0.4334, 0.9950, 0.0087 }, + { 0.5121, 1.0000, 0.0057 }, + { 0.5945, 0.9950, 0.0039 }, + { 0.6784, 0.9786, 0.0027 }, + { 0.7621, 0.9520, 0.0021 }, + { 0.8425, 0.9154, 0.0018 }, + { 0.9163, 0.8700, 0.0017 }, + { 0.9786, 0.8163, 0.0014 }, + { 1.0263, 0.7570, 0.0011 }, + { 1.0567, 0.6949, 0.0010 }, + { 1.0622, 0.6310, 0.0008 }, + { 1.0456, 0.5668, 0.0006 }, + { 1.0026, 0.5030, 0.0003 }, + { 0.9384, 0.4412, 0.0002 }, + { 0.8544, 0.3810, 0.0002 }, + { 0.7514, 0.3210, 0.0001 }, + { 0.6424, 0.2650, 0.0000 }, + { 0.5419, 0.2170, 0.0000 }, + { 0.4479, 0.1750, 0.0000 }, + { 0.3608, 0.1382, 0.0000 }, + { 0.2835, 0.1070, 0.0000 }, + { 0.2187, 0.0816, 0.0000 }, + { 0.1649, 0.0610, 0.0000 }, + { 0.1212, 0.0446, 0.0000 }, + { 0.0874, 0.0320, 0.0000 }, + { 0.0636, 0.0232, 0.0000 }, + { 0.0468, 0.0170, 0.0000 }, + { 0.0329, 0.0119, 0.0000 }, + { 0.0227, 0.0082, 0.0000 }, + { 0.0158, 0.0057, 0.0000 }, + { 0.0114, 0.0041, 0.0000 }, + { 0.0081, 0.0029, 0.0000 }, + { 0.0058, 0.0021, 0.0000 }, + { 0.0041, 0.0015, 0.0000 }, + { 0.0029, 0.0010, 0.0000 }, + { 0.0020, 0.0007, 0.0000 }, + { 0.0014, 0.0005, 0.0000 }, + { 0.0010, 0.0004, 0.0000 }, + { 0.0007, 0.0002, 0.0000 }, + { 0.0005, 0.0002, 0.0000 }, + { 0.0003, 0.0001, 0.0000 }, + { 0.0002, 0.0001, 0.0000 }, + { 0.0002, 0.0001, 0.0000 }, + { 0.0001, 0.0000, 0.0000 }, + { 0.0001, 0.0000, 0.0000 }, + { 0.0001, 0.0000, 0.0000 }, + { 0.0000, 0.0000, 0.0000 } /* 780 nm */ +}; + +/* The following table gives the spectral chromaticity co-ordinates + x(\lambda) and y(\lambda) for wavelengths in 5 nanometre increments + from 380 nm through 780 nm. These co-ordinates represent the + position in the CIE x-y space of pure spectral colors of the given + wavelength, and thus define the outline of the CIE "tongue" + diagram. */ + +static double spectral_chromaticity[81][3] = { + { 0.1741, 0.0050 }, /* 380 nm */ + { 0.1740, 0.0050 }, + { 0.1738, 0.0049 }, + { 0.1736, 0.0049 }, + { 0.1733, 0.0048 }, + { 0.1730, 0.0048 }, + { 0.1726, 0.0048 }, + { 0.1721, 0.0048 }, + { 0.1714, 0.0051 }, + { 0.1703, 0.0058 }, + { 0.1689, 0.0069 }, + { 0.1669, 0.0086 }, + { 0.1644, 0.0109 }, + { 0.1611, 0.0138 }, + { 0.1566, 0.0177 }, + { 0.1510, 0.0227 }, + { 0.1440, 0.0297 }, + { 0.1355, 0.0399 }, + { 0.1241, 0.0578 }, + { 0.1096, 0.0868 }, + { 0.0913, 0.1327 }, + { 0.0687, 0.2007 }, + { 0.0454, 0.2950 }, + { 0.0235, 0.4127 }, + { 0.0082, 0.5384 }, + { 0.0039, 0.6548 }, + { 0.0139, 0.7502 }, + { 0.0389, 0.8120 }, + { 0.0743, 0.8338 }, + { 0.1142, 0.8262 }, + { 0.1547, 0.8059 }, + { 0.1929, 0.7816 }, + { 0.2296, 0.7543 }, + { 0.2658, 0.7243 }, + { 0.3016, 0.6923 }, + { 0.3373, 0.6589 }, + { 0.3731, 0.6245 }, + { 0.4087, 0.5896 }, + { 0.4441, 0.5547 }, + { 0.4788, 0.5202 }, + { 0.5125, 0.4866 }, + { 0.5448, 0.4544 }, + { 0.5752, 0.4242 }, + { 0.6029, 0.3965 }, + { 0.6270, 0.3725 }, + { 0.6482, 0.3514 }, + { 0.6658, 0.3340 }, + { 0.6801, 0.3197 }, + { 0.6915, 0.3083 }, + { 0.7006, 0.2993 }, + { 0.7079, 0.2920 }, + { 0.7140, 0.2859 }, + { 0.7190, 0.2809 }, + { 0.7230, 0.2770 }, + { 0.7260, 0.2740 }, + { 0.7283, 0.2717 }, + { 0.7300, 0.2700 }, + { 0.7311, 0.2689 }, + { 0.7320, 0.2680 }, + { 0.7327, 0.2673 }, + { 0.7334, 0.2666 }, + { 0.7340, 0.2660 }, + { 0.7344, 0.2656 }, + { 0.7346, 0.2654 }, + { 0.7347, 0.2653 }, + { 0.7347, 0.2653 }, + { 0.7347, 0.2653 }, + { 0.7347, 0.2653 }, + { 0.7347, 0.2653 }, + { 0.7347, 0.2653 }, + { 0.7347, 0.2653 }, + { 0.7347, 0.2653 }, + { 0.7347, 0.2653 }, + { 0.7347, 0.2653 }, + { 0.7347, 0.2653 }, + { 0.7347, 0.2653 }, + { 0.7347, 0.2653 }, + { 0.7347, 0.2653 }, + { 0.7347, 0.2653 }, + { 0.7347, 0.2653 }, + { 0.7347, 0.2653 } /* 780 nm */ +}; + +static pixel **pixels; /* Pixel map */ +static int pixcols, pixrows; /* Pixel map size */ +static int sxsize = 512, sysize = 512; /* X, Y size */ + +/* Standard white point chromaticities. */ + +#define IlluminantC 0.3101, 0.3162 /* For NTSC television */ +#define IlluminantD65 0.3127, 0.3291 /* For EBU and SMPTE */ + +/* Gamma of nonlinear correction. + See Charles Poynton's ColorFAQ Item 45 and GammaFAQ Item 6 at + http://www.inforamp.net/~poynton/ColorFAQ.html + http://www.inforamp.net/~poynton/GammaFAQ.html +*/ + +#define GAMMA_REC709 0. /* Rec. 709 */ + +static struct colorSystem const + NTSCsystem = { + "NTSC", + 0.67, 0.33, 0.21, 0.71, 0.14, 0.08, + IlluminantC, GAMMA_REC709 + }, + EBUsystem = { + "EBU (PAL/SECAM)", + 0.64, 0.33, 0.29, 0.60, 0.15, 0.06, + IlluminantD65, GAMMA_REC709 + }, + SMPTEsystem = { + "SMPTE", + 0.630, 0.340, 0.310, 0.595, 0.155, 0.070, + IlluminantD65, GAMMA_REC709 + }, + HDTVsystem = { + "HDTV", + 0.670, 0.330, 0.210, 0.710, 0.150, 0.060, + IlluminantD65, GAMMA_REC709 + }, + /* Huh? -ajsh + CIEsystem = { + "CIE", + 0.7355, 0.2645, 0.2658, 0.7243, 0.1669, 0.0085, + 0.3894, 0.3324, GAMMA_REC709 + }, + */ + CIEsystem = { + "CIE", + 0.7355,0.2645,0.2658,0.7243,0.1669,0.0085, 0.33333333, 0.33333333, + GAMMA_REC709 + }, + Rec709system = { + "CIE REC 709", + 0.64, 0.33, 0.30, 0.60, 0.15, 0.06, + IlluminantD65, GAMMA_REC709 + }; + +/* Customsystem is a variable that is initialized to CIE Rec 709, but + we modify it with information specified by the user's options. +*/ +static struct colorSystem Customsystem = { + "Custom", + 0.64, 0.33, 0.30, 0.60, 0.15, 0.06, + IlluminantD65, GAMMA_REC709 +}; + + + +static void +upvp_to_xy(double const up, + double const vp, + double * const xc, + double * const yc) { +/*---------------------------------------------------------------------------- + Given 1976 coordinates u', v', determine 1931 chromaticities x, y +-----------------------------------------------------------------------------*/ + *xc = 9*up / (6*up - 16*vp + 12); + *yc = 4*vp / (6*up - 16*vp + 12); +} + + + +static void +xy_to_upvp(double const xc, + double const yc, + double * const up, + double * const vp) { +/*---------------------------------------------------------------------------- + Given 1931 chromaticities x, y, determine 1976 coordinates u', v' +-----------------------------------------------------------------------------*/ + *up = 4*xc / (- 2*xc + 12*yc + 3); + *vp = 9*yc / (- 2*xc + 12*yc + 3); +} + + + +static void +xyz_to_rgb(const struct colorSystem * const cs, + double const xc, + double const yc, + double const zc, + double * const r, + double * const g, + double * const b) { +/*---------------------------------------------------------------------------- + Given an additive tricolor system CS, defined by the CIE x and y + chromaticities of its three primaries (z is derived trivially as + 1-(x+y)), and a desired chromaticity (XC, YC, ZC) in CIE space, + determine the contribution of each primary in a linear combination + which sums to the desired chromaticity. If the requested + chromaticity falls outside the Maxwell triangle (color gamut) + formed by the three primaries, one of the r, g, or b weights will + be negative. + + Caller can use constrain_rgb() to desaturate an outside-gamut + color to the closest representation within the available + gamut. +-----------------------------------------------------------------------------*/ + double xr, yr, zr, xg, yg, zg, xb, yb, zb; + double xw, yw, zw; + double rx, ry, rz, gx, gy, gz, bx, by, bz; + double rw, gw, bw; + + xr = cs->xRed; yr = cs->yRed; zr = 1 - (xr + yr); + xg = cs->xGreen; yg = cs->yGreen; zg = 1 - (xg + yg); + xb = cs->xBlue; yb = cs->yBlue; zb = 1 - (xb + yb); + + xw = cs->xWhite; yw = cs->yWhite; zw = 1 - (xw + yw); + + /* xyz -> rgb matrix, before scaling to white. */ + rx = yg*zb - yb*zg; ry = xb*zg - xg*zb; rz = xg*yb - xb*yg; + gx = yb*zr - yr*zb; gy = xr*zb - xb*zr; gz = xb*yr - xr*yb; + bx = yr*zg - yg*zr; by = xg*zr - xr*zg; bz = xr*yg - xg*yr; + + /* White scaling factors. + Dividing by yw scales the white luminance to unity, as conventional. */ + rw = (rx*xw + ry*yw + rz*zw) / yw; + gw = (gx*xw + gy*yw + gz*zw) / yw; + bw = (bx*xw + by*yw + bz*zw) / yw; + + /* xyz -> rgb matrix, correctly scaled to white. */ + rx = rx / rw; ry = ry / rw; rz = rz / rw; + gx = gx / gw; gy = gy / gw; gz = gz / gw; + bx = bx / bw; by = by / bw; bz = bz / bw; + + /* rgb of the desired point */ + *r = rx*xc + ry*yc + rz*zc; + *g = gx*xc + gy*yc + gz*zc; + *b = bx*xc + by*yc + bz*zc; +} + + + +static int +constrain_rgb(double * const r, + double * const g, + double * const b) { +/*---------------------------------------------------------------------------- + If the requested RGB shade contains a negative weight for one of + the primaries, it lies outside the color gamut accessible from + the given triple of primaries. Desaturate it by adding white, + equal quantities of R, G, and B, enough to make RGB all positive. +-----------------------------------------------------------------------------*/ + double w; + + /* Amount of white needed is w = - min(0, *r, *g, *b) */ + w = (0 < *r) ? 0 : *r; + w = (w < *g) ? w : *g; + w = (w < *b) ? w : *b; + w = - w; + + /* Add just enough white to make r, g, b all positive. */ + if (w > 0) { + *r += w; *g += w; *b += w; + + return 1; /* Color modified to fit RGB gamut */ + } + + return 0; /* Color within RGB gamut */ +} + + + +static void +gamma_correct(const struct colorSystem * const cs, + double * const c) { +/*---------------------------------------------------------------------------- + Transform linear RGB values to nonlinear RGB values. + + Rec. 709 is ITU-R Recommendation BT. 709 (1990) + ``Basic Parameter Values for the HDTV Standard for the Studio and for + International Programme Exchange'', formerly CCIR Rec. 709. + + For details see + http://www.inforamp.net/~poynton/ColorFAQ.html + http://www.inforamp.net/~poynton/GammaFAQ.html +-----------------------------------------------------------------------------*/ + double gamma; + + gamma = cs->gamma; + + if (gamma == 0.) { + /* Rec. 709 gamma correction. */ + double cc = 0.018; + if (*c < cc) { + *c *= (1.099 * pow(cc, 0.45) - 0.099) / cc; + } else { + *c = 1.099 * pow(*c, 0.45) - 0.099; + } + } else { + /* Nonlinear color = (Linear color)^(1/gamma) */ + *c = pow(*c, 1./gamma); + } +} + + + +static void +gamma_correct_rgb(const struct colorSystem * const cs, + double * const r, + double * const g, + double * const b) { + gamma_correct(cs, r); + gamma_correct(cs, g); + gamma_correct(cs, b); +} + + + +#define Sz(x) (((x) * MIN(pixcols, pixrows)) / 512) +#define B(x, y) ((x) + xBias), (y) +#define Bixels(y, x) pixels[y][x + xBias] + + + +static void +computeMonochromeColorLocation( + double const waveLength, + int const pxcols, + int const pxrows, + bool const upvp, + int * const xP, + int * const yP) { + + int const ix = (waveLength - 380) / 5; + double const px = spectral_chromaticity[ix][0]; + double const py = spectral_chromaticity[ix][1]; + + if (upvp) { + double up, vp; + xy_to_upvp(px, py, &up, &vp); + *xP = up * (pxcols - 1); + *yP = (pxrows - 1) - vp * (pxrows - 1); + } else { + *xP = px * (pxcols - 1); + *yP = (pxrows - 1) - py * (pxrows - 1); + } +} + + + +static void +makeAllBlack(pixel ** const pixels, + unsigned int const cols, + unsigned int const rows) { + + unsigned int row; + for (row = 0; row < rows; ++row) { + unsigned int col; + for (col = 0; col < cols; ++col) + PPM_ASSIGN(pixels[row][col], 0, 0, 0); + } +} + + + +static void +drawTongueOutline(pixel ** const pixels, + int const pixcols, + int const pixrows, + pixval const maxval, + bool const upvp, + int const xBias, + int const yBias) { + + int const pxcols = pixcols - xBias; + int const pxrows = pixrows - yBias; + + pixel rgbcolor; + int wavelength; + int lx, ly; + int fx, fy; + + PPM_ASSIGN(rgbcolor, maxval, maxval, maxval); + + for (wavelength = 380; wavelength <= 700; wavelength += 5) { + int icx, icy; + + computeMonochromeColorLocation(wavelength, pxcols, pxrows, upvp, + &icx, &icy); + + if (wavelength > 380) + ppmd_line(pixels, pixcols, pixrows, Maxval, + B(lx, ly), B(icx, icy), + PPMD_NULLDRAWPROC, (char *) &rgbcolor); + else { + fx = icx; + fy = icy; + } + lx = icx; + ly = icy; + } + ppmd_line(pixels, pixcols, pixrows, maxval, + B(lx, ly), B(fx, fy), + PPMD_NULLDRAWPROC, (char *) &rgbcolor); +} + + + +static void +findTongue(pixel ** const pixels, + int const pxcols, + int const xBias, + int const row, + bool * const presentP, + int * const leftEdgeP, + int * const rightEdgeP) { +/*---------------------------------------------------------------------------- + Find out if there is any tongue on row 'row' of image 'pixels', and if + so, where. + + We assume the image consists of all black except a white outline of the + tongue. +-----------------------------------------------------------------------------*/ + int i; + + for (i = 0; + i < pxcols && PPM_GETR(Bixels(row, i)) == 0; + ++i); + + if (i >= pxcols) + *presentP = FALSE; + else { + int j; + int const leftEdge = i; + + *presentP = TRUE; + + for (j = pxcols - 1; + j >= leftEdge && PPM_GETR(Bixels(row, j)) == 0; + --j); + + *rightEdgeP = j; + *leftEdgeP = leftEdge; + } +} + + + +static void +fillInTongue(pixel ** const pixels, + int const pixcols, + int const pixrows, + pixval const maxval, + const struct colorSystem * const cs, + bool const upvp, + int const xBias, + int const yBias, + bool const highlightGamut) { + + int const pxcols = pixcols - xBias; + int const pxrows = pixrows - yBias; + + int y; + + /* Scan the image line by line and fill the tongue outline + with the RGB values determined by the color system for the x-y + co-ordinates within the tongue. + */ + + for (y = 0; y < pxrows; ++y) { + bool present; /* There is some tongue on this line */ + int leftEdge; /* x position of leftmost pixel in tongue on this line */ + int rightEdge; /* same, but rightmost */ + + findTongue(pixels, pxcols, xBias, y, &present, &leftEdge, &rightEdge); + + if (present) { + int x; + + for (x = leftEdge; x <= rightEdge; ++x) { + double cx, cy, cz, jr, jg, jb, jmax; + int r, g, b, mx; + + if (upvp) { + double up, vp; + up = ((double) x) / (pxcols - 1); + vp = 1.0 - ((double) y) / (pxrows - 1); + upvp_to_xy(up, vp, &cx, &cy); + cz = 1.0 - (cx + cy); + } else { + cx = ((double) x) / (pxcols - 1); + cy = 1.0 - ((double) y) / (pxrows - 1); + cz = 1.0 - (cx + cy); + } + + xyz_to_rgb(cs, cx, cy, cz, &jr, &jg, &jb); + + mx = Maxval; + + /* Check whether the requested color is within the + gamut achievable with the given color system. If + not, draw it in a reduced intensity, interpolated + by desaturation to the closest within-gamut color. */ + + if (constrain_rgb(&jr, &jg, &jb)) { + mx = highlightGamut ? Maxval : ((Maxval + 1) * 3) / 4; + } + /* Scale to max(rgb) = 1. */ + jmax = MAX(jr, MAX(jg, jb)); + if (jmax > 0) { + jr = jr / jmax; + jg = jg / jmax; + jb = jb / jmax; + } + /* gamma correct from linear rgb to nonlinear rgb. */ + gamma_correct_rgb(cs, &jr, &jg, &jb); + r = mx * jr; + g = mx * jg; + b = mx * jb; + PPM_ASSIGN(Bixels(y, x), (pixval) r, (pixval) g, (pixval) b); + } + } + } +} + + + +static void +drawAxes(pixel ** const pixels, + int const pixcols, + int const pixrows, + pixval const maxval, + bool const upvp, + int const xBias, + int const yBias) { + + int const pxcols = pixcols - xBias; + int const pxrows = pixrows - yBias; + + pixel rgbcolor; /* Color of axes */ + int i; + + PPM_ASSIGN(rgbcolor, maxval, maxval, maxval); + ppmd_line(pixels, pixcols, pixrows, maxval, + B(0, 0), B(0, pxrows - 1), PPMD_NULLDRAWPROC, + (char *) &rgbcolor); + ppmd_line(pixels, pixcols, pixrows, maxval, + B(0, pxrows - 1), B(pxcols - 1, pxrows - 1), + PPMD_NULLDRAWPROC, (char *) &rgbcolor); + + /* Draw tick marks on X and Y axes every 0.1 units. Also + label axes. + */ + + for (i = 1; i <= 9; i += 1) { + char s[20]; + + /* X axis tick */ + + sprintf(s, "0.%d", i); + ppmd_line(pixels, pixcols, pixrows, maxval, + B((i * (pxcols - 1)) / 10, pxrows - Sz(1)), + B((i * (pxcols - 1)) / 10, pxrows - Sz(4)), + PPMD_NULLDRAWPROC, (char *) &rgbcolor); + ppmd_text(pixels, pixcols, pixrows, maxval, + B((i * (pxcols - 1)) / 10 - Sz(11), pxrows + Sz(12)), + Sz(10), 0, s, PPMD_NULLDRAWPROC, (char *) &rgbcolor); + + /* Y axis tick */ + + sprintf(s, "0.%d", 10 - i); + ppmd_line(pixels, pixcols, pixrows, maxval, + B(0, (i * (pxrows - 1)) / 10), + B(Sz(3), (i * (pxrows - 1)) / 10), + PPMD_NULLDRAWPROC, (char *) &rgbcolor); + + ppmd_text(pixels, pixcols, pixrows, maxval, + B(Sz(-30), (i * (pxrows - 1)) / 10 + Sz(5)), + Sz(10), 0, s, + PPMD_NULLDRAWPROC, (char *) &rgbcolor); + } + ppmd_text(pixels, pixcols, pixrows, maxval, + B((98 * (pxcols - 1)) / 100 - Sz(11), pxrows + Sz(12)), + Sz(10), 0, (upvp ? "u'" : "x"), + PPMD_NULLDRAWPROC, (char *) &rgbcolor); + ppmd_text(pixels, pixcols, pixrows, maxval, + B(Sz(-22), (2 * (pxrows - 1)) / 100 + Sz(5)), + Sz(10), 0, (upvp ? "v'" : "y"), + PPMD_NULLDRAWPROC, (char *) &rgbcolor); +} + + + +static void +plotWhitePoint(pixel ** const pixels, + int const pixcols, + int const pixrows, + pixval const maxval, + const struct colorSystem * const cs, + bool const upvp, + int const xBias, + int const yBias) { + + int const pxcols = pixcols - xBias; + int const pxrows = pixrows - yBias; + + int wx, wy; + + pixel rgbcolor; /* Color of the white point mark */ + + PPM_ASSIGN(rgbcolor, 0, 0, 0); + + if (upvp) { + double wup, wvp; + xy_to_upvp(cs->xWhite, cs->yWhite, &wup, &wvp); + wx = wup; + wy = wvp; + wx = (pxcols - 1) * wup; + wy = (pxrows - 1) - ((int) ((pxrows - 1) * wvp)); + } else { + wx = (pxcols - 1) * cs->xWhite; + wy = (pxrows - 1) - ((int) ((pxrows - 1) * cs->yWhite)); + } + + PPM_ASSIGN(rgbcolor, 0, 0, 0); + /* We draw the four arms of the cross separately so as to + leave the pixel representing the precise white point + undisturbed. + */ + ppmd_line(pixels, pixcols, pixrows, maxval, + B(wx + Sz(3), wy), B(wx + Sz(10), wy), + PPMD_NULLDRAWPROC, (char *) &rgbcolor); + ppmd_line(pixels, pixcols, pixrows, maxval, + B(wx - Sz(3), wy), B(wx - Sz(10), wy), + PPMD_NULLDRAWPROC, (char *) &rgbcolor); + ppmd_line(pixels, pixcols, pixrows, maxval, + B(wx, wy + Sz(3)), B(wx, wy + Sz(10)), + PPMD_NULLDRAWPROC, (char *) &rgbcolor); + ppmd_line(pixels, pixcols, pixrows, maxval, + B(wx, wy - Sz(3)), B(wx, wy - Sz(10)), + PPMD_NULLDRAWPROC, (char *) &rgbcolor); +} + + + +static void +plotBlackBodyCurve(pixel ** const pixels, + int const pixcols, + int const pixrows, + pixval const maxval, + bool const upvp, + int const xBias, + int const yBias) { + + int const pxcols = pixcols - xBias; + int const pxrows = pixrows - yBias; + + double t; /* temperature of black body */ + pixel rgbcolor; /* color of the curve */ + int lx, ly; /* Last point we've drawn on curve so far */ + + PPM_ASSIGN(rgbcolor, 0, 0, 0); + + /* Plot black body curve from 1000 to 30000 kelvins. */ + + for (t = 1000.0; t < 30000.0; t += 50.0) { + double lambda, X = 0, Y = 0, Z = 0; + int xb, yb; + int ix; + + /* Determine X, Y, and Z for blackbody by summing color + match functions over the visual range. */ + + for (ix = 0, lambda = 380; lambda <= 780.0; ix++, lambda += 5) { + double Me; + + /* Evaluate Planck's black body equation for the + power at this wavelength. */ + + Me = 3.74183e-16 * pow(lambda * 1e-9, -5.0) / + (exp(1.4388e-2/(lambda * 1e-9 * t)) - 1.0); + X += Me * cie_color_match[ix][0]; + Y += Me * cie_color_match[ix][1]; + Z += Me * cie_color_match[ix][2]; + } + if (upvp) { + double up, vp; + xy_to_upvp(X / (X + Y + Z), Y / (X + Y + Z), &up, &vp); + xb = (pxcols - 1) * up; + yb = (pxrows - 1) - ((pxrows - 1) * vp); + } else { + xb = (pxcols - 1) * X / (X + Y + Z); + yb = (pxrows - 1) - ((pxrows - 1) * Y / (X + Y + Z)); + } + + if (t > 1000) { + ppmd_line(pixels, pixcols, pixrows, Maxval, + B(lx, ly), B(xb, yb), PPMD_NULLDRAWPROC, + (char *) &rgbcolor); + + /* Draw tick mark every 1000 kelvins */ + + if ((((int) t) % 1000) == 0) { + ppmd_line(pixels, pixcols, pixrows, Maxval, + B(lx, ly - Sz(2)), B(lx, ly + Sz(2)), + PPMD_NULLDRAWPROC, (char *) &rgbcolor); + + /* Label selected tick marks with decreasing density. */ + + if (t <= 5000.1 || (t > 5000.0 && + ((((int) t) % 5000) == 0) && + t != 20000.0)) { + char bb[20]; + + sprintf(bb, "%g", t); + ppmd_text(pixels, pixcols, pixrows, Maxval, + B(lx - Sz(12), ly - Sz(4)), Sz(6), 0, bb, + PPMD_NULLDRAWPROC, (char *) &rgbcolor); + } + + } + } + lx = xb; + ly = yb; + } +} + + + +static bool +overlappingLegend(bool const upvp, + int const waveLength) { + + bool retval; + + if (upvp) + retval = (waveLength == 430 || waveLength == 640); + else + retval = (waveLength == 460 || waveLength == 630 || waveLength == 640); + return retval; +} + + + +static void +plotMonochromeWavelengths( + pixel ** const pixels, + int const pixcols, + int const pixrows, + pixval const maxval, + const struct colorSystem * const cs, + bool const upvp, + int const xBias, + int const yBias) { + + int const pxcols = pixcols - xBias; + int const pxrows = pixrows - yBias; + + int x; /* The wavelength we're plotting */ + + for (x = (upvp? 420 : 450); + x <= 650; + x += (upvp? 10 : (x > 470 && x < 600) ? 5 : 10)) { + + pixel rgbcolor; + + /* Ick. Drop legends that overlap and twiddle position + so they appear at reasonable positions with respect to + the tongue. + */ + + if (!overlappingLegend(upvp, x)) { + double cx, cy, cz, jr, jg, jb, jmax; + char wl[20]; + int bx = 0, by = 0, tx, ty, r, g, b; + int icx, icy; /* Location of the color on the tongue */ + + if (x < 520) { + bx = Sz(-22); + by = Sz(2); + } else if (x < 535) { + bx = Sz(-8); + by = Sz(-6); + } else { + bx = Sz(4); + } + + computeMonochromeColorLocation(x, pxcols, pxrows, upvp, + &icx, &icy); + + /* Draw the tick mark */ + PPM_ASSIGN(rgbcolor, maxval, maxval, maxval); + tx = icx + ((x < 520) ? Sz(-2) : ((x >= 535) ? Sz(2) : 0)); + ty = icy + ((x < 520) ? 0 : ((x >= 535) ? Sz(-1) : Sz(-2))); + ppmd_line(pixels, pixcols, pixrows, Maxval, + B(icx, icy), B(tx, ty), + PPMD_NULLDRAWPROC, (char *) &rgbcolor); + + /* The following flailing about sets the drawing color to + the hue corresponding to the pure wavelength (constrained + to the display gamut). */ + + if (upvp) { + double up, vp; + up = ((double) icx) / (pxcols - 1); + vp = 1.0 - ((double) icy) / (pxrows - 1); + upvp_to_xy(up, vp, &cx, &cy); + cz = 1.0 - (cx + cy); + } else { + cx = ((double) icx) / (pxcols - 1); + cy = 1.0 - ((double) icy) / (pxrows - 1); + cz = 1.0 - (cx + cy); + } + + xyz_to_rgb(cs, cx, cy, cz, &jr, &jg, &jb); + (void) constrain_rgb(&jr, &jg, &jb); + + /* Scale to max(rgb) = 1 */ + jmax = MAX(jr, MAX(jg, jb)); + if (jmax > 0) { + jr = jr / jmax; + jg = jg / jmax; + jb = jb / jmax; + } + /* gamma correct from linear rgb to nonlinear rgb. */ + gamma_correct_rgb(cs, &jr, &jg, &jb); + r = Maxval * jr; + g = Maxval * jg; + b = Maxval * jb; + PPM_ASSIGN(rgbcolor, (pixval) r, (pixval) g, (pixval) b); + + sprintf(wl, "%d", x); + ppmd_text(pixels, pixcols, pixrows, Maxval, + B(icx + bx, icy + by), Sz(6), 0, wl, + PPMD_NULLDRAWPROC, (char *) &rgbcolor); + } + } +} + + + +static void +writeLabel(pixel ** const pixels, + int const pixcols, + int const pixrows, + pixval const maxval, + const struct colorSystem * const cs) { + + pixel rgbcolor; /* color of text */ + char sysdesc[256]; + + PPM_ASSIGN(rgbcolor, maxval, maxval, maxval); + + snprintfN(sysdesc, sizeof(sysdesc), + "System: %s\n" + "Primary illuminants (X, Y)\n" + " Red: %0.4f, %0.4f\n" + " Green: %0.4f, %0.4f\n" + " Blue: %0.4f, %0.4f\n" + "White point (X, Y): %0.4f, %0.4f", + cs->name, cs->xRed, cs->yRed, cs->xGreen, cs->yGreen, + cs->xBlue, cs->yBlue, cs->xWhite, cs->yWhite); + sysdesc[sizeof(sysdesc)-1] = '\0'; /* for robustness */ + + ppmd_text(pixels, pixcols, pixrows, Maxval, + pixcols / 3, Sz(24), Sz(12), 0, sysdesc, + PPMD_NULLDRAWPROC, (char *) &rgbcolor); +} + + + +int +main(int argc, + char * argv[]) { + + int argn; + const char * const usage = "[-[no]black] [-[no]wpoint] [-[no]label] [-no[axes]] [-full]\n\ +[-xy|-upvp] [-rec709|-ntsc|-ebu|-smpte|-hdtv|-cie]\n\ +[-red <x> <y>] [-green <x> <y>] [-blue <x> <y>]\n\ +[-white <x> <y>] [-gamma <g>]\n\ +[-size <s>] [-xsize|-width <x>] [-ysize|-height <y>]"; + const struct colorSystem *cs; + + int widspec = FALSE, hgtspec = FALSE; + int xBias, yBias; + int upvp = FALSE; /* xy or u'v' color coordinates? */ + int showWhite = TRUE; /* Show white point ? */ + int showBlack = TRUE; /* Show black body curve ? */ + int fullChart = FALSE; /* Fill entire tongue ? */ + int showLabel = TRUE; /* Show labels ? */ + int showAxes = TRUE; /* Plot axes ? */ + + ppm_init(&argc, argv); + argn = 1; + + cs = &Rec709system; /* default */ + while (argn < argc && argv[argn][0] == '-' && argv[argn][1] != '\0') { + if (pm_keymatch(argv[argn], "-xy", 2)) { + upvp = FALSE; + } else if (pm_keymatch(argv[argn], "-upvp", 1)) { + upvp = TRUE; + } else if (pm_keymatch(argv[argn], "-xsize", 1) || + pm_keymatch(argv[argn], "-width", 2)) { + if (widspec) { + pm_error("already specified a size/width/xsize"); + } + argn++; + if ((argn == argc) || (sscanf(argv[argn], "%d", &sxsize) != 1)) + pm_usage(usage); + widspec = TRUE; + } else if (pm_keymatch(argv[argn], "-ysize", 1) || + pm_keymatch(argv[argn], "-height", 2)) { + if (hgtspec) { + pm_error("already specified a size/height/ysize"); + } + argn++; + if ((argn == argc) || (sscanf(argv[argn], "%d", &sysize) != 1)) + pm_usage(usage); + hgtspec = TRUE; + } else if (pm_keymatch(argv[argn], "-size", 2)) { + if (hgtspec || widspec) { + pm_error("already specified a size/height/ysize"); + } + argn++; + if ((argn == argc) || (sscanf(argv[argn], "%d", &sysize) != 1)) + pm_usage(usage); + sxsize = sysize; + hgtspec = widspec = TRUE; + } else if (pm_keymatch(argv[argn], "-rec709", 1)) { + cs = &Rec709system; + } else if (pm_keymatch(argv[argn], "-ntsc", 1)) { + cs = &NTSCsystem; + } else if (pm_keymatch(argv[argn], "-ebu", 1)) { + cs = &EBUsystem; + } else if (pm_keymatch(argv[argn], "-smpte", 2)) { + cs = &SMPTEsystem; + } else if (pm_keymatch(argv[argn], "-hdtv", 2)) { + cs = &HDTVsystem; + } else if (pm_keymatch(argv[argn], "-cie", 1)) { + cs = &CIEsystem; + } else if (pm_keymatch(argv[argn], "-black", 3)) { + showBlack = TRUE; /* Show black body curve */ + } else if (pm_keymatch(argv[argn], "-wpoint", 2)) { + showWhite = TRUE; /* Show white point of color system */ + } else if (pm_keymatch(argv[argn], "-noblack", 3)) { + showBlack = FALSE; /* Don't show black body curve */ + } else if (pm_keymatch(argv[argn], "-nowpoint", 3)) { + showWhite = FALSE; /* Don't show white point of system */ + } else if (pm_keymatch(argv[argn], "-label", 1)) { + showLabel = TRUE; /* Show labels. */ + } else if (pm_keymatch(argv[argn], "-nolabel", 3)) { + showLabel = FALSE; /* Don't show labels */ + } else if (pm_keymatch(argv[argn], "-axes", 1)) { + showAxes = TRUE; /* Show axes. */ + } else if (pm_keymatch(argv[argn], "-noaxes", 3)) { + showAxes = FALSE; /* Don't show axes */ + } else if (pm_keymatch(argv[argn], "-full", 1)) { + fullChart = TRUE; /* Fill whole tongue full-intensity */ + } else if (pm_keymatch(argv[argn], "-gamma", 2)) { + cs = &Customsystem; + argn++; + if ((argn == argc) || + (sscanf(argv[argn], "%lf", &Customsystem.gamma) != 1)) + pm_usage(usage); + } else if (pm_keymatch(argv[argn], "-red", 1)) { + cs = &Customsystem; + argn++; + if ((argn == argc) || + (sscanf(argv[argn], "%lf", &Customsystem.xRed) != 1)) + pm_usage(usage); + argn++; + if ((argn == argc) || + (sscanf(argv[argn], "%lf", &Customsystem.yRed) != 1)) + pm_usage(usage); + } else if (pm_keymatch(argv[argn], "-green", 1)) { + cs = &Customsystem; + argn++; + if ((argn == argc) || + (sscanf(argv[argn], "%lf", &Customsystem.xGreen) != 1)) + pm_usage(usage); + argn++; + if ((argn == argc) || + (sscanf(argv[argn], "%lf", &Customsystem.yGreen) != 1)) + pm_usage(usage); + } else if (pm_keymatch(argv[argn], "-blue", 1)) { + cs = &Customsystem; + argn++; + if ((argn == argc) || + (sscanf(argv[argn], "%lf", &Customsystem.xBlue) != 1)) + pm_usage(usage); + argn++; + if ((argn == argc) || + (sscanf(argv[argn], "%lf", &Customsystem.yBlue) != 1)) + pm_usage(usage); + } else if (pm_keymatch(argv[argn], "-white", 1)) { + cs = &Customsystem; + argn++; + if ((argn == argc) || + (sscanf(argv[argn], "%lf", &Customsystem.xWhite) != 1)) + pm_usage(usage); + argn++; + if ((argn == argc) || + (sscanf(argv[argn], "%lf", &Customsystem.yWhite) != 1)) + pm_usage(usage); + } else { + pm_usage(usage); + } + argn++; + } + + if (argn != argc) { /* Extra bogus arguments ? */ + pm_usage(usage); + } + + pixcols = sxsize; + pixrows = sysize; + + pixels = ppm_allocarray(pixcols, pixrows); + + /* Partition into plot area and axes and establish subwindow. */ + + xBias = Sz(32); + yBias = Sz(20); + + makeAllBlack(pixels, pixcols, pixrows); + + drawTongueOutline(pixels, pixcols, pixrows, Maxval, upvp, xBias, yBias); + + fillInTongue(pixels, pixcols, pixrows, Maxval, cs, upvp, xBias, yBias, + fullChart); + + if (showAxes) + drawAxes(pixels, pixcols, pixrows, Maxval, upvp, xBias, yBias); + + if (showWhite) + plotWhitePoint(pixels, pixcols, pixrows, Maxval, + cs, upvp, xBias, yBias); + + if (showBlack) + plotBlackBodyCurve(pixels, pixcols, pixrows, Maxval, + upvp, xBias, yBias); + + /* Plot wavelengths around periphery of the tongue. */ + + if (showAxes) + plotMonochromeWavelengths(pixels, pixcols, pixrows, Maxval, + cs, upvp, xBias, yBias); + + if (showLabel) + writeLabel(pixels, pixcols, pixrows, Maxval, cs); + + ppm_writeppm(stdout, pixels, pixcols, pixrows, Maxval, FALSE); + + return 0; +} diff --git a/generator/ppmcolors.c b/generator/ppmcolors.c new file mode 100644 index 00000000..9112e1b8 --- /dev/null +++ b/generator/ppmcolors.c @@ -0,0 +1,87 @@ +/****************************************************************************** + ppmcolors +******************************************************************************* + Generate a color map containing all the colors representable with a certain + maxval. + + THIS PROGRAM IS OBSOLETE. USE PAMSEQ INSTEAD. WE KEEP THIS AROUND + FOR BACKWARD COMPATIBILITY. + +******************************************************************************/ + +#include "ppm.h" +#include "shhopt.h" +#include "nstring.h" + +struct cmdlineInfo { + /* All the information the user supplied in the command line, + in a form easy for the program to use. + */ + unsigned int maxval; +}; + + + +static void +parseCommandLine(int argc, 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. +-----------------------------------------------------------------------------*/ + optStruct3 opt; /* set by OPTENT3 */ + optEntry *option_def = malloc(100*sizeof(optEntry)); + unsigned int option_def_index; + + option_def_index = 0; /* incremented by OPTENT3 */ + OPTENT3(0, "maxval", OPT_UINT, + &cmdlineP->maxval, NULL, 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 */ + + /* defaults */ + cmdlineP->maxval = 5; + + optParseOptions3(&argc, argv, opt, sizeof(opt), 0); + /* Uses and sets argc, argv, and some of *cmdlineP and others. */ + + if (cmdlineP->maxval < 1) + pm_error("-maxval must be at least 1"); + else if (cmdlineP->maxval > PPM_OVERALLMAXVAL) + pm_error("-maxval too high. The maximum allowable value is %u", + PPM_OVERALLMAXVAL); + + if (argc-1 > 0) + pm_error("This program takes no arguments. You specified %d", + argc-1); +} + + + +int +main(int argc, char *argv[]) { + + struct cmdlineInfo cmdline; + const char * cmd; /* malloc'ed */ + int rc; + + ppm_init(&argc, argv); + + parseCommandLine(argc, argv, &cmdline); + + asprintfN(&cmd, "pamseq 3 %u -tupletype=RGB | pamtopnm", cmdline.maxval); + + rc = system(cmd); + + if (rc != 0) + pm_error("pamseq|pamtopnm pipeline failed. system() rc = %d", rc); + + strfree(cmd); + exit(rc); +} + + + + diff --git a/generator/ppmforge.c b/generator/ppmforge.c new file mode 100644 index 00000000..64b1ad79 --- /dev/null +++ b/generator/ppmforge.c @@ -0,0 +1,1182 @@ +/* + + Fractal forgery generator for the PPM toolkit + + Originally designed and implemented in December of 1989 by + John Walker as a stand-alone program for the Sun and MS-DOS + under Turbo C. Adapted in September of 1991 for use with Jef + Poskanzer's raster toolkit. + + References cited in the comments are: + + Foley, J. D., and Van Dam, A., Fundamentals of Interactive + Computer Graphics, Reading, Massachusetts: Addison + Wesley, 1984. + + Peitgen, H.-O., and Saupe, D. eds., The Science Of Fractal + Images, New York: Springer Verlag, 1988. + + Press, W. H., Flannery, B. P., Teukolsky, S. A., Vetterling, + W. T., Numerical Recipes In C, New Rochelle: Cambridge + University Press, 1988. + + Author: + John Walker + http://www.fourmilab.ch/ + + Permission to use, copy, modify, and distribute this software and + its documentation for any purpose and without fee is hereby + granted, without any conditions or restrictions. This software is + provided "as is" without express or implied warranty. + +*/ + +#define _XOPEN_SOURCE /* get M_PI in math.h */ + +#include <math.h> +#include <assert.h> + +#include "pm_c_util.h" +#include "ppm.h" +#include "mallocvar.h" + +#ifdef VMS +static double const hugeVal = HUGE_VAL; +#else +static double const hugeVal = 1e50; +#endif + +/* Definitions used to address real and imaginary parts in a two-dimensional + array of complex numbers as stored by fourn(). */ + +#define Real(v, x, y) v[1 + (((x) * meshsize) + (y)) * 2] +#define Imag(v, x, y) v[2 + (((x) * meshsize) + (y)) * 2] + +/* Co-ordinate indices within arrays. */ + +typedef struct { + double x; + double y; + double z; +} vector; + +/* Definition for obtaining random numbers. */ + +#define nrand 4 /* Gauss() sample count */ +#define Cast(low, high) ((low)+(((high)-(low)) * ((rand() & 0x7FFF) / arand))) + +/* prototypes */ +static void fourn ARGS((float data[], int nn[], int ndim, int isign)); +static void initgauss ARGS((unsigned int seed)); +static double gauss ARGS((void)); +static void spectralsynth ARGS((float **x, unsigned int n, double h)); +static void temprgb ARGS((double temp, double *r, double *g, double *b)); +static void etoile ARGS((pixel *pix)); +/* Local variables */ + +static double arand, gaussadd, gaussfac; /* Gaussian random parameters */ +static double fracdim; /* Fractal dimension */ +static double powscale; /* Power law scaling exponent */ +static int meshsize = 256; /* FFT mesh size */ +static unsigned int seedarg; /* Seed specified by user */ +static bool seedspec = FALSE; /* Did the user specify a seed ? */ +static bool clouds = FALSE; /* Just generate clouds */ +static bool stars = FALSE; /* Just generate stars */ +static int screenxsize = 256; /* Screen X size */ +static int screenysize = 256; /* Screen Y size */ +static double inclangle, hourangle; /* Star position relative to planet */ +static bool inclspec = FALSE; /* No inclination specified yet */ +static bool hourspec = FALSE; /* No hour specified yet */ +static double icelevel; /* Ice cap theshold */ +static double glaciers; /* Glacier level */ +static int starfraction; /* Star fraction */ +static int starcolor; /* Star color saturation */ + +/* FOURN -- Multi-dimensional fast Fourier transform + + Called with arguments: + + data A one-dimensional array of floats (NOTE!!! NOT + DOUBLES!!), indexed from one (NOTE!!! NOT ZERO!!), + containing pairs of numbers representing the complex + valued samples. The Fourier transformed results are + returned in the same array. + + nn An array specifying the edge size in each dimension. + THIS ARRAY IS INDEXED FROM ONE, AND ALL THE EDGE + SIZES MUST BE POWERS OF TWO!!! + + ndim Number of dimensions of FFT to perform. Set to 2 for + two dimensional FFT. + + isign If 1, a Fourier transform is done; if -1 the inverse + transformation is performed. + + This function is essentially as given in Press et al., "Numerical + Recipes In C", Section 12.11, pp. 467-470. +*/ + +static void fourn(data, nn, ndim, isign) + float data[]; + int nn[], ndim, isign; +{ + register int i1, i2, i3; + int i2rev, i3rev, ip1, ip2, ip3, ifp1, ifp2; + int ibit, idim, k1, k2, n, nprev, nrem, ntot; + float tempi, tempr; + double theta, wi, wpi, wpr, wr, wtemp; + +#define SWAP(a,b) tempr=(a); (a) = (b); (b) = tempr + + ntot = 1; + for (idim = 1; idim <= ndim; idim++) + ntot *= nn[idim]; + nprev = 1; + for (idim = ndim; idim >= 1; idim--) { + n = nn[idim]; + nrem = ntot / (n * nprev); + ip1 = nprev << 1; + ip2 = ip1 * n; + ip3 = ip2 * nrem; + i2rev = 1; + for (i2 = 1; i2 <= ip2; i2 += ip1) { + if (i2 < i2rev) { + for (i1 = i2; i1 <= i2 + ip1 - 2; i1 += 2) { + for (i3 = i1; i3 <= ip3; i3 += ip2) { + i3rev = i2rev + i3 - i2; + SWAP(data[i3], data[i3rev]); + SWAP(data[i3 + 1], data[i3rev + 1]); + } + } + } + ibit = ip2 >> 1; + while (ibit >= ip1 && i2rev > ibit) { + i2rev -= ibit; + ibit >>= 1; + } + i2rev += ibit; + } + ifp1 = ip1; + while (ifp1 < ip2) { + ifp2 = ifp1 << 1; + theta = isign * (M_PI * 2) / (ifp2 / ip1); + wtemp = sin(0.5 * theta); + wpr = -2.0 * wtemp * wtemp; + wpi = sin(theta); + wr = 1.0; + wi = 0.0; + for (i3 = 1; i3 <= ifp1; i3 += ip1) { + for (i1 = i3; i1 <= i3 + ip1 - 2; i1 += 2) { + for (i2 = i1; i2 <= ip3; i2 += ifp2) { + k1 = i2; + k2 = k1 + ifp1; + tempr = wr * data[k2] - wi * data[k2 + 1]; + tempi = wr * data[k2 + 1] + wi * data[k2]; + data[k2] = data[k1] - tempr; + data[k2 + 1] = data[k1 + 1] - tempi; + data[k1] += tempr; + data[k1 + 1] += tempi; + } + } + wr = (wtemp = wr) * wpr - wi * wpi + wr; + wi = wi * wpr + wtemp * wpi + wi; + } + ifp1 = ifp2; + } + nprev *= n; + } +} +#undef SWAP + +/* INITGAUSS -- Initialize random number generators. As given in + Peitgen & Saupe, page 77. */ + +static void initgauss(seed) + unsigned int seed; +{ + /* Range of random generator */ + arand = pow(2.0, 15.0) - 1.0; + gaussadd = sqrt(3.0 * nrand); + gaussfac = 2 * gaussadd / (nrand * arand); + srand(seed); +} + +/* GAUSS -- Return a Gaussian random number. As given in Peitgen + & Saupe, page 77. */ + +static double gauss() +{ + int i; + double sum = 0.0; + + for (i = 1; i <= nrand; i++) { + sum += (rand() & 0x7FFF); + } + return gaussfac * sum - gaussadd; +} + +/* SPECTRALSYNTH -- Spectrally synthesized fractal motion in two + dimensions. This algorithm is given under the + name SpectralSynthesisFM2D on page 108 of + Peitgen & Saupe. */ + +static void spectralsynth(x, n, h) + float **x; + unsigned int n; + double h; +{ + unsigned bl; + int i, j, i0, j0, nsize[3]; + double rad, phase, rcos, rsin; + float *a; + + bl = ((((unsigned long) n) * n) + 1) * 2 * sizeof(float); + a = (float *) calloc(bl, 1); + if (a == (float *) 0) { + pm_error("Cannot allocate %d x %d result array (% d bytes).", + n, n, bl); + } + *x = a; + + for (i = 0; i <= n / 2; i++) { + for (j = 0; j <= n / 2; j++) { + phase = 2 * M_PI * ((rand() & 0x7FFF) / arand); + if (i != 0 || j != 0) { + rad = pow((double) (i * i + j * j), -(h + 1) / 2) * gauss(); + } else { + rad = 0; + } + rcos = rad * cos(phase); + rsin = rad * sin(phase); + Real(a, i, j) = rcos; + Imag(a, i, j) = rsin; + i0 = (i == 0) ? 0 : n - i; + j0 = (j == 0) ? 0 : n - j; + Real(a, i0, j0) = rcos; + Imag(a, i0, j0) = - rsin; + } + } + Imag(a, n / 2, 0) = 0; + Imag(a, 0, n / 2) = 0; + Imag(a, n / 2, n / 2) = 0; + for (i = 1; i <= n / 2 - 1; i++) { + for (j = 1; j <= n / 2 - 1; j++) { + phase = 2 * M_PI * ((rand() & 0x7FFF) / arand); + rad = pow((double) (i * i + j * j), -(h + 1) / 2) * gauss(); + rcos = rad * cos(phase); + rsin = rad * sin(phase); + Real(a, i, n - j) = rcos; + Imag(a, i, n - j) = rsin; + Real(a, n - i, j) = rcos; + Imag(a, n - i, j) = - rsin; + } + } + + nsize[0] = 0; + nsize[1] = nsize[2] = n; /* Dimension of frequency domain array */ + fourn(a, nsize, 2, -1); /* Take inverse 2D Fourier transform */ +} + + +static unsigned int +initseed(void) { + /* Generate initial random seed. */ + + int i; + + i = time(NULL) ^ 0xF37C; + srand(i); + for (i = 0; i < 7; ++i) + rand(); + return rand(); +} + + + +/* TEMPRGB -- Calculate the relative R, G, and B components for a + black body emitting light at a given temperature. + The Planck radiation equation is solved directly for + the R, G, and B wavelengths defined for the CIE 1931 + Standard Colorimetric Observer. The color + temperature is specified in degrees Kelvin. */ + +static void temprgb(temp, r, g, b) + double temp; + double *r, *g, *b; +{ + double c1 = 3.7403e10, + c2 = 14384.0, + er, eg, eb, es; + +/* Lambda is the wavelength in microns: 5500 angstroms is 0.55 microns. */ + +#define Planck(lambda) ((c1 * pow((double) lambda, -5.0)) / \ + (pow(M_E, c2 / (lambda * temp)) - 1)) + + er = Planck(0.7000); + eg = Planck(0.5461); + eb = Planck(0.4358); +#undef Planck + + es = 1.0 / MAX(er, MAX(eg, eb)); + + *r = er * es; + *g = eg * es; + *b = eb * es; +} + +/* ETOILE -- Set a pixel in the starry sky. */ + +static void etoile(pix) + pixel *pix; +{ + if ((rand() % 1000) < starfraction) { +#define StarQuality 0.5 /* Brightness distribution exponent */ +#define StarIntensity 8 /* Brightness scale factor */ +#define StarTintExp 0.5 /* Tint distribution exponent */ + double v = StarIntensity * pow(1 / (1 - Cast(0, 0.9999)), + (double) StarQuality), + temp, + r, g, b; + + if (v > 255) { + v = 255; + } + + /* We make a special case for star color of zero in order to + prevent floating point roundoff which would otherwise + result in more than 256 star colors. We can guarantee + that if you specify no star color, you never get more than + 256 shades in the image. */ + + if (starcolor == 0) { + int vi = v; + + PPM_ASSIGN(*pix, vi, vi, vi); + } else { + temp = 5500 + starcolor * + pow(1 / (1 - Cast(0, 0.9999)), StarTintExp) * + ((rand() & 7) ? -1 : 1); + /* Constrain temperature to a reasonable value: >= 2600K + (S Cephei/R Andromedae), <= 28,000 (Spica). */ + temp = MAX(2600, MIN(28000, temp)); + temprgb(temp, &r, &g, &b); + PPM_ASSIGN(*pix, (int) (r * v + 0.499), + (int) (g * v + 0.499), + (int) (b * v + 0.499)); + } + } else { + PPM_ASSIGN(*pix, 0, 0, 0); + } +} + + + +static double +uprj(unsigned int const a, + unsigned int const size) { + + return (double)a/(size-1); +} + + + +static double +atSat(double const x, + double const y, + double const dsat) { + + return x*(1.0-dsat) + y*dsat; +} + + + +static unsigned char * +makeCp(float * const a, + unsigned int const n, + pixval const maxval) { + + /* Prescale the grid points into intensities. */ + + unsigned char * cp; + unsigned char * ap; + + if (UINT_MAX / n < n) + pm_error("arithmetic overflow squaring %u", n); + cp = malloc(n * n); + if (cp == NULL) + pm_error("Unable to allocate %u bytes for cp array", n); + + ap = cp; + { + unsigned int i; + for (i = 0; i < n; i++) { + unsigned int j; + for (j = 0; j < n; j++) + *ap++ = ((double)maxval * (Real(a, i, j) + 1.0)) / 2.0; + } + } + return cp; +} + + + +static void +createPlanetStuff(float * const a, + unsigned int const n, + double ** const uP, + double ** const u1P, + unsigned int ** const bxfP, + unsigned int ** const bxcP, + unsigned char ** const cpP, + vector * const sunvecP, + unsigned int const cols, + pixval const maxval) { + + double *u, *u1; + unsigned int *bxf, *bxc; + unsigned char * cp; + double shang, siang; + bool flipped; + + /* Compute incident light direction vector. */ + + shang = hourspec ? hourangle : Cast(0, 2 * M_PI); + siang = inclspec ? inclangle : Cast(-M_PI * 0.12, M_PI * 0.12); + + sunvecP->x = sin(shang) * cos(siang); + sunvecP->y = sin(siang); + sunvecP->z = cos(shang) * cos(siang); /* initial value */ + + /* Allow only 25% of random pictures to be crescents */ + + if (!hourspec && ((rand() % 100) < 75)) { + flipped = (sunvecP->z < 0); + sunvecP->z = fabs(sunvecP->z); + } else + flipped = FALSE; + + if (!clouds) { + pm_message( + " -inclination %.0f -hour %d -ice %.2f -glaciers %.2f", + (siang * (180.0 / M_PI)), + (int) (((shang * (12.0 / M_PI)) + 12 + + (flipped ? 12 : 0)) + 0.5) % 24, icelevel, glaciers); + pm_message(" -stars %d -saturation %d.", + starfraction, starcolor); + } + + cp = makeCp(a, n, maxval); + + /* Fill the screen from the computed intensity grid by mapping + screen points onto the grid, then calculating each pixel value + by bilinear interpolation from the surrounding grid points. + (N.b. the pictures would undoubtedly look better when generated + with small grids if a more well-behaved interpolation were + used.) + + Also compute the line-level interpolation parameters that + caller will need every time around his inner loop. + */ + + MALLOCARRAY(u, cols); + MALLOCARRAY(u1, cols); + MALLOCARRAY(bxf, cols); + MALLOCARRAY(bxc, cols); + + if (u == NULL || u1 == NULL || bxf == NULL || bxc == NULL) + pm_error("Cannot allocate %d element interpolation tables.", cols); + { + unsigned int j; + for (j = 0; j < cols; j++) { + double const bx = (n - 1) * uprj(j, cols); + + bxf[j] = floor(bx); + bxc[j] = bxf[j] + 1; + u[j] = bx - bxf[j]; + u1[j] = 1 - u[j]; + } + } + *uP = u; *u1P = u1; + *bxfP = bxf; *bxcP = bxc; + *cpP = cp; +} + + + +static void +generateStarrySkyRow(pixel * const pixels, + unsigned int const cols) { +/*---------------------------------------------------------------------------- + Generate a starry sky. Note that no FFT is performed; + the output is generated directly from a power law + mapping of a pseudorandom sequence into intensities. +-----------------------------------------------------------------------------*/ + unsigned int j; + + for (j = 0; j < cols; j++) + etoile(pixels + j); +} + + + +static void +generateCloudRow(pixel * const pixels, + unsigned int const cols, + double const t, + double const t1, + double * const u, + double * const u1, + unsigned char * const cp, + unsigned int * const bxc, + unsigned int * const bxf, + int const byc, + int const byf, + pixval const maxval) { + + /* Render the FFT output as clouds. */ + + unsigned int j; + + for (j = 0; j < cols; j++) { + double r; + pixval w; + + r = 0.0; /* initial value */ + /* Note that where t1 and t are zero, the cp[] element + referenced below does not exist. + */ + if (t1 > 0.0) + r += t1 * u1[j] * cp[byf + bxf[j]] + + t1 * u[j] * cp[byf + bxc[j]]; + if (t > 0.0) + r += t * u1[j] * cp[byc + bxf[j]] + + t * u[j] * cp[byc + bxc[j]]; + + w = (r > 127.0) ? (maxval * ((r - 127.0) / 128.0)) : 0; + + PPM_ASSIGN(*(pixels + j), w, w, maxval); + } +} + + + +static void +makeLand(int * const irP, + int * const igP, + int * const ibP, + double const r) { +/*---------------------------------------------------------------------------- + Land area. Look up color based on elevation from precomputed + color map table. +-----------------------------------------------------------------------------*/ + static unsigned char pgnd[][3] = { + {206, 205, 0}, {208, 207, 0}, {211, 208, 0}, + {214, 208, 0}, {217, 208, 0}, {220, 208, 0}, + {222, 207, 0}, {225, 205, 0}, {227, 204, 0}, + {229, 202, 0}, {231, 199, 0}, {232, 197, 0}, + {233, 194, 0}, {234, 191, 0}, {234, 188, 0}, + {233, 185, 0}, {232, 183, 0}, {231, 180, 0}, + {229, 178, 0}, {227, 176, 0}, {225, 174, 0}, + {223, 172, 0}, {221, 170, 0}, {219, 168, 0}, + {216, 166, 0}, {214, 164, 0}, {212, 162, 0}, + {210, 161, 0}, {207, 159, 0}, {205, 157, 0}, + {203, 156, 0}, {200, 154, 0}, {198, 152, 0}, + {195, 151, 0}, {193, 149, 0}, {190, 148, 0}, + {188, 147, 0}, {185, 145, 0}, {183, 144, 0}, + {180, 143, 0}, {177, 141, 0}, {175, 140, 0}, + {172, 139, 0}, {169, 138, 0}, {167, 137, 0}, + {164, 136, 0}, {161, 135, 0}, {158, 134, 0}, + {156, 133, 0}, {153, 132, 0}, {150, 132, 0}, + {147, 131, 0}, {145, 130, 0}, {142, 130, 0}, + {139, 129, 0}, {136, 128, 0}, {133, 128, 0}, + {130, 127, 0}, {127, 127, 0}, {125, 127, 0}, + {122, 127, 0}, {119, 127, 0}, {116, 127, 0}, + {113, 127, 0}, {110, 128, 0}, {107, 128, 0}, + {104, 128, 0}, {102, 127, 0}, { 99, 126, 0}, + { 97, 124, 0}, { 95, 122, 0}, { 93, 120, 0}, + { 92, 117, 0}, { 92, 114, 0}, { 92, 111, 0}, + { 93, 108, 0}, { 94, 106, 0}, { 96, 104, 0}, + { 98, 102, 0}, {100, 100, 0}, {103, 99, 0}, + {106, 99, 0}, {109, 99, 0}, {111, 100, 0}, + {114, 101, 0}, {117, 102, 0}, {120, 103, 0}, + {123, 102, 0}, {125, 102, 0}, {128, 100, 0}, + {130, 98, 0}, {132, 96, 0}, {133, 94, 0}, + {134, 91, 0}, {134, 88, 0}, {134, 85, 0}, + {133, 82, 0}, {131, 80, 0}, {129, 78, 0} + }; + + unsigned int const ix = ((r - 128) * (ARRAY_SIZE(pgnd) - 1)) / 127; + + *irP = pgnd[ix][0]; + *igP = pgnd[ix][1]; + *ibP = pgnd[ix][2]; +} + + + +static void +makeWater(int * const irP, + int * const igP, + int * const ibP, + double const r, + pixval const maxval) { + + /* Water. Generate clouds above water based on elevation. */ + + *irP = *igP = r > 64 ? (r - 64) * 4 : 0; + *ibP = maxval; +} + + + +static void +addIce(int * const irP, + int * const igP, + int * const ibP, + double const r, + double const azimuth, + double const icelevel, + double const glaciers, + pixval const maxval) { + + /* Generate polar ice caps. */ + + double const icet = pow(fabs(sin(azimuth)), 1.0 / icelevel) - 0.5; + double const ice = MAX(0.0, + (icet + glaciers * MAX(-0.5, (r - 128) / 128.0))); + if (ice > 0.125) { + *irP = maxval; + *igP = maxval; + *ibP = maxval; + } +} + + + +static void +limbDarken(int * const irP, + int * const igP, + int * const ibP, + unsigned int const col, + unsigned int const row, + unsigned int const cols, + unsigned int const rows, + vector const sunvec, + pixval const maxval) { + + /* With Gcc 2.95.3 compiler optimization level > 1, I have seen this + function confuse all the variables and ultimately generate a + completely black image. Adding an extra reference to 'rows' seems + to put things back in order, and the assert() below does that. + Take it out, and the problem comes back! 04.02.21. + */ + + /* Apply limb darkening by cosine rule. */ + + double const atthick = 1.03; + double const atSatFac = 1.0; + double const athfac = sqrt(atthick * atthick - 1.0); + /* Atmosphere thickness as a percentage of planet's diameter */ + + double const dy = 2 * ((double)rows/2 - row) / rows; + double const dysq = dy * dy; + /* Note: we are in fact normalizing this horizontal position by the + vertical size of the picture. And we know rows >= cols. + */ + double const dx = 2 * ((double)cols/2 - col) / rows; + double const dxsq = dx * dx; + + double const ds = MIN(1.0, sqrt(dxsq + dysq)); + + /* Calculate atmospheric absorption based on the thickness of + atmosphere traversed by light on its way to the surface. + */ + double const dsq = ds * ds; + double const dsat = atSatFac * ((sqrt(atthick * atthick - dsq) - + sqrt(1.0 * 1.0 - dsq)) / athfac); + + assert(rows >= cols); /* An input requirement */ + + *irP = atSat(*irP, maxval/2, dsat); + *igP = atSat(*igP, maxval/2, dsat); + *ibP = atSat(*ibP, maxval, dsat); + { + double const PlanetAmbient = 0.05; + + double const sqomdysq = sqrt(1.0 - dysq); + double const svx = sunvec.x; + double const svy = sunvec.y * dy; + double const svz = sunvec.z * sqomdysq; + double const di = + MAX(0, MIN(1.0, svx * dx + svy + svz * sqrt(1.0 - dxsq))); + double const inx = PlanetAmbient * 1.0 + (1.0 - PlanetAmbient) * di; + + *irP *= inx; + *igP *= inx; + *ibP *= inx; + } +} + + + +static void +generatePlanetRow(pixel * const pixelrow, + unsigned int const row, + unsigned int const rows, + unsigned int const cols, + double const t, + double const t1, + double * const u, + double * const u1, + unsigned char * const cp, + unsigned int * const bxc, + unsigned int * const bxf, + int const byc, + int const byf, + vector const sunvec, + pixval const maxval) { + + unsigned int const StarClose = 2; + + double const azimuth = asin(((((double) row) / (rows - 1)) * 2) - 1); + unsigned int const lcos = (rows / 2) * fabs(cos(azimuth)); + + unsigned int col; + + for (col = 0; col < cols; ++col) + PPM_ASSIGN(pixelrow[col], 0, 0, 0); + + for (col = cols/2 - lcos; col <= cols/2 + lcos; ++col) { + double r; + int ir, ig, ib; + + r = 0.0; /* initial value */ + + /* Note that where t1 and t are zero, the cp[] element + referenced below does not exist. + */ + if (t1 > 0.0) + r += t1 * u1[col] * cp[byf + bxf[col]] + + t1 * u[col] * cp[byf + bxc[col]]; + if (t > 0.0) + r += t * u1[col] * cp[byc + bxf[col]] + + t * u[col] * cp[byc + bxc[col]]; + + if (r >= 128) + makeLand(&ir, &ig, &ib, r); + else + makeWater(&ir, &ig, &ib, r, maxval); + + addIce(&ir, &ig, &ib, r, azimuth, icelevel, glaciers, maxval); + + limbDarken(&ir, &ig, &ib, col, row, cols, rows, sunvec, maxval); + + PPM_ASSIGN(pixelrow[col], ir, ig, ib); + } + + /* Left stars */ + + for (col = 0; (int)col < (int)(cols/2 - (lcos + StarClose)); ++col) + etoile(&pixelrow[col]); + + /* Right stars */ + + for (col = cols/2 + (lcos + StarClose); col < cols; ++col) + etoile(&pixelrow[col]); +} + + + +static void +genplanet(bool const stars, + bool const clouds, + float * const a, + unsigned int const cols, + unsigned int const rows, + unsigned int const n, + unsigned int const rseed) { +/*---------------------------------------------------------------------------- + Generate planet from elevation array. + + If 'stars' is true, a is undefined. Otherwise, it is defined. +-----------------------------------------------------------------------------*/ + pixval const maxval = PPM_MAXMAXVAL; + + unsigned char *cp; + double *u, *u1; + unsigned int *bxf, *bxc; + + pixel *pixelrow; + unsigned int row; + + vector sunvec; + + ppm_writeppminit(stdout, cols, rows, maxval, FALSE); + + if (stars) { + pm_message("night: -seed %d -stars %d -saturation %d.", + rseed, starfraction, starcolor); + cp = NULL; + u = NULL; u1 = NULL; + bxf = NULL; bxc = NULL; + } else { + pm_message("%s: -seed %d -dimension %.2f -power %.2f -mesh %d", + clouds ? "clouds" : "planet", + rseed, fracdim, powscale, meshsize); + createPlanetStuff(a, n, &u, &u1, &bxf, &bxc, &cp, &sunvec, + cols, maxval); + } + + pixelrow = ppm_allocrow(cols); + for (row = 0; row < rows; ++row) { + if (stars) + generateStarrySkyRow(pixelrow, cols); + else { + double const by = (n - 1) * uprj(row, rows); + int const byf = floor(by) * n; + int const byc = byf + n; + double const t = by - floor(by); + double const t1 = 1 - t; + + if (clouds) + generateCloudRow(pixelrow, cols, + t, t1, u, u1, cp, bxc, bxf, byc, byf, + maxval); + else + generatePlanetRow(pixelrow, row, rows, cols, + t, t1, u, u1, cp, bxc, bxf, byc, byf, + sunvec, + maxval); + } + ppm_writeppmrow(stdout, pixelrow, cols, maxval, FALSE); + } + pm_close(stdout); + + ppm_freerow(pixelrow); + if (cp) free(cp); + if (u) free(u); + if (u1) free(u1); + if (bxf) free(bxf); + if (bxc) free(bxc); +} + + + +static void +applyPowerLawScaling(float * const a, + int const meshsize, + double const powscale) { + + /* Apply power law scaling if non-unity scale is requested. */ + + if (powscale != 1.0) { + unsigned int i; + for (i = 0; i < meshsize; i++) { + unsigned int j; + for (j = 0; j < meshsize; j++) { + double const r = Real(a, i, j); + if (r > 0) + Real(a, i, j) = pow(r, powscale); + } + } + } +} + + + +static void +computeExtremeReal(const float * const a, + int const meshsize, + double * const rminP, + double * const rmaxP) { + + /* Compute extrema for autoscaling. */ + + double rmin, rmax; + unsigned int i; + + rmin = hugeVal; + rmax = -hugeVal; + + for (i = 0; i < meshsize; i++) { + unsigned int j; + for (j = 0; j < meshsize; j++) { + double r = Real(a, i, j); + + rmin = MIN(rmin, r); + rmax = MAX(rmax, r); + } + } + *rminP = rmin; + *rmaxP = rmax; +} + + + +static void +replaceWithSpread(float * const a, + int const meshsize) { +/*---------------------------------------------------------------------------- + Replace the real part of each element of the 'a' array with a + measure of how far the real is from the middle; sort of a standard + deviation. +-----------------------------------------------------------------------------*/ + double rmin, rmax; + double rmean, rrange; + unsigned int i; + + computeExtremeReal(a, meshsize, &rmin, &rmax); + + rmean = (rmin + rmax) / 2; + rrange = (rmax - rmin) / 2; + + for (i = 0; i < meshsize; i++) { + unsigned int j; + for (j = 0; j < meshsize; j++) { + Real(a, i, j) = (Real(a, i, j) - rmean) / rrange; + } + } +} + + + +static bool +planet(unsigned int const cols, + unsigned int const rows, + bool const stars, + bool const clouds) { +/*---------------------------------------------------------------------------- + Make a planet. +-----------------------------------------------------------------------------*/ + float * a; + bool error; + unsigned int rseed; /* Current random seed */ + + if (seedspec) + rseed = seedarg; + else + rseed = initseed(); + + initgauss(rseed); + + if (stars) { + a = NULL; + error = FALSE; + } else { + spectralsynth(&a, meshsize, 3.0 - fracdim); + if (a == NULL) { + error = TRUE; + } else { + applyPowerLawScaling(a, meshsize, powscale); + + replaceWithSpread(a, meshsize); + + error = FALSE; + } + } + if (!error) + genplanet(stars, clouds, a, cols, rows, meshsize, rseed); + + if (a != NULL) + free(a); + + return !error; +} + + + + +int +main(int argc, char ** argv) { + + bool success; + int i; + const char * const usage = "\n\ +[-width|-xsize <x>] [-height|-ysize <y>] [-mesh <n>]\n\ +[-clouds] [-dimension <f>] [-power <f>] [-seed <n>]\n\ +[-hour <f>] [-inclination|-tilt <f>] [-ice <f>] [-glaciers <f>]\n\ +[-night] [-stars <n>] [-saturation <n>]"; + bool dimspec = FALSE, meshspec = FALSE, powerspec = FALSE, + widspec = FALSE, hgtspec = FALSE, icespec = FALSE, + glacspec = FALSE, starspec = FALSE, starcspec = FALSE; + + int cols, rows; /* Dimensions of our output image */ + + ppm_init(&argc, argv); + i = 1; + + while ((i < argc) && (argv[i][0] == '-') && (argv[i][1] != '\0')) { + + if (pm_keymatch(argv[i], "-clouds", 2)) { + clouds = TRUE; + } else if (pm_keymatch(argv[i], "-night", 2)) { + stars = TRUE; + } else if (pm_keymatch(argv[i], "-dimension", 2)) { + if (dimspec) { + pm_error("already specified a dimension"); + } + i++; + if ((i == argc) || (sscanf(argv[i], "%lf", &fracdim) != 1)) + pm_usage(usage); + if (fracdim <= 0.0) { + pm_error("fractal dimension must be greater than 0"); + } + dimspec = TRUE; + } else if (pm_keymatch(argv[i], "-hour", 3)) { + if (hourspec) { + pm_error("already specified an hour"); + } + i++; + if ((i == argc) || (sscanf(argv[i], "%lf", &hourangle) != 1)) + pm_usage(usage); + hourangle = (M_PI / 12.0) * (hourangle + 12.0); + hourspec = TRUE; + } else if (pm_keymatch(argv[i], "-inclination", 3) || + pm_keymatch(argv[i], "-tilt", 2)) { + if (inclspec) { + pm_error("already specified an inclination/tilt"); + } + i++; + if ((i == argc) || (sscanf(argv[i], "%lf", &inclangle) != 1)) + pm_usage(usage); + inclangle = (M_PI / 180.0) * inclangle; + inclspec = TRUE; + } else if (pm_keymatch(argv[i], "-mesh", 2)) { + unsigned int j; + + if (meshspec) { + pm_error("already specified a mesh size"); + } + i++; + if ((i == argc) || (sscanf(argv[i], "%d", &meshsize) != 1)) + pm_usage(usage); + + /* Force FFT mesh to the next larger power of 2. */ + + for (j = meshsize; (j & 1) == 0; j >>= 1) ; + + if (j != 1) { + for (j = 2; j < meshsize; j <<= 1) ; + meshsize = j; + } + meshspec = TRUE; + } else if (pm_keymatch(argv[i], "-power", 2)) { + if (powerspec) { + pm_error("already specified a power factor"); + } + i++; + if ((i == argc) || (sscanf(argv[i], "%lf", &powscale) != 1)) + pm_usage(usage); + if (powscale <= 0.0) { + pm_error("power factor must be greater than 0"); + } + powerspec = TRUE; + } else if (pm_keymatch(argv[i], "-ice", 3)) { + if (icespec) { + pm_error("already specified ice cap level"); + } + i++; + if ((i == argc) || (sscanf(argv[i], "%lf", &icelevel) != 1)) + pm_usage(usage); + if (icelevel <= 0.0) { + pm_error("ice cap level must be greater than 0"); + } + icespec = TRUE; + } else if (pm_keymatch(argv[i], "-glaciers", 2)) { + if (glacspec) { + pm_error("already specified glacier level"); + } + i++; + if ((i == argc) || (sscanf(argv[i], "%lf", &glaciers) != 1)) + pm_usage(usage); + if (glaciers <= 0.0) { + pm_error("glacier level must be greater than 0"); + } + glacspec = TRUE; + } else if (pm_keymatch(argv[i], "-stars", 3)) { + if (starspec) { + pm_error("already specified a star fraction"); + } + i++; + if ((i == argc) || (sscanf(argv[i], "%d", &starfraction) != 1)) + pm_usage(usage); + starspec = TRUE; + } else if (pm_keymatch(argv[i], "-saturation", 3)) { + if (starcspec) { + pm_error("already specified a star color saturation"); + } + i++; + if ((i == argc) || (sscanf(argv[i], "%d", &starcolor) != 1)) + pm_usage(usage); + starcspec = TRUE; + } else if (pm_keymatch(argv[i], "-seed", 3)) { + if (seedspec) { + pm_error("already specified a random seed"); + } + i++; + if ((i == argc) || (sscanf(argv[i], "%u", &seedarg) != 1)) + pm_usage(usage); + seedspec = TRUE; + } else if (pm_keymatch(argv[i], "-xsize", 2) || + pm_keymatch(argv[i], "-width", 2)) { + if (widspec) { + pm_error("already specified a width/xsize"); + } + i++; + if ((i == argc) || (sscanf(argv[i], "%d", &screenxsize) != 1)) + pm_usage(usage); + widspec = TRUE; + } else if (pm_keymatch(argv[i], "-ysize", 2) || + pm_keymatch(argv[i], "-height", 3)) { + if (hgtspec) { + pm_error("already specified a height/ysize"); + } + i++; + if ((i == argc) || (sscanf(argv[i], "%d", &screenysize) != 1)) + pm_usage(usage); + hgtspec = TRUE; + } else { + pm_usage(usage); + } + i++; + } + + + /* Set defaults when explicit specifications were not given. + + The default fractal dimension and power scale depend upon + whether we are generating a planet or clouds. + */ + + if (!dimspec) { + fracdim = clouds ? 2.15 : 2.4; + } + if (!powerspec) { + powscale = clouds ? 0.75 : 1.2; + } + if (!icespec) { + icelevel = 0.4; + } + if (!glacspec) { + glaciers = 0.75; + } + if (!starspec) { + starfraction = 100; + } + if (!starcspec) { + starcolor = 125; + } + + /* Force screen to be at least as wide as it is high. Long, + skinny screens cause crashes because picture width is + calculated based on height. + */ + + cols = (MAX(screenysize, screenxsize) + 1) & (~1); + rows = screenysize; + + success = planet(cols, rows, stars, clouds); + + exit(success ? 0 : 1); +} diff --git a/generator/ppmmake.c b/generator/ppmmake.c new file mode 100644 index 00000000..eee32485 --- /dev/null +++ b/generator/ppmmake.c @@ -0,0 +1,117 @@ +/* ppmmake.c - create a pixmap of a specified color and size +** +** Copyright (C) 1991 by Jef Poskanzer. +** +** 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. +*/ + +#include "pm_c_util.h" +#include "mallocvar.h" +#include "shhopt.h" +#include "ppm.h" + +struct cmdlineInfo { + /* All the information the user supplied in the command line, + in a form easy for the program to use. + */ + pixel color; + unsigned int cols; + unsigned int rows; + pixval maxval; +}; + + + +static void +parseCommandLine(int argc, char ** argv, + struct cmdlineInfo * const cmdlineP) { +/*---------------------------------------------------------------------------- + Convert program invocation arguments (argc,argv) into a format the + program can use easily, struct cmdlineInfo. Validate arguments along + the way and exit program with message if invalid. + + Note that some string information we return as *cmdlineP is in the storage + argv[] points to. +-----------------------------------------------------------------------------*/ + optEntry * option_def; + /* Instructions to OptParseOptions3 on how to parse our options. + */ + optStruct3 opt; + + unsigned int maxvalSpec; + unsigned int option_def_index; + + MALLOCARRAY(option_def, 100); + + option_def_index = 0; /* incremented by OPTENTRY */ + OPTENT3(0, "maxval", OPT_UINT, &cmdlineP->maxval, &maxvalSpec, 0); + + opt.opt_table = option_def; + opt.short_allowed = false; /* We have no short (old-fashioned) options */ + opt.allowNegNum = false; /* We have no parms that are negative numbers */ + + optParseOptions3(&argc, argv, opt, sizeof(opt), 0); + /* Uses and sets argc, argv, and some of *cmdlineP and others. */ + + if (!maxvalSpec) + cmdlineP->maxval = PPM_MAXMAXVAL; + else { + if (cmdlineP->maxval > PPM_OVERALLMAXVAL) + pm_error("The value you specified for -maxval (%u) is too big. " + "Max allowed is %u", cmdlineP->maxval, PPM_OVERALLMAXVAL); + + if (cmdlineP->maxval < 1) + pm_error("You cannot specify 0 for -maxval"); + } + + if (argc-1 < 3) + pm_error("Need 3 arguments: color, width, height."); + else if (argc-1 > 3) + pm_error("Only 3 arguments allowed: color, width, height. " + "You specified %d", argc-1); + else { + cmdlineP->color = ppm_parsecolor(argv[1], cmdlineP->maxval); + cmdlineP->cols = atoi(argv[2]); + cmdlineP->rows = atoi(argv[3]); + if (cmdlineP->cols <= 0) + pm_error("width argument must be a positive number. You " + "specified '%s'", argv[2]); + if (cmdlineP->rows <= 0) + pm_error("height argument must be a positive number. You " + "specified '%s'", argv[3]); + } +} + + + +int +main(int argc, char *argv[]) { + + struct cmdlineInfo cmdline; + pixel * pixrow; + unsigned int row; + + ppm_init(&argc, argv); + + parseCommandLine(argc, argv, &cmdline); + + ppm_writeppminit(stdout, cmdline.cols, cmdline.rows, cmdline.maxval, 0); + pixrow = ppm_allocrow(cmdline.cols); + + for (row = 0; row < cmdline.rows; ++row) { + unsigned int col; + for (col = 0; col < cmdline.cols; ++col) + pixrow[col] = cmdline.color; + ppm_writeppmrow(stdout, pixrow, cmdline.cols, cmdline.maxval, 0); + } + + ppm_freerow(pixrow); + pm_close(stdout); + + return 0; +} diff --git a/generator/ppmpat.c b/generator/ppmpat.c new file mode 100644 index 00000000..343100d5 --- /dev/null +++ b/generator/ppmpat.c @@ -0,0 +1,1060 @@ +/* ppmpat.c - make a pixmap +** +** Copyright (C) 1989, 1991 by Jef Poskanzer. +** +** 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. +*/ + +#define _XOPEN_SOURCE /* get M_PI in math.h */ + +#include <math.h> + +#include "pm_c_util.h" +#include "ppm.h" +#include "ppmdraw.h" + + + + +static pixel +random_anticamo_color(pixval const maxval) { + + int v1, v2, v3; + pixel p; + + v1 = (maxval + 1) / 4; + v2 = (maxval + 1) / 2; + v3 = 3 * v1; + + switch (rand() % 15) { + case 0: case 1: + PPM_ASSIGN(p, rand() % v1 + v3, rand() % v2, rand() % v2); + break; + + case 2: + case 3: + PPM_ASSIGN(p, rand() % v2, rand() % v1 + v3, rand() % v2); + break; + + case 4: + case 5: + PPM_ASSIGN(p, rand() % v2, rand() % v2, rand() % v1 + v3); + break; + + case 6: + case 7: + case 8: + PPM_ASSIGN(p, rand() % v2, rand() % v1 + v3, rand() % v1 + v3); + break; + + case 9: + case 10: + case 11: + PPM_ASSIGN(p, rand() % v1 + v3, rand() % v2, rand() % v1 + v3); + break; + + case 12: + case 13: + case 14: + PPM_ASSIGN(p, rand() % v1 + v3, rand() % v1 + v3, rand() % v2); + break; + } + + return p; +} + + + +/* Camouflage stuff. */ + +static pixel +random_camo_color(pixval const maxval) { + + int v1, v2, v3; + pixel p; + + v1 = (maxval + 1 ) / 8; + v2 = (maxval + 1 ) / 4; + v3 = (maxval + 1 ) / 2; + + switch (rand() % 10) { + case 0: + case 1: + case 2: + /* light brown */ + PPM_ASSIGN(p, rand() % v3 + v3, rand() % v3 + v2, rand() % v3 + v2); + break; + + case 3: + case 4: + case 5: + /* dark green */ + PPM_ASSIGN(p, rand() % v2, rand() % v2 + 3 * v1, rand() % v2); + break; + + case 6: + case 7: + /* brown */ + PPM_ASSIGN(p, rand() % v2 + v2, rand() % v2, rand() % v2); + break; + + case 8: + case 9: + /* dark brown */ + PPM_ASSIGN(p, rand() % v1 + v1, rand() % v1, rand() % v1); + break; + } + + return p; +} + + + +static float +rnduni(void) { + return rand() % 32767 / 32767.0; +} + + + +#define BLOBRAD 50 + +#define MIN_POINTS 7 +#define MAX_POINTS 13 + +#define MIN_ELLIPSE_FACTOR 0.5 +#define MAX_ELLIPSE_FACTOR 2.0 + +#define MIN_POINT_FACTOR 0.5 +#define MAX_POINT_FACTOR 2.0 + + + +static void +camo(pixel ** const pixels, + unsigned int const cols, + unsigned int const rows, + pixval const maxval, + bool const antiflag) { + + pixel color; + int n, i, cx, cy; + struct fillobj * fh; + + /* Clear background. */ + if (antiflag) + color = random_anticamo_color( maxval ); + else + color = random_camo_color( maxval ); + + ppmd_filledrectangle( + pixels, cols, rows, maxval, 0, 0, cols, rows, PPMD_NULLDRAWPROC, + &color); + + n = (rows * cols) / (BLOBRAD * BLOBRAD) * 5; + for (i = 0; i < n; ++i) { + int points, p, xs[MAX_POINTS], ys[MAX_POINTS], x0, y0; + float a, b, c, theta, tang, tx, ty; + + cx = rand() % cols; + cy = rand() % rows; + + points = rand() % ( MAX_POINTS - MIN_POINTS + 1 ) + MIN_POINTS; + a = rnduni() * (MAX_ELLIPSE_FACTOR - MIN_ELLIPSE_FACTOR) + + MIN_ELLIPSE_FACTOR; + b = rnduni() * (MAX_ELLIPSE_FACTOR - MIN_ELLIPSE_FACTOR) + + MIN_ELLIPSE_FACTOR; + theta = rnduni() * 2.0 * M_PI; + for (p = 0; p < points; ++p) { + tx = a * sin(p * 2.0 * M_PI / points); + ty = b * cos(p * 2.0 * M_PI / points); + tang = atan2(ty, tx) + theta; + c = rnduni() * (MAX_POINT_FACTOR - MIN_POINT_FACTOR) + + MIN_POINT_FACTOR; + xs[p] = cx + BLOBRAD * c * sin(tang); + ys[p] = cy + BLOBRAD * c * cos(tang); + } + x0 = (xs[0] + xs[points - 1]) / 2; + y0 = (ys[0] + ys[points - 1]) / 2; + + fh = ppmd_fill_create(); + + ppmd_polyspline( + pixels, cols, rows, maxval, x0, y0, points, xs, ys, x0, y0, + ppmd_fill_drawproc, fh ); + + if (antiflag) + color = random_anticamo_color(maxval); + else + color = random_camo_color(maxval); + ppmd_fill(pixels, cols, rows, maxval, fh, PPMD_NULLDRAWPROC, &color); + + ppmd_fill_destroy(fh); + } +} + + + +static pixel +random_color(pixval const maxval) { + + pixel p; + + PPM_ASSIGN(p, + rand() % (maxval + 1), + rand() % (maxval + 1), + rand() % (maxval + 1) + ); + + return p; +} + + + +#define DARK_THRESH 0.25 + +#if __STDC__ +static pixel +random_bright_color( pixval maxval ) +#else /*__STDC__*/ +static pixel +random_bright_color( maxval ) + pixval maxval; +#endif /*__STDC__*/ + { + pixel p; + + do + { + p = random_color( maxval ); + } + while ( PPM_LUMIN( p ) <= maxval * DARK_THRESH ); + + return p; + } + +#if __STDC__ +static pixel +random_dark_color( pixval maxval ) +#else /*__STDC__*/ +static pixel +random_dark_color( maxval ) + pixval maxval; +#endif /*__STDC__*/ + { + pixel p; + + do + { + p = random_color( maxval ); + } + while ( PPM_LUMIN( p ) > maxval * DARK_THRESH ); + + return p; + } + +static pixel +average_two_colors( p1, p2 ) +pixel p1, p2; + { + pixel p; + + PPM_ASSIGN( + p, ( (int) PPM_GETR(p1) + (int) PPM_GETR(p2) ) / 2, + ( (int) PPM_GETG(p1) + (int) PPM_GETG(p2) ) / 2, + ( (int) PPM_GETB(p1) + (int) PPM_GETB(p2) ) / 2 ); + + return p; + } + +/* Gingham stuff. */ + +static void +average_drawproc(pixel** const pixels, + int const cols, + int const rows, + pixval const maxval, + int const col, + int const row, + const void* const clientdata ) +{ + if ( col >= 0 && col < cols && row >= 0 && row < rows ) + pixels[row][col] = + average_two_colors( pixels[row][col], *( (pixel*) clientdata ) ); +} + +static void +gingham2( pixel** pixels, int cols, int rows, pixval maxval ) +{ + pixel const backcolor = random_dark_color( maxval ); + pixel const forecolor = random_bright_color( maxval ); + int const colso2 = cols / 2; + int const rowso2 = rows / 2; + + /* Warp. */ + ppmd_filledrectangle( + pixels, cols, rows, maxval, 0, 0, colso2, rows, PPMD_NULLDRAWPROC, + &backcolor ); + ppmd_filledrectangle( + pixels, cols, rows, maxval, colso2, 0, cols - colso2, rows, + PPMD_NULLDRAWPROC, &forecolor ); + + /* Woof. */ + ppmd_filledrectangle( + pixels, cols, rows, maxval, 0, 0, cols, rowso2, average_drawproc, + &backcolor ); + ppmd_filledrectangle( + pixels, cols, rows, maxval, 0, rowso2, cols, rows - rowso2, + average_drawproc, &forecolor ); + } + +#if __STDC__ +static void +gingham3( pixel** pixels, int cols, int rows, pixval maxval ) +#else /*__STDC__*/ +static void +gingham3( pixels, cols, rows, maxval ) + pixel** pixels; + int cols, rows; + pixval maxval; +#endif /*__STDC__*/ + { + int colso4, rowso4; + pixel backcolor, fore1color, fore2color; + + colso4 = cols / 4; + rowso4 = rows / 4; + backcolor = random_dark_color( maxval ); + fore1color = random_bright_color( maxval ); + fore2color = random_bright_color( maxval ); + + /* Warp. */ + ppmd_filledrectangle( + pixels, cols, rows, maxval, 0, 0, colso4, rows, PPMD_NULLDRAWPROC, + &backcolor ); + ppmd_filledrectangle( + pixels, cols, rows, maxval, colso4, 0, colso4, rows, PPMD_NULLDRAWPROC, + &fore1color ); + ppmd_filledrectangle( + pixels, cols, rows, maxval, 2 * colso4, 0, colso4, rows, + PPMD_NULLDRAWPROC, &fore2color ); + ppmd_filledrectangle( + pixels, cols, rows, maxval, 3 * colso4, 0, cols - colso4, rows, + PPMD_NULLDRAWPROC, &fore1color ); + + /* Woof. */ + ppmd_filledrectangle( + pixels, cols, rows, maxval, 0, 0, cols, rowso4, average_drawproc, + &backcolor ); + ppmd_filledrectangle( + pixels, cols, rows, maxval, 0, rowso4, cols, rowso4, average_drawproc, + &fore1color ); + ppmd_filledrectangle( + pixels, cols, rows, maxval, 0, 2 * rowso4, cols, rowso4, + average_drawproc, &fore2color ); + ppmd_filledrectangle( + pixels, cols, rows, maxval, 0, 3 * rowso4, cols, rows - rowso4, + average_drawproc, &fore1color ); + } + +#if __STDC__ +static void +madras( pixel** pixels, int cols, int rows, pixval maxval ) +#else /*__STDC__*/ +static void +madras( pixels, cols, rows, maxval ) + pixel** pixels; + int cols, rows; + pixval maxval; +#endif /*__STDC__*/ + { + int cols2, rows2, cols3, rows3, cols12, rows12, cols6a, rows6a, cols6b, + rows6b; + pixel backcolor, fore1color, fore2color; + + cols2 = cols * 2 / 44; + rows2 = rows * 2 / 44; + cols3 = cols * 3 / 44; + rows3 = rows * 3 / 44; + cols12 = cols - 10 * cols2 - 4 * cols3; + rows12 = rows - 10 * rows2 - 4 * rows3; + cols6a = cols12 / 2; + rows6a = rows12 / 2; + cols6b = cols12 - cols6a; + rows6b = rows12 - rows6a; + backcolor = random_dark_color( maxval ); + fore1color = random_bright_color( maxval ); + fore2color = random_bright_color( maxval ); + + /* Warp. */ + ppmd_filledrectangle( + pixels, cols, rows, maxval, 0, 0, cols2, rows, PPMD_NULLDRAWPROC, + &backcolor ); + ppmd_filledrectangle( + pixels, cols, rows, maxval, cols2, 0, cols3, rows, PPMD_NULLDRAWPROC, + &fore1color ); + ppmd_filledrectangle( + pixels, cols, rows, maxval, cols2 + cols3, 0, cols2, rows, + PPMD_NULLDRAWPROC, &backcolor ); + ppmd_filledrectangle( + pixels, cols, rows, maxval, 2 * cols2 + cols3, 0, cols2, rows, + PPMD_NULLDRAWPROC, &fore2color ); + ppmd_filledrectangle( + pixels, cols, rows, maxval, 3 * cols2 + cols3, 0, cols2, rows, + PPMD_NULLDRAWPROC, &backcolor ); + ppmd_filledrectangle( + pixels, cols, rows, maxval, 4 * cols2 + cols3, 0, cols6a, rows, + PPMD_NULLDRAWPROC, &fore1color ); + ppmd_filledrectangle( + pixels, cols, rows, maxval, 4 * cols2 + cols3 + cols6a, 0, cols2, rows, + PPMD_NULLDRAWPROC, &backcolor ); + ppmd_filledrectangle( + pixels, cols, rows, maxval, 5 * cols2 + cols3 + cols6a, 0, cols3, rows, + PPMD_NULLDRAWPROC, &fore2color ); + ppmd_filledrectangle( + pixels, cols, rows, maxval, 5 * cols2 + 2 * cols3 + cols6a, 0, cols2, + rows, PPMD_NULLDRAWPROC, &backcolor ); + ppmd_filledrectangle( + pixels, cols, rows, maxval, 6 * cols2 + 2 * cols3 + cols6a, 0, cols3, + rows, PPMD_NULLDRAWPROC, &fore2color ); + ppmd_filledrectangle( + pixels, cols, rows, maxval, 6 * cols2 + 3 * cols3 + cols6a, 0, cols2, + rows, PPMD_NULLDRAWPROC, &backcolor ); + ppmd_filledrectangle( + pixels, cols, rows, maxval, 7 * cols2 + 3 * cols3 + cols6a, 0, cols6b, + rows, PPMD_NULLDRAWPROC, &fore1color ); + ppmd_filledrectangle( + pixels, cols, rows, maxval, 7 * cols2 + 3 * cols3 + cols6a + cols6b, 0, + cols2, rows, PPMD_NULLDRAWPROC, &backcolor ); + ppmd_filledrectangle( + pixels, cols, rows, maxval, 8 * cols2 + 3 * cols3 + cols6a + cols6b, 0, + cols2, rows, PPMD_NULLDRAWPROC, &fore2color ); + ppmd_filledrectangle( + pixels, cols, rows, maxval, 9 * cols2 + 3 * cols3 + cols6a + cols6b, 0, + cols2, rows, PPMD_NULLDRAWPROC, &backcolor ); + ppmd_filledrectangle( + pixels, cols, rows, maxval, 10 * cols2 + 3 * cols3 + cols6a + cols6b, + 0, cols3, rows, PPMD_NULLDRAWPROC, &fore1color ); + + /* Woof. */ + ppmd_filledrectangle( + pixels, cols, rows, maxval, 0, 0, cols, rows2, average_drawproc, + &backcolor ); + ppmd_filledrectangle( + pixels, cols, rows, maxval, 0, rows2, cols, rows3, average_drawproc, + &fore2color ); + ppmd_filledrectangle( + pixels, cols, rows, maxval, 0, rows2 + rows3, cols, rows2, + average_drawproc, &backcolor ); + ppmd_filledrectangle( + pixels, cols, rows, maxval, 0, 2 * rows2 + rows3, cols, rows2, + average_drawproc, &fore1color ); + ppmd_filledrectangle( + pixels, cols, rows, maxval, 0, 3 * rows2 + rows3, cols, rows2, + average_drawproc, &backcolor ); + ppmd_filledrectangle( + pixels, cols, rows, maxval, 0, 4 * rows2 + rows3, cols, rows6a, + average_drawproc, &fore2color ); + ppmd_filledrectangle( + pixels, cols, rows, maxval, 0, 4 * rows2 + rows3 + rows6a, cols, rows2, + average_drawproc, &backcolor ); + ppmd_filledrectangle( + pixels, cols, rows, maxval, 0, 5 * rows2 + rows3 + rows6a, cols, rows3, + average_drawproc, &fore1color ); + ppmd_filledrectangle( + pixels, cols, rows, maxval, 0, 5 * rows2 + 2 * rows3 + rows6a, cols, + rows2, average_drawproc, &backcolor ); + ppmd_filledrectangle( + pixels, cols, rows, maxval, 0, 6 * rows2 + 2 * rows3 + rows6a, cols, + rows3, average_drawproc, &fore1color ); + ppmd_filledrectangle( + pixels, cols, rows, maxval, 0, 6 * rows2 + 3 * rows3 + rows6a, cols, + rows2, average_drawproc, &backcolor ); + ppmd_filledrectangle( + pixels, cols, rows, maxval, 0, 7 * rows2 + 3 * rows3 + rows6a, cols, + rows6b, average_drawproc, &fore2color ); + ppmd_filledrectangle( + pixels, cols, rows, maxval, 0, 7 * rows2 + 3 * rows3 + rows6a + rows6b, + cols, rows2, average_drawproc, &backcolor ); + ppmd_filledrectangle( + pixels, cols, rows, maxval, 0, 8 * rows2 + 3 * rows3 + rows6a + rows6b, + cols, rows2, average_drawproc, &fore1color ); + ppmd_filledrectangle( + pixels, cols, rows, maxval, 0, 9 * rows2 + 3 * rows3 + rows6a + rows6b, + cols, rows2, average_drawproc, &backcolor ); + ppmd_filledrectangle( + pixels, cols, rows, maxval, 0, + 10 * rows2 + 3 * rows3 + rows6a + rows6b, + cols, rows3, average_drawproc, &fore2color ); + } + +#if __STDC__ +static void +tartan( pixel** pixels, int cols, int rows, pixval maxval ) +#else /*__STDC__*/ +static void +tartan( pixels, cols, rows, maxval ) + pixel** pixels; + int cols, rows; + pixval maxval; +#endif /*__STDC__*/ + { + int cols1, rows1, cols3, rows3, cols10, rows10, cols5a, rows5a, cols5b, + rows5b; + pixel backcolor, fore1color, fore2color; + + cols1 = cols / 22; + rows1 = rows / 22; + cols3 = cols * 3 / 22; + rows3 = rows * 3 / 22; + cols10 = cols - 3 * cols1 - 3 * cols3; + rows10 = rows - 3 * rows1 - 3 * rows3; + cols5a = cols10 / 2; + rows5a = rows10 / 2; + cols5b = cols10 - cols5a; + rows5b = rows10 - rows5a; + backcolor = random_dark_color( maxval ); + fore1color = random_bright_color( maxval ); + fore2color = random_bright_color( maxval ); + + /* Warp. */ + ppmd_filledrectangle( + pixels, cols, rows, maxval, 0, 0, cols5a, rows, PPMD_NULLDRAWPROC, + &backcolor ); + ppmd_filledrectangle( + pixels, cols, rows, maxval, cols5a, 0, cols1, rows, PPMD_NULLDRAWPROC, + &fore1color ); + ppmd_filledrectangle( + pixels, cols, rows, maxval, cols5a + cols1, 0, cols5b, rows, + PPMD_NULLDRAWPROC, &backcolor ); + ppmd_filledrectangle( + pixels, cols, rows, maxval, cols10 + cols1, 0, cols3, rows, + PPMD_NULLDRAWPROC, &fore2color ); + ppmd_filledrectangle( + pixels, cols, rows, maxval, cols10 + cols1 + cols3, 0, cols1, rows, + PPMD_NULLDRAWPROC, &backcolor ); + ppmd_filledrectangle( + pixels, cols, rows, maxval, cols10 + 2 * cols1 + cols3, 0, cols3, rows, + PPMD_NULLDRAWPROC, &fore2color ); + ppmd_filledrectangle( + pixels, cols, rows, maxval, cols10 + 2 * cols1 + 2 * cols3, 0, cols1, + rows, PPMD_NULLDRAWPROC, (char*) &backcolor ); + ppmd_filledrectangle( + pixels, cols, rows, maxval, cols10 + 3 * cols1 + 2 * cols3, 0, cols3, + rows, PPMD_NULLDRAWPROC, &fore2color ); + + /* Woof. */ + ppmd_filledrectangle( + pixels, cols, rows, maxval, 0, 0, cols, rows5a, average_drawproc, + &backcolor ); + ppmd_filledrectangle( + pixels, cols, rows, maxval, 0, rows5a, cols, rows1, average_drawproc, + &fore1color ); + ppmd_filledrectangle( + pixels, cols, rows, maxval, 0, rows5a + rows1, cols, rows5b, + average_drawproc, &backcolor ); + ppmd_filledrectangle( + pixels, cols, rows, maxval, 0, rows10 + rows1, cols, rows3, + average_drawproc, &fore2color ); + ppmd_filledrectangle( + pixels, cols, rows, maxval, 0, rows10 + rows1 + rows3, cols, rows1, + average_drawproc, &backcolor ); + ppmd_filledrectangle( + pixels, cols, rows, maxval, 0, rows10 + 2 * rows1 + rows3, cols, rows3, + average_drawproc, &fore2color ); + ppmd_filledrectangle( + pixels, cols, rows, maxval, 0, rows10 + 2 * rows1 + 2 * rows3, cols, + rows1, average_drawproc, &backcolor ); + ppmd_filledrectangle( + pixels, cols, rows, maxval, 0, rows10 + 3 * rows1 + 2 * rows3, cols, + rows3, average_drawproc, &fore2color ); + } + +/* Poles stuff. */ + +#define MAXPOLES 500 + +#if __STDC__ +static void +poles( pixel** pixels, int cols, int rows, pixval maxval ) +#else /*__STDC__*/ +static void +poles( pixels, cols, rows, maxval ) + pixel** pixels; + int cols, rows; + pixval maxval; +#endif /*__STDC__*/ + { + int poles, i, xs[MAXPOLES], ys[MAXPOLES], col, row; + pixel colors[MAXPOLES]; + + poles = cols * rows / 30000; + + /* Place and color poles randomly. */ + for ( i = 0; i < poles; ++i ) + { + xs[i] = rand() % cols; + ys[i] = rand() % rows; + colors[i] = random_bright_color( maxval ); + } + + /* Now interpolate points. */ + for ( row = 0; row < rows; ++row ) + for ( col = 0; col < cols; ++col ) + { + register long dist1, dist2, newdist, r, g, b; + pixel color1, color2; + + /* Find two closest poles. */ + dist1 = dist2 = 2000000000; + for ( i = 0; i < poles; ++i ) + { + newdist = ( col - xs[i] ) * ( col - xs[i] ) + + ( row - ys[i] ) * ( row - ys[i] ); + if ( newdist < dist1 ) + { + dist1 = newdist; + color1 = colors[i]; + } + else if ( newdist < dist2 ) + { + dist2 = newdist; + color2 = colors[i]; + } + } + + /* And assign interpolated color. */ + newdist = dist1 + dist2; + r = PPM_GETR(color1)*dist2/newdist + PPM_GETR(color2)*dist1/newdist; + g = PPM_GETG(color1)*dist2/newdist + PPM_GETG(color2)*dist1/newdist; + b = PPM_GETB(color1)*dist2/newdist + PPM_GETB(color2)*dist1/newdist; + PPM_ASSIGN( pixels[row][col], r, g, b ); + } + } + +/* Squig stuff. */ + +#define SQUIGS 5 +#define SQ_POINTS 7 +#define SQ_MAXCIRCLE_POINTS 5000 + +static int sq_radius, sq_circlecount; +static pixel sq_colors[SQ_MAXCIRCLE_POINTS]; +static int sq_xoffs[SQ_MAXCIRCLE_POINTS], sq_yoffs[SQ_MAXCIRCLE_POINTS]; + +static void +sq_measurecircle_drawproc(pixel** const pixels, + int const cols, + int const rows, + pixval const maxval, + int const col, + int const row, + const void* const clientdata) +{ + sq_xoffs[sq_circlecount] = col; + sq_yoffs[sq_circlecount] = row; + ++sq_circlecount; +} + +static void +sq_rainbowcircle_drawproc(pixel** const pixels, + int const cols, + int const rows, + pixval const maxval, + int const col, + int const row, + const void* const clientdata ) +{ + int i; + + for ( i = 0; i < sq_circlecount; ++i ) + ppmd_point_drawproc( + pixels, cols, rows, maxval, col + sq_xoffs[i], row + sq_yoffs[i], + &(sq_colors[i]) ); + } + + + +static void +sq_assign_colors(int const circlecount, + pixval const maxval, + pixel * const colors) { + + pixel rc1, rc2, rc3; + float cco3; + unsigned int i; + + rc1 = random_bright_color(maxval); + rc2 = random_bright_color(maxval); + rc3 = random_bright_color(maxval); + cco3 = (circlecount - 1) / 3.0; + + for (i = 0; i < circlecount ; ++i) { + if (i < cco3) { + float const frac = (float)i/cco3; + PPM_ASSIGN(colors[i], + (float) PPM_GETR(rc1) + + ((float) PPM_GETR(rc2) - (float) PPM_GETR(rc1)) * frac, + (float) PPM_GETG(rc1) + + ((float) PPM_GETG(rc2) - (float) PPM_GETG(rc1)) * frac, + (float) PPM_GETB(rc1) + + ((float) PPM_GETB(rc2) - (float) PPM_GETB(rc1)) * frac + ); + } else if (i < 2.0 * cco3) { + float const frac = (float)i/cco3 - 1.0; + PPM_ASSIGN(colors[i], + (float) PPM_GETR(rc2) + + ((float) PPM_GETR(rc3) - (float) PPM_GETR(rc2)) * frac, + (float) PPM_GETG(rc2) + + ((float) PPM_GETG(rc3) - (float) PPM_GETG(rc2)) * frac, + (float) PPM_GETB(rc2) + + ((float) PPM_GETB(rc3) - (float) PPM_GETB(rc2)) * frac + ); + } else { + float const frac = (float)i/cco3 - 2.0; + PPM_ASSIGN(colors[i], + (float) PPM_GETR(rc3) + + ((float) PPM_GETR(rc1) - (float) PPM_GETR(rc3)) * frac, + (float) PPM_GETG(rc3) + + ((float) PPM_GETG(rc1) - (float) PPM_GETG(rc3)) * frac, + (float) PPM_GETB(rc3) + + ((float) PPM_GETB(rc1) - (float) PPM_GETB(rc3)) * frac + ); + } + } +} + + +#if __STDC__ +static void +squig( pixel** pixels, int cols, int rows, pixval maxval ) +#else /*__STDC__*/ +static void +squig( pixels, cols, rows, maxval ) + pixel** pixels; + int cols, rows; + pixval maxval; +#endif /*__STDC__*/ + { + pixel color; + int i, j, xc[SQ_POINTS], yc[SQ_POINTS], x0, y0, x1, y1, x2, y2, x3, y3; + + /* Clear image to black. */ + PPM_ASSIGN( color, 0, 0, 0 ); + ppmd_filledrectangle( + pixels, cols, rows, maxval, 0, 0, cols, rows, PPMD_NULLDRAWPROC, + &color ); + + /* Draw the squigs. */ + (void) ppmd_setlinetype( PPMD_LINETYPE_NODIAGS ); + (void) ppmd_setlineclip( 0 ); + for ( i = SQUIGS; i > 0; --i ) + { + /* Measure circle. */ + sq_radius = ( cols + rows ) / 2 / ( 25 + i * 2 ); + sq_circlecount = 0; + ppmd_circle( + pixels, cols, rows, maxval, 0, 0, sq_radius, + sq_measurecircle_drawproc, NULL ); + sq_assign_colors( sq_circlecount, maxval, sq_colors ); + + /* Choose wrap-around point. */ + switch ( rand() % 4 ) + { + case 0: + x1 = rand() % cols; + y1 = 0; + if ( x1 < cols / 2 ) + xc[0] = rand() % ( x1 * 2 ); + else + xc[0] = cols - 1 - rand() % ( ( cols - x1 ) * 2 ); + yc[0] = rand() % rows; + x2 = x1; + y2 = rows - 1; + xc[SQ_POINTS - 1] = 2 * x2 - xc[0]; + yc[SQ_POINTS - 1] = y2 - yc[0]; + x0 = xc[SQ_POINTS - 1]; + y0 = yc[SQ_POINTS - 1] - rows; + x3 = xc[0]; + y3 = yc[0] + rows; + break; + + case 1: + x2 = rand() % cols; + y2 = 0; + if ( x2 < cols / 2 ) + xc[SQ_POINTS - 1] = rand() % ( x2 * 2 ); + else + xc[SQ_POINTS - 1] = cols - 1 - rand() % ( ( cols - x2 ) * 2 ); + yc[SQ_POINTS - 1] = rand() % rows; + x1 = x2; + y1 = rows - 1; + xc[0] = 2 * x1 - xc[SQ_POINTS - 1]; + yc[0] = y1 - yc[SQ_POINTS - 1]; + x0 = xc[SQ_POINTS - 1]; + y0 = yc[SQ_POINTS - 1] + rows; + x3 = xc[0]; + y3 = yc[0] - rows; + break; + + case 2: + x1 = 0; + y1 = rand() % rows; + xc[0] = rand() % cols; + if ( y1 < rows / 2 ) + yc[0] = rand() % ( y1 * 2 ); + else + yc[0] = rows - 1 - rand() % ( ( rows - y1 ) * 2 ); + x2 = cols - 1; + y2 = y1; + xc[SQ_POINTS - 1] = x2 - xc[0]; + yc[SQ_POINTS - 1] = 2 * y2 - yc[0]; + x0 = xc[SQ_POINTS - 1] - cols; + y0 = yc[SQ_POINTS - 1]; + x3 = xc[0] + cols; + y3 = yc[0]; + break; + + case 3: + x2 = 0; + y2 = rand() % rows; + xc[SQ_POINTS - 1] = rand() % cols; + if ( y2 < rows / 2 ) + yc[SQ_POINTS - 1] = rand() % ( y2 * 2 ); + else + yc[SQ_POINTS - 1] = rows - 1 - rand() % ( ( rows - y2 ) * 2 ); + x1 = cols - 1; + y1 = y2; + xc[0] = x1 - xc[SQ_POINTS - 1]; + yc[0] = 2 * y1 - yc[SQ_POINTS - 1]; + x0 = xc[SQ_POINTS - 1] + cols; + y0 = yc[SQ_POINTS - 1]; + x3 = xc[0] - cols; + y3 = yc[0]; + break; + } + + for ( j = 1; j < SQ_POINTS - 1; ++j ) + { + xc[j] = ( rand() % ( cols - 2 * sq_radius ) ) + sq_radius; + yc[j] = ( rand() % ( rows - 2 * sq_radius ) ) + sq_radius; + } + + ppmd_line( + pixels, cols, rows, maxval, x0, y0, x1, y1, + sq_rainbowcircle_drawproc, NULL ); + ppmd_polyspline( + pixels, cols, rows, maxval, x1, y1, SQ_POINTS, xc, yc, x2, y2, + sq_rainbowcircle_drawproc, NULL ); + ppmd_line( + pixels, cols, rows, maxval, x2, y2, x3, y3, + sq_rainbowcircle_drawproc, NULL ); + } + } + + + +/* Test pattern. Just a place to put ppmdraw exercises. */ + +static void +test(pixel ** const pixels, + unsigned int const cols, + unsigned int const rows, + pixval const maxval) { + + pixel color; + struct fillobj * fh; + + /* Clear image to black. */ + PPM_ASSIGN( color, 0, 0, 0 ); + ppmd_filledrectangle( + pixels, cols, rows, maxval, 0, 0, cols, rows, PPMD_NULLDRAWPROC, + &color); + + fh = ppmd_fill_create(); + + ppmd_line(pixels, cols, rows, maxval, + cols/8, rows/8, cols/2, rows/4, ppmd_fill_drawproc, fh); + ppmd_line(pixels, cols, rows, maxval, + cols/2, rows/4, cols-cols/8, rows/8, ppmd_fill_drawproc, fh); + ppmd_line(pixels, cols, rows, maxval, + cols-cols/8, rows/8, cols/2, rows/2, ppmd_fill_drawproc, fh); + ppmd_spline3(pixels, cols, rows, maxval, + cols/2, rows/2, cols/2-cols/16, rows/2-rows/10, + cols/2-cols/8, rows/2, ppmd_fill_drawproc, fh); + ppmd_spline3(pixels, cols, rows, maxval, + cols/2-cols/8, rows/2, cols/4+cols/16, rows/2+rows/10, + cols/4, rows/2, ppmd_fill_drawproc, fh); + ppmd_line(pixels, cols, rows, maxval, + cols/4, rows/2, cols/8, rows/2, ppmd_fill_drawproc, fh); + ppmd_line(pixels, cols, rows, maxval, + cols/8, rows/2, cols/8, rows/8, ppmd_fill_drawproc, fh); + + PPM_ASSIGN(color, maxval, maxval, maxval); + ppmd_fill(pixels, cols, rows, maxval, fh, PPMD_NULLDRAWPROC, &color); + + ppmd_fill_destroy(fh); + +} + + + +int +main(int argc, char ** argv) { + + pixel** pixels; + int argn, pattern, cols, rows; +#define PAT_NONE 0 +#define PAT_GINGHAM2 1 +#define PAT_GINGHAM3 2 +#define PAT_MADRAS 3 +#define PAT_TARTAN 4 +#define PAT_POLES 5 +#define PAT_SQUIG 6 +#define PAT_CAMO 7 +#define PAT_ANTICAMO 8 +#define PAT_TEST 9 + const char* const usage = "-gingham|-g2|-gingham3|-g3|-madras|-tartan|-poles|-squig|-camo|-anticamo <width> <height>"; + + + ppm_init(&argc, argv); + + argn = 1; + pattern = PAT_NONE; + + while ( argn < argc && argv[argn][0] == '-' && argv[argn][1] != '\0' ) + { + if ( pm_keymatch( argv[argn], "-gingham2", 9 ) || + pm_keymatch( argv[argn], "-g2", 3 ) ) + { + if ( pattern != PAT_NONE ) + pm_error( "only one base pattern may be specified" ); + pattern = PAT_GINGHAM2; + } + else if ( pm_keymatch( argv[argn], "-gingham3", 9 ) || + pm_keymatch( argv[argn], "-g3", 3 ) ) + { + if ( pattern != PAT_NONE ) + pm_error( "only one base pattern may be specified" ); + pattern = PAT_GINGHAM3; + } + else if ( pm_keymatch( argv[argn], "-madras", 2 ) ) + { + if ( pattern != PAT_NONE ) + pm_error( "only one base pattern may be specified" ); + pattern = PAT_MADRAS; + } + else if ( pm_keymatch( argv[argn], "-tartan", 2 ) ) + { + if ( pattern != PAT_NONE ) + pm_error( "only one base pattern may be specified" ); + pattern = PAT_TARTAN; + } + else if ( pm_keymatch( argv[argn], "-poles", 2 ) ) + { + if ( pattern != PAT_NONE ) + pm_error( "only one base pattern may be specified" ); + pattern = PAT_POLES; + } + else if ( pm_keymatch( argv[argn], "-squig", 2 ) ) + { + if ( pattern != PAT_NONE ) + pm_error( "only one base pattern may be specified" ); + pattern = PAT_SQUIG; + } + else if ( pm_keymatch( argv[argn], "-camo", 2 ) ) + { + if ( pattern != PAT_NONE ) + pm_error( "only one base pattern may be specified" ); + pattern = PAT_CAMO; + } + else if ( pm_keymatch( argv[argn], "-anticamo", 2 ) ) + { + if ( pattern != PAT_NONE ) + pm_error( "only one base pattern may be specified" ); + pattern = PAT_ANTICAMO; + } + else if ( pm_keymatch( argv[argn], "-test", 3 ) ) + { + if ( pattern != PAT_NONE ) + pm_error( "only one base pattern may be specified" ); + pattern = PAT_TEST; + } + else + pm_usage( usage ); + ++argn; + } + if ( pattern == PAT_NONE ) + pm_error( "a base pattern must be specified" ); + + if ( argn == argc ) + pm_usage( usage); + if ( sscanf( argv[argn], "%d", &cols ) != 1 ) + pm_usage( usage ); + ++argn; + if ( argn == argc ) + pm_usage( usage); + if ( sscanf( argv[argn], "%d", &rows ) != 1 ) + pm_usage( usage ); + ++argn; + + if ( argn != argc ) + pm_usage( usage); + + srand( (int) ( time( 0 ) ^ getpid( ) ) ); + pixels = ppm_allocarray( cols, rows ); + + switch ( pattern ) + { + case PAT_GINGHAM2: + gingham2( pixels, cols, rows, PPM_MAXMAXVAL ); + break; + + case PAT_GINGHAM3: + gingham3( pixels, cols, rows, PPM_MAXMAXVAL ); + break; + + case PAT_MADRAS: + madras( pixels, cols, rows, PPM_MAXMAXVAL ); + break; + + case PAT_TARTAN: + tartan( pixels, cols, rows, PPM_MAXMAXVAL ); + break; + + case PAT_POLES: + poles( pixels, cols, rows, PPM_MAXMAXVAL ); + break; + + case PAT_SQUIG: + squig( pixels, cols, rows, PPM_MAXMAXVAL ); + break; + + case PAT_CAMO: + camo( pixels, cols, rows, PPM_MAXMAXVAL, 0 ); + break; + + case PAT_ANTICAMO: + camo( pixels, cols, rows, PPM_MAXMAXVAL, 1 ); + break; + + case PAT_TEST: + test( pixels, cols, rows, PPM_MAXMAXVAL ); + break; + + default: + pm_error( "can't happen!" ); + } + + /* All done, write it out. */ + ppm_writeppm( stdout, pixels, cols, rows, PPM_MAXMAXVAL, 0 ); + pm_close( stdout ); + + exit( 0 ); +} + diff --git a/generator/ppmrainbow b/generator/ppmrainbow new file mode 100755 index 00000000..0effeecf --- /dev/null +++ b/generator/ppmrainbow @@ -0,0 +1,74 @@ +#!/usr/bin/perl -wl +use strict; +use Getopt::Long; + +my ($FALSE, $TRUE) = (0,1); + +(my $myname = $0) =~ s#\A.*/##; + +my ($Twid, $Thgt, $tmpdir, $norepeat, $verbose); + +# set defaults +$Twid = 600; +$Thgt = 8; +$tmpdir = $ENV{"TMPDIR"} || "/tmp"; +$norepeat = $FALSE; +$verbose = $FALSE; + +GetOptions("width=i" => \$Twid, + "height=i" => \$Thgt, + "tmpdir=s" => \$tmpdir, + "norepeat!" => \$norepeat, + "verbose!" => \$verbose); + +die "invalid width and/or height\n" unless $Twid >= 1 && $Thgt >= 1; + +my $verboseCommand = $verbose ? "set -x;" : ""; + +if (@ARGV < 1) { + die("You must specify at least one color as an argument"); +} elsif (@ARGV < 2 && $norepeat) { + die("With the -norepeat option, you must specify at least two colors " . + "as arguments."); +} + +my @colorlist; + +@colorlist = @ARGV; +if (!$norepeat) { + push @colorlist, $ARGV[0]; +} + +my $tmpprefix = $tmpdir . "/$myname.$$."; + +my $widthRemaining; +my $n; +my @outlist; + +$n = 0; +$widthRemaining = $Twid; +@outlist = (); + +while (@colorlist >= 2) { + my $outfile = sprintf("%s%03u.ppm", $tmpprefix, $n); + push(@outlist, $outfile); + + my $w = int(($widthRemaining-1)/(@colorlist-1))+1; + my $rc = system("$verboseCommand pgmramp -lr $w $Thgt | " . + "pgmtoppm \"$colorlist[0]-$colorlist[1]\" >$outfile"); + if ($rc != 0) { + die("pgmramp|pgmtoppm failed."); + } + $widthRemaining -= $w; + $n++; + shift @colorlist; +} + +0 == system qq{$verboseCommand pnmcat -lr @outlist} + or exit 1; + +exit 0; + +END { + unlink @outlist if @outlist; +} diff --git a/generator/ppmrough.c b/generator/ppmrough.c new file mode 100644 index 00000000..f8823173 --- /dev/null +++ b/generator/ppmrough.c @@ -0,0 +1,292 @@ +/* ppmrough.c - create a PPM image containing two colors with a ragged + border between them +** +** Copyright (C) 2002 by Eckard Specht. +** +** 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. */ + +#include <math.h> +#include <sys/time.h> +#include "ppm.h" +#include "shhopt.h" + +static pixel** PIX; +static pixval BG_RED, BG_GREEN, BG_BLUE; + + +struct cmdlineInfo { + /* All the information the user supplied in the command line, + in a form easy for the program to use. + */ + unsigned int left, right, top, bottom; + unsigned int width, height, var; + const char *bg_rgb; + const char *fg_rgb; + unsigned init; + unsigned int verbose; +}; + + +static void +/*-------------------------------------------------------------------------- */ + parseCommandLine(int argc, char ** argv, struct cmdlineInfo *cmdlineP) +/*-------------------------------------------------------------------------- */ +{ + optEntry *option_def = malloc(100*sizeof(optEntry)); + /* Instructions to OptParseOptions2 on how to parse our options. */ + optStruct3 opt; + + unsigned int option_def_index; + + option_def_index = 0; /* incremented by OPTENTRY */ + OPTENT3(0, "width", OPT_UINT, &cmdlineP->width, NULL, 0); + OPTENT3(0, "height", OPT_UINT, &cmdlineP->height, NULL, 0); + OPTENT3(0, "left", OPT_UINT, &cmdlineP->left, NULL, 0); + OPTENT3(0, "right", OPT_UINT, &cmdlineP->right, NULL, 0); + OPTENT3(0, "top", OPT_UINT, &cmdlineP->top, NULL, 0); + OPTENT3(0, "bottom", OPT_UINT, &cmdlineP->bottom, NULL, 0); + OPTENT3(0, "bg", OPT_STRING, &cmdlineP->bg_rgb, NULL, 0); + OPTENT3(0, "fg", OPT_STRING, &cmdlineP->fg_rgb, NULL, 0); + OPTENT3(0, "var", OPT_UINT, &cmdlineP->var, NULL, 0); + OPTENT3(0, "init", OPT_UINT, &cmdlineP->init, NULL, 0); + OPTENT3(0, "verbose", OPT_FLAG, NULL, &cmdlineP->verbose, 0); + + /* Set the defaults */ + cmdlineP->width = 100; + cmdlineP->height = 100; + cmdlineP->left = cmdlineP->right = cmdlineP->top = cmdlineP->bottom = -1; + cmdlineP->bg_rgb = NULL; + cmdlineP->fg_rgb = NULL; + cmdlineP->var = 10; + + opt.opt_table = option_def; + opt.short_allowed = FALSE; /* We have no short (old-fashioned) options */ + opt.allowNegNum = FALSE; /* We have no parms that are negative numbers */ + + optParseOptions3(&argc, argv, opt, sizeof(opt), 0); + + if (argc-1 != 0) + pm_error("There are no arguments. You specified %d.", argc-1); +} + +static void +/* ----------------------------------------- */ + proc_left(int const r1, int const r2, int const c1, int const c2, + unsigned int const var) +/* ----------------------------------------- */ +{ + int cm, rm, c; + + if (r1 + 1 == r2) return; + rm = (r1 + r2) >> 1; + cm = (c1 + c2) >> 1; + cm += (int)floor(((float)rand() / RAND_MAX - 0.5) * var + 0.5); + + for (c = 0; c < cm; c++) + PPM_ASSIGN(PIX[rm][c], BG_RED, BG_GREEN, BG_BLUE); + + proc_left(r1, rm, c1, cm, var); + proc_left(rm, r2, cm, c2, var); +} + +static void +/* ------------------------------------------ */ + proc_right(int const r1, int const r2, int const c1, int const c2, + unsigned int const width, unsigned int const var) +/* ------------------------------------------ */ +{ + int cm, rm, c; + + if (r1 + 1 == r2) return; + rm = (r1 + r2) >> 1; + cm = (c1 + c2) >> 1; + cm += (int)floor(((float)rand() / RAND_MAX - 0.5) * var + 0.5); + + for (c = cm; c < width; c++) + PPM_ASSIGN(PIX[rm][c], BG_RED, BG_GREEN, BG_BLUE); + + proc_right(r1, rm, c1, cm, width, var); + proc_right(rm, r2, cm, c2, width, var); +} + +static void +/* ---------------------------------------- */ + proc_top(int const c1, int const c2, int const r1, int const r2, + unsigned int const var) +/* ---------------------------------------- */ +{ + int rm, cm, r; + + if (c1 + 1 == c2) return; + cm = (c1 + c2) >> 1; + rm = (r1 + r2) >> 1; + rm += (int)floor(((float)rand() / RAND_MAX - 0.5) * var + 0.5); + + for (r = 0; r < rm; r++) + PPM_ASSIGN(PIX[r][cm], BG_RED, BG_GREEN, BG_BLUE); + + proc_top(c1, cm, r1, rm, var); + proc_top(cm, c2, rm, r2, var); +} + +static void +/* ------------------------------------------- */ + proc_bottom(int const c1, int const c2, int const r1, int const r2, + unsigned int const height, unsigned int const var) +/* ------------------------------------------- */ +{ + int rm, cm, r; + + if (c1 + 1 == c2) return; + cm = (c1 + c2) >> 1; + rm = (r1 + r2) >> 1; + rm += (int)floor(((float)rand() / RAND_MAX - 0.5) * var + 0.5); + + for (r = rm; r < height; r++) + PPM_ASSIGN(PIX[r][cm], BG_RED, BG_GREEN, BG_BLUE); + + proc_bottom(c1, cm, r1, rm, height, var); + proc_bottom(cm, c2, rm, r2, height, var); +} + + +int +/* ============================ */ + main(int argc, char* argv[]) +/* ============================ */ +{ + struct cmdlineInfo cmdline; + pixel bgcolor, fgcolor; + pixval fg_red, fg_green, fg_blue; + int rows, cols, row; + int left, right, top, bottom; + int left_r1, left_r2, left_c1, left_c2; + int right_r1, right_r2, right_c1, right_c2; + int top_r1, top_r2, top_c1, top_c2; + int bottom_r1, bottom_r2, bottom_c1, bottom_c2; + unsigned init; + struct timeval tv; + int col; + + ppm_init(&argc, argv); + + parseCommandLine(argc, argv, &cmdline); + + init = cmdline.init; + if (init == 0) { + gettimeofday(&tv, NULL); + srand((unsigned int)tv.tv_usec); + } + else + srand(init); + + cols = cmdline.width; + rows = cmdline.height; + left = cmdline.left; + right = cmdline.right; + top = cmdline.top; + bottom = cmdline.bottom; + + if (cmdline.bg_rgb) + bgcolor = ppm_parsecolor(cmdline.bg_rgb, PPM_MAXMAXVAL); + else + PPM_ASSIGN(bgcolor, 0, 0, 0); + BG_RED = PPM_GETR(bgcolor); + BG_GREEN = PPM_GETG(bgcolor); + BG_BLUE = PPM_GETB(bgcolor); + + if (cmdline.fg_rgb) + fgcolor = ppm_parsecolor(cmdline.fg_rgb, PPM_MAXMAXVAL); + else + PPM_ASSIGN(fgcolor, PPM_MAXMAXVAL, PPM_MAXMAXVAL, PPM_MAXMAXVAL); + fg_red = PPM_GETR(fgcolor); + fg_green = PPM_GETG(fgcolor); + fg_blue = PPM_GETB(fgcolor); + + if (cmdline.verbose) { + pm_message("width is %d, height is %d, variance is %d.", + cols, rows, cmdline.var); + if (left >= 0) pm_message("ragged left border is required"); + if (right >= 0) pm_message("ragged right border is required"); + if (top >= 0) pm_message("ragged top border is required"); + if (bottom >= 0) pm_message("ragged bottom border is required"); + pm_message("background is %s", ppm_colorname(&bgcolor, PPM_MAXMAXVAL, 1)); + pm_message("foreground is %s", ppm_colorname(&fgcolor, PPM_MAXMAXVAL, 1)); + if (init >= 0) pm_message("srand() initialized with seed %u", init); + } + + /* Allocate memory for the whole pixmap */ + PIX = ppm_allocarray(cols, rows); + + /* First, set all pixel to foreground color */ + for (row = 0; row < rows; row++) + for (col = 0; col < cols; col++) + PPM_ASSIGN(PIX[row][col], fg_red, fg_green, fg_blue); + + /* Make a ragged left border */ + if (left >= 0) { + left_c1 = left_c2 = left; + left_r1 = 0; + left_r2 = rows - 1; + for (col = 0; col < left_c1; col++) + PPM_ASSIGN(PIX[left_r1][col], BG_RED, BG_GREEN, BG_BLUE); + for (col = 0; col < left_c2; col++) + PPM_ASSIGN(PIX[left_r2][col], BG_RED, BG_GREEN, BG_BLUE); + + proc_left(left_r1, left_r2, left_c1, left_c2, cmdline.var); + } + + /* Make a ragged right border */ + if (right >= 0) { + right_c1 = right_c2 = cols - right - 1; + right_r1 = 0; + right_r2 = rows - 1; + for (col = right_c1; col < cols; col++) + PPM_ASSIGN(PIX[right_r1][col], BG_RED, BG_GREEN, BG_BLUE); + for (col = right_c2; col < cols; col++) + PPM_ASSIGN(PIX[right_r2][col], BG_RED, BG_GREEN, BG_BLUE); + + proc_right(right_r1, right_r2, right_c1, right_c2, + cmdline.width, cmdline.var); + } + + /* Make a ragged top border */ + if (top >= 0) { + top_r1 = top_r2 = top; + top_c1 = 0; + top_c2 = cols - 1; + for (row = 0; row < top_r1; row++) + PPM_ASSIGN(PIX[row][top_c1], BG_RED, BG_GREEN, BG_BLUE); + for (row = 0; row < top_r2; row++) + PPM_ASSIGN(PIX[row][top_c2], BG_RED, BG_GREEN, BG_BLUE); + + proc_top(top_c1, top_c2, top_r1, top_r2, cmdline.var); + } + + /* Make a ragged bottom border */ + if (bottom >= 0) { + bottom_r1 = bottom_r2 = rows - bottom - 1; + bottom_c1 = 0; + bottom_c2 = cols - 1; + for (row = bottom_r1; row < rows; row++) + PPM_ASSIGN(PIX[row][bottom_c1], BG_RED, BG_GREEN, BG_BLUE); + for (row = bottom_r2; row < rows; row++) + PPM_ASSIGN(PIX[row][bottom_c2], BG_RED, BG_GREEN, BG_BLUE); + + proc_bottom(bottom_c1, bottom_c2, bottom_r1, bottom_r2, + cmdline.height, cmdline.var); + } + + /* Write pixmap */ + ppm_writeppm(stdout, PIX, cols, rows, PPM_MAXMAXVAL, 0); + + ppm_freearray(PIX, rows); + + pm_close(stdout); + exit(0); +} diff --git a/generator/ppmwheel.c b/generator/ppmwheel.c new file mode 100644 index 00000000..ef5021f9 --- /dev/null +++ b/generator/ppmwheel.c @@ -0,0 +1,157 @@ +/* ppmwheel.c - create a color circle of a specified size +** +** This was adapted by Bryan Henderson in January 2003 from ppmcirc.c by +** Peter Kirchgessner: +** +** Copyright (C) 1995 by Peter Kirchgessner. +** +** 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. +*/ + +#include <string.h> +#include <math.h> + +#include "ppm.h" + +#ifndef PI +#define PI 3.14159265358979323846 +#endif + +#ifndef ABS +#define ABS(a) ((a) < 0 ? -(a) : (a)) +#endif + +static void +hsv_rgb(double const in_h, double const in_s, double const in_v, + double * const r, double * const g, double * const b) { +/*---------------------------------------------------------------------------- + This is a stripped down hsv->rgb converter that works only for + Saturation of zero. +-----------------------------------------------------------------------------*/ + double h, s, v; + + h = in_h < 0.0 ? 0.0 : in_h > 360.0 ? 360.0 : in_h; + + v = in_v < 0.0 ? 0.0 : in_v > 1.0 ? 1.0 : in_v; + + s = in_s < 0.0 ? 0.0 : in_s > 1.0 ? 1.0 : in_s; + + if (s != 0.0) + pm_error("Internal error: non-zero saturation"); + + if (h <= 60.0) { /* from red to yellow */ + *r = 1.0; + *g = h / 60.0; + *b = 0.0; + } else if ( h <= 120.0 ) { /* from yellow to green */ + *r = 1.0 - (h - 60.0) / 60.0; + *g = 1.0; + *b = 0.0; + } else if ( h <= 180.0 ) { /* from green to cyan */ + *r = 0.0; + *g = 1.0; + *b = (h - 120.0) / 60.0; + } else if ( h <= 240.0 ) { /* from cyan to blue */ + *r = 0.0; + *g = 1.0 - (h - 180.0) / 60.0; + *b = 1.0; + } else if ( h <= 300.0) { /* from blue to magenta */ + *r = (h - 240.0) / 60.0; + *g = 0.0; + *b = 1.0; + } else { /* from magenta to red */ + *r = 1.0; + *g = 0.0; + *b = 1.0 - (h - 300.0) / 60.0; + } + + if ( v >= 0.5) { + v = 2.0 - 2.0 * v; + v = sqrt (v); + *r = 1.0 + v * (*r - 1.0); + *g = 1.0 + v * (*g - 1.0); + *b = 1.0 + v * (*b - 1.0); + } else { + v *= 2.0; + v = sqrt (sqrt ( sqrt (v))); + *r *= v; + *g *= v; + *b *= v; + } +} + + +int +main(int argc, char *argv[]) { + pixel *orow; + int rows, cols; + pixval maxval; + unsigned int row; + unsigned int xcenter, ycenter, radius; + long diameter; + char * tailptr; + + ppm_init( &argc, argv ); + + if (argc-1 != 1) + pm_error("Program takes one argument: diameter of color wheel"); + + diameter = strtol(argv[1], &tailptr, 10); + if (strlen(argv[1]) == 0 || *tailptr != '\0') + pm_error("You specified an invalid diameter: '%s'", argv[1]); + if (diameter <= 0) + pm_error("Diameter must be positive. You specified %ld.", diameter); + if (diameter < 4) + pm_error("Diameter must be at least 4. You specified %ld", diameter); + + cols = rows = diameter; + + orow = ppm_allocrow(cols); + + maxval = PPM_MAXMAXVAL; + ppm_writeppminit(stdout, cols, rows, maxval, 0); + + radius = diameter/2 - 1; + + xcenter = cols / 2; + ycenter = rows / 2; + + for (row = 0; row < rows; ++row) { + unsigned int col; + for (col = 0; col < cols; ++col) { + double const dx = (int)col - (int)xcenter; + double const dy = (int)row - (int)ycenter; + double const dist = sqrt(dx*dx + dy*dy); + + pixval r, g, b; + + if (dist > radius) { + r = g = b = maxval; + } else { + double hue, sat, val; + double dr, dg, db; + + hue = atan2(dx, dy) / PI * 180.0; + if (hue < 0.0) + hue = 360.0 + hue; + sat = 0.0; + val = dist / radius; + + hsv_rgb(hue, sat, val, &dr, &dg, &db); + + r = (pixval)(maxval * dr); + g = (pixval)(maxval * dg); + b = (pixval)(maxval * db); + } + PPM_ASSIGN (orow[col], r, g, b ); + } + ppm_writeppmrow(stdout, orow, cols, maxval, 0); + } + pm_close(stdout); + exit(0); +} |