diff options
author | giraffedata <giraffedata@9d0c8265-081b-0410-96cb-a4ca84ce46f8> | 2019-06-28 23:45:11 +0000 |
---|---|---|
committer | giraffedata <giraffedata@9d0c8265-081b-0410-96cb-a4ca84ce46f8> | 2019-06-28 23:45:11 +0000 |
commit | cdf6e0151411d887fef61245cb303ef190b29335 (patch) | |
tree | 678c2212e125e66e0a868773e2b4ec460794da4e /generator | |
parent | de1311e820dc892f1a3c5c9ae70dbc56868030d8 (diff) | |
download | netpbm-mirror-cdf6e0151411d887fef61245cb303ef190b29335.tar.gz netpbm-mirror-cdf6e0151411d887fef61245cb303ef190b29335.tar.xz netpbm-mirror-cdf6e0151411d887fef61245cb303ef190b29335.zip |
Promote Advanced to Stable
git-svn-id: http://svn.code.sf.net/p/netpbm/code/stable@3641 9d0c8265-081b-0410-96cb-a4ca84ce46f8
Diffstat (limited to 'generator')
32 files changed, 4949 insertions, 1194 deletions
diff --git a/generator/Makefile b/generator/Makefile index d0ea6b60..d54a6cc5 100644 --- a/generator/Makefile +++ b/generator/Makefile @@ -7,6 +7,8 @@ VPATH=.:$(SRCDIR)/$(SUBDIR) include $(BUILDDIR)/config.mk +SUBDIRS = pamtris + # 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 @@ -16,10 +18,13 @@ include $(BUILDDIR)/config.mk PORTBINARIES = pamcrater pamgauss pamgradient \ pamseq pamshadedrelief pamstereogram \ - pbmpage pbmmake pbmtext pbmtextps pbmupc \ + pbmpage pbmmake pbmtext pbmupc \ pgmkernel pgmmake pgmnoise pgmramp \ ppmcie ppmcolors ppmforge ppmmake ppmpat ppmrough ppmwheel \ +ifneq ($(DONT_HAVE_PROCESS_MGMT),Y) +PORTBINARIES += pbmtextps +endif # 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. @@ -36,6 +41,6 @@ OBJECTS = $(BINARIES:%=%.o) MERGE_OBJECTS = $(MERGEBINARIES:%=%.o2) .PHONY: all -all: $(BINARIES) +all: $(BINARIES) $(SUBDIRS:%=%/all) include $(SRCDIR)/common.mk diff --git a/generator/pamcrater.c b/generator/pamcrater.c index 8c1fda40..43c27dbc 100644 --- a/generator/pamcrater.c +++ b/generator/pamcrater.c @@ -41,7 +41,7 @@ right edge. Make craters wrap around the image (enables tiling of image). */ -#define _XOPEN_SOURCE /* get M_PI in math.h */ +#define _XOPEN_SOURCE 500 /* get M_PI in math.h */ #include <assert.h> #include <math.h> diff --git a/generator/pamgauss.c b/generator/pamgauss.c index 2dd6a726..9656708b 100644 --- a/generator/pamgauss.c +++ b/generator/pamgauss.c @@ -1,3 +1,4 @@ +#include <assert.h> #include <string.h> #include <unistd.h> #include <stdlib.h> @@ -18,7 +19,9 @@ struct CmdlineInfo { unsigned int width; unsigned int height; unsigned int maxval; - float sigma; + float sigma; + unsigned int oversample; + unsigned int maximize; const char * tupletype; }; @@ -35,23 +38,27 @@ parseCommandLine(int argc, const char ** argv, Note that some string information we return as *cmdlineP is in the storage argv[] points to. -----------------------------------------------------------------------------*/ - optEntry *option_def; + optEntry * option_def; /* Instructions to OptParseOptions2 on how to parse our options. */ optStruct3 opt; - unsigned int tupletypeSpec, maxvalSpec, sigmaSpec; + unsigned int tupletypeSpec, maxvalSpec, sigmaSpec, oversampleSpec; 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); + &tupletypeSpec, 0); OPTENT3(0, "maxval", OPT_UINT, &cmdlineP->maxval, - &maxvalSpec, 0); + &maxvalSpec, 0); OPTENT3(0, "sigma", OPT_FLOAT, &cmdlineP->sigma, - &sigmaSpec, 0); + &sigmaSpec, 0); + OPTENT3(0, "maximize", OPT_FLAG, NULL, + &cmdlineP->maximize, 0); + OPTENT3(0, "oversample", OPT_UINT, &cmdlineP->oversample, + &oversampleSpec, 0); opt.opt_table = option_def; opt.short_allowed = FALSE; /* We have no short (old-fashioned) options */ @@ -86,6 +93,13 @@ parseCommandLine(int argc, const char ** argv, pm_error("-maxval must be at least 1"); } + if (oversampleSpec) { + if (cmdlineP->oversample < 1) + pm_error("The oversample factor (-oversample) " + "must be at least 1."); + } else + cmdlineP->oversample = ceil(5.0 / cmdlineP->sigma); + if (argc-1 < 2) pm_error("Need two arguments: width and height."); else if (argc-1 > 2) @@ -107,12 +121,13 @@ parseCommandLine(int argc, const char ** argv, static double -distFromCenter(struct pam * const pamP, - int const col, - int const row) { - - return sqrt(SQR(0.5 + col - (double)pamP->width/2) + - SQR(0.5 + row - (double)pamP->height/2)); +distFromCenter(unsigned int const width, + unsigned int const height, + double const x, + double const y) +{ + return sqrt(SQR(x - (double)width / 2) + + SQR(y - (double)height / 2)); } @@ -121,87 +136,214 @@ 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'. + Compute the value of the gaussian function centered at zero with + standard deviation 'sigma' and amplitude 1, at 'arg'. -----------------------------------------------------------------------------*/ - double const pi = 3.14159; - double const coefficient = 1 / (sigma * sqrt(2*pi)); - double const exponent = - SQR(arg-0) / (2 * SQR(sigma)); + double const exponent = - SQR(arg) / (2 * SQR(sigma)); - return coefficient * exp(exponent); + return exp(exponent); } static double -imageNormalizer(struct pam * const pamP, - double const sigma) { +pixelValue(unsigned int const width, + unsigned int const height, + unsigned int const row, + unsigned int const col, + unsigned int const subpixDivision, + 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. + The gaussian value for the pixel at row 'row', column 'col' in an image + described by *pamP. + + This is the mean of the values of the gaussian function computed at + all the subpixel locations within the pixel when it is divided into + subpixels 'subpixDivision' times horizontally and vertically. + + The gaussian function has standard deviation 'sigma' and amplitude 1. -----------------------------------------------------------------------------*/ - double volume; + double const offset = 1.0 / (subpixDivision * 2); + double const y0 = (double)row + offset; + double const x0 = (double)col + offset; + + double const subpixSize = 1.0 / subpixDivision; + unsigned int i; + double total; + /* Running total of the gaussian values at all subpixel locations */ + + for (i = 0, total = 0.0; i < subpixDivision; ++i) { + /* Sum up one column of subpixels */ + + unsigned int j; + + for (j = 0; j < subpixDivision; ++j) { + double const dist = + distFromCenter(width, height, + x0 + i * subpixSize, + y0 + j * subpixSize); + + total += gauss(dist, sigma); + } + } + + return total / SQR(subpixDivision); +} + + + +static double ** +gaussianKernel(unsigned int const width, + unsigned int const height, + unsigned int const subpixDivision, + double const sigma) { +/*---------------------------------------------------------------------------- + A Gaussian matrix 'width' by 'height', with each value being the mean + of a Gaussian function evaluated at 'subpixDivision' x 'subpixDivision' + locations. + + Return value is newly malloc'ed storage that Caller must free. +-----------------------------------------------------------------------------*/ + double ** kernel; unsigned int row; - volume = 0.0; /* initial value */ + MALLOCARRAY2(kernel, height, width); - for (row = 0; row < pamP->height; ++row) { + if (!kernel) + pm_error("Unable to allocate %u x %u array in which to build kernel", + height, width); + + for (row = 0; row < height; ++row) { unsigned int col; - for (col = 0; col < pamP->width; ++col) - volume += gauss(distFromCenter(pamP, col, row), sigma); + for (col = 0; col < width; ++col) { + double const gaussval = + pixelValue(width, height, row, col, subpixDivision, sigma); + kernel[row][col] = gaussval; + } } - return 1.0 / volume; + return kernel; } -int -main(int argc, const char **argv) { +static double +maximumKernelValue(double ** const kernel, + unsigned int const width, + unsigned int const height) { - struct CmdlineInfo cmdline; + /* As this is Gaussian in both directions, centered at the center, + we know the maximum value is at the center. + */ + return kernel[height/2][width/2]; +} + + + +static double +totalKernelValue(double ** const kernel, + unsigned int const width, + unsigned int const height) { + + double total; + unsigned int row; + + for (row = 0, total = 0.0; row < height; ++row) { + unsigned int col; + + for (col = 0; col < width; ++col) + total += kernel[row][col]; + } + + return total; +} + + + +static void +initpam(struct pam * const pamP, + unsigned int const width, + unsigned int const height, + sample const maxval, + const char * const tupleType, + FILE * const ofP) { + + pamP->size = sizeof(*pamP); + pamP->len = PAM_STRUCT_SIZE(tuple_type); + pamP->file = ofP; + pamP->format = PAM_FORMAT; + pamP->plainformat = 0; + pamP->width = width; + pamP->height = height; + pamP->depth = 1; + pamP->maxval = maxval; + strcpy(pamP->tuple_type, tupleType); +} + + + +static void +writePam(double ** const kernel, + unsigned int const width, + unsigned int const height, + sample const maxval, + const char * const tupleType, + double const normalizer, + FILE * const ofP) { +/*---------------------------------------------------------------------------- + Write the kernel 'kernel', which is 'width' by 'height', as a PAM image + with maxval 'maxval' and tuple type 'tupleType' to file *ofP. + + Divide the kernel values by 'normalizer' to get the normalized PAM sample + value. Assume that no value in 'kernel' is greater that 'normalizer'. +-----------------------------------------------------------------------------*/ struct pam pam; - int row; - double normalizer; + unsigned int row; tuplen * tuplerown; - - pm_proginit(&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); - + initpam(&pam, width, height, maxval, tupleType, ofP); + pnm_writepaminit(&pam); - + tuplerown = pnm_allocpamrown(&pam); for (row = 0; row < pam.height; ++row) { - int col; + unsigned int col; for (col = 0; col < pam.width; ++col) { - double const gauss1 = gauss(distFromCenter(&pam, col, row), - cmdline.sigma); - - tuplerown[col][0] = gauss1 * normalizer; + tuplerown[col][0] = kernel[row][col] / normalizer; + + assert(tuplerown[col][0] <= 1.0); } pnm_writepamrown(&pam, tuplerown); } - + pnm_freepamrown(tuplerown); +} + + + +int +main(int argc, const char **argv) { + struct CmdlineInfo cmdline; + double ** kernel; + double normalizer; + + pm_proginit(&argc, argv); + + parseCommandLine(argc, argv, &cmdline); + kernel = gaussianKernel(cmdline.width, cmdline.height, cmdline.oversample, + cmdline.sigma); + + normalizer = cmdline.maximize ? + maximumKernelValue(kernel, cmdline.width, cmdline.height) : + totalKernelValue(kernel, cmdline.width, cmdline.height); + + writePam(kernel, + cmdline.width, cmdline.height, cmdline.maxval, cmdline.tupletype, + normalizer, stdout); + + pm_freearray2((void **)kernel); + return 0; } diff --git a/generator/pamgradient.c b/generator/pamgradient.c index 57e78288..526efdae 100644 --- a/generator/pamgradient.c +++ b/generator/pamgradient.c @@ -7,7 +7,7 @@ -struct cmdlineInfo { +struct CmdlineInfo { tuple colorTopLeft; tuple colorTopRight; tuple colorBottomLeft; @@ -19,13 +19,13 @@ struct cmdlineInfo { static void parseCommandLine(int argc, const char **argv, - struct cmdlineInfo * const cmdlineP) { + struct CmdlineInfo * const cmdlineP) { /*---------------------------------------------------------------------------- - Convert program invocation arguments (argc,argv) into a format the - program can use easily, struct cmdlineInfo. Validate arguments along + 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 + Note that some string information we return as *cmdlineP is in the storage argv[] points to. -----------------------------------------------------------------------------*/ optEntry * option_def; @@ -55,14 +55,14 @@ parseCommandLine(int argc, const char **argv, 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"); + "colorBottomLeft, colorBottomRight, width, height"); } else { cmdlineP->colorTopLeft = pnm_parsecolor(argv[1], cmdlineP->maxval); cmdlineP->colorTopRight = pnm_parsecolor(argv[2], cmdlineP->maxval); @@ -77,12 +77,13 @@ parseCommandLine(int argc, const char **argv, pm_error("height argument must be a positive number. You " "specified '%s'", argv[6]); } + free(option_def); } static void -freeCmdline(struct cmdlineInfo const cmdline) { +freeCmdline(struct CmdlineInfo const cmdline) { pnm_freepamtuple(cmdline.colorTopLeft); pnm_freepamtuple(cmdline.colorTopRight); @@ -99,7 +100,7 @@ interpolate(struct pam * const pamP, tuple const last) { unsigned int plane; - + for (plane = 0; plane < pamP->depth; ++plane) { int const spread = last[plane] - first[plane]; @@ -155,13 +156,13 @@ createEdge(const struct pam * const pamP, int main(int argc, const char *argv[]) { - struct cmdlineInfo cmdline; + struct CmdlineInfo cmdline; struct pam pam; tuple * tupleRow; tuple * leftEdge; tuple * rightEdge; unsigned int row; - + pm_proginit(&argc, argv); parseCommandLine(argc, argv, &cmdline); @@ -187,7 +188,7 @@ main(int argc, const char *argv[]) { } pnm_writepaminit(&pam); - + tupleRow = pnm_allocpamrow(&pam); leftEdge = createEdge(&pam, @@ -198,7 +199,7 @@ main(int argc, const char *argv[]) { /* 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); + pnm_writepamrow(&pam, tupleRow); } pm_close(stdout); diff --git a/generator/pamshadedrelief.c b/generator/pamshadedrelief.c index 89996c83..35d1e3e0 100644 --- a/generator/pamshadedrelief.c +++ b/generator/pamshadedrelief.c @@ -38,7 +38,7 @@ edge. */ -#define _XOPEN_SOURCE /* get M_PI in math.h */ +#define _XOPEN_SOURCE 500 /* get M_PI in math.h */ #include <assert.h> #include <math.h> diff --git a/generator/pamtris/Makefile b/generator/pamtris/Makefile new file mode 100644 index 00000000..d27606e3 --- /dev/null +++ b/generator/pamtris/Makefile @@ -0,0 +1,27 @@ +ifeq ($(SRCDIR)x,x) + SRCDIR = $(CURDIR)/../.. + BUILDDIR = $(SRCDIR) +endif +SUBDIR = generator/pamtris +VPATH=.:$(SRCDIR)/$(SUBDIR) + +include $(BUILDDIR)/config.mk + +PORTBINARIES = pamtris + +MERGEBINARIES = $(PORTBINARIES) + +BINARIES = $(MERGEBINARIES) $(NOMERGEBINARIES) + +ADDL_OBJECTS = boundaries.o framebuffer.o input.o triangle.o utils.o + +OBJECTS = pamtris.o $(ADDL_OBJECTS) + +MERGE_OBJECTS = pamtris.o2 $(ADDL_OBJECTS) + +.PHONY: all +all: $(BINARIES) + +pamtris:%:%.o $(ADDL_OBJECTS) + +include $(SRCDIR)/common.mk diff --git a/generator/pamtris/boundaries.c b/generator/pamtris/boundaries.c new file mode 100644 index 00000000..7045cbc7 --- /dev/null +++ b/generator/pamtris/boundaries.c @@ -0,0 +1,262 @@ +/*============================================================================= + boundaries.c +=============================================================================== + Boundary buffer functions + + New triangles are drawn one row at a time, and for every such row we have + left and right boundary columns within the frame buffer such that the + fraction of the triangle's area within that scanline is enclosed between + those two points (inclusive). Those coordinates may correspond to columns + outside the frame buffer's actual limits, in which case proper + post-processing should be made wherever such coordinates are used to + actually plot anything into the frame buffer. +=============================================================================*/ + +#include <stdlib.h> + +#include <netpbm/mallocvar.h> +#include <netpbm/pm.h> + +#include "varying.h" +#include "utils.h" + + +#include "boundaries.h" + + + +void +init_boundary_buffer(boundary_info * const bi, + int16_t const height) { + + MALLOCARRAY(bi->buffer, height * 2); + + if (!bi->buffer) { + pm_error("unable to get memory for %u-row high boundary buffer.", + height); + } +} + + + +void +free_boundary_buffer(boundary_info * bi) { + free(bi->buffer); +} + + + +bool +gen_triangle_boundaries(Xy const xy, + boundary_info * const bi, + int16_t const width, + int16_t const height) { +/*---------------------------------------------------------------------------- + Generate an entry in the boundary buffer for the boundaries of every + VISIBLE row of a particular triangle. In case there is no such row, + start_scanline is accordingly set to -1. "xy" is a 3-element array + of pairs of integers representing the coordinates of the vertices of + a triangle. Those vertices MUST be already sorted in order from the + uppermost to the lowermost vertex (which is what draw_triangle, the + only function which uses this one, does with the help of sort3). + + The return value indicates whether the middle vertex is to the left of + the line connecting the top vertex to the bottom vertex or not. +-----------------------------------------------------------------------------*/ + int16_t leftmost_x; + int16_t rightmost_x; + int mid_is_to_the_left; + varying top_x; + varying mid_x; + varying bot_x; + varying top2mid; + varying top2bot; + varying mid2bot; + varying* upper_left; + varying* lower_left; + varying* upper_right; + varying* lower_right; + varying* left[2]; + varying* right[2]; + int16_t* num_rows_ptr[2]; + int32_t y; + int32_t i; + uint8_t k; + + leftmost_x = xy._[0][0]; /* initial value */ + rightmost_x = xy._[0][0]; /* initial value */ + + bi->start_scanline = -1; + bi->num_upper_rows = 0; + bi->num_lower_rows = 0; + + if (xy._[2][1] < 0 || xy._[0][1] >= height) { + /* Triangle is either completely above the uppermost scanline or + completely below the lowermost scanline. + */ + + return false; /* Actual value doesn't matter. */ + } + + { + unsigned int i; + + for (i = 1; i < 3; i++) { + if (xy._[i][0] < leftmost_x) { + leftmost_x = xy._[i][0]; + } + + if (xy._[i][0] > rightmost_x) { + rightmost_x = xy._[i][0]; + } + } + } + if (rightmost_x < 0 || leftmost_x >= width) { + /* Triangle is either completely to the left of the leftmost + framebuffer column or completely to the right of the rightmost + framebuffer column. + */ + return false; /* Actual value doesn't matter. */ + } + + if (xy._[0][1] == xy._[1][1] && xy._[1][1] == xy._[2][1]) { + /* Triangle is degenarate: its visual representation consists only of + a horizontal straight line. + */ + + bi->start_scanline = xy._[0][1]; + + return false; /* Actual value doesn't matter. */ + } + + mid_is_to_the_left = 2; + + int32_to_varying_array(&xy._[0][0], &top_x, 1); + int32_to_varying_array(&xy._[1][0], &mid_x, 1); + int32_to_varying_array(&xy._[2][0], &bot_x, 1); + + if (xy._[0][1] == xy._[1][1]) { + /* Triangle has only a lower part. */ + k = 1; + + mid_is_to_the_left = 0; + } else { + k = 0; + + if (xy._[1][1] == xy._[2][1]) { + /* Triangle has only an upper part (plus the row of the middle + vertex). + */ + mid_is_to_the_left = 1; + } + } + + prepare_for_interpolation(&top_x, &mid_x, &top2mid, xy._[1][1] - xy._[0][1], 1); + prepare_for_interpolation(&top_x, &bot_x, &top2bot, xy._[2][1] - xy._[0][1], 1); + prepare_for_interpolation(&mid_x, &bot_x, &mid2bot, xy._[2][1] - xy._[1][1], 1); + + if (mid_is_to_the_left == 2) { + mid_is_to_the_left = top2mid.s < top2bot.s; + } + + if (mid_is_to_the_left) { + upper_left = &top2mid; + lower_left = &mid2bot; + upper_right = &top2bot; + lower_right = upper_right; + } else { + upper_right = &top2mid; + lower_right = &mid2bot; + upper_left = &top2bot; + lower_left = upper_left; + } + + left[0] = upper_left; + left[1] = lower_left; + right[0] = upper_right; + right[1] = lower_right; + + num_rows_ptr[0] = &bi->num_upper_rows; + num_rows_ptr[1] = &bi->num_lower_rows; + + y = xy._[0][1]; + + i = 0; + + while (k < 2) { + int32_t end; + + end = xy._[k + 1][1] + k; /* initial value */ + + if (y < 0) { + int32_t delta; + + if (end > 0) { + delta = -y; + } else { + delta = xy._[k + 1][1] - y; + } + + y += delta; + + multi_step_up(left[k], delta, 1); + multi_step_up(right[k], delta, 1); + + if (y < 0) { + k++; + continue; + } + } else if(y >= height) { + return mid_is_to_the_left; + } + + if (end > height) { + end = height; + } + + while (y < end) { + if (round_varying(*left[k]) >= width || round_varying(*right[k]) < 0) { + if (bi->start_scanline > -1) { + return mid_is_to_the_left; + } + } else { + if (bi->start_scanline == -1) { + bi->start_scanline = y; + } + + bi->buffer[i++] = round_varying(*left[k]); + bi->buffer[i++] = round_varying(*right[k]); + + (*(num_rows_ptr[k]))++; + } + + step_up(left[k], 1); + step_up(right[k], 1); + + y++; + } + k++; + } + return mid_is_to_the_left; +} + + + +void +get_triangle_boundaries(uint16_t const row_index, + int32_t * const left, + int32_t * const right, + const boundary_info * const bi) { +/*---------------------------------------------------------------------------- + Return the left and right boundaries for a given VISIBLE triangle row (the + row index is relative to the first visible row). These values may be out of + the horizontal limits of the frame buffer, which is necessary in order to + compute correct attribute interpolations. +-----------------------------------------------------------------------------*/ + uint32_t const i = row_index << 1; + + *left = bi->buffer[i]; + *right = bi->buffer[i + 1]; +} + + diff --git a/generator/pamtris/boundaries.h b/generator/pamtris/boundaries.h new file mode 100644 index 00000000..70f7f90d --- /dev/null +++ b/generator/pamtris/boundaries.h @@ -0,0 +1,72 @@ +#ifndef BOUNDARIES_H_INCLUDED +#define BOUNDARIES_H_INCLUDED + +#include <stdbool.h> +#include <stdint.h> + +#include "triangle.h" + +typedef struct boundary_info { +/*---------------------------------------------------------------------------- + Information about visible triangle rows' boundaries. Also see the + "boundary buffer functions" below. + + A "visible" triangle row is one which: + + 1. Corresponds to a frame buffer row whose index (from top to bottom) is + equal to or greater than 0 and smaller than the image height; and + + 2. Has at least some of its pixels between the frame buffer columns whose + index (from left to right) is equal to or greater than 0 and smaller + than the image width. +-----------------------------------------------------------------------------*/ + int16_t start_scanline; + /* Index of the frame buffer scanline which contains the first visible + row of the current triangle, if there is any such row. If not, it + contains the value -1. + */ + + int16_t num_upper_rows; + /* The number of visible rows in the upper part of the triangle. The + upper part of a triangle is composed of all the rows starting from + the top vertex down to the middle vertex, but not including this + last one. + */ + + int16_t num_lower_rows; + /* The number of visible rows in the lower part of the triangle. The + lower part of a triangle is composed of all the rows from the + middle vertex to the bottom vertex -- all inclusive. + */ + + int16_t * buffer; + /* This is the "boundary buffer": a pointer to an array of int16_t's + where each consecutive pair of values indicates, in this order, the + columns of the left and right boundary pixels for a particular + visible triangle row. Those boundaries are inclusive on both sides + and may be outside the limits of the frame buffer. This field is + initialized and freed by the functions "init_boundary_buffer" and + "free_boundary_buffer", respectively. + */ +} boundary_info; + +void +init_boundary_buffer(boundary_info * const bdi, + int16_t const height); + +void +free_boundary_buffer(boundary_info *); + +bool +gen_triangle_boundaries(Xy const xy, + boundary_info * const bdi, + int16_t const width, + int16_t const height); + +void +get_triangle_boundaries(uint16_t const row_index, + int32_t * const left, + int32_t * const right, + const boundary_info * const bdi); + +#endif diff --git a/generator/pamtris/framebuffer.c b/generator/pamtris/framebuffer.c new file mode 100644 index 00000000..93263c91 --- /dev/null +++ b/generator/pamtris/framebuffer.c @@ -0,0 +1,339 @@ +/*============================================================================= + framebuffer.c +=============================================================================== + Frame buffer functions + + Every drawing operation is applied on an internal "frame buffer", which is + simply an "image buffer" which represents the picture currently being drawn, + along with a "Z-Buffer" which contains the depth values for every pixel in + the image buffer. Once all desired drawing operations for a particular + picture are effected, a function is provided to print the current contents + of the image buffer as a PAM image on standard output. Another function is + provided to clear the contents of the frame buffer (i. e. set all image + samples and Z-Buffer entries to 0), with the option of only clearing either + the image buffer or the Z-Buffer individually. + + The Z-Buffer works as follows: Every pixel in the image buffer has a + corresponding entry in the Z-Buffer. Initially, every entry in the Z-Buffer + is set to 0. Every time we desire to plot a pixel at some particular + position in the frame buffer, the current value of the corresponding entry + in the Z-Buffer is compared against the the Z component of the incoming + pixel. If MAX_Z minus the value of the Z component of the incoming pixel is + equal to or greater than the current value of the corresponding entry in the + Z-Buffer, the frame buffer is changed as follows: + + 1. All the samples but the last of the corresponding position in the + image buffer are set to equal those of the incoming pixel. + + 2. The last sample, that is, the A-component of the corresponding position + in the image buffer is set to equal the maxval. + + 3. The corresponding entry in the Z-Buffer is set to equal MAX_Z minus the + value of the Z component of the incoming pixel. + + Otherwise, no changes are made on the frame buffer. +=============================================================================*/ +#include <stdbool.h> +#include <stdint.h> +#include <stdlib.h> +#include <string.h> +#include <math.h> + +#include "utils.h" +#include "varying.h" +#include "limits_pamtris.h" + +#include "framebuffer.h" + + + +int +set_tupletype(const char * const str, + char * const tupletype) { +/*---------------------------------------------------------------------------- + Set the tuple type for the output PAM images given a string ("str") of 255 + characters or less. If the string has more than 255 characters, the function + returns 0. Otherwise, it returns 1. If NULL is given for the "str" argument, + the tuple type is set to a null string. This function is called during + program initialization and whenever a "r" command is executed. The second + argument must point to the tuple_type member of the "outpam" field in the + framebuffer_info struct. +-----------------------------------------------------------------------------*/ + if (str == NULL) { + memset(tupletype, 0, 256); + } else { + size_t len; + + len = strlen(str); /* initial value */ + + if (len > 255) { + return 0; + } + + if (len > 0) { + memcpy(tupletype, str, len); + } + + tupletype[len--] = '\0'; + + while(len > 0 && isspace(tupletype[len])) { + tupletype[len--] = '\0'; + } + } + + return 1; +} + + + +int +init_framebuffer(framebuffer_info * const fbi) { + + uint8_t const num_planes = fbi->num_attribs + 1; + + uint32_t const elements = fbi->width * fbi->height; + + fbi->img.bytes = elements * (num_planes * sizeof(uint16_t)); + fbi->z.bytes = elements * sizeof(uint32_t); + + fbi->img.buffer = + calloc(fbi->img.bytes / sizeof(uint16_t), sizeof(uint16_t)); + fbi->z.buffer = + calloc(fbi->z.bytes / sizeof(uint32_t), sizeof(uint32_t)); + + if(fbi->img.buffer == NULL || fbi->z.buffer == NULL) { + free(fbi->img.buffer); + free(fbi->z.buffer); + + return 0; + } + + fbi->outpam.size = sizeof(struct pam); + fbi->outpam.len = sizeof(struct pam); + fbi->outpam.file = stdout; + fbi->outpam.format = PAM_FORMAT; + fbi->outpam.plainformat = 0; + fbi->outpam.height = fbi->height; + fbi->outpam.width = fbi->width; + fbi->outpam.depth = num_planes; + fbi->outpam.maxval = fbi->maxval; + fbi->outpam.allocation_depth = 0; + fbi->outpam.comment_p = NULL; + + fbi->pamrow = NULL; + fbi->pamrow = pnm_allocpamrow(&fbi->outpam); + + if (fbi->pamrow == NULL) { + free(fbi->img.buffer); + free(fbi->z.buffer); + + return 0; + } + + return 1; +} + + + +void +free_framebuffer(framebuffer_info * const fbi) { + + free(fbi->img.buffer); + free(fbi->z.buffer); + + pnm_freepamrow(fbi->pamrow); +} + + + +int +realloc_image_buffer(int32_t const new_maxval, + int32_t const new_num_attribs, + framebuffer_info * const fbi) { +/*---------------------------------------------------------------------------- + Reallocate the image buffer with a new maxval and depth, given the struct + with information about the framebuffer. The fields variables "maxval" and + "num_attribs". + + From the point this function is called onwards, new PAM images printed on + standard output will have the new maxval for the maxval and num_attribs + 1 + for the depth. + + This function does *not* check whether the new maxval and num_attribs are + within the proper allowed limits. That is done inside the input processing + function "process_next_command", which is the only function that calls this + one. + + If the function suceeds, the image buffer is left in cleared state. The + Z-Buffer, however, is not touched at all. + + If the new depth is equal to the previous one, no actual reallocation is + performed: only the global variable "maxval" is changed. But the image + buffer is nonetheless left in cleared state regardless. +-----------------------------------------------------------------------------*/ + uint8_t num_planes; + + pnm_freepamrow(fbi->pamrow); + fbi->pamrow = NULL; + + num_planes = fbi->num_attribs + 1; /* initial value */ + + if (new_num_attribs != fbi->num_attribs) { + fbi->num_attribs = new_num_attribs; + num_planes = fbi->num_attribs + 1; + + fbi->img.bytes = + fbi->width * fbi->height * (num_planes * sizeof(uint16_t)); + + { + uint16_t * const new_ptr = + realloc(fbi->img.buffer, fbi->img.bytes); + + if (new_ptr == NULL) { + free(fbi->img.buffer); + fbi->img.buffer = NULL; + + return 0; + } + fbi->img.buffer = new_ptr; + } + } + + fbi->maxval = new_maxval; + + fbi->outpam.size = sizeof(struct pam); + fbi->outpam.len = sizeof(struct pam); + fbi->outpam.file = stdout; + fbi->outpam.format = PAM_FORMAT; + fbi->outpam.plainformat = 0; + fbi->outpam.height = fbi->height; + fbi->outpam.width = fbi->width; + fbi->outpam.depth = num_planes; + fbi->outpam.maxval = fbi->maxval; + fbi->outpam.allocation_depth = 0; + fbi->outpam.comment_p = NULL; + + fbi->pamrow = pnm_allocpamrow(&fbi->outpam); + + if (fbi->pamrow == NULL) { + free(fbi->img.buffer); + fbi->img.buffer = NULL; + + return 0; + } + + memset(fbi->img.buffer, 0, fbi->img.bytes); + + return 1; +} + + + +void +print_framebuffer(framebuffer_info * const fbi) { + + uint8_t const num_planes = fbi->num_attribs + 1; + uint32_t const end = fbi->width * fbi->height; + + uint32_t i; + + pnm_writepaminit(&fbi->outpam); + + for (i = 0; i != end; ) { + int j; + for (j = 0; j < fbi->width; j++) { + uint32_t const k = (i + j) * num_planes; + + unsigned int l; + + for (l = 0; l < num_planes; l++) { + fbi->pamrow[j][l] = fbi->img.buffer[k + l]; + } + } + + pnm_writepamrow(&fbi->outpam, fbi->pamrow); + + i += fbi->width; + } +} + + + +void +clear_framebuffer(bool const clear_image_buffer, + bool const clear_z_buffer, + framebuffer_info * const fbi) { + + if (clear_image_buffer) { + memset(fbi->img.buffer, 0, fbi->img.bytes); + } + + if (clear_z_buffer) { + memset(fbi->z.buffer, 0, fbi->z.bytes); + } +} + + + +void +draw_span(uint32_t const base, + uint16_t const length, + varying * const attribs, + framebuffer_info * const fbi) { +/*---------------------------------------------------------------------------- + Draw a horizontal span of "length" pixels into the frame buffer, performing + the appropriate depth tests. "base" must equal the row of the frame buffer + where one desires to draw the span *times* the image width, plus the column + of the first pixel in the span. + + This function does not perform any kind of bounds checking. +-----------------------------------------------------------------------------*/ + static double const depth_range = MAX_Z; + + uint16_t const maxval = fbi->maxval; + uint8_t const z = fbi->num_attribs; + uint8_t const w = z + 1; + uint8_t const n = w + 1; + + uint8_t const num_planes = w; + + unsigned int i; + + /* Process each pixel in the span: */ + + for (i = 0; i < length; i++) { + int32_t const d = round(depth_range * attribs[z].v); + uint32_t const d_mask = geq_mask64(d, fbi->z.buffer[base + i]); + + uint32_t const j = base + i; + uint32_t const k = j * num_planes; + + varying const inverse_w = inverse_varying(attribs[w]); + + unsigned int l; + + /* The following statements will only have any effect if the depth + test, performed above, has suceeded. I. e. if the depth test fails, + no changes will be made on the frame buffer; otherwise, the + frame buffer will be updated with the new values. + */ + fbi->z.buffer[j] = (fbi->z.buffer[j] & ~d_mask) | (d & d_mask); + + for (l = 0; l < z; l++) { + varying const newval = multiply_varyings(attribs[l], inverse_w); + + fbi->img.buffer[k + l] = + (fbi->img.buffer[k + l] & ~d_mask) | + (round_varying(newval) & d_mask); + } + + fbi->img.buffer[k + z] |= (maxval & d_mask); + + /* Compute the attribute values for the next pixel: */ + + step_up(attribs, n); + } +} + + + diff --git a/generator/pamtris/framebuffer.h b/generator/pamtris/framebuffer.h new file mode 100644 index 00000000..b3d4f7a3 --- /dev/null +++ b/generator/pamtris/framebuffer.h @@ -0,0 +1,75 @@ +#ifndef FRAMEBUFFER_H_INCLUDED +#define FRAMEBUFFER_H_INCLUDED + +#include <stdint.h> +#include <stdbool.h> + +#include "varying.h" +#include "netpbm/pam.h" + +typedef struct framebuffer_info { +/*---------------------------------------------------------------------------- + Information about the frame buffer and PAM output +-----------------------------------------------------------------------------*/ + /* These fields are initialized once by reading the command line + arguments. "maxval" and "num_attribs" may be modified later + through "realloc_image_buffer"; "correct" may also be modified + if the eponymous command is given. + */ + int32_t width; + int32_t height; + int32_t maxval; + int32_t num_attribs; + + /* The fields below must be initialized by "init_framebuffer" and + freed by "free_framebuffer", except for the tuple_type field in + "outpam" which is initialized once by reading the command line + arguments and may be modified later through "set_tupletype". + */ + struct { + uint16_t * buffer; + uint32_t bytes; + } img; /* Image buffer */ + + struct { + uint32_t * buffer; + uint32_t bytes; + } z; /* Z-buffer */ + + struct pam outpam; + + tuple * pamrow; +} framebuffer_info; + + + +int +set_tupletype(const char * const str, + char * const tupletype); + +int +init_framebuffer(framebuffer_info * const fbi); + +void +free_framebuffer(framebuffer_info * const fbi); + +void +print_framebuffer(framebuffer_info * const fbi); + +void +clear_framebuffer(bool const clear_image_buffer, + bool const clear_z_buffer, + framebuffer_info * const fbi); + +int +realloc_image_buffer(int32_t const new_maxval, + int32_t const new_num_attribs, + framebuffer_info * const fbi); + +void +draw_span(uint32_t const base, + uint16_t const length, + varying * const attribs, + framebuffer_info * const fbi); + +#endif diff --git a/generator/pamtris/input.c b/generator/pamtris/input.c new file mode 100644 index 00000000..ffb2a859 --- /dev/null +++ b/generator/pamtris/input.c @@ -0,0 +1,695 @@ +/*============================================================================= + input.c +=============================================================================== + Input handling functions +=============================================================================*/ +#include <stdbool.h> +#include <stdint.h> +#include <stdlib.h> +#include <stdio.h> +#include <ctype.h> + +#include "netpbm/mallocvar.h" +#include "netpbm/pm.h" +#include "netpbm/nstring.h" + +#include "limits_pamtris.h" +#include "framebuffer.h" +#include "triangle.h" + +#include "input.h" + +#define DRAW_MODE_TRIANGLES 1 +#define DRAW_MODE_STRIP 2 +#define DRAW_MODE_FAN 3 + +#define CMD_SET_MODE "mode" +#define CMD_SET_ATTRIBS "attribs" +#define CMD_VERTEX "vertex" +#define CMD_PRINT "print" +#define CMD_CLEAR "clear" +#define CMD_RESET "reset" +#define CMD_QUIT "quit" + +#define ARG_TRIANGLES "triangles" +#define ARG_STRIP "strip" +#define ARG_FAN "fan" +#define ARG_IMAGE "image" +#define ARG_DEPTH "depth" + + +typedef struct { + Xy v_xy; + /* X- and Y-coordinates of the vertices for the current triangle. + */ + Attribs v_attribs; + /* Vertex attributes for the current triangle. Includes the + Z-coordinates. + */ + int32_t curr_attribs[MAX_NUM_ATTRIBS]; + /* Attributes that will be assigned to the next vertex. Does not + include the Z-coordinate. + */ + uint8_t next; + /* Index of the next vertex to be read. */ + bool draw; + /* If true, draws a new triangle upon reading a new vertex. */ + + uint8_t mode; + /* Drawing mode. */ + + bool initialized; +} state_info; + + + +static void +clearAttribs(state_info * const si, + int32_t const maxval, + int16_t const num_attribs) { + + unsigned int i; + + for (i = 0; i < num_attribs; ++i) { + si->curr_attribs[i] = maxval; + } +} + + + +void +input_init(Input * const inputP) { + + inputP->buffer = NULL; + inputP->length = 0; + inputP->number = 1; +} + + + +void +input_term(Input * const inputP) { + + if (inputP->buffer) + free(inputP->buffer); +} + + + +typedef struct { +/*---------------------------------------------------------------------------- + Indicates a whitespace-delimited input symbol. "begin" points to its first + character, and "end" points to one position past its last character. +-----------------------------------------------------------------------------*/ + char * begin; + char * end; +} Token; + + + +static Token +nextToken(char * const startPos) { + + Token retval; + char * p; + + for (p = startPos; *p && isspace(*p); ++p); + + retval.begin = p; + + for (; *p && !isspace(*p); ++p); + + retval.end = p; + + return retval; +} + + + +static bool +stringIsValid(const char * const target, + const char * const srcBegin, + const char * const srcEnd) { + + unsigned int charsMatched; + const char * p; + + for (p = srcBegin, charsMatched = 0; + p != srcEnd && target[charsMatched] != '\0'; ++p) { + + if (*p == target[charsMatched]) + ++charsMatched; + else + break; + } + + return (*p == '\0' || isspace(*p)); +} + + + +static void +initState(state_info * const siP) { + + siP->next = 0; + siP->draw = false; + siP->mode = DRAW_MODE_TRIANGLES; +} + + + +static void +makeLowercase(Token const t) { + + char * p; + + for (p = t.begin; p != t.end; ++p) + *p = tolower(*p); +} + + + +static void +removeComments(char * const str) { + + char * p; + + for (p = &str[0]; *p; ++p) { + if (*p == '#') { + *p = '\0'; + + break; + } + } +} + + + +static void +processM(Token * const ntP, + state_info * const stateP, + bool * const unrecognizedCmdP, + const char ** const errorP) { + + if (!stringIsValid(CMD_SET_MODE, ntP->begin, ntP->end)) { + *unrecognizedCmdP = true; + } else { + *ntP = nextToken(ntP->end); + + *unrecognizedCmdP = false; + + if (*ntP->begin == '\0') + pm_asprintf(errorP, "syntax error"); + else { + makeLowercase(*ntP); + + switch (*ntP->begin) { + case 't': + if (!stringIsValid(ARG_TRIANGLES, ntP->begin, ntP->end)) + pm_asprintf(errorP, "unrecognized drawing mode"); + else { + stateP->mode = DRAW_MODE_TRIANGLES; + stateP->draw = false; + stateP->next = 0; + + *errorP = NULL; + } + break; + case 's': + if (!stringIsValid(ARG_STRIP, ntP->begin, ntP->end)) + pm_asprintf(errorP, "unrecognized drawing mode"); + else { + stateP->mode = DRAW_MODE_STRIP; + stateP->draw = false; + stateP->next = 0; + + *errorP = NULL; + } + break; + case 'f': + if (!stringIsValid(ARG_FAN, ntP->begin, ntP->end)) + pm_asprintf(errorP, "unrecognized drawing mode"); + else { + stateP->mode = DRAW_MODE_FAN; + stateP->draw = false; + stateP->next = 0; + + *errorP = NULL; + } + break; + default: + pm_asprintf(errorP, "unrecognized drawing mode"); + } + } + } +} + + + +static void +processA(Token * const ntP, + state_info * const stateP, + framebuffer_info * const fbiP, + bool * const unrecognizedCmdP, + long int * const iArgs, + const char ** const errorP) { + + if (!stringIsValid(CMD_SET_ATTRIBS, ntP->begin, ntP->end)) { + *unrecognizedCmdP = true; + } else { + unsigned int i; + + *unrecognizedCmdP = false; + + for (i = 0, *errorP = NULL; i < fbiP->num_attribs && !*errorP; ++i) { + char * strtolEnd; + + *ntP = nextToken(ntP->end); + + iArgs[i] = strtol(ntP->begin, &strtolEnd, 10); + + if (*ntP->begin == '\0' || strtolEnd != ntP->end) + pm_asprintf(errorP, "syntax error"); + else { + if (iArgs[i] < 0 || iArgs[i] > fbiP->maxval) + pm_asprintf(errorP, "argument(s) out of bounds"); + } + } + + if (!*errorP) { + unsigned int i; + + for (i = 0; i < fbiP->num_attribs; ++i) + stateP->curr_attribs[i] = iArgs[i]; + } + } +} + + + +static void +processV(Token * const ntP, + state_info * const stateP, + struct boundary_info * const biP, + framebuffer_info * const fbiP, + bool * const unrecognizedCmdP, + long int * const iArgs, + const char ** const errorP) { + + if (!stringIsValid(CMD_VERTEX, ntP->begin, ntP->end)) + *unrecognizedCmdP = true; + else { + unsigned int i; + + *unrecognizedCmdP = false; + + for (i = 0, *errorP = NULL; i < 4 && !*errorP; ++i) { + char * strtolEnd; + + *ntP = nextToken(ntP->end); + + iArgs[i] = strtol(ntP->begin, &strtolEnd, 10); + + if (*ntP->begin == '\0') { + if (i != 3) + pm_asprintf(errorP, "syntax error"); + else + iArgs[i] = 1; + } else { + if (strtolEnd != ntP->end) + pm_asprintf(errorP, "syntax error"); + } + + if (!*errorP) { + if (i < 3) { + if (iArgs[i] < MIN_COORD || iArgs[i] > MAX_COORD) + pm_asprintf(errorP, "coordinates out of bounds"); + } else { + if (iArgs[i] < MIN_INPUT_W || iArgs[i] > MAX_INPUT_W) + pm_asprintf(errorP, + "perspective correction factor (w) " + "out of bounds"); + } + } + } + + if (!*errorP) { + unsigned int i; + + for (i = 0; i < fbiP->num_attribs; ++i) { + stateP->v_attribs._[stateP->next][i] = stateP->curr_attribs[i]; + } + + stateP->v_attribs._[stateP->next][fbiP->num_attribs + 0] = + iArgs[2]; + stateP->v_attribs._[stateP->next][fbiP->num_attribs + 1] = + iArgs[3]; + + stateP->v_xy._[stateP->next][0] = iArgs[0]; + stateP->v_xy._[stateP->next][1] = iArgs[1]; + + ++stateP->next; + + if (!stateP->draw) { + if (stateP->next == 3) + stateP->draw = true; + } + + if (stateP->draw) + draw_triangle(stateP->v_xy, stateP->v_attribs, biP, fbiP); + + if (stateP->next == 3) { + switch(stateP->mode) { + case DRAW_MODE_FAN: + stateP->next = 1; + break; + case DRAW_MODE_TRIANGLES: + stateP->draw = false; + stateP->next = 0; + break; + case DRAW_MODE_STRIP: + stateP->next = 0; + break; + default: + stateP->next = 0; + } + } + } + } +} + + + +static void +processP(Token * const ntP, + framebuffer_info * const fbiP, + bool * const unrecognizedCmdP, + const char ** const errorP) { + + if (!stringIsValid(CMD_PRINT, ntP->begin, ntP->end)) + *unrecognizedCmdP = true; + else { + *unrecognizedCmdP = false; + + print_framebuffer(fbiP); + + *errorP = NULL; + } +} + + + + +static void +processExcl(Token * const ntP, + framebuffer_info * const fbiP, + bool * const unrecognizedCmdP, + const char ** const errorP) { + + if (ntP->end - ntP->begin > 1) + *unrecognizedCmdP = true; + else { + *unrecognizedCmdP = false; + + print_framebuffer(fbiP); + + *errorP = NULL; + } +} + + + +static void +clear(Token * const ntP, + framebuffer_info * const fbiP, + const char ** const errorP) { + + *ntP = nextToken(ntP->end); + + if (*ntP->begin != '\0') { + makeLowercase(*ntP); + + switch(*ntP->begin) { + case 'i': + if (!stringIsValid("image", ntP->begin, ntP->end)) + pm_asprintf(errorP, "unrecognized argument"); + else { + clear_framebuffer(true, false, fbiP); + *errorP = NULL; + } + break; + case 'd': + if (!stringIsValid("depth", ntP->begin, ntP->end)) + pm_asprintf(errorP, "unrecognized argument"); + else { + clear_framebuffer(false, true, fbiP); + *errorP = NULL; + } + break; + case 'z': + if (ntP->end - ntP->begin > 1) + pm_asprintf(errorP, "unrecognized argument"); + else { + clear_framebuffer(false, true, fbiP); + *errorP = NULL; + } + break; + default: + pm_asprintf(errorP, "unrecognized argument"); + } + } else { + clear_framebuffer(true, true, fbiP); + *errorP = NULL; + } +} + + + +static void +processC(Token * const ntP, + framebuffer_info * const fbiP, + bool * const unrecognizedCmdP, + const char ** const errorP) { + + if (!stringIsValid(CMD_CLEAR, ntP->begin, ntP->end)) + *unrecognizedCmdP = true; + else { + *unrecognizedCmdP = false; + + clear(ntP, fbiP, errorP); + } +} + + + +static void +processAsterisk(Token * const ntP, + framebuffer_info * const fbiP, + bool * const unrecognizedCmdP, + const char ** const errorP) { + + if (ntP->end - ntP->begin > 1) + *unrecognizedCmdP = true; + else { + *unrecognizedCmdP = false; + + clear(ntP, fbiP, errorP); + } +} + + + +static void +processR(Token * const ntP, + state_info * const stateP, + framebuffer_info * const fbiP, + bool * const unrecognizedCmdP, + long int * const iArgs, + const char ** const errorP) { + + if (!stringIsValid(CMD_RESET, ntP->begin, ntP->end)) + *unrecognizedCmdP = true; + else { + unsigned int i; + + *unrecognizedCmdP = false; + + for (i = 0, *errorP = NULL; i < 2 && !*errorP; ++i) { + char * strtolEnd; + + *ntP = nextToken(ntP->end); + + iArgs[i] = strtol(ntP->begin, &strtolEnd, 10); + + if (*ntP->begin == '\0' || ntP->end != strtolEnd) + pm_asprintf(errorP, "syntax error"); + } + + if (!*errorP) { + if (iArgs[0] < 1 || iArgs[0] > PAM_OVERALL_MAXVAL) + pm_asprintf(errorP, "invalid new maxval"); + else { + if (iArgs[1] < 1 || iArgs[1] > MAX_NUM_ATTRIBS) + pm_asprintf(errorP, "invalid new number of generic vertex " + "attributes"); + else { + *ntP = nextToken(ntP->end); + + if (*ntP->begin != '\0') { + if (!set_tupletype(ntP->begin, + fbiP->outpam.tuple_type)) { + pm_message( + "warning: could not set new tuple type; " + "using a null string"); + set_tupletype(NULL, fbiP->outpam.tuple_type); + } + } else + set_tupletype(NULL, fbiP->outpam.tuple_type); + + if (!realloc_image_buffer(iArgs[0], iArgs[1], fbiP)) { + pm_error("Unable to allocate memory for " + "image buffer"); + } + + stateP->next = 0; + stateP->draw = false; + + clearAttribs(stateP, fbiP->maxval, fbiP->num_attribs); + } + } + } + } +} + + + +static void +processQ(Token * const ntP, + bool * const unrecognizedCmdP, + bool * const noMoreCommandsP, + const char ** const errorP) { + + if (!stringIsValid(CMD_QUIT, ntP->begin, ntP->end)) + *unrecognizedCmdP = true; + else { + *unrecognizedCmdP = false; + + *noMoreCommandsP = true; + + *errorP = NULL; + } +} + + + +void +input_process_next_command(Input * const inputP, + struct boundary_info * const biP, + framebuffer_info * const fbiP, + bool * const noMoreCommandsP) { +/*---------------------------------------------------------------------------- + Doesn't necessarily process a command, just the next line of input, which + may be empty. + + Return *noMoreCommandsP true iff the next command is a quit command of + there is no next command. +-----------------------------------------------------------------------------*/ + static state_info state; + + Token nt; + + long int iArgs[MAX_NUM_ATTRIBS]; + /* For storing potential integer arguments. */ + bool unrecognizedCmd; + /* Unrecognized command detected */ + bool noMoreCommands; + const char * error; + /* Description of problem with the command; NULL if no problem. + Meaningful only when 'unrecognizedCmd' is false. + */ + + if (!state.initialized) { + initState(&state); + clearAttribs(&state, fbiP->maxval, fbiP->num_attribs); + + state.initialized = true; + } + + { + int eof; + size_t lineLen; + + pm_getline(stdin, &inputP->buffer, &inputP->length, &eof, &lineLen); + + if (eof) { + *noMoreCommandsP = true; + return; + } + } + + removeComments(inputP->buffer); + + nt = nextToken(inputP->buffer); + + makeLowercase(nt); + + noMoreCommands = false; /* initial assumption */ + + switch (nt.begin[0]) { + case 'm': + processM(&nt, &state, &unrecognizedCmd, &error); + break; + case 'a': + processA(&nt, &state, fbiP, &unrecognizedCmd, iArgs, &error); + break; + case 'v': + processV(&nt, &state, biP, fbiP, &unrecognizedCmd, iArgs, &error); + break; + case 'p': + processP(&nt, fbiP, &unrecognizedCmd, &error); + break; + case '!': + processExcl(&nt, fbiP, &unrecognizedCmd, &error); + break; + case 'c': + processC(&nt, fbiP, &unrecognizedCmd, &error); + break; + case '*': + processAsterisk(&nt, fbiP, &unrecognizedCmd, &error); + break; + case 'r': + processR(&nt, &state, fbiP, &unrecognizedCmd, iArgs, &error); + break; + case 'q': + processQ(&nt, &unrecognizedCmd, &noMoreCommands, &error); + break; + case '\0': + break; + default: + unrecognizedCmd = true; + } + + if (!noMoreCommands) { + char const next = *nextToken(nt.end).begin; + + if (unrecognizedCmd) { + pm_errormsg("error: unrecognized command: line %u.", + (unsigned)inputP->number); + } else { + if (error) { + pm_errormsg("Error in line %u: %s", + (unsigned)inputP->number, error); + pm_strfree(error); + } else { + if (next != '\0') + pm_message("warning: ignoring excess arguments: line %u", + (unsigned)inputP->number); + } + } + } + ++inputP->number; + + *noMoreCommandsP = noMoreCommands; +} + + diff --git a/generator/pamtris/input.h b/generator/pamtris/input.h new file mode 100644 index 00000000..d34de3a1 --- /dev/null +++ b/generator/pamtris/input.h @@ -0,0 +1,27 @@ +#ifndef INPUT_H_INCLUDED +#define INPUT_H_INCLUDED + +#include <stdint.h> + +struct boundary_info; +struct framebuffer_info; + +typedef struct { + char * buffer; + size_t length; + uint64_t number; +} Input; + +void +input_init(Input * const inputP); + +void +input_term(Input * const inputP); + +void +input_process_next_command(Input * const inputP, + struct boundary_info * const bdiP, + struct framebuffer_info * const fbiP, + bool * const noMoreCommandsP); + +#endif diff --git a/generator/pamtris/limits_pamtris.h b/generator/pamtris/limits_pamtris.h new file mode 100644 index 00000000..a7ed503f --- /dev/null +++ b/generator/pamtris/limits_pamtris.h @@ -0,0 +1,11 @@ +#ifndef LIMITS_H_INCLUDED +#define LIMITS_H_INCLUDED + +#define MAX_NUM_ATTRIBS 20 +#define MAX_COORD 32767 +#define MIN_COORD (-MAX_COORD) +#define MAX_INPUT_W 1048575 +#define MIN_INPUT_W 1 +#define MAX_Z 0x3FFFFFFF + +#endif diff --git a/generator/pamtris/pamtris.c b/generator/pamtris/pamtris.c new file mode 100644 index 00000000..e0becf7a --- /dev/null +++ b/generator/pamtris/pamtris.c @@ -0,0 +1,171 @@ +#include <stdbool.h> +#include <stdint.h> +#include <stdlib.h> + +#include "netpbm/mallocvar.h" +#include "netpbm/shhopt.h" +#include "netpbm/pam.h" + +#include "limits_pamtris.h" +#include "framebuffer.h" +#include "boundaries.h" +#include "input.h" + +#define MAX_METRICS 8192 + + + +static int +parse_command_line(int * const argc_ptr, + const char ** const argv, + int32_t * const width, + int32_t * const height, + int32_t * const maxval, + int32_t * const num_attribs, + char * const tupletype) { + + optEntry * option_def; + optStruct3 opt; + /* Instructions to pm_optParseOptions3 on how to parse our options */ + unsigned int option_def_index; + + char * tupletype_tmp; + + unsigned int width_spec, height_spec, attribs_spec, tupletype_spec; + unsigned int rgb_spec, grayscale_spec, maxval_spec; + + MALLOCARRAY_NOFAIL(option_def, 100); + + option_def_index = 0; /* incremented by OPTENT3 */ + OPTENT3(0, "width", OPT_INT, width, &width_spec, 0); + OPTENT3(0, "height", OPT_INT, height, &height_spec, 0); + OPTENT3(0, "num_attribs", OPT_INT, num_attribs, &attribs_spec, 0); + OPTENT3(0, "tupletype", OPT_STRING, &tupletype_tmp, &tupletype_spec, 0); + OPTENT3(0, "rgb", OPT_FLAG, NULL, &rgb_spec, 0); + OPTENT3(0, "grayscale", OPT_FLAG, NULL, &grayscale_spec, 0); + OPTENT3(0, "maxval", OPT_INT, maxval, &maxval_spec, 0); + + opt.opt_table = option_def; + opt.short_allowed = false; + opt.allowNegNum = false; + + pm_optParseOptions3(argc_ptr, (char **)argv, opt, sizeof(opt), 0); + + if (!width_spec || !height_spec || (!attribs_spec && !(rgb_spec || grayscale_spec))) { + pm_errormsg( + "you must at least specify -width, -height and " + "either -num_attribs, -rgb or -grayscale."); + + return 0; + } + + if (rgb_spec + grayscale_spec + attribs_spec != 1) { + pm_errormsg("you must provide either only -num_attribs, " + "-rgb or -grayscale; not a combination of those."); + + return 0; + } + + if (*width < 1 || *width > MAX_METRICS) { + pm_errormsg("invalid width."); + + return 0; + } + + if (*height < 1 || *height > MAX_METRICS) { + pm_errormsg("invalid height."); + + return 0; + } + + if (maxval_spec) { + if (*maxval < 1 || *maxval > PAM_OVERALL_MAXVAL) { + pm_errormsg("invalid maxval."); + + return 0; + } + } else { + *maxval = 255; + } + + if (rgb_spec) { + *num_attribs = 3; + set_tupletype("RGB_ALPHA", tupletype); + } + + if (grayscale_spec) { + *num_attribs = 1; + set_tupletype("GRAYSCALE_ALPHA", tupletype); + } + + if (*num_attribs < 1 || *num_attribs > MAX_NUM_ATTRIBS) { + pm_errormsg("invalid number of generic attributes per vertex."); + + return 0; + } + + if (tupletype_spec) { + if(rgb_spec || grayscale_spec) { + pm_errormsg("you may not provide -tupletype together with " + "-rgb or -grayscale."); + + return 0; + } + + if (!set_tupletype(tupletype_tmp, tupletype)) { + pm_errormsg("warning: invalid tuple type; using empty string."); + + set_tupletype(NULL, tupletype); + } + } + + free(option_def); + + return 1; +} + + + +int +main(int argc, const char ** argv) { + + framebuffer_info fbi; + boundary_info bi; + Input input; + bool no_more_commands; + + pm_proginit(&argc, (const char**)argv); + + set_tupletype(NULL, fbi.outpam.tuple_type); + + if (!parse_command_line(&argc, + argv, + &fbi.width, + &fbi.height, + &fbi.maxval, + &fbi.num_attribs, + fbi.outpam.tuple_type)) { + return 1; + } + + if (!init_framebuffer(&fbi)) { + pm_errormsg("out of memory."); + + return 3; + } + + init_boundary_buffer(&bi, fbi.height); + + input_init(&input); + + for (no_more_commands = false; !no_more_commands; ) + input_process_next_command(&input, &bi, &fbi, &no_more_commands); + + input_term(&input); + free_boundary_buffer(&bi); + free_framebuffer(&fbi); + + return 0; +} + + diff --git a/generator/pamtris/triangle.c b/generator/pamtris/triangle.c new file mode 100644 index 00000000..5143f9ee --- /dev/null +++ b/generator/pamtris/triangle.c @@ -0,0 +1,327 @@ +/*============================================================================= + triangle.c +=============================================================================== + Triangle functions +=============================================================================*/ +#include <stdlib.h> +#include <string.h> + +#include "netpbm/mallocvar.h" + +#include "utils.h" +#include "varying.h" +#include "boundaries.h" +#include "framebuffer.h" + +#include "triangle.h" + +static void +draw_partial_triangle( + const varying * const left_attribs_input, + const varying * const rght_attribs_input, + bool const upper_part, + const boundary_info * const bi, + framebuffer_info * const fbi) { + + uint8_t const z = fbi->num_attribs; + uint8_t const w = z + 1; + uint8_t const n = w + 1; + + varying * left_attribs; + varying * rght_attribs; + + varying * attribs; + + int32_t first_row_index; + int32_t last_row_index; + + MALLOCARRAY_NOFAIL(left_attribs, n); + MALLOCARRAY_NOFAIL(rght_attribs, n); + MALLOCARRAY_NOFAIL(attribs, n); + + memcpy(left_attribs, left_attribs_input, n * sizeof(varying)); + memcpy(rght_attribs, rght_attribs_input, n * sizeof(varying)); + + if (upper_part) { + first_row_index = 0; + last_row_index = bi->num_upper_rows - 1; + } else { + first_row_index = bi->num_upper_rows; + last_row_index = bi->num_upper_rows + bi->num_lower_rows - 1; + } + + { + int32_t const row_delta = last_row_index - first_row_index; + + int32_t row; + + int32_t left_boundary; + int32_t rght_boundary; + + for (row = first_row_index; row <= last_row_index; row++) { + get_triangle_boundaries(row, &left_boundary, &rght_boundary, bi); + { + int32_t const column_delta = rght_boundary - left_boundary; + int32_t start_column; + int32_t span_length; + + start_column = left_boundary; /* initial value */ + span_length = column_delta; /* initial value */ + + prepare_for_interpolation(left_attribs, rght_attribs, + attribs, column_delta, + n); + + if (left_boundary < 0) { + start_column = 0; + + span_length += left_boundary; + + multi_step_up(attribs, -left_boundary, n); + } + + if (rght_boundary >= fbi->width) { + span_length -= rght_boundary - fbi->width; + } else { + span_length++; + } + + draw_span( + (bi->start_scanline + row) * fbi->width + start_column, + span_length, attribs, fbi); + + if (row_delta > 0) { + step_up(left_attribs, n); + step_up(rght_attribs, n); + } + } + } + } + free(attribs); + free(rght_attribs); + free(left_attribs); +} + + + +static void +draw_degenerate_horizontal(Xy const xy, + varying * const top2mid, + varying * const top2bot, + varying * const mid2bot, + framebuffer_info * const fbi) { + + uint8_t const n = fbi->num_attribs + 2; + + { + int16_t const y = xy._[0][1]; + + int16_t x[3]; + int16_t x_start[3]; + varying * attribs[3]; + int32_t span_length[3]; + unsigned int i; + + x[0] = xy._[0][0]; + x[1] = xy._[1][0]; + x[2] = xy._[2][0]; + + x_start[0] = x[0]; + x_start[1] = x[0]; + x_start[2] = x[1]; + + attribs[0] = top2bot; + attribs[1] = top2mid; + attribs[2] = mid2bot; + + span_length[0] = x[2] - x[0]; + span_length[1] = x[1] - x[0]; + span_length[2] = x[2] - x[1]; + + for (i = 0; i < 3; i++) { + if (x_start[i] >= fbi->width || x_start[i] + span_length[i] < 0) { + continue; + } + + if (x_start[i] < 0) { + multi_step_up(attribs[i], -x_start[i], n); + + span_length[i] += x_start[i]; + + x_start[i] = 0; + } + + if (x_start[i] + span_length[i] >= fbi->width) { + span_length[i] -= x_start[i] + span_length[i] - fbi->width; + } else { + span_length[i]++; + } + + draw_span(y * fbi->width + x_start[i], span_length[i], + attribs[i], fbi); + } + } +} + + + +void +draw_triangle(Xy const xy_input, + Attribs const attribs_input, + boundary_info * const bi, + framebuffer_info * const fbi) { + + uint8_t const z = fbi->num_attribs; + uint8_t const w = z + 1; + uint8_t const n = w + 1; + + Xy xy; + varying * attribs[3]; + unsigned int i; + uint8_t index_array[3]; + int32_t y_array[3]; + int32_t x_array[3]; + + MALLOCARRAY_NOFAIL(attribs[0], n); + MALLOCARRAY_NOFAIL(attribs[1], n); + MALLOCARRAY_NOFAIL(attribs[2], n); + + xy = xy_input; + + for (i = 0; i < 3; i++) { + int32_to_varying_array(attribs_input._[i], attribs[i], n); + attribs[i][z] = compute_varying_z(attribs_input._[i][z]); + attribs[i][w] = inverse_varying(attribs[i][w]); + multiply_varying_array_by_varying(attribs[i], attribs[i][w], z); + } + + /* Argument preparations for sort3: */ + + index_array[0] = 0; index_array[1] = 1; index_array[2] = 2; + y_array[0] = xy._[0][1]; y_array[1] = xy._[1][1]; y_array[2] = xy._[2][1]; + x_array[0] = xy._[0][0]; x_array[1] = xy._[1][0]; x_array[2] = xy._[2][0]; + + sort3(index_array, y_array, x_array); + + { + uint8_t const top = index_array[0]; + uint8_t const mid = index_array[1]; + uint8_t const bot = index_array[2]; + + bool mid_is_to_the_left; + + Xy xy_sorted; + + xy_sorted._[0][0] = xy._[top][0]; + xy_sorted._[0][1] = xy._[top][1]; + xy_sorted._[1][0] = xy._[mid][0]; + xy_sorted._[1][1] = xy._[mid][1]; + xy_sorted._[2][0] = xy._[bot][0]; + xy_sorted._[2][1] = xy._[bot][1]; + + mid_is_to_the_left = + gen_triangle_boundaries(xy_sorted, bi, fbi->width, fbi->height); + + if (bi->start_scanline == -1) { + /* Triangle is completely out of the bounds of the frame buffer. */ + } else { + bool const no_upper_part = + (xy_sorted._[1][1] == xy_sorted._[0][1]); + + bool const horizontal = + (xy._[0][1] == xy._[1][1] && xy._[1][1] == xy._[2][1]); + /* Tells whether we are dealing with a degenerate + * horizontal triangle */ + + uint8_t const t = horizontal ^ 1; + + int32_t top2mid_delta = xy._[mid][t] - xy._[top][t]; + int32_t top2bot_delta = xy._[bot][t] - xy._[top][t]; + int32_t mid2bot_delta = xy._[bot][t] - xy._[mid][t]; + + varying * top2mid; + varying * top2bot; + varying * mid2bot; + + varying * upper_left_attribs; + varying * lower_left_attribs; + varying * upper_rght_attribs; + varying * lower_rght_attribs; + + MALLOCARRAY_NOFAIL(top2mid, n); + MALLOCARRAY_NOFAIL(top2bot, n); + MALLOCARRAY_NOFAIL(mid2bot, n); + + prepare_for_interpolation(attribs[top], attribs[mid], top2mid, top2mid_delta, n); + prepare_for_interpolation(attribs[top], attribs[bot], top2bot, top2bot_delta, n); + prepare_for_interpolation(attribs[mid], attribs[bot], mid2bot, mid2bot_delta, n); + + if (mid_is_to_the_left) { + upper_left_attribs = top2mid; + lower_left_attribs = mid2bot; + upper_rght_attribs = top2bot; + lower_rght_attribs = upper_rght_attribs; + } else { + upper_rght_attribs = top2mid; + lower_rght_attribs = mid2bot; + upper_left_attribs = top2bot; + lower_left_attribs = upper_left_attribs; + } + + if (!(horizontal || no_upper_part)) { + int32_t delta; + + if (bi->num_upper_rows > 0) { + if (bi->start_scanline > xy._[top][1]) { + delta = bi->start_scanline - xy._[top][1]; + + multi_step_up(upper_left_attribs, delta, n); + multi_step_up(upper_rght_attribs, delta, n); + } + + draw_partial_triangle( + upper_left_attribs, + upper_rght_attribs, + true, + bi, + fbi + ); + + delta = xy._[mid][1] - bi->start_scanline; + } else { + delta = top2mid_delta; + } + + multi_step_up(upper_left_attribs, delta, n); + multi_step_up(upper_rght_attribs, delta, n); + } + + if (horizontal) { + draw_degenerate_horizontal( + xy_sorted, + top2mid, top2bot, mid2bot, + fbi + ); + } else { + if (bi->start_scanline > xy._[mid][1]) { + int32_t const delta = bi->start_scanline - xy._[mid][1]; + + multi_step_up(lower_left_attribs, delta, n); + multi_step_up(lower_rght_attribs, delta, n); + } + + draw_partial_triangle( + lower_left_attribs, + lower_rght_attribs, + false, + bi, + fbi + ); + } + free(mid2bot); free(top2bot); free(top2mid); + } + } + free(attribs[2]); free(attribs[1]); free(attribs[0]); +} + + diff --git a/generator/pamtris/triangle.h b/generator/pamtris/triangle.h new file mode 100644 index 00000000..e043e95c --- /dev/null +++ b/generator/pamtris/triangle.h @@ -0,0 +1,25 @@ +#ifndef TRIANGLE_H_INCLUDED +#define TRIANGLE_H_INCLUDED + +#include <stdint.h> + +#include "limits_pamtris.h" + +struct boundary_info; +struct framebuffer_info; + +typedef struct { + int32_t _[3][2]; +} Xy; + +typedef struct { + int32_t _[3][MAX_NUM_ATTRIBS + 2]; +} Attribs; + +void +draw_triangle(Xy const xy, + Attribs const attribs, + struct boundary_info * const bdi, + struct framebuffer_info * const fbi); + +#endif diff --git a/generator/pamtris/utils.c b/generator/pamtris/utils.c new file mode 100644 index 00000000..a6b6e7d4 --- /dev/null +++ b/generator/pamtris/utils.c @@ -0,0 +1,266 @@ +/*============================================================================= + utils.c +=============================================================================== + Utility functions +=============================================================================*/ + +#include <stdlib.h> +#include <stdint.h> +#include <math.h> + +#include "limits_pamtris.h" +#include "varying.h" + +#include "utils.h" + + + +void +prepare_for_interpolation(const varying * const begin, + const varying * const end, + varying * const out, + int32_t num_steps, + uint8_t const elements) { + + double inverse_num_steps; + unsigned int i; + + if (num_steps < 1) { + num_steps = 1; + } + + inverse_num_steps = 1.0 / num_steps; + + for (i = 0; i < elements; i++) { + out[i].v = begin[i].v; + out[i].s = (end[i].v - begin[i].v) * inverse_num_steps; + } +} + + + +varying +compute_varying_z(int32_t const input_z) { + + varying retval; + + retval.v = 1.0 / (1 + input_z - MIN_COORD); + retval.s = 0.0; + + return retval; +} + + + +void +multiply_varying_array_by_varying(varying * const vars, + varying const multiplier, + uint8_t const elements) { + + unsigned int i; + + for (i = 0; i < elements; i++) { + vars[i].v *= multiplier.v; + vars[i].s = 0.0; + } +} + + +void +divide_varying_array_by_varying(varying * const vars, + varying const divisor, + uint8_t const elements) { + + double const inverse_divisor = 1.0 / divisor.v; + + unsigned int i; + + for (i = 0; i < elements; i++) { + vars[i].v *= inverse_divisor; + vars[i].s = 0.0; + } +} + + + +varying +inverse_varying(varying const var) { + + varying retval; + + retval.v = 1.0 / var.v; + retval.s = 0.0; + + return retval; +} + + + +varying +multiply_varyings(varying const a, + varying const b) { + + varying retval; + + retval.v = a.v * b.v; + retval.s = 0.0; + + return retval; +} + + + +void +step_up(varying * const vars, + uint8_t const elements) { + + unsigned int i; + + for (i = 0; i < elements; i++) { + vars[i].v += vars[i].s; + } +} + + + +void +multi_step_up(varying * const vars, + int32_t const times, + uint8_t const elements) { + + unsigned int i; + + for (i = 0; i < elements; i++) { + vars[i].v += times * vars[i].s; + } +} + + + +void +int32_to_varying_array(const int32_t * const in, + varying * const out, + uint8_t const elements) { + + unsigned int i; + + for (i = 0; i < elements; i++) { + out[i].v = in[i]; + out[i].s = 0.0; + } +} + + + +/* static int64_t +abs64(int64_t x) +{ + + int64_t const nm = ~geq_mask64(x, 0); + + return (-x & nm) | (x & ~nm); +} */ + + + +int32_t +round_varying(varying const var) { + + return round(var.v); +} + + + +int64_t +geq_mask64(int64_t a, int64_t b) { + + uint64_t const diff = a - b; + + return -((~diff) >> 63); +} + + + +static void +swap(uint8_t * const a, + uint8_t * const b) { +/*---------------------------------------------------------------------------- + Swap the contents pointed to by a and b. +-----------------------------------------------------------------------------*/ + uint8_t const temp = *a; + + *a = *b; + *b = temp; +} + + + +void +sort3(uint8_t * const index_array, + const int32_t * const y_array, + const int32_t * const x_array) { +/*---------------------------------------------------------------------------- + Sort an index array of 3 elements. This function is used to sort vertices + with regard to relative row from top to bottom, but instead of sorting + an array of vertices with all their coordinates, we simply sort their + indices. Each element in the array pointed to by "index_array" should + contain one of the numbers 0, 1 or 2, and each one of them should be + different. "y_array" should point to an array containing the corresponding + Y coordinates (row) of each vertex and "x_array" should point to an array + containing the corresponding X coordinates (column) of each vertex. + + If the Y coordinates are all equal, the indices are sorted with regard to + relative X coordinate from left to right. If only the top two vertex have + the same Y coordinate, the array is sorted normally with regard to relative + Y coordinate, but the first two indices are then sorted with regard to + relative X coordinate. Finally, If only the bottom two vertex have the same + Y coordinate, the array is sorted normally with regard to relative Y + coordinate, but the last two indices are then sorted with regard to relative + X coordinate. +-----------------------------------------------------------------------------*/ + uint8_t * const ia = index_array; + + const int32_t * ya; + const int32_t * xa; + + ya = y_array; /* initial value */ + xa = x_array; /* initial value */ + + if (ya[0] == ya[1] && ya[1] == ya[2]) { + /* In case the vertices represent a degenerate horizontal triangle, we + sort according to relative X coordinate, as opposed to Y. + */ + ya = xa; + } + + if (ya[ia[2]] < ya[ia[1]]) { + swap(ia, ia + 2); + if (ya[ia[2]] < ya[ia[1]]) { + swap(ia + 1, ia + 2); + if (ya[ia[1]] < ya[ia[0]]) { + swap(ia, ia + 1); + } + } + } else if (ya[ia[1]] < ya[ia[0]]) { + swap(ia, ia + 1); + if (ya[ia[2]] < ya[ia[1]]) { + swap(ia + 1, ia + 2); + } + } + + if (ya == xa) { + return; + } + + if (ya[ia[0]] == ya[ia[1]]) { + if (xa[ia[1]] < xa[ia[0]]) { + swap(ia, ia + 1); + } + } else if (ya[ia[1]] == ya[ia[2]]) { + if (xa[ia[2]] < xa[ia[1]]) { + swap(ia + 1, ia + 2); + } + } +} + + diff --git a/generator/pamtris/utils.h b/generator/pamtris/utils.h new file mode 100644 index 00000000..bd9dcdbe --- /dev/null +++ b/generator/pamtris/utils.h @@ -0,0 +1,58 @@ +#ifndef UTIL_H_INCLUDED +#define UTIL_H_INCLUDED + +#include "varying.h" + +void +prepare_for_interpolation(const varying * const begin, + const varying * const end, + varying * const out, + int32_t num_steps, + uint8_t const elements); + +varying +compute_varying_z(int32_t const input_z); + +void +multiply_varying_array_by_varying(varying * const vars, + varying const divisor, + uint8_t const elements); + +void +divide_varying_array_by_varying(varying * const vars, + varying const divisor, + uint8_t const elements); + +varying +inverse_varying(varying const var); + +varying +multiply_varyings(varying const a, + varying const b); + +void +step_up(varying * const vars, + uint8_t const elements); + +void +multi_step_up(varying * const vars, + int32_t const times, + uint8_t const elements); + +void +int32_to_varying_array(const int32_t * const in, + varying * const out, + uint8_t const elements); + +int32_t +round_varying(varying const var); + +int64_t +geq_mask64(int64_t a, int64_t b); + +void +sort3(uint8_t * const index_array, + const int32_t * const y_array, + const int32_t * const x_array); + +#endif diff --git a/generator/pamtris/varying.h b/generator/pamtris/varying.h new file mode 100644 index 00000000..6605f02d --- /dev/null +++ b/generator/pamtris/varying.h @@ -0,0 +1,12 @@ +#ifndef VARYING_H_INCLUDED +#define VARYING_H_INCLUDED + +#include <stdint.h> + + +typedef struct { + double v; /* Value */ + double s; /* Step */ +} varying; + +#endif diff --git a/generator/pbmmake.c b/generator/pbmmake.c index 600440f0..0352d007 100644 --- a/generator/pbmmake.c +++ b/generator/pbmmake.c @@ -61,7 +61,7 @@ parseCommandLine(int argc, char ** argv, 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) @@ -83,8 +83,8 @@ parseCommandLine(int argc, char ** argv, -static void -writeGrayRaster(unsigned int const cols, +static void +writeGrayRaster(unsigned int const cols, unsigned int const rows, FILE * const ofP) { @@ -96,7 +96,7 @@ writeGrayRaster(unsigned int const cols, bitrow0 = pbm_allocrow_packed(cols); bitrow1 = pbm_allocrow_packed(cols); - for (i=0; i <= lastCol; ++i) { + 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 */ @@ -119,7 +119,7 @@ writeGrayRaster(unsigned int const cols, pbm_freerow(bitrow1); } - + static void writeSingleColorRaster(unsigned int const cols, @@ -134,7 +134,7 @@ writeSingleColorRaster(unsigned int const cols, bitrow0 = pbm_allocrow_packed(cols); - for (i = 0; i <= lastCol; ++i) + for (i = 0; i <= lastCol; ++i) bitrow0[i] = color*0xff; if (color != 0) @@ -161,7 +161,7 @@ main(int argc, char * 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 { @@ -169,6 +169,6 @@ main(int argc, char * argv[]) { writeSingleColorRaster(cmdline.width, cmdline.height, color, stdout); } pm_close(stdout); - + return 0; } diff --git a/generator/pbmtext.c b/generator/pbmtext.c index 9f4366d4..e6f27865 100644 --- a/generator/pbmtext.c +++ b/generator/pbmtext.c @@ -10,41 +10,93 @@ ** implied warranty. */ +#define _DEFAULT_SOURCE 1 /* New name for SVID & BSD source defines */ #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 <math.h> #include <limits.h> #include <assert.h> +#include <setjmp.h> +#include <locale.h> +#include <wchar.h> #include "pm_c_util.h" #include "mallocvar.h" +#include "nstring.h" #include "shhopt.h" +#include "pm.h" #include "pbm.h" #include "pbmfont.h" -struct cmdlineInfo { +#define MAXLINECHARS 5000 + +struct CmdlineInfo { /* 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 PM_WCHAR * 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 */ - unsigned int verbose; + float space; /* -space option value or default */ + int lspace; /* -lspace option value or default */ + unsigned int width; /* -width option value or zero */ + unsigned int wchar; /* -wchar option specified */ + unsigned int nomargins; /* -nomargins option specified */ + unsigned int dryrun; /* -dry-run option specified */ + unsigned int textdump; /* -text-dump option specified */ + unsigned int verbose; /* -verbose option specified */ + /* undocumented option */ + unsigned int dumpsheet; /* font data sheet in PBM format for -font */ }; +static const PM_WCHAR * +textFmCmdLine(int argc, const char ** argv) { + + char * text; + PM_WCHAR * wtext; + unsigned int i; + unsigned int totaltextsize; + + MALLOCARRAY(text, MAXLINECHARS+1); + + if (!text) + pm_error("Unable to allocate memory for a buffer of up to %u " + "characters of text", MAXLINECHARS); + + text[0] = '\0'; + + for (i = 1, totaltextsize = 1; i < argc; ++i) { + if (i > 1) { + strcat(text, " "); + } + totaltextsize += strlen(argv[i]) + 1; + if (totaltextsize > MAXLINECHARS) + pm_error("input text too long"); + strcat(text, argv[i]); + } + MALLOCARRAY(wtext, totaltextsize * sizeof(PM_WCHAR)); + + if (!wtext) + pm_error("Unable to allocate memory for a buffer of up to %u " + "wide characters of text", totaltextsize); + + for (i = 0; i < totaltextsize; ++i) + wtext[i] = (PM_WCHAR) text[i]; + + free(text); + + return wtext; +} + + static void parseCommandLine(int argc, const char ** argv, - struct cmdlineInfo * const cmdlineP) { + struct CmdlineInfo * const cmdlineP) { /*---------------------------------------------------------------------------- Note that the file spec array we return is stored in the storage that was passed to us as the argv array. @@ -59,110 +111,151 @@ parseCommandLine(int argc, const char ** argv, MALLOCARRAY_NOFAIL(option_def, 100); option_def_index = 0; /* incremented by OPTENTRY */ - OPTENT3(0, "font", OPT_STRING, &cmdlineP->font, NULL, 0); - OPTENT3(0, "builtin", OPT_STRING, &cmdlineP->builtin, NULL, 0); - OPTENT3(0, "dump", OPT_FLAG, NULL, &cmdlineP->dump, 0); - OPTENT3(0, "space", OPT_FLOAT, &cmdlineP->space, NULL, 0); - OPTENT3(0, "width", OPT_UINT, &cmdlineP->width, NULL, 0); - OPTENT3(0, "lspace", OPT_INT, &cmdlineP->lspace, NULL, 0); - OPTENT3(0, "nomargins", OPT_FLAG, NULL, &cmdlineP->nomargins, 0); - OPTENT3(0, "verbose", OPT_FLAG, NULL, &cmdlineP->verbose, 0); + OPTENT3(0, "font", OPT_STRING, &cmdlineP->font, NULL, 0); + OPTENT3(0, "builtin", OPT_STRING, &cmdlineP->builtin, NULL, 0); + OPTENT3(0, "space", OPT_FLOAT, &cmdlineP->space, NULL, 0); + OPTENT3(0, "lspace", OPT_INT, &cmdlineP->lspace, NULL, 0); + OPTENT3(0, "width", OPT_UINT, &cmdlineP->width, NULL, 0); + OPTENT3(0, "nomargins", OPT_FLAG, NULL, &cmdlineP->nomargins, 0); + OPTENT3(0, "wchar", OPT_FLAG, NULL, &cmdlineP->wchar, 0); + OPTENT3(0, "verbose", OPT_FLAG, NULL, &cmdlineP->verbose, 0); + OPTENT3(0, "dry-run", OPT_FLAG, NULL, &cmdlineP->dryrun, 0); + OPTENT3(0, "text-dump", OPT_FLAG, NULL, &cmdlineP->textdump, 0); + OPTENT3(0, "dump-sheet", OPT_FLAG, NULL, &cmdlineP->dumpsheet, 0); /* Set the defaults */ - cmdlineP->font = NULL; + cmdlineP->font = NULL; cmdlineP->builtin = NULL; - cmdlineP->space = 0.0; - cmdlineP->width = 0; - cmdlineP->lspace = 0; + cmdlineP->space = 0.0; + cmdlineP->width = 0; + cmdlineP->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 */ pm_optParseOptions3(&argc, (char **)argv, opt, sizeof(opt), 0); - /* Uses and sets argc, argv, and some of *cmdlineP and others. */ + /* Uses and sets argc, argv, and some of *cmdlineP and others. */ + + if (cmdlineP->width > 0 && cmdlineP->nomargins) { + pm_message("-nomargins has no effect when -width is specified"); + cmdlineP->nomargins = FALSE; + } else if (cmdlineP->width > INT_MAX-10) + pm_error("-width value too large"); + + if (cmdlineP->space > pbm_maxfontwidth()) + pm_error("-space value too large"); + else if (cmdlineP->space < -pbm_maxfontwidth()) + pm_error("negative -space value too large"); + + if (cmdlineP->lspace > pbm_maxfontheight()) + pm_error("-lspace value too large"); + else if (cmdlineP->lspace < -pbm_maxfontheight()) + pm_error("negative -lspace value too large"); + + if (cmdlineP->textdump) { + if (cmdlineP->dryrun) + pm_error("You cannot specify both -dry-run and -text-dump"); + else if (cmdlineP->dumpsheet) + pm_error("You cannot specify both -dump-sheet and -text-dump"); + } + + if (cmdlineP->dryrun && cmdlineP->dumpsheet) + pm_error("You cannot specify both -dry-run and -dump-sheet"); if (argc-1 == 0) cmdlineP->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]); - } - cmdlineP->text = text; + else { /* Text to render is part of command line */ + if (cmdlineP->wchar) + pm_error("-wchar is not valid when text is from command line"); + + cmdlineP->text = textFmCmdLine(argc, argv); + + } + free(option_def); } static void -reportFont(struct font * const fontP) { - - unsigned int n; - unsigned int c; +reportFont(const struct font2 * const fontP) { pm_message("FONT:"); - pm_message(" character dimensions: %uw x %uh", + pm_message(" Name: %s", fontP->name); + pm_message(" Encoding: %s", fontP->charset_string); + pm_message(" Origin: %s", pbmFontOrigin[fontP->load_fn]); + pm_message(" Character dimensions: %uw x %uh", fontP->maxwidth, fontP->maxheight); pm_message(" Additional vert white space: %d pixels", fontP->y); + pm_message(" # characters loaded: %u", fontP->chars); +} + + + + + + +static struct font2 * +font2FromFile(const char * const fileName, + PM_WCHAR const maxmaxglyph) { + + struct font2 * font2P; + + jmp_buf jmpbuf; + int rc; - for (c = 0, n = 0; c < ARRAY_SIZE(fontP->glyph); ++c) - if (fontP->glyph[c]) - ++n; + rc = setjmp(jmpbuf); - pm_message(" # characters: %u", n); + if (rc == 0) { + /* This is the normal program flow */ + pm_setjmpbuf(&jmpbuf); + + font2P = pbm_loadfont2(fileName, maxmaxglyph); + + pm_setjmpbuf(NULL); + } else { + /* This is the second pass, after pbm_loadbdffont2 does a longjmp + because it fails. + */ + pm_setjmpbuf(NULL); + + pm_error("Failed to load font from file '%s'", fileName); + } + + return font2P; } static void -computeFont(struct cmdlineInfo const cmdline, - struct font ** const fontPP) { +computeFont(struct CmdlineInfo const cmdline, + struct font2 ** const fontPP) { - struct font * fontP; + struct font2 * font2P; if (cmdline.font) - fontP = pbm_loadfont(cmdline.font); - else { - if (cmdline.builtin) - fontP = pbm_defaultfont(cmdline.builtin); - else - fontP = pbm_defaultfont("bdf"); - } + font2P = font2FromFile(cmdline.font, + cmdline.wchar ? PM_FONT2_MAXGLYPH : + PM_FONT_MAXGLYPH); + else if (cmdline.builtin) + font2P = pbm_defaultfont2(cmdline.builtin); + else + font2P = pbm_defaultfont2(cmdline.wchar ? "bdf" : "bdf"); if (cmdline.verbose) - reportFont(fontP); + reportFont(font2P); - if (cmdline.dump) { - pbm_dumpfont(fontP); - exit(0); - } - *fontPP = fontP; + *fontPP = font2P; } -struct text { - char ** textArray; /* malloc'ed */ +struct Text { + PM_WCHAR ** textArray; /* malloc'ed */ + /* This is strictly characters that are in user's font - no control + characters, no undefined code points. + */ unsigned int allocatedLineCount; unsigned int lineCount; }; @@ -170,41 +263,49 @@ struct text { static void -allocTextArray(struct text * const textP, +allocTextArray(struct Text * const textP, unsigned int const maxLineCount, unsigned int const maxColumnCount) { unsigned int line; - textP->allocatedLineCount = maxLineCount; - + textP->allocatedLineCount = maxColumnCount > 0 ? maxLineCount : 0; MALLOCARRAY_NOFAIL(textP->textArray, maxLineCount); - - for (line = 0; line < maxLineCount; ++line) - MALLOCARRAY_NOFAIL(textP->textArray[line], maxColumnCount+1); + for (line = 0; line < maxLineCount; ++line) { + if (maxColumnCount > 0) + MALLOCARRAY_NOFAIL(textP->textArray[line], maxColumnCount+1); + else + textP->textArray[line] = NULL; + } textP->lineCount = 0; } static void -freeTextArray(struct text const text) { +freeTextArray(struct Text const text) { unsigned int line; for (line = 0; line < text.allocatedLineCount; ++line) - free((char **)text.textArray[line]); + free((PM_WCHAR **)text.textArray[line]); free(text.textArray); } +enum FixMode {SILENT, /* convert silently */ + WARN, /* output message to stderr */ + QUIT /* abort */ }; + + static void -fixControlChars(const char * const input, - struct font * const fontP, - const char ** const outputP) { +fixControlChars(const PM_WCHAR * const input, + struct font2 * const fontP, + const PM_WCHAR ** const outputP, + enum FixMode const fixMode) { /*---------------------------------------------------------------------------- Return a translation of input[] that can be rendered as glyphs in the font 'fontP'. Return it as newly malloced *outputP. @@ -214,8 +315,9 @@ fixControlChars(const char * const input, Remove any trailing newline. (But leave intermediate ones as line delimiters). - Turn anything that isn't a code point in the font to a single space - (which isn't guaranteed to be in the font either, of course). + Depending on value of fixMode, turn anything that isn't a code point + in the font to a single space (which isn't guaranteed to be in the + font either, of course). -----------------------------------------------------------------------------*/ /* We don't know in advance how big the output will be because of the tab expansions. So we make sure before processing each input @@ -228,10 +330,10 @@ fixControlChars(const char * const input, unsigned int const tabSize = 8; unsigned int inCursor, outCursor; - char * output; /* Output buffer. Malloced */ + PM_WCHAR * output; /* Output buffer. Malloced */ size_t outputSize; /* Currently allocated size of 'output' */ - outputSize = strlen(input) + 1 + tabSize; + outputSize = wcslen(input) + 1 + tabSize; /* Leave room for one worst case tab expansion and NUL terminator */ MALLOCARRAY(output, outputSize); @@ -239,7 +341,8 @@ fixControlChars(const char * const input, pm_error("Couldn't allocate %u bytes for a line of text.", (unsigned)outputSize); - for (inCursor = 0, outCursor = 0; input[inCursor] != '\0'; ++inCursor) { + for (inCursor = 0, outCursor = 0; input[inCursor] != L'\0'; ++inCursor) { + PM_WCHAR const currentChar = input[inCursor]; if (outCursor + 1 + tabSize > outputSize) { outputSize = outCursor + 1 + 4 * tabSize; REALLOCARRAY(output, outputSize); @@ -247,24 +350,43 @@ fixControlChars(const char * const input, pm_error("Couldn't allocate %u bytes for a line of text.", (unsigned)outputSize); } - if (input[inCursor] == '\n' && input[inCursor+1] == '\0') { + if (currentChar == L'\n' && input[inCursor+1] == L'\0') { /* This is a terminating newline. We don't do those. */ - } else if (input[inCursor] == '\t') { + } else if (currentChar == L'\t') { /* Expand this tab into the right number of spaces. */ unsigned int const nextTabStop = (outCursor + tabSize) / tabSize * tabSize; + if (fontP->glyph[L' '] == NULL) + pm_error("space character not defined in font"); + while (outCursor < nextTabStop) - output[outCursor++] = ' '; - } else if (!fontP->glyph[(unsigned char)input[inCursor]]) { + output[outCursor++] = L' '; + } else if (currentChar > fontP->maxglyph || + !fontP->glyph[currentChar]) { + if (currentChar > PM_FONT2_MAXGLYPH) + pm_message("code point %X is beyond what this program " + "can handle. Max=%X", + (unsigned int)currentChar, PM_FONT2_MAXGLYPH); + /* Turn this unknown char into a single space. */ - output[outCursor++] = ' '; + if (fontP->glyph[L' '] == NULL) + pm_error("space character not defined in font"); + else if (fixMode == QUIT) + pm_error("code point %X not defined in font", + (unsigned int) currentChar ); + else { + if (fixMode == WARN) + pm_message("converting code point %X to space", + (unsigned int) currentChar ); + output[outCursor++] = ' '; + } } else output[outCursor++] = input[inCursor]; assert(outCursor <= outputSize); } - output[outCursor++] = '\0'; + output[outCursor++] = L'\0'; assert(outCursor <= outputSize); @@ -274,478 +396,702 @@ fixControlChars(const char * const input, 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; +clearBackground(bit ** const bits, + int const cols, + int const rows) { + + unsigned int row; + + for (row = 0; row < rows; ++row) { + unsigned int colChar; + for (colChar = 0; colChar < pbm_packed_bytes(cols); ++colChar) + bits[row][colChar] = 0x00; } } static void -get_line_dimensions(const char line[], const struct font * const font_p, - const float intercharacter_space, - double * const bwidP, int * const backup_space_needed_p) { +getEdges(double const currentPosition, + PM_WCHAR const currentChar, + const struct glyph * const glyphP, + int const currLeftEdge, + double const currRightEdge, + int * const newLeftEdgeP, + double * const newRightEdgeP) { + + int leftEdge; + double rightEdge; + + if (glyphP == NULL) + pm_error("Unrenderable char: %04X", (unsigned int) currentChar); + else { + leftEdge = (int) MIN(currentPosition + glyphP->x, currLeftEdge); + rightEdge = MAX(currentPosition + glyphP->x + glyphP->width, + currRightEdge); + } + *newLeftEdgeP = leftEdge; + *newRightEdgeP = rightEdge; +} + + + +static void +advancePosition(double const currentPosition, + PM_WCHAR const currentChar, + const struct glyph * const glyphP, + float const space, + double const accumulatedSpace, + double * const newPositionP, + double * const newAccumulatedSpaceP) { +/*---------------------------------------------------------------------------- + Advance position according to value for glyph. + Add extra intercharacter space if -space option was used. + + The advance value must be zero or positive. +----------------------------------------------------------------------------*/ + + /* Start position of next character */ + /* Must not move left from current position */ + int const fullPixels = (int) (accumulatedSpace + space); + /* round toward 0 */ + int const advance = (int) glyphP->xadd + fullPixels; + + if (advance < 0) { + if (space < 0) + pm_error("Negative -space value too large"); + else + pm_error("Abnormal horizontal advance value %d " + "for code point 0x%lx.", + glyphP->xadd, (unsigned long int) currentChar); + } + else if (currentPosition + advance > INT_MAX) + pm_error("Image is too wide"); + else { + *newPositionP = currentPosition + advance; + *newAccumulatedSpaceP = accumulatedSpace + space + - (double) fullPixels; + } +} + + + +static void +getLineDimensions(PM_WCHAR const line[], + const struct font2 * const fontP, + float const intercharacterSpace, + double * const rightEdgeP, + int * const leftEdgeP) { /*---------------------------------------------------------------------------- - Determine the width in pixels of the line of text line[] in the font - *font_p, and return it as *bwidP. 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. + Determine the left edge and right edge in pixels of the line of text + line[] in the font *fontP, and return them as *leftEdgeP and *rightEdgeP. + *leftEdgeP will be negative if the leftmost character in the line has a + "backup" distance. + + Note that the right (left) edge may not belong to the last (first) + character in the text line. This happens when the font is slanted + (xadd is smaller than width) and/or intercharacter space is negative. + This is illustrated by the following: + + pbmtext -nomargin "ART." | pnmshear -30 -noantialias + + Also note that there may be no black pixels on what is reported as an edge. + This often happens with fixed-width font in which the white areas on the + sides are not trimmed. -----------------------------------------------------------------------------*/ - int cursor; /* cursor into the line of text */ - double 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. Note that this can be negative - (which means were crowding, rather than spreading, text). + unsigned int cursor; /* cursor into the line of text */ + double currentPosition; + /* sum of xadd values and intercharacter space so far in line. this + is never negative. */ - double bwid; - bool no_chars_yet; - /* 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'. + double 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. Note that this can be negative (which means were + crowding, rather than spreading, text). */ + int leftEdge; + double rightEdge; - no_chars_yet = TRUE; /* initial value */ - accumulatedIcs = 0.0; /* initial value */ - bwid = 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 += glyphP->x; - } - } else { - /* handle extra intercharacter space (-space option) */ - accumulatedIcs += intercharacter_space; - if (accumulatedIcs >= INT_MAX) - pm_error("Image width too large."); - if (accumulatedIcs <= INT_MIN) - pm_error("Absurdly large negative -space value."); - { - int const fullPixels = (int) accumulatedIcs; - bwid += fullPixels; - accumulatedIcs -= fullPixels; - } - } - lastGlyphP = glyphP; - bwid += glyphP->xadd; - } + currentPosition = 0; /* initial value */ + accumulatedIcs = 0.0; /* initial value */ + + leftEdge = INT_MAX; /* initial value */ + rightEdge = INT_MIN; /* initial value */ + + for (cursor = 0; line[cursor] != L'\0'; ++cursor) { + PM_WCHAR const currentChar = line[cursor]; + unsigned long int const glyphIndex = (unsigned long int) currentChar; + struct glyph * const glyphP = fontP->glyph[glyphIndex]; + + getEdges(currentPosition, currentChar, glyphP, leftEdge, rightEdge, + &leftEdge, &rightEdge); + + advancePosition(currentPosition, currentChar, glyphP, + intercharacterSpace, accumulatedIcs, + ¤tPosition, &accumulatedIcs); } - 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 -= lastGlyphP->xadd; - bwid += lastGlyphP->width + lastGlyphP->x; + + if (line[0] == L'\0') { /* Empty line */ + leftEdge = 0; + rightEdge = 0.0; } - if (bwid > INT_MAX) - pm_error("Image width too large."); - else - *bwidP = bwid; + + *leftEdgeP = leftEdge; + *rightEdgeP = rightEdge; } static void -insert_character(const struct glyph * const glyph, - int const toprow, - int const leftcol, - bit ** const bits) { +getCharsWithinWidth(PM_WCHAR const line[], + const struct font2 * const fontP, + float const intercharacter_space, + unsigned int const targetWidth, + unsigned int * const charCountP, + int * const leftEdgeP) { +/*---------------------------------------------------------------------------- + Determine how many characters of text line[] fit into an image of target + width targetWidth. + + *leftEdgeP will be negative if the leftmost character in the line has a + "backup" distance and zero if it does not. +-----------------------------------------------------------------------------*/ + if (line[0] == L'\0') { + /* Empty line */ + *leftEdgeP = 0; + *charCountP = 0; + } else { + unsigned int cursor; /* cursor into the line of text */ + double currentPosition; + double accumulatedIcs; + int leftEdge; + double rightEdge; + unsigned int currentWidth; + + currentPosition = 0; /* initial value */ + accumulatedIcs = 0.0; /* initial value */ + + leftEdge = INT_MAX; /* initial value */ + rightEdge = INT_MIN; /* initial value */ + + for (cursor = 0, currentWidth = 0; + currentWidth <= targetWidth && line[cursor] != L'\0'; + ++cursor) { + PM_WCHAR const currentChar = line[cursor]; + unsigned long int const glyphIndex = + (unsigned long int) currentChar; + struct glyph * const glyphP = fontP->glyph[glyphIndex]; + + getEdges(currentPosition, currentChar, glyphP, leftEdge, rightEdge, + &leftEdge, &rightEdge); + + advancePosition(currentPosition, currentChar, glyphP, + intercharacter_space, accumulatedIcs, + ¤tPosition, &accumulatedIcs); + + currentWidth = rightEdge - ((leftEdge > 0 ) ? 0 : leftEdge); + } + + if (currentWidth > targetWidth) { + if (cursor == 1) + pm_error("-width value too small " + "to accomodate single character"); + else + *charCountP = cursor - 1; + } else + *charCountP = cursor; + + *leftEdgeP = leftEdge; + } +} + + + +static void +insertCharacter(const struct glyph * const glyphP, + int const toprow, + int const leftcol, + unsigned int const cols, + unsigned int const rows, + bit ** const bits) { /*---------------------------------------------------------------------------- Insert one character (whose glyph is 'glyph') into the image bits[]. Its top left corner shall be row 'toprow', column 'leftcol'. -----------------------------------------------------------------------------*/ + if (glyphP->width == 0 && glyphP->height == 0) { + /* No bitmap data. Some BDF files code space this way */ + } else { + unsigned int glyph_y; /* Y position within the glyph */ + + if (leftcol + glyphP->x < 0 || + leftcol + glyphP->x + glyphP->width > cols || + toprow < 0 || + toprow + glyphP->height >rows ) + pm_error("internal error. Rendering out of bounds"); - int glyph_y, glyph_x; /* position within the glyph */ + for (glyph_y = 0; glyph_y < glyphP->height; ++glyph_y) { + unsigned int 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; + for (glyph_x = 0; glyph_x < glyphP->width; ++glyph_x) { + if (glyphP->bmap[glyph_y * glyphP->width + glyph_x]) { + unsigned int const col = leftcol + glyphP->x + glyph_x; + bits[toprow+glyph_y][col/8] |= PBM_BLACK << (7-col%8); + } + } } } -} +} 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) { +insertCharacters(bit ** const bits, + struct Text const lp, + struct font2 * const fontP, + int const topmargin, + int const leftmargin, + float const intercharacter_space, + unsigned int const cols, + unsigned int const rows, + int const lspace, + bool const fixedAdvance) { /*---------------------------------------------------------------------------- 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 */ + unsigned 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; + unsigned int row; /* row in image of top of current typeline */ + double leftcol; /* Column in image of left edge of current glyph */ + unsigned int cursor; /* cursor into a line of input text */ + double accumulatedIcs; /* 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. + full pixels whenever we have more than one pixel. */ row = topmargin + line * (fontP->maxheight + lspace); leftcol = leftmargin; - accumulated_ics = 0.0; /* initial value */ - + accumulatedIcs = 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; - } - } - } + PM_WCHAR const currentChar = lp.textArray[line][cursor]; + unsigned long int const glyphIndex = + (unsigned long int)currentChar; + struct glyph * const glyphP = fontP->glyph[glyphIndex]; + int const toprow = + row + fontP->maxheight + fontP->y - glyphP->height - glyphP->y; + /* row number in image of top row in glyph */ + + assert(glyphP != NULL); + + insertCharacter(glyphP, toprow, leftcol, cols, rows, bits); + + if (fixedAdvance) + leftcol += fontP->maxwidth; + else + advancePosition(leftcol, currentChar, glyphP, + intercharacter_space, accumulatedIcs, + &leftcol, &accumulatedIcs); } } } -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. Note that this is negative if we're crowding, rather - than spreading, characters. - */ -}; +static void +flowText(struct Text const inputText, + int const targetWidth, + struct font2 * const fontP, + float const intercharacterSpace, + struct Text * const outputTextP, + unsigned int * const maxleftbP) { + unsigned int outputLineNum; + unsigned int incursor; /* cursor into the line we are reading */ + unsigned int const maxLineCount = 50; /* max output lines */ + int leftEdge; + int leftExtreme = 0; + unsigned int charCount; -static void -initializeFlowedOutputLine(struct outputTextCursor * const cursorP) { + allocTextArray(outputTextP, maxLineCount, 0); - cursorP->columnNo = 0; - cursorP->noCharsYet = TRUE; - cursorP->widthSoFar = 0.0; - cursorP->accumulatedIcs = 0.0; -} + for (incursor = 0, outputLineNum = 0; + inputText.textArray[0][incursor] != L'\0'; ) { + unsigned int outcursor; + getCharsWithinWidth(&inputText.textArray[0][incursor], fontP, + intercharacterSpace, targetWidth, + &charCount, &leftEdge); -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); -} + MALLOCARRAY(outputTextP->textArray[outputLineNum], charCount+1); + if (!outputTextP->textArray[outputLineNum]) + pm_error("Unable to allocate memory for the text of line %u, " + "%u characters long", outputLineNum, charCount); + ++outputTextP->allocatedLineCount; -static void -finishOutputLine(struct outputTextCursor * const cursorP) { + for (outcursor = 0; outcursor < charCount; ++outcursor, ++incursor) + outputTextP->textArray[outputLineNum][outcursor] = + inputText.textArray[0][incursor]; + + outputTextP->textArray[outputLineNum][charCount] = L'\0'; + ++outputLineNum; + if (outputLineNum >= maxLineCount) + pm_error("-width too small. too many output lines"); - if (cursorP->text.lineCount < cursorP->text.allocatedLineCount) { - char * const currentLine = - cursorP->text.textArray[cursorP->text.lineCount]; - currentLine[cursorP->columnNo++] = '\0'; - ++cursorP->text.lineCount; + leftExtreme = MIN(leftEdge, leftExtreme); } + outputTextP->lineCount = outputLineNum; + *maxleftbP = (unsigned int) -leftExtreme; } 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. +truncateText(struct Text const inputText, + unsigned int const targetWidth, + struct font2 * const fontP, + float const intercharacterSpace, + unsigned int * const maxleftbP) { - 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) */ - cursorP->accumulatedIcs += cursorP->intercharacterSpace; - { - int const fullPixels = (int)cursorP->accumulatedIcs; - 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); + unsigned int lineNum; /* Line number on which we are currently working */ + int leftEdge; + int leftExtreme = 0; + + for (lineNum = 0; lineNum < inputText.lineCount; ++lineNum) { + PM_WCHAR * const currentLine = inputText.textArray[lineNum]; + + unsigned int charCount; + + getCharsWithinWidth(currentLine, fontP, + intercharacterSpace, targetWidth, + &charCount, &leftEdge); + + if (currentLine[charCount] != L'\0') { + pm_message("truncating line %u from %u to %u characters", + lineNum, (unsigned) wcslen(currentLine), charCount); + currentLine[charCount] = L'\0'; } + + leftExtreme = MIN(leftEdge, leftExtreme); } + *maxleftbP = (unsigned int) - leftExtreme; } 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); +fgetWideString(PM_WCHAR * const widestring, + unsigned int const size, + FILE * const ifP, + bool * const eofP, + const char ** const errorP) { + + wchar_t * rc; + + assert(widestring); + assert(size > 0); + + rc = fgetws(widestring, size, ifP); + + if (rc == NULL) { + if (feof(ifP)) { + *eofP = true; + *errorP = NULL; + } else if (ferror(ifP) && errno == EILSEQ) + pm_asprintf(errorP, + "fgetws(): conversion error: sequence is " + "invalid for locale '%s'", + setlocale(LC_CTYPE, NULL)); + else + pm_asprintf(errorP, + "fgetws() of max %u bytes failed", + size); + } else { + *eofP = false; + *errorP = NULL; } - *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. Note that this is negative if we're - crowding, not spreading, characters. - */ +fgetNarrowString(PM_WCHAR * const widestring, + unsigned int const size, + FILE * const ifP, + bool * const eofP, + const char ** const errorP) { - 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 */ + char * bufNarrow; + char * rc; - 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) */ - accumulatedIcs += intercharacterSpace; - { - int const fullPixels = (int) intercharacterSpace; - 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'; - } - } + assert(widestring); + assert(size > 0); + + MALLOCARRAY_NOFAIL(bufNarrow, MAXLINECHARS+1); + + rc = fgets(bufNarrow, size, ifP); + + if (rc == NULL) { + if (feof(ifP)) { + *eofP = true; + *errorP = NULL; + } else + pm_asprintf(errorP, "Error reading file"); + } else { + size_t cnt; + + for (cnt = 0; cnt < size && bufNarrow[cnt] != '\0'; ++cnt) + widestring[cnt] = (PM_WCHAR)(unsigned char) bufNarrow[cnt]; + + widestring[cnt] = L'\0'; + + *eofP = false; + *errorP = NULL; } - truncatedText.lineCount = inputText.lineCount; - *outputTextP = truncatedText; + free(bufNarrow); } static void -getText(const char cmdline_text[], - struct font * const fontP, - struct text * const input_textP) { - - struct text input_text; - - if (cmdline_text) { - MALLOCARRAY_NOFAIL(input_text.textArray, 1); - input_text.allocatedLineCount = 1; - input_text.lineCount = 1; - fixControlChars(cmdline_text, fontP, - (const char**)&input_text.textArray[0]); +fgetNarrowWideString(PM_WCHAR * const widestring, + unsigned int const size, + FILE * const ifP, + bool * const eofP, + const char ** const errorP) { +/*---------------------------------------------------------------------------- + Return the next line from file *ifP, as *widestring. + + Lines are delimited by newline characters and EOF. + + 'size' is the size in characters of the buffer at *widestring. If the line + to which the file is positioned is longer than that minus 1, we consider it + to be only that long and consider the next character of the actual line to + be the first character of the next line. We leave the file positioned + to that character. + + Return *eofP == true iff we encounter end of file (and therefore don't read + a line). + + If we can't read the file (or sense EOF), return as *errorP a text + explanation of why; otherwise, return *errorP = NULL. + + The line we return is null-terminated. But it also includes any embedded + null characters that are within the line in the file. It is not strictly + possible for Caller to tell whether a null character in *widestring comes + from the file or is the one we put there, so Caller should just ignore any + null character and anything after it. It is also not possible for Caller to + tell if we trunctaed the actual line because of 'size' if there is a null + character in the line. This means there just isn't any way to get + reasonable behavior from this function if the input file contains null + characters (but at least the damage is limited to presenting arbitrary text + as the contents of the file - the program won't crash). + + Null characters never appear within normal text (including wide-character + text). If there is one in the input file, it is probably because the input + is corrupted. + + The line we return may or may not end in a newline character. It ends in a + newline character unless it doesn't fit in 'size' characters or it is the + last line in the file and doesn't end in newline. +-----------------------------------------------------------------------------*/ + /* The limitations described above with respect to null characters in + *ifP are derived from the same limitations in POSIX 'fgets' and + 'fgetws'. To avoid them, we would have to read *ifP one character + at a time with 'fgetc' and 'fgetwc'. + */ + + int const wideCode = fwide(ifP, 0); + /* Width orientation for *ifP: positive means wide, negative means + byte, zero means undecided. + */ + + assert(widestring); + assert(size > 0); + + if (wideCode > 0) + /* *ifP is wide-oriented */ + fgetWideString(widestring, size, ifP, eofP, errorP); + else + fgetNarrowString(widestring, size, ifP, eofP, errorP); +} + + + + +static void +getText(PM_WCHAR const cmdlineText[], + struct font2 * const fontP, + struct Text * const inputTextP, + enum FixMode const fixMode) { +/*---------------------------------------------------------------------------- + Get as *inputTextP the text to format, given that the text on the + command line (one word per command line argument, separated by spaces), + is 'cmdlineText'. + + If 'cmdlineText' is null, that means to get the text from Standard Input. + Otherwise, 'cmdlineText' is that text. + + But we return text as only renderable characters - characters in *fontP - + with control characters interpreted or otherwise fixed, according to + 'fixMode'. + + If *inputTextP indicates Standard Input and Standard Input contains null + characters, we will truncate lines or consider a single line to be multiple + lines. +-----------------------------------------------------------------------------*/ + struct Text inputText; + + if (cmdlineText) { + MALLOCARRAY_NOFAIL(inputText.textArray, 1); + inputText.allocatedLineCount = 1; + inputText.lineCount = 1; + fixControlChars(cmdlineText, fontP, + (const PM_WCHAR**)&inputText.textArray[0], fixMode); + free((void *) cmdlineText); } else { /* Read text from stdin. */ - unsigned int maxlines; - /* Maximum number of lines for which we presently have space - in the text array + unsigned int maxlines; + /* Maximum number of lines for which we presently have space in + the text array */ - char buf[5000]; - char ** text_array; + PM_WCHAR * buf; + PM_WCHAR ** textArray; unsigned int lineCount; + bool eof; + + MALLOCARRAY(buf, MAXLINECHARS+1); + + if (!buf) + pm_error("Unable to allocate memory for up to %u characters of " + "text", MAXLINECHARS); maxlines = 50; /* initial value */ - MALLOCARRAY_NOFAIL(text_array, maxlines); - - lineCount = 0; /* initial value */ - while (fgets(buf, sizeof(buf), stdin) != NULL) { - if (strlen(buf) + 1 >= sizeof(buf)) - pm_error("A line of input text is longer than %u characters." - "Cannot process.", (unsigned)sizeof(buf)-1); - if (lineCount >= maxlines) { - maxlines *= 2; - REALLOCARRAY(text_array, maxlines); - if (text_array == NULL) - pm_error("out of memory"); + MALLOCARRAY(textArray, maxlines); + + if (!textArray) + pm_error("Unable to allocate memory for a buffer for up to %u " + "lines of text", maxlines); + + for (lineCount = 0, eof = false; !eof; ) { + const char * error; + fgetNarrowWideString(buf, MAXLINECHARS, stdin, &eof, &error); + if (error) + pm_error("Unable to read line %u from file. %s", + lineCount, error); + else { + if (!eof) { + if (wcslen(buf) + 1 >= MAXLINECHARS) + pm_error( + "Line %u (starting at zero) of input text " + "is longer than %u characters." + "Cannot process", + lineCount, (unsigned int) MAXLINECHARS-1); + if (lineCount >= maxlines) { + maxlines *= 2; + REALLOCARRAY(textArray, maxlines); + if (textArray == NULL) + pm_error("out of memory"); + } + fixControlChars(buf, fontP, + (const PM_WCHAR **)&textArray[lineCount], + fixMode); + if (textArray[lineCount] == NULL) + pm_error("out of memory"); + ++lineCount; + } } - fixControlChars(buf, fontP, (const char **)&text_array[lineCount]); - if (text_array[lineCount] == NULL) - pm_error("out of memory"); - ++lineCount; } - input_text.textArray = text_array; - input_text.lineCount = lineCount; - input_text.allocatedLineCount = lineCount; + inputText.textArray = textArray; + inputText.lineCount = lineCount; + inputText.allocatedLineCount = lineCount; + free(buf); + } + *inputTextP = inputText; +} + + + +static void +computeMargins(struct CmdlineInfo const cmdline, + struct Text const inputText, + struct font2 * const fontP, + unsigned int * const vmarginP, + unsigned int * const hmarginP) { + + if (cmdline.nomargins) { + *vmarginP = 0; + *hmarginP = 0; + } else { + if (inputText.lineCount == 1) { + *vmarginP = fontP->maxheight / 2; + *hmarginP = fontP->maxwidth; + } else { + *vmarginP = fontP->maxheight; + *hmarginP = 2 * fontP->maxwidth; + } } - *input_textP = input_text; } static void -computeImageHeight(struct text const formattedText, - const struct font * const fontP, - int const interlineSpace, - unsigned int const vmargin, - unsigned int * const rowsP) { +formatText(struct CmdlineInfo const cmdline, + struct Text const inputText, + struct font2 * const fontP, + unsigned int const hmargin, + struct Text * const formattedTextP, + unsigned int * const maxleftb0P) { +/*---------------------------------------------------------------------------- + Flow or truncate lines to meet user's width request. +-----------------------------------------------------------------------------*/ + if (cmdline.width > 0) { + unsigned int const fontMargin = fontP->x < 0 ? -fontP->x : 0; + + if (cmdline.width > INT_MAX -10) + pm_error("-width value too large: %u", cmdline.width); + else if (cmdline.width < 2 * hmargin) + pm_error("-width value too small: %u", cmdline.width); + else if (inputText.lineCount == 1) { + flowText(inputText, cmdline.width - fontMargin, + fontP, cmdline.space, formattedTextP, maxleftb0P); + freeTextArray(inputText); + } else { + truncateText(inputText, cmdline.width - fontMargin, + fontP, cmdline.space, maxleftb0P); + *formattedTextP = inputText; + } + } else + *formattedTextP = inputText; +} + + + +static void +computeImageHeight(struct Text const formattedText, + const struct font2 * const fontP, + int const interlineSpace, + unsigned int const vmargin, + unsigned int * const rowsP) { if (interlineSpace < 0 && fontP->maxheight < -interlineSpace) pm_error("-lspace value (%d) negative and exceeds font height.", - interlineSpace); + interlineSpace); else { - double const rowsD = 2 * (double) vmargin + - (double) formattedText.lineCount * fontP->maxheight + + double const rowsD = 2 * (double) vmargin + + (double) formattedText.lineCount * fontP->maxheight + (double) (formattedText.lineCount-1) * interlineSpace; - + if (rowsD > INT_MAX-10) pm_error("Image height too large."); else @@ -756,128 +1102,286 @@ computeImageHeight(struct text const formattedText, static void -computeImageWidth(struct text const formattedText, - const struct font * const fontP, - float const intercharacterSpace, - unsigned int const hmargin, - unsigned int * const colsP, - int * const maxleftbP) { +computeImageWidth(struct Text const formattedText, + const struct font2 * const fontP, + float const intercharacterSpace, + unsigned int const hmargin, + unsigned int * const colsP, + unsigned int * const maxleftbP) { if (intercharacterSpace < 0 && fontP->maxwidth < -intercharacterSpace) - pm_error("-space value (%f) negative; exceeds font width.", - intercharacterSpace); + pm_error("negative -space value %.2f exceeds font width", + intercharacterSpace); else { /* Find the widest line, and the one that backs up the most past the nominal start of the line. */ - - unsigned int line; - double maxwidth; - int maxleftb; + + unsigned int lineNum; + double rightExtreme; + int leftExtreme; double colsD; - for (line = 0, maxwidth = 0.0, maxleftb = 0; - line < formattedText.lineCount; - ++line) { - - double bwid; - int backupSpaceNeeded; - - get_line_dimensions(formattedText.textArray[line], fontP, - intercharacterSpace, - &bwid, &backupSpaceNeeded); - - maxwidth = MAX(maxwidth, bwid); - maxleftb = MAX(maxleftb, backupSpaceNeeded); + rightExtreme = 0.0; /* initial value */ + leftExtreme = 0; /* initial value */ + + for (lineNum = 0; lineNum < formattedText.lineCount; ++lineNum) { + double rightEdge; + int leftEdge; + + getLineDimensions(formattedText.textArray[lineNum], fontP, + intercharacterSpace, + &rightEdge, &leftEdge); + rightExtreme = MAX(rightExtreme, rightEdge); + leftExtreme = MIN(leftExtreme, leftEdge); } - colsD = 2 * (double) hmargin + (double) maxwidth; - + leftExtreme = MIN(leftExtreme, 0); + + colsD = (double) (-leftExtreme) + rightExtreme + 2 * hmargin; + if (colsD > INT_MAX-10) pm_error("Image width too large."); else *colsP = (unsigned int) colsD; - - *maxleftbP = maxleftb; + + *maxleftbP = (unsigned int) - leftExtreme; } } -int -main(int argc, const char *argv[]) { +static void +renderText(unsigned int const cols, + unsigned int const rows, + struct font2 * const fontP, + unsigned int const hmargin, + unsigned int const vmargin, + struct Text const formattedText, + unsigned int const maxleftb, + float const space, + int const lspace, + bool const fixedAdvance, + FILE * const ofP) { + + bit ** const bits = pbm_allocarray(pbm_packed_bytes(cols), rows); - struct cmdlineInfo cmdline; - bit ** bits; - unsigned int rows, cols; - struct font * fontP; - unsigned int vmargin, hmargin; - struct text inputText; - struct text formattedText; - int maxleftb; + /* Fill background with white */ + clearBackground(bits, cols, rows); - pm_proginit(&argc, argv); + /* Put the text in */ + insertCharacters(bits, formattedText, fontP, vmargin, hmargin + maxleftb, + space, cols, rows, lspace, fixedAdvance); - parseCommandLine(argc, argv, &cmdline); - - computeFont(cmdline, &fontP); + /* Free all font data */ + pbm_destroybdffont2(fontP); - getText(cmdline.text, fontP, &inputText); - - if (cmdline.nomargins) { - vmargin = 0; - hmargin = 0; + { + unsigned int row; + + pbm_writepbminit(ofP, cols, rows, 0); + + for (row = 0; row < rows; ++row) + pbm_writepbmrow_packed(ofP, bits[row], cols, 0); + } + + pbm_freearray(bits, rows); +} + + + +static PM_WCHAR const * sheetTextArray[] = { +L"M \",/^_[`jpqy| M", +L" ", +L"/ !\"#$%&'()*+ /", +L"< ,-./01234567 <", +L"> 89:;<=>?@ABC >", +L"@ DEFGHIJKLMNO @", +L"_ PQRSTUVWXYZ[ _", +L"{ \\]^_`abcdefg {", +L"} hijklmnopqrs }", +L"~ tuvwxyz{|}~ ~", +L" ", +L"M \",/^_[`jpqy| M" }; + + + +static void +validateText(const PM_WCHAR ** const textArray, + struct font2 * const fontP) { +/*---------------------------------------------------------------------------- + Abort the program if there are characters in 'textArray' which cannot be + rendered in font *fontP. +-----------------------------------------------------------------------------*/ + const PM_WCHAR * output; + unsigned int textRow; + + for (textRow = 0; textRow < 12; ++textRow) + fixControlChars(textArray[textRow], fontP, &output, QUIT); + + free((PM_WCHAR *)output); +} + + + +static void +renderSheet(struct font2 * const fontP, + FILE * const ofP) { + + int const cols = fontP->maxwidth * 16; + int const rows = fontP->maxheight * 12; + struct Text const sheetText = + { (PM_WCHAR ** const) sheetTextArray, 12, 12}; + + validateText(sheetTextArray, fontP); + + renderText(cols, rows, fontP, 0, 0, sheetText, MAX(-(fontP->x),0), + 0.0, 0, TRUE, ofP); +} + + + +static void +dryrunOutput(unsigned int const cols, + unsigned int const rows, + FILE * const ofP) { + + fprintf(ofP, "%u %u\n", cols, rows); +} + + + +static void +textDumpOutput(struct Text const lp, + FILE * const ofP) { +/*---------------------------------------------------------------------------- + Output the text 'lp' as characters. (Do not render.) + + Note that the output stream is wide-oriented; it cannot be mixed with + narrow-oriented output. The libnetpbm library functions are + narrow-oriented. Thus, when this output is specified, it must not be mixed + with any output from the library; it should be the sole output. +-----------------------------------------------------------------------------*/ + int rc; + + rc = fwide(ofP, 1); + if (rc != 1) { + /* This occurs when narrow-oriented output to ofP happens before we + get here. + */ + pm_error("Failed to set output stream to wide " + "(fwide() returned %d. Maybe the output file " + "was written in narrow mode before this program was invoked?", + rc); } else { - if (inputText.lineCount == 1) { - vmargin = fontP->maxheight / 2; - hmargin = fontP->maxwidth; - } else { - vmargin = fontP->maxheight; - hmargin = 2 * fontP->maxwidth; + unsigned int line; /* Line number in input text */ + + for (line = 0; line < lp.lineCount; ++line) { + fputws(lp.textArray[line], ofP); + fputwc(L'\n', ofP); } } - - if (cmdline.width > 0) { - if (cmdline.width > INT_MAX -10) - pm_error("-width value too large: %u", cmdline.width); - - /* 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; - +} + + + +static void +pbmtext(struct CmdlineInfo const cmdline, + struct font2 * const fontP, + FILE * const ofP) { + + unsigned int rows, cols; + /* Dimensions in pixels of the output image */ + unsigned int cols0; + unsigned int vmargin, hmargin; + /* Margins in pixels we add to the output image */ + unsigned int hmargin0; + struct Text inputText; + struct Text formattedText; + unsigned int maxleftb, maxleftb0; + + getText(cmdline.text, fontP, &inputText, + cmdline.verbose ? WARN : SILENT); + + computeMargins(cmdline, inputText, fontP, &vmargin, &hmargin0); + + formatText(cmdline, inputText, fontP, hmargin0, + &formattedText, &maxleftb0); + if (formattedText.lineCount == 0) - pm_error("No input text."); - - computeImageHeight(formattedText, fontP, cmdline.lspace, vmargin, - &rows); + pm_error("No input text"); + + computeImageHeight(formattedText, fontP, cmdline.lspace, vmargin, &rows); - computeImageWidth(formattedText, fontP, cmdline.space, hmargin, - &cols, &maxleftb); + computeImageWidth(formattedText, fontP, cmdline.space, + cmdline.width > 0 ? 0 : hmargin0, &cols0, &maxleftb); - if (cols == 0 || rows == 0) + if (cols0 == 0 || rows == 0) pm_error("Input is all whitespace and/or non-renderable characters."); - bits = pbm_allocarray(cols, rows); + if (cmdline.width == 0) { + cols = cols0; + hmargin = hmargin0; + } else { + if (cmdline.width < cols0) + pm_error("internal error: calculated image width (%u) exceeds " + "specified -width value: %u", + cols0, cmdline.width); + else if (maxleftb0 != maxleftb) + pm_error("internal error: contradicting backup values"); + else { + hmargin = MIN(hmargin0, (cmdline.width - cols0) / 2); + cols = cmdline.width; + } + } - /* Fill background with white */ - fill_rect(bits, 0, 0, rows, cols, PBM_WHITE); + if (cmdline.dryrun) + dryrunOutput(cols, rows, ofP); + else if (cmdline.textdump) + textDumpOutput(formattedText, ofP); + else + renderText(cols, rows, fontP, hmargin, vmargin, formattedText, + maxleftb, cmdline.space, cmdline.lspace, FALSE, ofP); - /* Put the text in */ - insert_characters(bits, formattedText, fontP, vmargin, hmargin + maxleftb, - cmdline.space, cmdline.lspace); + freeTextArray(formattedText); +} - pbm_writepbm(stdout, bits, cols, rows, 0); - pbm_freearray(bits, rows); - freeTextArray(formattedText); +int +main(int argc, const char *argv[]) { + + struct CmdlineInfo cmdline; + struct font2 * fontP; + + pm_proginit(&argc, argv); + + parseCommandLine(argc, argv, &cmdline); + + if (cmdline.wchar) { + char * newLocale; + newLocale = setlocale(LC_ALL, ""); + if (!newLocale) + pm_error("Failed to set locale (LC_ALL) from environment"); + + /* Orient standard input stream to wide */ + fwide(stdin, 1); + } else + fwide(stdin, -1); + + if (cmdline.verbose) + pm_message("LC_CTYPE is set to '%s'", setlocale(LC_CTYPE, NULL) ); + + computeFont(cmdline, &fontP); + + if (cmdline.dumpsheet) + renderSheet(fontP, stdout); + else + pbmtext(cmdline, fontP, stdout); + pm_close(stdout); return 0; } + + + diff --git a/generator/pbmtextps.c b/generator/pbmtextps.c index e6367530..f543618d 100644 --- a/generator/pbmtextps.c +++ b/generator/pbmtextps.c @@ -1,4 +1,4 @@ -/* + /* * pbmtextps.c - render text into a bitmap using a postscript interpreter * * Copyright (C) 2002 by James McCann. @@ -14,68 +14,33 @@ * * Additions by Bryan Henderson contributed to public domain by author. * + * PostScript(R) Language Reference, Third Edition (a.k.a. "Red Book") + * http://www.adobe.com/products/postscript/pdfs/PLRM.pdf + * ISBN 0-201-37922-8 + * + * Postscript Font Naming Issues: + * https://partners.adobe.com/public/developer/en/font/5088.FontNames.pdf + * + * Other resources: + * http://partners.adobe.com/public/developer/ps/index_specs.html */ -#define _XOPEN_SOURCE /* Make sure popen() is in stdio.h */ -#define _BSD_SOURCE /* Make sure stdrup() is in string.h */ + +#define _XOPEN_SOURCE 500 + /* Make sure popen() is in stdio.h, strdup() is in string.h */ #include <unistd.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <errno.h> +#include <assert.h> #include "pm_c_util.h" #include "mallocvar.h" #include "nstring.h" #include "shhopt.h" +#include "pm_system.h" #include "pbm.h" - -#define BUFFER_SIZE 2048 - -struct cmdlineInfo { - /* All the information the user supplied in the command line, - in a form easy for the program to use. - */ - unsigned int res; /* resolution, DPI */ - unsigned 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 -writeFileToStdout(const char * const fileName){ - /* simple pbmtopbm */ - - FILE * ifP; - int format; - int cols, rows, row ; - unsigned char * bitrow; - - ifP = pm_openr(fileName); - pbm_readpbminit(ifP, &cols, &rows, &format); - - if (cols==0 || rows==0 || cols>INT_MAX-10 || rows>INT_MAX-10) - pm_error("Abnormal output from gs program. " - "width x height = %u x %u", cols, rows); - - pbm_writepbminit(stdout, cols, rows, 0); - - bitrow = pbm_allocrow_packed(cols); - - for (row = 0; row < rows; ++row) { - pbm_readpbmrow_packed(ifP, bitrow, cols, format); - pbm_writepbmrow_packed(stdout, bitrow, cols, 0); - } - pbm_freerow_packed(bitrow); -} - - - static void validateFontName(const char * const name) { /*----------------------------------------------------------------------------- @@ -88,11 +53,11 @@ validateFontName(const char * const name) { unsigned int idx; for (idx = 0; name[idx] != '\0'; ++idx) { - char const c = name[idx]; + char const c = name[idx]; if (c < 32 || c > 125) pm_error("Invalid character in font name"); - else + else switch (c) { case '[': case ']': case '(': case ')': case '{': case '}': case '/': case '\\': @@ -119,7 +84,7 @@ asciiHexEncode(char * const inbuff, unsigned int idx; for (idx = 0; inbuff[idx] != '\0'; ++idx) { - unsigned int const item = (unsigned char) inbuff[idx]; + unsigned int const item = (unsigned char) inbuff[idx]; outbuff[idx*2] = hexits[item >> 4]; outbuff[idx*2+1] = hexits[item & 0xF]; @@ -132,7 +97,7 @@ asciiHexEncode(char * const inbuff, static void buildTextFromArgs(int const argc, - char ** const argv, + const char ** const argv, const char ** const asciiHexTextP) { /*---------------------------------------------------------------------------- Build the array of text to be included in the Postscript program to @@ -161,7 +126,7 @@ buildTextFromArgs(int const argc, if (text == NULL) pm_error("out of memory"); strcat(text, " "); - } + } totalTextSize += strlen(argv[i]); text = realloc(text, totalTextSize); if (text == NULL) @@ -169,7 +134,7 @@ buildTextFromArgs(int const argc, strcat(text, argv[i]); } - { + { char * asciiHexText; MALLOCARRAY(asciiHexText, totalTextSize * 2); @@ -186,9 +151,31 @@ buildTextFromArgs(int const argc, +struct CmdlineInfo { + /* All the information the user supplied in the command line, + in a form easy for the program to use. + */ + unsigned int res; + float fontsize; + const char * font; + float stroke; + float ascent; + float descent; + float leftmargin; + float rightmargin; + float topmargin; + float bottommargin; + unsigned int pad; + unsigned int verbose; + unsigned int dump; + const char * text; +}; + + + static void -parseCommandLine(int argc, char ** argv, - struct cmdlineInfo *cmdlineP) { +parseCommandLine(int argc, const char ** argv, + struct CmdlineInfo * const cmdlineP) { /*--------------------------------------------------------------------------- Note that the file spec array we return is stored in the storage that was passed to us as the argv array. @@ -199,37 +186,104 @@ parseCommandLine(int argc, char ** argv, optStruct3 opt; unsigned int option_def_index; + unsigned int cropSpec, ascentSpec, descentSpec; + unsigned int leftmarginSpec, rightmarginSpec; + unsigned int topmarginSpec, bottommarginSpec; MALLOCARRAY(option_def, 100); option_def_index = 0; /* incremented by OPTENTRY */ - OPTENT3(0, "resolution", OPT_UINT, &cmdlineP->res, NULL, 0); - OPTENT3(0, "font", OPT_STRING, &cmdlineP->font, NULL, 0); - OPTENT3(0, "fontsize", OPT_UINT, &cmdlineP->fontsize, NULL, 0); - OPTENT3(0, "stroke", OPT_FLOAT, &cmdlineP->stroke, NULL, 0); - OPTENT3(0, "verbose", OPT_FLAG, NULL, &cmdlineP->verbose, 0); + OPTENT3(0, "resolution", OPT_UINT, + &cmdlineP->res, NULL, 0); + OPTENT3(0, "font", OPT_STRING, + &cmdlineP->font, NULL, 0); + OPTENT3(0, "fontsize", OPT_FLOAT, + &cmdlineP->fontsize, NULL, 0); + OPTENT3(0, "stroke", OPT_FLOAT, + &cmdlineP->stroke, NULL, 0); + OPTENT3(0, "ascent", OPT_FLOAT, + &cmdlineP->ascent, &ascentSpec, 0); + OPTENT3(0, "descent", OPT_FLOAT, + &cmdlineP->descent, &descentSpec, 0); + OPTENT3(0, "leftmargin", OPT_FLOAT, + &cmdlineP->leftmargin, &leftmarginSpec, 0); + OPTENT3(0, "rightmargin", OPT_FLOAT, + &cmdlineP->rightmargin, &rightmarginSpec, 0); + OPTENT3(0, "topmargin", OPT_FLOAT, + &cmdlineP->topmargin, &topmarginSpec, 0); + OPTENT3(0, "bottommargin", OPT_FLOAT, + &cmdlineP->bottommargin, &bottommarginSpec, 0); + OPTENT3(0, "crop", OPT_FLAG, + NULL, &cropSpec, 0); + OPTENT3(0, "pad", OPT_FLAG, + NULL, &cmdlineP->pad, 0); + OPTENT3(0, "verbose", OPT_FLAG, + NULL, &cmdlineP->verbose, 0); + OPTENT3(0, "dump-ps", OPT_FLAG, + NULL, &cmdlineP->dump, 0); /* Set the defaults */ cmdlineP->res = 150; cmdlineP->fontsize = 24; cmdlineP->font = "Times-Roman"; - cmdlineP->stroke = -1; + cmdlineP->stroke = -1; + cmdlineP->ascent = 0; + cmdlineP->descent = 0; + cmdlineP->rightmargin = 0; + cmdlineP->leftmargin = 0; + cmdlineP->topmargin = 0; + cmdlineP->bottommargin = 0; + cropSpec = FALSE; + cmdlineP->pad = FALSE; opt.opt_table = option_def; opt.short_allowed = FALSE; /* We have no short (old-fashioned) options */ opt.allowNegNum = FALSE; - pm_optParseOptions3(&argc, argv, opt, sizeof(opt), 0); + pm_optParseOptions3(&argc, (char **)argv, opt, sizeof(opt), 0); validateFontName(cmdlineP->font); + if (cmdlineP->fontsize <= 0) + pm_error("-fontsize must be positive"); + if (cmdlineP->ascent < 0) + pm_error("-ascent must not be negative"); + if (cmdlineP->descent < 0) + pm_error("-descent must not be negative"); + if (cmdlineP->leftmargin <0) + pm_error("-leftmargin must not be negative"); + if (cmdlineP->rightmargin <0) + pm_error("-rightmargin must not be negative"); + if (cmdlineP->topmargin <0) + pm_error("-topmargin must not be negative"); + if (cmdlineP->bottommargin <0) + pm_error("-bottommargin must not be negative"); + + if (cropSpec == TRUE) { + if (ascentSpec || descentSpec || + leftmarginSpec || rightmarginSpec || + topmarginSpec || bottommarginSpec || + cmdlineP->pad) + pm_error("-crop cannot be specified with -ascent, -descent, " + "-leftmargin, -rightmargin, " + "-topmargin, -bottommargin or -pad"); + } else { + if (!descentSpec && !bottommarginSpec && !cmdlineP->pad) + cmdlineP->descent = cmdlineP->fontsize * 1.5; + + if (!leftmarginSpec) + cmdlineP->leftmargin = cmdlineP->fontsize / 2; + } + buildTextFromArgs(argc, argv, &cmdlineP->text); + + free(option_def); } static void -termCmdline(struct cmdlineInfo const cmdline) { +termCmdline(struct CmdlineInfo const cmdline) { pm_strfree(cmdline.text); } @@ -237,305 +291,431 @@ termCmdline(struct cmdlineInfo const cmdline) { static const char * -construct_postscript(struct cmdlineInfo const cmdline) { +postscriptProgram(struct CmdlineInfo const cmdline) { +/*----------------------------------------------------------------------------- + In Postscript, the bottom of the page is row zero. Postscript allows + negative values but negative regions are clipped from the output image. + We make adjustments to ensure that nothing is lost. + + Postscript also allow fonts to have negative values in the bounding box + coordinates. The bottom edge of "L" is row zero: this row is called the + "baseline". The feet of "g" "p" "y" extend into negative region. In a + similar manner the left edge of the bounding box may be negative. We add + margins on the left and the bottom with "xorigin" and "yorigin" to + provide for such characters. + + The sequence "textstring false charpath flattenpath pathbbox" determines + the bounding box of the entire text when rendered. +-----------------------------------------------------------------------------*/ + + /* C89 limits the size of a string constant, so we have to build the + Postscript command in pieces. + + psVariable, psTemplate: Set variables. + psFixed1: Scale font. Calculate pad metrics. + psFixed2: Determine width, height, xorigin, yorigin. + psFixed3: Render. + psFixed4: Verbose mode: Report font name, metrics. + + We could add code to psFixed2 for handling right-to-left writing + (Hebrew, Arabic) and vertical writing (Chinese, Korean, Japanese). + */ + + const char * const psTemplate = + "/FindFont {/%s findfont} def\n" + "/fontsize %f def\n" + "/pensize %f def\n" + "/textstring <%s> def\n" + "/ascent %f def\n" + "/descent %f def\n" + "/leftmargin %f def\n" + "/rightmargin %f def\n" + "/topmargin %f def\n" + "/bottommargin %f def\n" + "/pad %s def\n" + "/verbose %s def\n"; + + const char * const psFixed1 = + "FindFont fontsize scalefont\n" + "pad { dup dup\n" + " /FontMatrix get 3 get /yscale exch def\n" + " /FontBBox get dup\n" + " 1 get yscale mul neg /padbottom exch def\n" + " 3 get yscale mul /padtop exch def}\n" + " {/padbottom 0 def /padtop 0 def}\n" + " ifelse\n" + "setfont\n"; + + const char * const psFixed2 = + "0 0 moveto\n" + "textstring false charpath flattenpath pathbbox\n" + "/BBtop exch def\n" + "/BBright exch def\n" + "/BBbottom exch neg def\n" + "/BBleft exch neg def\n" + "/max { 2 copy lt { exch } if pop } bind def\n" + "/yorigin descent padbottom max BBbottom max bottommargin add def\n" + "/xorigin leftmargin BBleft max def\n" + "/width xorigin BBright add rightmargin add def\n" + "/height ascent BBtop max padtop max topmargin add yorigin add def\n"; + + const char * const psFixed3 = + "<</PageSize [width height]>> setpagedevice\n" + "xorigin yorigin moveto\n" + "pensize 0 lt\n" + " {textstring show}\n" + " {pensize setlinewidth 0 setgray\n" + " textstring true charpath stroke}\n" + " ifelse\n" + "showpage\n"; + + const char * const psFixed4 = + "verbose\n" + " {xorigin yorigin moveto\n" + " [(width height) width height] ==\n" + " [(ascent descent) height yorigin sub yorigin] ==\n" + " [(bounding box) \n" + " textstring false charpath flattenpath pathbbox] ==\n" + " [(Fontname) FindFont dup /FontName\n" + " known\n" + " {/FontName get}\n" + " {pop (anonymous)}\n" + " ifelse] ==}\n" + " if"; const char * retval; - const char * template; - - if (cmdline.stroke < 0) - template = - "/%s findfont\n" - "%d scalefont\n" - "setfont\n" - "12 36 moveto\n" - "<%s> show\n" - "showpage\n"; - else - template = - "/%s findfont\n" - "%d scalefont\n" - "setfont\n" - "12 36 moveto\n" - "%f setlinewidth\n" - "0 setgray\n" - "<%s> true charpath\n" - "stroke\n" - "showpage\n"; - - if (cmdline.stroke < 0) - pm_asprintf(&retval, template, cmdline.font, cmdline.fontsize, - cmdline.text); - else - pm_asprintf(&retval, template, cmdline.font, cmdline.fontsize, - cmdline.stroke, cmdline.text); + const char * psVariable; + + pm_asprintf(&psVariable, psTemplate, cmdline.font, + cmdline.fontsize, cmdline.stroke, cmdline.text, + cmdline.ascent, cmdline.descent, + cmdline.leftmargin, cmdline.rightmargin, + cmdline.topmargin, cmdline.bottommargin, + cmdline.pad ? "true" : "false", + cmdline.verbose ? "true" : "false" ); + + pm_asprintf(&retval, "%s%s%s%s%s", psVariable, + psFixed1, psFixed2, psFixed3, psFixed4); + + pm_strfree(psVariable); return retval; } -static const char * -gsExecutableName() { +static const char ** +gsArgList(const char * const outputFilename, + struct CmdlineInfo const cmdline) { + + unsigned int const maxArgCt = 50; + + const char ** retval; + unsigned int argCt; /* Number of arguments in 'retval' so far */ + + if (cmdline.res <= 0) + pm_error("Resolution (dpi) must be positive."); - const char * const which = "which gs"; + if (cmdline.fontsize <= 0) + pm_error("Font size must be positive."); - static char buffer[BUFFER_SIZE]; + MALLOCARRAY_NOFAIL(retval, maxArgCt+2); - FILE * f; + argCt = 0; /* initial value */ - memset(buffer, 0, BUFFER_SIZE); + pm_asprintf(&retval[argCt++], "ghostscript"); + pm_asprintf(&retval[argCt++], "-r%d", cmdline.res); + pm_asprintf(&retval[argCt++], "-sDEVICE=pbmraw"); + pm_asprintf(&retval[argCt++], "-sOutputFile=%s", outputFilename); + pm_asprintf(&retval[argCt++], "-q"); + pm_asprintf(&retval[argCt++], "-dBATCH"); + pm_asprintf(&retval[argCt++], "-dSAFER"); + pm_asprintf(&retval[argCt++], "-dNOPAUSE"); + pm_asprintf(&retval[argCt++], "-"); - f = popen(which, "r"); - if (!f) - pm_error("Can't find ghostscript"); + retval[argCt++] = NULL; - 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"); + assert(argCt < maxArgCt); - return buffer; + return retval; } -static const char * -cropExecutableName(void) { +static void +reportGhostScript(const char * const executableNm, + const char ** const argList) { - const char * const which = "which pnmcrop"; + unsigned int i; - static char buffer[BUFFER_SIZE]; - const char * retval; + pm_message("Running Ghostscript interpreter '%s'", executableNm); - FILE * f; - - memset(buffer, 0, BUFFER_SIZE); - - f = popen(which, "r"); - if (!f) - retval = NULL; - else { - fread(buffer, 1, BUFFER_SIZE, f); - if (buffer[strlen(buffer) - 1] == '\n') - buffer[strlen(buffer) - 1] = 0; - pclose(f); - - if (buffer[0] != '/' && buffer[0] != '.') { - retval = NULL; - pm_message("Can't find pnmcrop"); - } else - retval = buffer; - } - return retval; + pm_message("Program arguments:"); + + for (i = 0; argList[i]; ++i) + pm_message(" '%s'", argList[i]); } -static const char * -gsCommand(const char * const psFname, - const char * const outputFilename, - struct cmdlineInfo const cmdline) { +static void +freeArgList(const char ** const argList) { - const char * retval; - double const x = (double) cmdline.res * 11; - double const y = (double) cmdline.res * - ((double) cmdline.fontsize * 2 + 72) / 72; - - if (cmdline.res <= 0) - pm_error("Resolution (dpi) must be positive."); - - if (cmdline.fontsize <= 0) - pm_error("Font size must be positive."); - - /* The following checks are for guarding against overflows in this - function. Huge x,y values that pass these checks may be - rejected by the 'gs' program. - */ - - if (x > (double) INT_MAX-10) - pm_error("Absurdly fine resolution: %u. Output width too large.", - cmdline.res ); - if (y > (double) INT_MAX-10) - pm_error("Absurdly fine resolution (%u) and/or huge font size (%u). " - "Output height too large.", cmdline.res, cmdline.fontsize); - - pm_asprintf(&retval, "%s -g%dx%d -r%d -sDEVICE=pbm " - "-sOutputFile=%s -q -dBATCH -dNOPAUSE %s " - "</dev/null >/dev/null", - gsExecutableName(), (int) x, (int) y, cmdline.res, - outputFilename, psFname); + unsigned int i; - return retval; + for (i = 0; argList[i]; ++i) + pm_strfree(argList[i]); + + free(argList); } -static const char * -cropCommand(const char * const inputFileName) { +static void +reportFontName(const char * const fontname) { - const char * retval; - const char * plainOpt = pm_plain_output ? "-plain" : "" ; - - if (cropExecutableName()) { - pm_asprintf(&retval, "%s -top -right %s %s", - cropExecutableName(), plainOpt, inputFileName); - if (retval == pm_strsol) - pm_error("Unable to allocate memory"); - } else - retval = NULL; + pm_message("Font: '%s'", fontname); - return retval; } static void -writeProgram(const char * const psFname, - struct cmdlineInfo const cmdline) { +reportMetrics(float const width, + float const height, + float const ascent, + float const descent, + float const BBoxleft, + float const BBoxbottom, + float const BBoxright, + float const BBoxtop) { + + pm_message("-- Metrics in points. Bottom left is (0,0) --"); + pm_message("Width: %f", width); + pm_message("Height: %f", height); + pm_message("Ascent: %f", ascent); + pm_message("Descent: %f", descent); + pm_message("BoundingBox_Left: %f", BBoxleft); + pm_message("BoundingBox_Right: %f", BBoxright); + pm_message("BoundingBox_Top: %f", BBoxtop); + pm_message("BoundingBox_Bottom: %f", BBoxbottom); - 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"); +static void +acceptGSoutput(int const pipetosuckFd, + void * const nullParams ) { +/*----------------------------------------------------------------------------- + Accept text written to stdout by the PostScript program. + + There are two kinds of output: + (1) Metrics and fontname reported, when verbose is on. + (2) Error messages from ghostscript. + + We read one line at a time. + + We cannot predict how long one line can be in case (2). In practice + the "execute stack" report gets long. We provide by setting lineBuffSize + to a large number. +-----------------------------------------------------------------------------*/ + unsigned int const lineBuffSize = 1024*32; + FILE * const inFileP = fdopen(pipetosuckFd, "r"); + + float width, height, ascent, descent; + float BBoxleft, BBoxbottom, BBoxright, BBoxtop; + char * lineBuff; /* malloc'd */ + char fontname [2048]; + bool fontnameReported, widthHeightReported; + bool ascentDescentReported, BBoxReported; + + assert(nullParams == NULL); + + fontnameReported = FALSE; /* Initial value */ + widthHeightReported = FALSE; /* Initial value */ + ascentDescentReported = FALSE; /* Initial value */ + BBoxReported = FALSE; /* Initial value */ - fclose(psfile); + MALLOCARRAY_NOFAIL(lineBuff, lineBuffSize); - pm_strfree(ps); + while (fgets(lineBuff, lineBuffSize, inFileP) != NULL) { + unsigned int rWidthHeight, rAscentDescent, rBBox, rFontname; + + rWidthHeight = sscanf(lineBuff, "[(width height) %f %f]", + &width, &height); + + rAscentDescent = sscanf(lineBuff, "[(ascent descent) %f %f]", + &ascent, &descent); + + rBBox = sscanf(lineBuff, "[(bounding box) %f %f %f %f]", + &BBoxleft, &BBoxbottom, &BBoxright, &BBoxtop); + + rFontname = sscanf(lineBuff, "[(Fontname) /%2047s", fontname); + + if (rFontname == 1) + fontnameReported = TRUE; + else if (rWidthHeight == 2) + widthHeightReported = TRUE; + else if (rAscentDescent == 2) + ascentDescentReported = TRUE; + else if (rBBox == 4) + BBoxReported = TRUE; + else + pm_message("[gs] %s", lineBuff); + } + + if (fontnameReported) { + fontname[strlen(fontname)-1] = 0; + reportFontName(fontname); + + if (widthHeightReported && ascentDescentReported && BBoxReported) + reportMetrics(width, height, ascent, descent, + BBoxleft, BBoxbottom, BBoxright, BBoxtop); + } + fclose(inFileP); + pm_strfree(lineBuff); } static void -executeProgram(const char * const psFname, +executeProgram(const char * const psProgram, const char * const outputFname, - struct cmdlineInfo const cmdline) { + struct CmdlineInfo const cmdline) { - const char * com; - int rc; + const char * const executableNm = "gs"; + const char ** const argList = gsArgList(outputFname, cmdline); + + struct bufferDesc feedBuffer; + int termStatus; + unsigned int bytesFed; + + bytesFed = 0; /* Initial value */ + + feedBuffer.buffer = (unsigned char *) psProgram; + feedBuffer.size = strlen(psProgram); + feedBuffer.bytesTransferredP = &bytesFed; - 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); + reportGhostScript(executableNm, argList); + + pm_system2_vp(executableNm, + argList, + &pm_feed_from_memory, &feedBuffer, + cmdline.verbose ? &acceptGSoutput : &pm_accept_null, NULL, + &termStatus); + + if (termStatus != 0) { + const char * const msg = pm_termStatusDesc(termStatus); - rc = system(com); - if (rc != 0) - pm_error("Failed to run Ghostscript process. rc=%d", rc); + pm_error("Failed to run Ghostscript process. %s", msg); - pm_strfree(com); + pm_strfree(msg); + } + freeArgList(argList); } static void -cropToStdout(const char * const inputFileName, - bool const verbose) { +writePbm(const char * const fileName, + FILE * const ofP) { +/*---------------------------------------------------------------------------- + Write the PBM image that is in the file named 'fileName" to file *ofP. + I.e. pbmtopbm. + + It's not a byte-for-byte copy because PBM allows the same image to be + represented many ways (all of which we can accept as our input), but we use + libnetpbm to write our output in its specific way. +----------------------------------------------------------------------------*/ + FILE * ifP; + int format; + int cols, rows, row ; + unsigned char * bitrow; - const char * com; + ifP = pm_openr(fileName); + pbm_readpbminit(ifP, &cols, &rows, &format); - com = cropCommand(inputFileName); + if (cols == 0 || rows == 0 || cols > INT_MAX - 10 || rows > INT_MAX - 10) + pm_error("Abnormal output from gs program. " + "width x height = %u x %u", cols, rows); - if (com == NULL) { - /* No pnmcrop. So don't crop. */ - pm_message("Can't find pnmcrop command, image will be large"); - writeFileToStdout(inputFileName); - } 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); - } - pm_strfree(com); + pbm_writepbminit(ofP, cols, rows, 0); + + bitrow = pbm_allocrow_packed(cols); + + for (row = 0; row < rows; ++row) { + pbm_readpbmrow_packed(ifP, bitrow, cols, format); + pbm_writepbmrow_packed(ofP, bitrow, cols, 0); } + pbm_freerow_packed(bitrow); + pm_close(ifP); } static void -createOutputFile(struct cmdlineInfo const cmdline) { +generatePbm(struct CmdlineInfo const cmdline, + FILE * const ofP) { + + const char * const psProgram = postscriptProgram(cmdline); + + const char * tempPbmFname; + FILE * pbmFileP; + + pm_make_tmpfile(&pbmFileP, &tempPbmFname); + assert(pbmFileP != NULL && tempPbmFname != NULL); + fclose(pbmFileP); + + executeProgram(psProgram, tempPbmFname, cmdline); + + /* Although Ghostscript created a legal PBM file, it uses a different + implementation of the format from libnetpbm's canonical output format, + so instead of copying the content of 'tempPbmFname' to *ofP byte for + byte, we copy it as a PBM image. + */ + writePbm(tempPbmFname, ofP); + + unlink(tempPbmFname); + pm_strfree(tempPbmFname); + pm_strfree(psProgram); +} - const char * const template = "./pstextpbm.%d.tmp.%s"; - - const char * psFname; - const char * uncroppedPbmFname; - pm_asprintf(&psFname, template, getpid(), "ps"); - if (psFname == NULL) - pm_error("Unable to allocate memory"); - - writeProgram(psFname, cmdline); - pm_asprintf(&uncroppedPbmFname, template, getpid(), "pbm"); - if (uncroppedPbmFname == NULL) - pm_error("Unable to allocate memory"); - - executeProgram(psFname, uncroppedPbmFname, cmdline); +static void +dumpPsProgram(struct CmdlineInfo const cmdline) { + + const char * psProgram; - unlink(psFname); - pm_strfree(psFname); + psProgram = postscriptProgram(cmdline); - cropToStdout(uncroppedPbmFname, cmdline.verbose); + puts(psProgram); - unlink(uncroppedPbmFname); - pm_strfree(uncroppedPbmFname); + pm_strfree(psProgram); } -int -main(int argc, char *argv[]) { +int +main(int argc, const char *argv[]) { - struct cmdlineInfo cmdline; + struct CmdlineInfo cmdline; - pbm_init(&argc, argv); + pm_proginit(&argc, argv); parseCommandLine(argc, argv, &cmdline); - createOutputFile(cmdline); + if (cmdline.dump) + dumpPsProgram(cmdline); + else + generatePbm(cmdline, stdout); termCmdline(cmdline); return 0; } + + + diff --git a/generator/pgmcrater b/generator/pgmcrater index 1c22ed70..c66f5576 100755 --- a/generator/pgmcrater +++ b/generator/pgmcrater @@ -37,6 +37,26 @@ use strict; use Getopt::Long; +sub doVersionHack($) { + my ($argvR) = @_; + + my $arg1 = $argvR->[0]; + + if (defined($arg1) && (($arg1 eq "--version") || ($arg1 eq "-version"))) { + my $termStatus = system('pamcrater', '--version'); + exit($termStatus == 0 ? 0 : 1); + } +} + + +############################################################################## +# +# MAINLINE +# +############################################################################## + +doVersionHack(\@ARGV); + my @pgmcraterArgv = @ARGV; my $validOptions = GetOptions( diff --git a/generator/pgmkernel.c b/generator/pgmkernel.c index ec634c16..37072c38 100644 --- a/generator/pgmkernel.c +++ b/generator/pgmkernel.c @@ -223,11 +223,13 @@ main(int argc, const char * argv[]) { unsigned int col; for (col = 0; col < (cmdline.cols +1) / 2; ++col) { + double const epsilon = 1e-15; double const dx2 = SQR(col - xcenter); double const normalized = t(dx2, dy2, cmdline.weight) / 2 / tMax; - gray const grayval = ROUNDU(cmdline.maxval * (0.5 + normalized)); + gray const grayval = + ROUNDU(cmdline.maxval * (0.5 + normalized + epsilon)); halfKernel[arow][col ] = grayval; halfKernel[arow][cmdline.cols - col - 1] = grayval; diff --git a/generator/pgmmake.c b/generator/pgmmake.c index f8f8b09c..ae706639 100644 --- a/generator/pgmmake.c +++ b/generator/pgmmake.c @@ -1,13 +1,18 @@ +#include <stdlib.h> +#include <string.h> + #include "pm_c_util.h" #include "mallocvar.h" #include "shhopt.h" #include "pgm.h" -struct cmdlineInfo { + + +struct CmdlineInfo { /* All the information the user supplied in the command line, in a form easy for the program to use. */ - gray grayLevel; + double grayLevel; unsigned int cols; unsigned int rows; gray maxval; @@ -15,9 +20,37 @@ struct cmdlineInfo { +static double +grayLevelFromArg(const char * const arg) { + + double retval; + + if (strlen(arg) < 1) + pm_error("Gray level argument is a null string"); + else { + char * endPtr; + + retval = strtod(arg, &endPtr); + + if (*endPtr != '\0') + pm_error("Gray level argument '%s' is not a floating point number", + arg); + + if (retval < 0.0) + pm_error("You can't have a negative gray level (%f)", retval); + if (retval > 1.0) + pm_error("Gray level must be in the range [0.0, 1.0]. " + "You specified %f", retval); + + } + return retval; +} + + + static void -parseCommandLine(int argc, char ** argv, - struct cmdlineInfo * const cmdlineP) { +parseCommandLine(int argc, const 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 @@ -34,7 +67,7 @@ parseCommandLine(int argc, char ** argv, unsigned int maxvalSpec; unsigned int option_def_index; - MALLOCARRAY(option_def, 100); + MALLOCARRAY_NOFAIL(option_def, 100); option_def_index = 0; /* incremented by OPTENTRY */ OPTENT3(0, "maxval", OPT_UINT, &cmdlineP->maxval, &maxvalSpec, 0); @@ -43,11 +76,9 @@ parseCommandLine(int argc, char ** argv, opt.short_allowed = false; /* We have no short (old-fashioned) options */ opt.allowNegNum = false; /* We have no parms that are negative numbers */ - pm_optParseOptions3(&argc, argv, opt, sizeof(opt), 0); + pm_optParseOptions3(&argc, (char **)argv, opt, sizeof(opt), 0); /* Uses and sets argc, argv, and some of *cmdlineP and others. */ - free (option_def); - if (!maxvalSpec) cmdlineP->maxval = PGM_MAXMAXVAL; else { @@ -65,37 +96,36 @@ parseCommandLine(int argc, char ** argv, 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->grayLevel = grayLevelFromArg(argv[1]); cmdlineP->cols = pm_parse_width(argv[2]); cmdlineP->rows = pm_parse_height(argv[3]); } + free(option_def); } int -main(int argc, char *argv[]) { +main(int argc, const char ** const argv) { - struct cmdlineInfo cmdline; + struct CmdlineInfo cmdline; gray * grayrow; unsigned int col, row; + gray grayLevel; - pgm_init(&argc, argv); + pm_proginit(&argc, argv); parseCommandLine(argc, argv, &cmdline); + grayLevel = pgm_unnormalize(cmdline.grayLevel, cmdline.maxval); + pgm_writepgminit(stdout, cmdline.cols, cmdline.rows, cmdline.maxval, 0); + grayrow = pgm_allocrow(cmdline.cols); /* All rows are identical. Fill once. */ for (col = 0; col < cmdline.cols; ++col) - grayrow[col] = cmdline.grayLevel; + grayrow[col] = grayLevel; for (row = 0; row < cmdline.rows; ++row) pgm_writepgmrow(stdout, grayrow, cmdline.cols, cmdline.maxval, 0); @@ -105,3 +135,5 @@ main(int argc, char *argv[]) { return 0; } + + diff --git a/generator/pgmnoise.c b/generator/pgmnoise.c index 442edc59..40d0e189 100644 --- a/generator/pgmnoise.c +++ b/generator/pgmnoise.c @@ -56,7 +56,7 @@ parseCommandLine(int argc, const char ** const argv, if (maxvalSpec) { if (cmdlineP->maxval > PGM_OVERALLMAXVAL) - pm_error("Maxval too large: %u. Maximu is %u", + pm_error("Maxval too large: %u. Maximu is %u", cmdlineP->maxval, PGM_OVERALLMAXVAL); else if (cmdlineP->maxval == 0) pm_error("Maxval must not be zero"); @@ -70,7 +70,7 @@ parseCommandLine(int argc, const char ** const argv, else { int const width = atoi(argv[1]); int const height = atoi(argv[2]); - + if (width <= 0) pm_error("Width must be positive, not %d", width); else @@ -90,12 +90,12 @@ randPool(unsigned int const digits) { /*---------------------------------------------------------------------------- Draw 'digits' bits from pool of random bits. If the number of random bits in pool is insufficient, call rand() and add 31 bits to it. - + 'digits' must be at most 16. We assume that each call to rand() generates 31 bits, or RAND_MAX == 2147483647. - + The underlying logic is flexible and endian-free. The above conditions can be relaxed. -----------------------------------------------------------------------------*/ @@ -114,7 +114,7 @@ randPool(unsigned int const digits) { hold >>= digits; len -= digits; } else { /* Load another 31 bits into hold */ - hold = rand(); + hold = rand(); retval |= (hold << len); hold >>= (digits - len); len = 31 - digits + len; @@ -164,11 +164,11 @@ pgmnoise(FILE * const ofP, unsigned int col; for (col = 0; col < cols; ++col) destrow[col] = randPool(bitLen); - } - else { + } + else { unsigned int col; for (col = 0; col < cols; ++col) - destrow[col] = rand() % (maxval + 1); + destrow[col] = rand() % (maxval + 1); } pgm_writepgmrow(ofP, destrow, cols, maxval, 0); } @@ -181,7 +181,7 @@ pgmnoise(FILE * const ofP, int main(int argc, const char * argv[]) { - + struct cmdlineInfo cmdline; pm_proginit(&argc, argv); @@ -194,3 +194,6 @@ main(int argc, return 0; } + + + diff --git a/generator/pgmramp.c b/generator/pgmramp.c index 225542fe..db32b9f0 100644 --- a/generator/pgmramp.c +++ b/generator/pgmramp.c @@ -35,7 +35,7 @@ static void parseCommandLine(int argc, char ** argv, struct cmdlineInfo * const cmdlineP) { /*---------------------------------------------------------------------------- - Convert program invocation arguments (argc,argv) into a format the + 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. @@ -111,12 +111,23 @@ parseCommandLine(int argc, char ** argv, +static int +diffu(unsigned int const subtrahend, + unsigned int const subtractor) { + + return (int)subtrahend - (int)subtractor; + + /* (Not the conventional terminology, but better) */ +} + + + int main(int argc, char *argv[]) { struct cmdlineInfo cmdline; gray *grayrow; - int rowso2, colso2; + unsigned int rowso2, colso2; unsigned int row; pgm_init( &argc, argv ); @@ -149,20 +160,17 @@ main(int argc, char *argv[]) { MAX((float) cmdline.cols + cmdline.rows-2, 1); break; case RT_RECT: { - float const r = fabs((int)(rowso2 - row)) / rowso2; - float const c = fabs((int)(colso2 - col)) / colso2; + float const r = fabs((float)diffu(rowso2, row)) / rowso2; + float const c = fabs((float)diffu(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; + float const r = fabs((float)diffu(rowso2, row)) / rowso2; + float const c = fabs((float)diffu(colso2, col)) / colso2; + float const v = MAX(0.0f, MIN(1.0f, SQR(r) + SQR(c))); - 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; } @@ -174,3 +182,6 @@ main(int argc, char *argv[]) { pm_close(stdout); return 0; } + + + diff --git a/generator/ppmcie.c b/generator/ppmcie.c index 717ed13b..86325ba6 100644 --- a/generator/ppmcie.c +++ b/generator/ppmcie.c @@ -10,7 +10,7 @@ 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. + 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. */ @@ -294,10 +294,10 @@ static struct colorSystem const */ static struct colorSystem Customsystem = { "Custom", - 0.64, 0.33, 0.30, 0.60, 0.15, 0.06, + 0.64, 0.33, 0.30, 0.60, 0.15, 0.06, IlluminantD65, GAMMA_REC709 }; - + static void @@ -344,11 +344,11 @@ xyz_to_rgb(const struct colorSystem * const cs, 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. + be negative. Caller can use constrain_rgb() to desaturate an outside-gamut color to the closest representation within the available - gamut. + gamut. -----------------------------------------------------------------------------*/ double xr, yr, zr, xg, yg, zg, xb, yb, zb; double xw, yw, zw; @@ -542,7 +542,7 @@ drawTongueOutline(pixel ** const pixels, computeMonochromeColorLocation(wavelength, pxcols, pxrows, upvp, &icx, &icy); - + if (wavelength > 380) ppmd_line(pixels, pixcols, pixrows, maxval, B(lx, ly), B(icx, icy), @@ -589,7 +589,7 @@ findTongue(pixel ** const pixels, int const leftEdge = i; *presentP = true; - + for (j = pxcols - 1; j >= leftEdge && PPM_GETR(Bixels(row, j)) == 0; --j); @@ -651,12 +651,12 @@ fillInTongue(pixel ** const pixels, 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; @@ -688,7 +688,7 @@ drawYAxis(pixel ** const pixels, unsigned int const xBias, unsigned int const yBias, pixel const axisColor) { - + unsigned int const pxrows = pixrows - yBias; ppmd_line(pixels, pixcols, pixrows, maxval, @@ -706,7 +706,7 @@ drawXAxis(pixel ** const pixels, unsigned int const xBias, unsigned int const yBias, pixel const axisColor) { - + unsigned int const pxcols = pixcols - xBias; unsigned int const pxrows = pixrows - yBias; @@ -778,7 +778,7 @@ tickY(pixel ** const pixels, /* Pixel row where the top of the tick goes */ unsigned int const tickThickness = Sz(3); /* Thickness of the tick in pixels */ - + char s[20]; assert(tenth < 10); @@ -970,7 +970,7 @@ plotBlackBodyCurve(pixel ** const pixels, /* Label selected tick marks with decreasing density. */ - if (t <= 5000.1 || (t > 5000.0 && + if (t <= 5000.1 || (t > 5000.0 && ((((int) t) % 5000) == 0) && t != 20000.0)) { char bb[20]; @@ -980,7 +980,7 @@ plotBlackBodyCurve(pixel ** const pixels, B(lx - Sz(12), ly - Sz(4)), Sz(6), 0, bb, PPMD_NULLDRAWPROC, (char *) &rgbcolor); } - + } } lx = xb; @@ -998,7 +998,7 @@ overlappingLegend(bool const upvp, if (upvp) retval = (waveLength == 430 || waveLength == 640); - else + else retval = (waveLength == 460 || waveLength == 630 || waveLength == 640); return retval; } @@ -1054,7 +1054,7 @@ plotMonochromeWavelengths( /* 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))); + 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); @@ -1113,7 +1113,7 @@ writeLabel(pixel ** const pixels, char sysdesc[256]; PPM_ASSIGN(rgbcolor, maxval, maxval, maxval); - + pm_snprintf(sysdesc, sizeof(sysdesc), "System: %s\n" "Primary illuminants (X, Y)\n" @@ -1198,9 +1198,9 @@ main(int argc, } else if (pm_keymatch(argv[argn], "-smpte", 2)) { cs = &SMPTEsystem; } else if (pm_keymatch(argv[argn], "-hdtv", 2)) { - cs = &HDTVsystem; + cs = &HDTVsystem; } else if (pm_keymatch(argv[argn], "-cie", 1)) { - cs = &CIEsystem; + cs = &CIEsystem; } else if (pm_keymatch(argv[argn], "-black", 3)) { showBlack = true; /* Show black body curve */ } else if (pm_keymatch(argv[argn], "-wpoint", 2)) { diff --git a/generator/ppmforge.c b/generator/ppmforge.c index 8ea86429..390180e3 100644 --- a/generator/ppmforge.c +++ b/generator/ppmforge.c @@ -31,7 +31,7 @@ */ -#define _XOPEN_SOURCE /* get M_PI in math.h */ +#define _XOPEN_SOURCE 500 /* get M_PI in math.h */ #include <math.h> #include <assert.h> diff --git a/generator/ppmpat.c b/generator/ppmpat.c index fe1a1d27..908c200f 100644 --- a/generator/ppmpat.c +++ b/generator/ppmpat.c @@ -10,15 +10,21 @@ ** implied warranty. */ -#define _XOPEN_SOURCE /* get M_PI in math.h */ +#define _DEFAULT_SOURCE /* New name for SVID & BSD source defines */ +#define _XOPEN_SOURCE 500 /* Make sure strdup() is in string.h */ + /* get M_PI in math.h */ +#define _BSD_SOURCE /* Make sure strdup() is in <string.h> */ +#define SPIROGRAPHS 0 /* Spirograph to be added soon */ #include <assert.h> #include <math.h> #include <limits.h> +#include <string.h> #include "pm_c_util.h" #include "mallocvar.h" #include "shhopt.h" +#include "nstring.h" #include "ppm.h" #include "ppmdraw.h" @@ -28,28 +34,138 @@ typedef enum { PAT_GINGHAM3, PAT_MADRAS, PAT_TARTAN, + PAT_ARGYLE1, + PAT_ARGYLE2, PAT_POLES, PAT_SQUIG, PAT_CAMO, - PAT_ANTICAMO -} pattern; + PAT_ANTICAMO, + PAT_SPIRO1, + PAT_SPIRO2, + PAT_SPIRO3 +} Pattern; -struct cmdlineInfo { +typedef struct { +/*---------------------------------------------------------------------------- + An ordered list of colors with a cursor. +-----------------------------------------------------------------------------*/ + unsigned int count; + unsigned int index; + /* Current position in the list */ + pixel * color; + /* Malloced array 'count' in size. */ +} ColorTable; + +struct CmdlineInfo { /* All the information the user supplied in the command line, in a form easy for the program to use. */ - pattern basePattern; + Pattern basePattern; unsigned int width; unsigned int height; + unsigned int colorSpec; + ColorTable colorTable; unsigned int randomseed; unsigned int randomseedSpec; }; +static void +validateColorCount(Pattern const basePattern, + unsigned int const colorCount) { + + if (colorCount == 0) + pm_error("-color: no colors specified"); + + switch (basePattern) { + case PAT_GINGHAM2: + case PAT_ARGYLE1: + case PAT_SPIRO1: + if (colorCount != 2) + pm_error("Wrong number of colors: %u. " + "2 colors are required for the specified pattern.", + colorCount); + break; + case PAT_GINGHAM3: + case PAT_MADRAS: + case PAT_TARTAN: + case PAT_ARGYLE2: + if (colorCount != 3) + pm_error("Wrong number of colors: %u. " + "3 colors are required for the specified pattern.", + colorCount); + break; + case PAT_POLES: + if (colorCount < 2) + pm_error("Too few colors: %u. " + "At least 2 colors are required " + "for the specified pattern.", + colorCount); + break; + case PAT_SQUIG: + case PAT_CAMO: + case PAT_ANTICAMO: + if (colorCount < 3) + pm_error("Wrong number of colors: %u. " + "At least 3 colors are required " + "for the specified pattern.", + colorCount); + break; + + case PAT_SPIRO2: + case PAT_SPIRO3: + default: + pm_error("INTERNAL ERROR."); + } +} + + + +static void +parseColorOpt(const char ** const colorText, + ColorTable * const colorTableP, + Pattern const basePattern) { +/*---------------------------------------------------------------------------- + String-list argument to -color is a comma-separated array of + color names or values, e.g.: + "-color=red,white,blue" + "-color=rgb:ff/ff/ff,rgb:00/00/00,rgb:80/80/ff" + + Input: + Color name/value string-list: colorText[] + + Output values: + Color array: colorTableP->color[] + Number of colors found: colorTableP->colors +----------------------------------------------------------------------------*/ + unsigned int colorCount; + unsigned int i; + pixel * inColor; + + for (colorCount = 0; colorText[colorCount] != NULL; ++colorCount) + ; + + MALLOCARRAY(inColor, colorCount); + + if (!inColor) + pm_error("Failed to allocate table space for %u colors " + "specified by -color", colorCount); + + for (i = 0; i < colorCount; ++i) + inColor[i] = ppm_parsecolor(colorText[i], PPM_MAXMAXVAL); + + validateColorCount(basePattern, colorCount); + + colorTableP->count = colorCount; + colorTableP->index = 0; /* initial value */ + colorTableP->color = inColor; +} + + static void parseCommandLine(int argc, const char ** argv, - struct cmdlineInfo * const cmdlineP) { + struct CmdlineInfo * const cmdlineP) { /*---------------------------------------------------------------------------- Note that the file spec array we return is stored in the storage that was passed to us as the argv array. @@ -60,15 +176,21 @@ parseCommandLine(int argc, const char ** argv, optStruct3 opt; unsigned int option_def_index; + const char ** colorText; unsigned int basePatternCount; unsigned int gingham2; unsigned int gingham3; unsigned int madras; unsigned int tartan; + unsigned int argyle1; + unsigned int argyle2; unsigned int poles; unsigned int squig; unsigned int camo; unsigned int anticamo; + unsigned int spiro1; + unsigned int spiro2; + unsigned int spiro3; MALLOCARRAY_NOFAIL(option_def, 100); @@ -85,6 +207,10 @@ parseCommandLine(int argc, const char ** argv, &madras, 0); OPTENT3(0, "tartan", OPT_FLAG, NULL, &tartan, 0); + OPTENT3(0, "argyle1", OPT_FLAG, NULL, + &argyle1, 0); + OPTENT3(0, "argyle2", OPT_FLAG, NULL, + &argyle2, 0); OPTENT3(0, "poles", OPT_FLAG, NULL, &poles, 0); OPTENT3(0, "squig", OPT_FLAG, NULL, @@ -93,7 +219,19 @@ parseCommandLine(int argc, const char ** argv, &camo, 0); OPTENT3(0, "anticamo", OPT_FLAG, NULL, &anticamo, 0); - OPTENT3(0, "randomseed", OPT_UINT, &cmdlineP->randomseed, +#if SPIROGRAPHS != 0 + OPTENT3(0, "spiro1", OPT_FLAG, NULL, + &spiro1, 0); + OPTENT3(0, "spiro2", OPT_FLAG, NULL, + &spiro1, 0); + OPTENT3(0, "spiro3", OPT_FLAG, NULL, + &spiro1, 0); +#else + spiro1 = spiro2 = spiro3 = 0; +#endif + OPTENT3(0, "color", OPT_STRINGLIST, &colorText, + &cmdlineP->colorSpec, 0); + OPTENT3(0, "randomseed", OPT_UINT, &cmdlineP->randomseed, &cmdlineP->randomseedSpec, 0); opt.opt_table = option_def; @@ -102,16 +240,14 @@ parseCommandLine(int argc, const char ** argv, pm_optParseOptions3(&argc, (char **)argv, opt, sizeof(opt), 0); /* Uses and sets argc, argv, and some of *cmdlineP and others. */ + free(option_def); basePatternCount = - gingham2 + - gingham3 + - madras + - tartan + + gingham2 + gingham3 + madras + tartan + argyle1 + argyle2 + poles + squig + - camo + - anticamo; + camo + anticamo + + spiro1 + spiro2 + spiro3; if (basePatternCount < 1) pm_error("You must specify a base pattern option such as -gingham2"); @@ -127,6 +263,10 @@ parseCommandLine(int argc, const char ** argv, cmdlineP->basePattern = PAT_MADRAS; else if (tartan) cmdlineP->basePattern = PAT_TARTAN; + else if (argyle1) + cmdlineP->basePattern = PAT_ARGYLE1; + else if (argyle2) + cmdlineP->basePattern = PAT_ARGYLE2; else if (poles) cmdlineP->basePattern = PAT_POLES; else if (squig) @@ -135,9 +275,22 @@ parseCommandLine(int argc, const char ** argv, cmdlineP->basePattern = PAT_CAMO; else if (anticamo) cmdlineP->basePattern = PAT_ANTICAMO; + else if (spiro1) + cmdlineP->basePattern = PAT_SPIRO1; + else if (spiro2) + cmdlineP->basePattern = PAT_SPIRO2; + else if (spiro3) + cmdlineP->basePattern = PAT_SPIRO3; else assert(false); /* Every possibility is accounted for */ } + + if (cmdlineP->colorSpec) { + parseColorOpt(colorText, &cmdlineP->colorTable, cmdlineP->basePattern); + free(colorText); + } else + cmdlineP->colorTable.count = 0; + if (argc-1 != 2) pm_error("You must specify 2 non-option arguments: width and height " "in pixels. You specified %u", argc-1); @@ -150,7 +303,15 @@ parseCommandLine(int argc, const char ** argv, if (cmdlineP->height < 1) pm_error("Height must be at least 1 pixel"); } - free(option_def); +} + + + +static void +freeCmdline(struct CmdlineInfo const cmdline) { + + if (cmdline.colorSpec) + free(cmdline.colorTable.color); } @@ -169,7 +330,7 @@ validateComputableDimensions(unsigned int const cols, PPMD functions use signed integers for pixel positions (because they allow you to specify points off the canvas). */ - + if (cols > INT_MAX/4 || rows > INT_MAX/4 || rows > INT_MAX/cols) pm_error("Width and/or height are way too large: %u x %u", cols, rows); @@ -187,7 +348,7 @@ randomColor(pixval const maxval) { rand() % (maxval + 1), rand() % (maxval + 1) ); - + return p; } @@ -242,12 +403,12 @@ averageTwoColors(pixel const p1, static ppmd_drawproc average_drawproc; static void -average_drawproc(pixel ** const pixels, - int const cols, - int const rows, - pixval const maxval, - int const col, - int const row, +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) @@ -257,6 +418,29 @@ average_drawproc(pixel ** const pixels, +static void +nextColor(ColorTable * const colorTableP) { +/*---------------------------------------------------------------------------- + Increment index, return it to 0 if we have used all the colors +-----------------------------------------------------------------------------*/ + colorTableP->index = (colorTableP->index + 1) % colorTableP->count; +} + + + +static void +nextColorBg(ColorTable * const colorTableP) { +/*---------------------------------------------------------------------------- + Increment index, return it to 1 if we have used all the colors (color[0] is + the background color, it's outside the cycle) +-----------------------------------------------------------------------------*/ + colorTableP->index = colorTableP->index % (colorTableP->count - 1) + 1; + /* Works when index == 0, but no callers rely on this. */ + +} + + + /*---------------------------------------------------------------------------- Camouflage stuff -----------------------------------------------------------------------------*/ @@ -282,7 +466,7 @@ randomAnticamoColor(pixval const maxval) { 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); @@ -306,7 +490,7 @@ randomAnticamoColor(pixval const maxval) { PPM_ASSIGN(p, rand() % v1 + v3, rand() % v1 + v3, rand() % v2); break; } - + return p; } @@ -335,7 +519,7 @@ randomCamoColor(pixval const maxval) { /* dark green */ PPM_ASSIGN(p, rand() % v2, rand() % v2 + 3 * v1, rand() % v2); break; - + case 6: case 7: /* brown */ @@ -362,15 +546,18 @@ rnduni(void) { static void -clearBackground(pixel ** const pixels, - unsigned int const cols, - unsigned int const rows, - pixval const maxval, - bool const antiflag) { +clearBackgroundCamo(pixel ** const pixels, + unsigned int const cols, + unsigned int const rows, + pixval const maxval, + ColorTable * const colorTableP, + bool const antiflag) { pixel color; - if (antiflag) + if (colorTableP->count > 0) { + color = colorTableP->color[0]; + } else if (antiflag) color = randomAnticamoColor(maxval); else color = randomCamoColor(maxval); @@ -381,23 +568,29 @@ clearBackground(pixel ** const pixels, } + static void camoFill(pixel ** const pixels, unsigned int const cols, unsigned int const rows, pixval const maxval, struct fillobj * const fh, + ColorTable * const colorTableP, bool const antiflag) { - + pixel color; - if (antiflag) + if (colorTableP->count > 0) { + assert(colorTableP->index < colorTableP->count); + color = colorTableP->color[colorTableP->index]; + nextColorBg(colorTableP); + } else if (antiflag) color = randomAnticamoColor(maxval); else color = randomCamoColor(maxval); ppmd_fill(pixels, cols, rows, maxval, fh, PPMD_NULLDRAWPROC, &color); -} +} @@ -428,9 +621,9 @@ computeXsYs(int * const xs, double const b = rnduni() * (MAX_ELLIPSE_FACTOR - MIN_ELLIPSE_FACTOR) + MIN_ELLIPSE_FACTOR; double const theta = rnduni() * 2.0 * M_PI; - + unsigned int p; - + for (p = 0; p < pointCt; ++p) { double const c = rnduni() * (MAX_POINT_FACTOR - MIN_POINT_FACTOR) + MIN_POINT_FACTOR; @@ -448,14 +641,20 @@ static void camo(pixel ** const pixels, unsigned int const cols, unsigned int const rows, + ColorTable * const colorTableP, pixval const maxval, bool const antiflag) { - unsigned int const n = (rows * cols) / (BLOBRAD * BLOBRAD) * 5; + unsigned int const n = (rows * cols) / SQR(BLOBRAD) * 5; unsigned int i; - clearBackground(pixels, cols, rows, maxval, antiflag); + clearBackgroundCamo(pixels, cols, rows, maxval, colorTableP, antiflag); + + if (colorTableP->count > 0) { + assert(colorTableP->count > 1); + colorTableP->index = 1; /* Foreground colors start at 1 */ + } for (i = 0; i < n; ++i) { unsigned int const pointCt = @@ -475,9 +674,9 @@ camo(pixel ** const pixels, ppmd_polyspline( pixels, cols, rows, maxval, x0, y0, pointCt, xs, ys, x0, y0, ppmd_fill_drawproc, fh); - - camoFill(pixels, cols, rows, maxval, fh, antiflag); - + + camoFill(pixels, cols, rows, maxval, fh, colorTableP, antiflag); + ppmd_fill_destroy(fh); } } @@ -485,19 +684,21 @@ camo(pixel ** const pixels, /*---------------------------------------------------------------------------- - Gingham stuff + Plaid patterns -----------------------------------------------------------------------------*/ - - static void gingham2(pixel ** const pixels, unsigned int const cols, unsigned int const rows, + ColorTable const colorTable, pixval const maxval) { - pixel const backcolor = randomDarkColor(maxval); - pixel const forecolor = randomBrightColor(maxval); + bool const colorSpec = (colorTable.count > 0); + pixel const backcolor = colorSpec ? + colorTable.color[0] : randomDarkColor(maxval); + pixel const forecolor = colorSpec ? + colorTable.color[1] : randomBrightColor(maxval); unsigned int const colso2 = cols / 2; unsigned int const rowso2 = rows / 2; @@ -524,15 +725,19 @@ static void gingham3(pixel ** const pixels, unsigned int const cols, unsigned int const rows, + ColorTable const colorTable, pixval const maxval) { + bool const colorSpec = (colorTable.count > 0); + pixel const backcolor = colorSpec ? + colorTable.color[0] : randomDarkColor(maxval); + pixel const fore1color = colorSpec ? + colorTable.color[1] : randomBrightColor(maxval); + pixel const fore2color = colorSpec ? + colorTable.color[2] : randomBrightColor(maxval); unsigned int const colso4 = cols / 4; unsigned int const rowso4 = rows / 4; - pixel const backcolor = randomDarkColor(maxval); - pixel const fore1color = randomBrightColor(maxval); - pixel const fore2color = randomBrightColor(maxval); - /* Warp. */ ppmd_filledrectangle( pixels, cols, rows, maxval, 0, 0, colso4, rows, PPMD_NULLDRAWPROC, @@ -568,8 +773,16 @@ static void madras(pixel ** const pixels, unsigned int const cols, unsigned int const rows, + ColorTable const colorTable, pixval const maxval) { + bool const colorSpec = (colorTable.count > 0); + pixel const backcolor = colorSpec ? + colorTable.color[0] : randomDarkColor(maxval); + pixel const fore1color = colorSpec ? + colorTable.color[1] : randomBrightColor(maxval); + pixel const fore2color = colorSpec ? + colorTable.color[2] : randomBrightColor(maxval); unsigned int const cols2 = cols * 2 / 44; unsigned int const rows2 = rows * 2 / 44; unsigned int const cols3 = cols * 3 / 44; @@ -580,9 +793,6 @@ madras(pixel ** const pixels, unsigned int const rows6a = rows12 / 2; unsigned int const cols6b = cols12 - cols6a; unsigned int const rows6b = rows12 - rows6a; - pixel const backcolor = randomDarkColor(maxval); - pixel const fore1color = randomBrightColor(maxval); - pixel const fore2color = randomBrightColor(maxval); /* Warp. */ ppmd_filledrectangle( @@ -631,7 +841,7 @@ madras(pixel ** const pixels, 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, + pixels, cols, rows, maxval, 10 * cols2 + 3 * cols3 + cols6a + cols6b, 0, cols3, rows, PPMD_NULLDRAWPROC, &fore1color); /* Woof. */ @@ -681,7 +891,7 @@ madras(pixel ** const pixels, pixels, cols, rows, maxval, 0, 9 * rows2 + 3 * rows3 + rows6a + rows6b, cols, rows2, average_drawproc, &backcolor); ppmd_filledrectangle( - pixels, cols, rows, maxval, 0, + pixels, cols, rows, maxval, 0, 10 * rows2 + 3 * rows3 + rows6a + rows6b, cols, rows3, average_drawproc, &fore2color); } @@ -692,8 +902,16 @@ static void tartan(pixel ** const pixels, unsigned int const cols, unsigned int const rows, + ColorTable const colorTable, pixval const maxval) { + bool const colorSpec = (colorTable.count > 0); + pixel const backcolor = colorSpec ? + colorTable.color[0] : randomDarkColor(maxval); + pixel const fore1color = colorSpec ? + colorTable.color[1] : randomBrightColor(maxval); + pixel const fore2color = colorSpec ? + colorTable.color[2] : randomBrightColor(maxval); unsigned int const cols1 = cols / 22; unsigned int const rows1 = rows / 22; unsigned int const cols3 = cols * 3 / 22; @@ -704,9 +922,6 @@ tartan(pixel ** const pixels, unsigned int const rows5a = rows10 / 2; unsigned int const cols5b = cols10 - cols5a; unsigned int const rows5b = rows10 - rows5a; - pixel const backcolor = randomDarkColor(maxval); - pixel const fore1color = randomBrightColor(maxval); - pixel const fore2color = randomBrightColor(maxval); /* Warp. */ ppmd_filledrectangle( @@ -763,6 +978,71 @@ tartan(pixel ** const pixels, +static void +drawAndFillDiamond(pixel ** const pixels, + unsigned int const cols, + unsigned int const rows, + pixval const maxval, + pixel const forecolor) { + + unsigned int const colso2 = cols / 2; + unsigned int const rowso2 = rows / 2; + + ppmd_pathbuilder * const pathBuilderP = ppmd_pathbuilder_create(); + + ppmd_pathbuilder_setBegPoint(pathBuilderP, + ppmd_makePoint (colso2, 0)); + + ppmd_pathbuilder_addLineLeg(pathBuilderP, + ppmd_makeLineLeg(ppmd_makePoint(cols-1, rowso2))); + ppmd_pathbuilder_addLineLeg(pathBuilderP, + ppmd_makeLineLeg(ppmd_makePoint(colso2, rows-1))); + ppmd_pathbuilder_addLineLeg(pathBuilderP, + ppmd_makeLineLeg(ppmd_makePoint(0, rowso2))); + ppmd_pathbuilder_addLineLeg(pathBuilderP, + ppmd_makeLineLeg(ppmd_makePoint(colso2, 0))); + + ppmd_fill_path(pixels, cols, rows, maxval, + ppmd_pathbuilder_pathP(pathBuilderP), forecolor); +} + + + +static void +argyle(pixel ** const pixels, + unsigned int const cols, + unsigned int const rows, + ColorTable const colorTable, + pixval const maxval, + bool const stripes) { + + bool const colorSpec = (colorTable.count > 0); + pixel const backcolor = colorSpec ? + colorTable.color[0] : randomDarkColor(maxval); + pixel const forecolor = colorSpec ? + colorTable.color[1] : randomBrightColor(maxval); + + /* Fill canvas with background to start */ + ppmd_filledrectangle( + pixels, cols, rows, maxval, 0, 0, cols, rows, PPMD_NULLDRAWPROC, + &backcolor); + + drawAndFillDiamond(pixels, cols, rows, maxval, forecolor); + + if (stripes) { + /* Connect corners with thin stripes */ + pixel const stripecolor = + colorSpec ? colorTable.color[2] : randomBrightColor(maxval); + + ppmd_line(pixels, cols, rows, maxval, 0, 0, cols-1, rows-1, + PPMD_NULLDRAWPROC, (char *) &stripecolor); + ppmd_line(pixels, cols, rows, maxval, cols-1, 0, 0, rows-1, + PPMD_NULLDRAWPROC, (char *) &stripecolor); + } +} + + + /*---------------------------------------------------------------------------- Poles stuff -----------------------------------------------------------------------------*/ @@ -780,14 +1060,21 @@ placeAndColorPolesRandomly(int * const xs, unsigned int const cols, unsigned int const rows, pixval const maxval, + ColorTable * const colorTableP, unsigned int const poleCt) { unsigned int i; for (i = 0; i < poleCt; ++i) { + xs[i] = rand() % cols; ys[i] = rand() % rows; - colors[i] = randomBrightColor(maxval); + + if (colorTableP->count > 0) { + colors[i] = colorTableP->color[colorTableP->index]; + nextColor(colorTableP); + } else + colors[i] = randomBrightColor(maxval); } } @@ -799,8 +1086,8 @@ assignInterpolatedColor(pixel * const resultP, double const dist1, pixel const color2, double const dist2) { - - if (dist1 == 0) + + if (dist1 == 0) /* pixel is a pole */ *resultP = color1; else { @@ -809,7 +1096,7 @@ assignInterpolatedColor(pixel * const resultP, pixval const r = (PPM_GETR(color1)*dist2 + PPM_GETR(color2)*dist1)/sum; pixval const g = (PPM_GETG(color1)*dist2 + PPM_GETG(color2)*dist1)/sum; pixval const b = (PPM_GETB(color1)*dist2 + PPM_GETB(color2)*dist1)/sum; - + PPM_ASSIGN(*resultP, r, g, b); } } @@ -820,15 +1107,17 @@ static void poles(pixel ** const pixels, unsigned int const cols, unsigned int const rows, + ColorTable * const colorTableP, pixval const maxval) { unsigned int const poleCt = MAX(2, MIN(MAXPOLES, cols * rows / 30000)); - + int xs[MAXPOLES], ys[MAXPOLES]; pixel colors[MAXPOLES]; unsigned int row; - placeAndColorPolesRandomly(xs, ys, colors, cols, rows, maxval, poleCt); + placeAndColorPolesRandomly(xs, ys, colors, cols, rows, maxval, + colorTableP, poleCt); /* Interpolate points */ @@ -871,11 +1160,15 @@ poles(pixel ** const pixels, #define SQ_POINTS 7 #define SQ_MAXCIRCLE_POINTS 5000 -static int sq_circlecount; -static pixel sq_colors[SQ_MAXCIRCLE_POINTS]; -static ppmd_point sq_offs[SQ_MAXCIRCLE_POINTS]; - +struct Squig { + unsigned int circleCt; + pixel color[SQ_MAXCIRCLE_POINTS]; + ppmd_point off[SQ_MAXCIRCLE_POINTS]; +}; +typedef struct { + struct Squig * squigP; +} SqClientData; static void validateSquigAspect(unsigned int const cols, @@ -884,7 +1177,7 @@ validateSquigAspect(unsigned int const cols, if (cols / rows >= 25 || rows / cols >= 25) pm_error("Image too narrow. Aspect ratio: %u/%u=%f " "is outside accepted range: 0.04 - 25.0", - cols, rows, (float)cols/rows ); + cols, rows, (float)cols/rows ); } @@ -902,14 +1195,18 @@ vectorSum(ppmd_point const a, static ppmd_drawprocp sqMeasureCircleDrawproc; static void -sqMeasureCircleDrawproc(pixel** const pixels, - unsigned int const cols, - unsigned int const rows, - pixval const maxval, +sqMeasureCircleDrawproc(pixel** const pixels, + unsigned int const cols, + unsigned int const rows, + pixval const maxval, ppmd_point const p, const void * const clientdata) { - sq_offs[sq_circlecount++] = p; + const SqClientData * const sqClientDataP = clientdata; + + struct Squig * const squigP = sqClientDataP->squigP; + + squigP->off[squigP->circleCt++] = p; } @@ -917,19 +1214,46 @@ sqMeasureCircleDrawproc(pixel** const pixels, static ppmd_drawprocp sqRainbowCircleDrawproc; static void -sqRainbowCircleDrawproc(pixel ** const pixels, - unsigned int const cols, - unsigned int const rows, - pixval const maxval, +sqRainbowCircleDrawproc(pixel ** const pixels, + unsigned int const cols, + unsigned int const rows, + pixval const maxval, ppmd_point const p, const void * const clientdata) { + const SqClientData * const sqClientDataP = clientdata; + + struct Squig * const squigP = sqClientDataP->squigP; + unsigned int i; - for (i = 0; i < sq_circlecount; ++i) + for (i = 0; i < squigP->circleCt; ++i) ppmd_point_drawprocp( - pixels, cols, rows, maxval, vectorSum(p, sq_offs[i]), - &sq_colors[i]); + pixels, cols, rows, maxval, vectorSum(p, squigP->off[i]), + &squigP->color[i]); +} + + + +static void +chooseSqPoleColors(ColorTable * const colorTableP, + pixval const maxval, + pixel * const color1P, + pixel * const color2P, + pixel * const color3P) { + + if (colorTableP->count > 0) { + *color1P = colorTableP->color[colorTableP->index]; + nextColor(colorTableP); + *color2P = colorTableP->color[colorTableP->index]; + nextColor(colorTableP); + *color3P = colorTableP->color[colorTableP->index]; + nextColor(colorTableP); + } else { + *color1P = randomBrightColor(maxval); + *color2P = randomBrightColor(maxval); + *color3P = randomBrightColor(maxval); + } } @@ -937,16 +1261,19 @@ sqRainbowCircleDrawproc(pixel ** const pixels, static void sqAssignColors(unsigned int const circlecount, pixval const maxval, + ColorTable * const colorTableP, pixel * const colors) { - pixel const rc1 = randomBrightColor(maxval); - pixel const rc2 = randomBrightColor(maxval); - pixel const rc3 = randomBrightColor(maxval); float const cco3 = (circlecount - 1) / 3.0; + pixel rc1; + pixel rc2; + pixel rc3; unsigned int i; - for (i = 0; i < circlecount ; ++i) { + chooseSqPoleColors(colorTableP, maxval, &rc1, &rc2, &rc3); + + for (i = 0; i < circlecount; ++i) { if (i < cco3) { float const frac = (float)i/cco3; PPM_ASSIGN(colors[i], @@ -984,14 +1311,19 @@ sqAssignColors(unsigned int const circlecount, static void -clearImageToBlack(pixel ** const pixels, - unsigned int const cols, - unsigned int const rows, - pixval const maxval) { +clearBackgroundSquig(pixel ** const pixels, + unsigned int const cols, + unsigned int const rows, + ColorTable * const colorTableP, + pixval const maxval) { pixel color; - PPM_ASSIGN(color, 0, 0, 0); + if (colorTableP->count > 0) { + color = colorTableP->color[0]; + colorTableP->index = 1; + } else + PPM_ASSIGN(color, 0, 0, 0); ppmd_filledrectangle( pixels, cols, rows, maxval, 0, 0, cols, rows, PPMD_NULLDRAWPROC, @@ -1009,7 +1341,7 @@ chooseWrapAroundPoint(unsigned int const cols, ppmd_point * const p1P, ppmd_point * const p2P, ppmd_point * const p3P) { - + switch (rand() % 4) { case 0: p1P->x = rand() % cols; @@ -1091,27 +1423,37 @@ static void squig(pixel ** const pixels, unsigned int const cols, unsigned int const rows, + ColorTable * const colorTableP, pixval const maxval) { int i; validateSquigAspect(cols, rows); - - clearImageToBlack(pixels, cols, rows, maxval); + + clearBackgroundSquig(pixels, cols, rows, colorTableP, maxval); /* Draw the squigs. */ ppmd_setlinetype(PPMD_LINETYPE_NODIAGS); ppmd_setlineclip(0); + for (i = SQUIGS; i > 0; --i) { unsigned int const radius = (cols + rows) / 2 / (25 + i * 2); + struct Squig squig; + + SqClientData sqClientData; + ppmd_point c[SQ_POINTS]; ppmd_point p0, p1, p2, p3; - sq_circlecount = 0; + + squig.circleCt = 0; + + sqClientData.squigP = &squig; + ppmd_circlep(pixels, cols, rows, maxval, ppmd_makePoint(0, 0), radius, - sqMeasureCircleDrawproc, NULL); - sqAssignColors(sq_circlecount, maxval, sq_colors); + sqMeasureCircleDrawproc, &sqClientData); + sqAssignColors(squig.circleCt, maxval, colorTableP, squig.color); chooseWrapAroundPoint(cols, rows, &c[0], &c[SQ_POINTS-1], &p0, &p1, &p2, &p3); @@ -1131,13 +1473,13 @@ squig(pixel ** const pixels, ppmd_linep( pixels, cols, rows, maxval, p0, p1, - sqRainbowCircleDrawproc, NULL); + sqRainbowCircleDrawproc, &sqClientData); ppmd_polysplinep( pixels, cols, rows, maxval, p1, SQ_POINTS, c, p2, - sqRainbowCircleDrawproc, NULL); + sqRainbowCircleDrawproc, &sqClientData); ppmd_linep( pixels, cols, rows, maxval, p2, p3, - sqRainbowCircleDrawproc, NULL); + sqRainbowCircleDrawproc, &sqClientData); } } @@ -1146,50 +1488,68 @@ squig(pixel ** const pixels, int main(int argc, const char ** argv) { - struct cmdlineInfo cmdline; + struct CmdlineInfo cmdline; pixel ** pixels; pm_proginit(&argc, argv); - + parseCommandLine(argc, argv, &cmdline); validateComputableDimensions(cmdline.width, cmdline.height); - + srand(cmdline.randomseedSpec ? cmdline.randomseed : pm_randseed()); pixels = ppm_allocarray(cmdline.width, cmdline.height); switch (cmdline.basePattern) { case PAT_GINGHAM2: - gingham2(pixels, cmdline.width, cmdline.height, PPM_MAXMAXVAL); + gingham2(pixels, cmdline.width, cmdline.height, + cmdline.colorTable, PPM_MAXMAXVAL); break; case PAT_GINGHAM3: - gingham3(pixels, cmdline.width, cmdline.height, PPM_MAXMAXVAL); + gingham3(pixels, cmdline.width, cmdline.height, + cmdline.colorTable, PPM_MAXMAXVAL); break; case PAT_MADRAS: - madras(pixels, cmdline.width, cmdline.height, PPM_MAXMAXVAL); + madras(pixels, cmdline.width, cmdline.height, + cmdline.colorTable, PPM_MAXMAXVAL); break; case PAT_TARTAN: - tartan(pixels, cmdline.width, cmdline.height, PPM_MAXMAXVAL); + tartan(pixels, cmdline.width, cmdline.height, + cmdline.colorTable, PPM_MAXMAXVAL); + break; + + case PAT_ARGYLE1: + argyle(pixels, cmdline.width, cmdline.height, + cmdline.colorTable, PPM_MAXMAXVAL, FALSE); + break; + + case PAT_ARGYLE2: + argyle(pixels, cmdline.width, cmdline.height, + cmdline.colorTable, PPM_MAXMAXVAL, TRUE); break; case PAT_POLES: - poles(pixels, cmdline.width, cmdline.height, PPM_MAXMAXVAL); + poles(pixels, cmdline.width, cmdline.height, + &cmdline.colorTable, PPM_MAXMAXVAL); break; case PAT_SQUIG: - squig(pixels, cmdline.width, cmdline.height, PPM_MAXMAXVAL); + squig(pixels, cmdline.width, cmdline.height, + &cmdline.colorTable, PPM_MAXMAXVAL); break; case PAT_CAMO: - camo(pixels, cmdline.width, cmdline.height, PPM_MAXMAXVAL, 0); + camo(pixels, cmdline.width, cmdline.height, + &cmdline.colorTable, PPM_MAXMAXVAL, 0); break; case PAT_ANTICAMO: - camo(pixels, cmdline.width, cmdline.height, PPM_MAXMAXVAL, 1); + camo(pixels, cmdline.width, cmdline.height, + &cmdline.colorTable, PPM_MAXMAXVAL, 1); break; default: @@ -1201,6 +1561,8 @@ main(int argc, const char ** argv) { ppm_freearray(pixels, cmdline.height); + freeCmdline(cmdline); + return 0; } diff --git a/generator/ppmrainbow b/generator/ppmrainbow index c0568d9b..e8a329ff 100755 --- a/generator/ppmrainbow +++ b/generator/ppmrainbow @@ -25,31 +25,57 @@ exec perl -w -x -S -- "$0" "$@" #!/usr/bin/perl use strict; use Getopt::Long; +use File::Temp; my ($FALSE, $TRUE) = (0,1); (my $myname = $0) =~ s#\A.*/##; + + +sub doVersionHack($) { + my ($argvR) = @_; + + my $arg1 = $argvR->[0]; + + if (defined($arg1) && (($arg1 eq "--version") || ($arg1 eq "-version"))) { + my $termStatus = system('pgmramp', '--version'); + exit($termStatus == 0 ? 0 : 1); + } +} + + + sub fatal($) { my ($msg) = @_; - print(STDERR "$msg\n"); + print(STDERR "ppmrainbow: $msg\n"); exit(1); } -my ($Twid, $Thgt, $tmpdir, $norepeat, $verbose); + + +############################################################################## +# +# MAINLINE +# +############################################################################## + +doVersionHack(\@ARGV); + +my ($Twid, $Thgt, $tmpdir, $repeat, $verbose); # set defaults $Twid = 600; $Thgt = 8; $tmpdir = $ENV{"TMPDIR"} || "/tmp"; -$norepeat = $FALSE; +$repeat = $TRUE; $verbose = $FALSE; GetOptions("width=i" => \$Twid, "height=i" => \$Thgt, "tmpdir=s" => \$tmpdir, - "norepeat!" => \$norepeat, + "repeat!" => \$repeat, "verbose!" => \$verbose); if ($Twid < 1 || $Thgt < 1) { @@ -59,7 +85,7 @@ my $verboseCommand = $verbose ? "set -x;" : ""; if (@ARGV < 1) { fatal("You must specify at least one color as an argument"); -} elsif (@ARGV < 2 && $norepeat) { +} elsif (@ARGV < 2 && ! $repeat) { fatal("With the -norepeat option, you must specify at least two colors " . "as arguments."); } @@ -67,14 +93,11 @@ if (@ARGV < 1) { my @colorlist; @colorlist = @ARGV; -if (!$norepeat) { +if ($repeat) { push @colorlist, $ARGV[0]; } -my $ourtmp = "$tmpdir/ppmrainbow$$"; -mkdir($ourtmp, 0777) or - die("Unable to create directory for temporary files '$ourtmp"); - +my $ourtmp = File::Temp::tempdir("$tmpdir/ppmrainbowXXXX", UNLINK=>1); my $widthRemaining; my $n; @@ -92,7 +115,7 @@ while (@colorlist >= 2) { my $rc = system("$verboseCommand pgmramp -lr $w $Thgt | " . "pgmtoppm \"$colorlist[0]-$colorlist[1]\" >$outfile"); if ($rc != 0) { - fatal("pgmramp|pgmtoppm failed."); + fatal("pgmramp|pgmtoppm pipe failed."); } $widthRemaining -= $w; $n++; diff --git a/generator/ppmwheel.c b/generator/ppmwheel.c index ef5021f9..29e4730c 100644 --- a/generator/ppmwheel.c +++ b/generator/ppmwheel.c @@ -16,142 +16,245 @@ #include <string.h> #include <math.h> +#include "pm_c_util.h" +#include "mallocvar.h" +#include "shhopt.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) { + +typedef enum {WT_HUE_VAL, WT_HUE_SAT, WT_PPMCIRC} WheelType; + + +struct CmdlineInfo { + unsigned int diameter; + WheelType wheelType; + pixval maxval; +}; + + + +static void +parseCommandLine(int argc, const char **argv, + struct CmdlineInfo * const cmdlineP) { /*---------------------------------------------------------------------------- - This is a stripped down hsv->rgb converter that works only for - Saturation of zero. + 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. -----------------------------------------------------------------------------*/ - 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; + optEntry * option_def; + /* Instructions to OptParseOptions3 on how to parse our options. + */ + optStruct3 opt; + + unsigned int maxvalSpec, huevalueSpec, huesaturationSpec; + unsigned int option_def_index; + + MALLOCARRAY_NOFAIL(option_def, 100); + + option_def_index = 0; + OPTENT3(0, "maxval", OPT_UINT, + &cmdlineP->maxval, &maxvalSpec, 0); + OPTENT3(0, "huevalue", OPT_FLAG, + NULL, &huevalueSpec, 0); + OPTENT3(0, "huesaturation", OPT_FLAG, + NULL, &huesaturationSpec, 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 */ + + pm_optParseOptions3(&argc, (char **)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 ( 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); + if (huevalueSpec + huesaturationSpec > 1) + pm_error("You may specify at most one of " + "-huevalue and -huesaturation"); + + cmdlineP->wheelType = + huevalueSpec ? WT_HUE_VAL : + huesaturationSpec ? WT_HUE_SAT : + WT_PPMCIRC; + + if (argc-1 != 1) { + pm_error("Need 1 argument diameter of the wheel in pixels"); } else { - v *= 2.0; - v = sqrt (sqrt ( sqrt (v))); - *r *= v; - *g *= v; - *b *= v; + const char * const diameterArg = argv[1]; + + if (strlen(diameterArg) == 0) + pm_error("Diameter argument is a null string"); + else { + long argNumber; + char * tailptr; + argNumber = strtol(diameterArg, &tailptr, 10); + + if (*tailptr != '\0') + pm_error("You specified an invalid number as diameter: '%s'", + diameterArg); + if (argNumber <= 0) + pm_error("Diameter must be positive. You specified %ld.", + argNumber); + if (argNumber < 4) + pm_error("Diameter must be at least 4. You specified %ld", + argNumber); + + cmdlineP->diameter = argNumber; + } } + free(option_def); } -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); +static pixel +ppmcircColor(pixel const normalColor, + pixval const maxval, + double const d) { +/*---------------------------------------------------------------------------- + The color that Ppmcirc (by Peter Kirchgessner, not part of Netpbm) puts at + 'd' units from the center where the normal color in a hue-value color wheel + is 'normalColor'. + + We have no idea what the point of this is. +-----------------------------------------------------------------------------*/ + pixel retval; + + if (d >= 0.5) { + double const scale = sqrt(2.0 - 2.0 * d); + + PPM_ASSIGN(retval, + maxval - scale * (maxval - normalColor.r/d), + maxval - scale * (maxval - normalColor.g/d), + maxval - scale * (maxval - normalColor.b/d)); + } else if (d == 0.0) { + PPM_ASSIGN(retval, 0, 0, 0); + } else { + double const scale = sqrt(sqrt(sqrt(2.0 * d)))/d; + PPM_ASSIGN(retval, + normalColor.r * scale, + normalColor.g * scale, + normalColor.b * scale); + } + return retval; +} + - radius = diameter/2 - 1; - xcenter = cols / 2; - ycenter = rows / 2; +static pixel +wheelColor(WheelType const wheelType, + double const dx, + double const dy, + double const radius, + pixval const maxval) { + + double const dist = sqrt(SQR(dx) + SQR(dy)); + + pixel retval; + + if (dist > radius) { + retval = ppm_whitepixel(maxval); + } else { + double const hue90 = atan2(dx, dy) / PI * 180.0; + struct hsv hsv; + + hsv.h = hue90 < 0.0 ? 360.0 + hue90 : hue90; + + switch (wheelType) { + case WT_HUE_SAT: + hsv.v = 1.0; + hsv.s = dist / radius; + retval = ppm_color_from_hsv(hsv, maxval); + break; + case WT_HUE_VAL: + hsv.s = 1.0; + hsv.v = dist / radius; + retval = ppm_color_from_hsv(hsv, maxval); + break; + case WT_PPMCIRC: + hsv.s = 1.0; + hsv.v = dist / radius; + { + pixel const hvColor = ppm_color_from_hsv(hsv, maxval); + retval = ppmcircColor(hvColor, maxval, dist/radius); + } + break; + } + } + return retval; +} + + + +static void +ppmwheel(WheelType const wheelType, + unsigned int const diameter, + pixval const maxval, + FILE * const ofP) { + + unsigned int const cols = diameter; + unsigned int const rows = diameter; + unsigned int const radius = diameter/2 - 1; + unsigned int const xcenter = cols / 2; + unsigned int const ycenter = rows / 2; + + unsigned int row; + pixel * orow; + + orow = ppm_allocrow(cols); + + ppm_writeppminit(ofP, cols, rows, maxval, 0); 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; + orow[col] = wheelColor(wheelType, dx, dy, radius, maxval); + } + ppm_writeppmrow(ofP, orow, cols, maxval, 0); + } + ppm_freerow(orow); +} - 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); +int +main(int argc, const char ** argv) { + + struct CmdlineInfo cmdline; + + pm_proginit(&argc, argv); + + parseCommandLine(argc, argv, &cmdline); + + ppmwheel(cmdline.wheelType, cmdline.diameter, cmdline.maxval, stdout); - 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); + return 0; } + + |