diff options
Diffstat (limited to 'generator/pamstereogram.c')
-rw-r--r-- | generator/pamstereogram.c | 947 |
1 files changed, 740 insertions, 207 deletions
diff --git a/generator/pamstereogram.c b/generator/pamstereogram.c index 0ce63853..6e5f5ce0 100644 --- a/generator/pamstereogram.c +++ b/generator/pamstereogram.c @@ -8,12 +8,15 @@ * The core of this program is a simple adaptation of the code in * "Displaying 3D Images: Algorithms for Single Image Random Dot * Stereograms" by Harold W. Thimbleby, Stuart Inglis, and Ian - * H. Witten in IEEE Computer, 27(10):38-48, October 1994. See that - * paper for a thorough explanation of what's going on here. + * H. Witten in IEEE Computer, 27(10):38-48, October 1994 plus some + * enhancements presented in "Stereograms: Technical Details" by + * W. A. Steer at http://www.techmind.org/stereo/stech.html. See + * those references for a thorough explanation of what's going on + * here. * * ---------------------------------------------------------------------- * - * Copyright (C) 2006 Scott Pakin <scott+pbm@pakin.org> + * Copyright (C) 2006-2015 Scott Pakin <scott+pbm@pakin.org> * * All rights reserved. * @@ -43,20 +46,21 @@ * ---------------------------------------------------------------------- */ +#define _ISOC99_SOURCE /* Make sure strtof() is in <stdlib.h> */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <time.h> +#include <limits.h> #include <assert.h> #include "pm_config.h" #include "pm_c_util.h" -#include "pam.h" -#include "shhopt.h" #include "mallocvar.h" +#include "nstring.h" +#include "shhopt.h" +#include "pam.h" -/* Define a few helper macros. */ -#define round2int(X) ((int)((X)+0.5)) /* Nonnegative numbers only */ enum outputType {OUTPUT_BW, OUTPUT_GRAYSCALE, OUTPUT_COLOR}; @@ -71,25 +75,65 @@ struct cmdlineInfo { unsigned int crosseyed; /* -crosseyed option */ unsigned int makemask; /* -makemask option */ unsigned int dpi; /* -dpi option */ - float eyesep; /* -eyesep option */ - float depth; /* -depth option */ + float eyesep; /* -eyesep option */ + float depth; /* -depth option */ unsigned int maxvalSpec; /* -maxval option count */ - unsigned int maxval; /* -maxval option value x*/ - int guidesize; /* -guidesize option */ + unsigned int maxval; /* -maxval option value */ + unsigned int guidetop; /* -guidetop option count */ + unsigned int guidebottom; /* -guidebottom option count */ + unsigned int guidesize; /* -guidesize option value */ unsigned int magnifypat; /* -magnifypat option */ - unsigned int xshift; /* -xshift option */ - unsigned int yshift; /* -yshift option */ - const char * patFilespec; /* -patfile option. Null if none */ + int xshift; /* -xshift option */ + int yshift; /* -yshift option */ + const char * patfile; /* -patfile option. Null if none */ + const char * texfile; /* -texfile option. Null if none */ + const char * bgcolor; /* -bgcolor option */ + unsigned int smoothing; /* -smoothing option */ unsigned int randomseed; /* -randomseed option */ + unsigned int randomseedSpec; /* -randomseed option count */ enum outputType outputType; /* Type of output file */ + unsigned int xbegin; /* -xbegin option */ + unsigned int xbeginSpec; /* -xbegin option count */ }; static void -parseCommandLine(int argc, - char ** argv, - struct cmdlineInfo *cmdlineP ) { +parseNearFarPlanes(const char ** const nearFarPlanes, + float * const nearPlaneP, + float * const farPlaneP) { +/*---------------------------------------------------------------------------- + Parse nearFarPlanes option value into exactly two positive numbers +-----------------------------------------------------------------------------*/ + float nearPlane, farPlane; + + if (nearFarPlanes == NULL || nearFarPlanes[0] == NULL || + nearFarPlanes[1] == NULL || nearFarPlanes[2] != NULL) + pm_error("-planes requires exactly two positive numbers"); + + errno = 0; + nearPlane = strtof(nearFarPlanes[0], NULL); + if (errno != 0 || nearPlane <= 0.0) + pm_error("-planes requires exactly two positive numbers"); + + farPlane = strtof(nearFarPlanes[1], NULL); + if (errno != 0 || farPlane <= 0.0) + pm_error("-planes requires exactly two positive numbers"); + + if (nearPlane >= farPlane) + pm_error("-planes requires the near-plane value " + "to be less than the far-plane value"); + + *nearPlaneP = nearPlane; + *farPlaneP = farPlane; +} + + + +static void +parseCommandLine(int argc, + const char ** argv, + struct cmdlineInfo * cmdlineP ) { /*---------------------------------------------------------------------------- Parse program command line described in Unix standard form by argc and argv. Return the information in the options as *cmdlineP. @@ -101,17 +145,18 @@ parseCommandLine(int argc, was passed to us as the argv array. We also trash *argv. -----------------------------------------------------------------------------*/ optEntry *option_def; - /* Instructions to optParseOptions3 on how to parse our options. + /* Instructions to pm_optParseOptions3 on how to parse our options. */ optStruct3 opt; unsigned int option_def_index; - unsigned int patfileSpec, dpiSpec, eyesepSpec, depthSpec, - guidesizeSpec, magnifypatSpec, xshiftSpec, yshiftSpec, randomseedSpec; + unsigned int patfileSpec, texfileSpec, dpiSpec, eyesepSpec, depthSpec, + guidesizeSpec, magnifypatSpec, xshiftSpec, yshiftSpec, + bgcolorSpec, smoothingSpec, planesSpec; unsigned int blackandwhite, grayscale, color; - + const char ** planes; MALLOCARRAY_NOFAIL(option_def, 100); @@ -136,27 +181,40 @@ parseCommandLine(int argc, &depthSpec, 0); OPTENT3(0, "maxval", OPT_UINT, &cmdlineP->maxval, &cmdlineP->maxvalSpec, 0); - OPTENT3(0, "guidesize", OPT_INT, &cmdlineP->guidesize, + OPTENT3(0, "guidetop", OPT_FLAG, NULL, + &cmdlineP->guidetop, 0); + OPTENT3(0, "guidebottom", OPT_FLAG, NULL, + &cmdlineP->guidebottom, 0); + OPTENT3(0, "guidesize", OPT_UINT, &cmdlineP->guidesize, &guidesizeSpec, 0); OPTENT3(0, "magnifypat", OPT_UINT, &cmdlineP->magnifypat, &magnifypatSpec, 0); - OPTENT3(0, "xshift", OPT_UINT, &cmdlineP->xshift, + OPTENT3(0, "xshift", OPT_INT, &cmdlineP->xshift, &xshiftSpec, 0); - OPTENT3(0, "yshift", OPT_UINT, &cmdlineP->yshift, + OPTENT3(0, "yshift", OPT_INT, &cmdlineP->yshift, &yshiftSpec, 0); - OPTENT3(0, "patfile", OPT_STRING, &cmdlineP->patFilespec, + OPTENT3(0, "patfile", OPT_STRING, &cmdlineP->patfile, &patfileSpec, 0); + OPTENT3(0, "texfile", OPT_STRING, &cmdlineP->texfile, + &texfileSpec, 0); + OPTENT3(0, "bgcolor", OPT_STRING, &cmdlineP->bgcolor, + &bgcolorSpec, 0); OPTENT3(0, "randomseed", OPT_UINT, &cmdlineP->randomseed, - &randomseedSpec, 0); + &cmdlineP->randomseedSpec, 0); + OPTENT3(0, "smoothing", OPT_UINT, &cmdlineP->smoothing, + &smoothingSpec, 0); + OPTENT3(0, "planes", OPT_STRINGLIST, &planes, + &planesSpec, 0); + OPTENT3(0, "xbegin", OPT_UINT, &cmdlineP->xbegin, + &cmdlineP->xbeginSpec, 0); opt.opt_table = option_def; opt.short_allowed = FALSE; /* We have no short (old-fashioned) options */ opt.allowNegNum = FALSE; /* We have no parms that are negative numbers */ - optParseOptions3( &argc, argv, opt, sizeof(opt), 0); + pm_optParseOptions3(&argc, (char **)argv, opt, sizeof(opt), 0); /* Uses and sets argc, argv, and some of *cmdlineP and others. */ - if (blackandwhite + grayscale + color == 0) cmdlineP->outputType = OUTPUT_BW; else if (blackandwhite + grayscale + color > 1) @@ -173,15 +231,21 @@ parseCommandLine(int argc, } } if (!patfileSpec) - cmdlineP->patFilespec = NULL; + cmdlineP->patfile = NULL; + if (!texfileSpec) + cmdlineP->texfile = NULL; + if (!bgcolorSpec) + cmdlineP->bgcolor = NULL; + if (!smoothingSpec) + cmdlineP->smoothing = 0; if (!dpiSpec) - cmdlineP->dpi = 96; + cmdlineP->dpi = 100; else if (cmdlineP->dpi < 1) pm_error("The argument to -dpi must be a positive integer"); if (!eyesepSpec) - cmdlineP->eyesep = 2.5; + cmdlineP->eyesep = 2.56; else if (cmdlineP->eyesep <= 0.0) pm_error("The argument to -eyesep must be a positive number"); @@ -197,9 +261,16 @@ parseCommandLine(int argc, pm_error("-maxval must be at most %u. You specified %u", PNM_OVERALLMAXVAL, cmdlineP->maxval); } + if (bgcolorSpec && !texfileSpec) + pm_message("warning: -bgcolor has no effect " + "except in conjunction with -texfile"); + + if (guidesizeSpec && !(cmdlineP->guidetop || cmdlineP->guidebottom)) + pm_error("-guidesize has no meaning " + "without -guidetop or -guidebottom"); if (!guidesizeSpec) - cmdlineP->guidesize = 0; + cmdlineP->guidesize = 20; if (!magnifypatSpec) cmdlineP->magnifypat = 1; @@ -212,26 +283,42 @@ parseCommandLine(int argc, if (!yshiftSpec) cmdlineP->yshift = 0; - if (!randomseedSpec) - cmdlineP->randomseed = time(NULL); - - if (xshiftSpec && !cmdlineP->patFilespec) + if (xshiftSpec && !cmdlineP->patfile) pm_error("-xshift is valid only with -patfile"); - if (yshiftSpec && !cmdlineP->patFilespec) + if (yshiftSpec && !cmdlineP->patfile) pm_error("-yshift is valid only with -patfile"); - if (cmdlineP->makemask && cmdlineP->patFilespec) + if (cmdlineP->makemask && cmdlineP->patfile) pm_error("You may not specify both -makemask and -patfile"); - if (cmdlineP->patFilespec && blackandwhite) + if (cmdlineP->patfile && blackandwhite) pm_error("-blackandwhite is not valid with -patfile"); - if (cmdlineP->patFilespec && grayscale) + if (cmdlineP->patfile && grayscale) pm_error("-grayscale is not valid with -patfile"); - if (cmdlineP->patFilespec && color) + if (cmdlineP->patfile && color) pm_error("-color is not valid with -patfile"); - if (cmdlineP->patFilespec && cmdlineP->maxvalSpec) + if (cmdlineP->patfile && cmdlineP->maxvalSpec) pm_error("-maxval is not valid with -patfile"); + if (cmdlineP->texfile && blackandwhite) + pm_error("-blackandwhite is not valid with -texfile"); + if (cmdlineP->texfile && grayscale) + pm_error("-grayscale is not valid with -texfile"); + if (cmdlineP->texfile && color) + pm_error("-color is not valid with -texfile"); + if (cmdlineP->texfile && cmdlineP->maxvalSpec) + pm_error("-maxval is not valid with -texfile"); + if (planesSpec && eyesepSpec) + pm_error("-planes is not valid with -eyesep"); + if (planesSpec && depthSpec) + pm_error("-planes is not valid with -depth"); + + if (planesSpec) { + float nearPlane, farPlane; + parseNearFarPlanes(planes, &nearPlane, &farPlane); + cmdlineP->eyesep = 2.0*farPlane/cmdlineP->dpi; + cmdlineP->depth = 2.0*(farPlane-nearPlane) / (2.0*farPlane-nearPlane); + } if (argc-1 < 1) cmdlineP->inputFilespec = "-"; @@ -244,7 +331,7 @@ parseCommandLine(int argc, -static int +static unsigned int separation(double const dist, double const eyesep, unsigned int const dpi, @@ -254,9 +341,9 @@ separation(double const dist, Return a separation in pixels which corresponds to a 3-D distance between the viewer's eyes and a point on an object. -----------------------------------------------------------------------------*/ - int const pixelEyesep = round2int(eyesep * dpi); + unsigned int const pixelEyesep = ROUNDU(eyesep * dpi); - return round2int((1.0 - dof * dist) * pixelEyesep / (2.0 - dof * dist)); + return ROUNDU((1.0 - dof * dist) * pixelEyesep / (2.0 - dof * dist)); } @@ -282,13 +369,27 @@ typedef tuple coord2Color(struct outGenerator *, int, int); /* A type to use for functions that map a 2-D coordinate to a color. */ typedef void outGenStateTerm(struct outGenerator *); +typedef struct { + struct pam pam; + tuple ** imageData; + tuple bgColor; + bool replaceBgColor; + /* replace background color with pattern color */ + unsigned int smoothing; + /* Number of background-smoothing iterations to perform */ +} texState; typedef struct outGenerator { - struct pam pam; - coord2Color * getTuple; + struct pam pam; + coord2Color * getTuple; /* Map from a height-map (x,y) coordinate to a tuple */ outGenStateTerm * terminateState; - void * stateP; + void * stateP; + texState * textureP; + /* Mapped-texture part of the state of operation. Null + means we are doing an ordinary stereogram instead of a + mapped-texture one. + */ } outGenerator; @@ -401,8 +502,8 @@ struct patternPixelState { /* This is the state of a patternPixel generator.*/ struct pam patPam; /* Descriptor of pattern image */ tuple ** patTuples; /* Entire image read from the pattern file */ - unsigned int xshift; - unsigned int yshift; + int xshift; + int yshift; unsigned int magnifypat; }; @@ -465,9 +566,9 @@ initPatternPixel(outGenerator * const outGenP, MALLOCVAR_NOFAIL(stateP); - assert(cmdline.patFilespec); - - patternFileP = pm_openr(cmdline.patFilespec); + assert(cmdline.patfile); + + patternFileP = pm_openr(cmdline.patfile); stateP->patTuples = pnm_readpam(patternFileP, @@ -495,22 +596,92 @@ initPatternPixel(outGenerator * const outGenP, static void +readTextureImage(struct cmdlineInfo const cmdline, + const struct pam * const inpamP, + const struct pam * const outpamP, + texState ** const texturePP) { + + FILE * textureFileP; + texState * textureP; + struct pam * texPamP; + + MALLOCVAR_NOFAIL(textureP); + texPamP = &textureP->pam; + + textureFileP = pm_openr(cmdline.texfile); + textureP->imageData = + pnm_readpam(textureFileP, texPamP, PAM_STRUCT_SIZE(tuple_type)); + pm_close(textureFileP); + + if (cmdline.bgcolor) + textureP->bgColor = + pnm_parsecolor(cmdline.bgcolor, texPamP->maxval); + else + textureP->bgColor = + pnm_backgroundtuple(texPamP, textureP->imageData); + textureP->replaceBgColor = (cmdline.patfile != NULL); + textureP->smoothing = cmdline.smoothing; + + if (cmdline.verbose) { + const char * const colorname = + pnm_colorname(texPamP, textureP->bgColor, 1); + + reportImageParameters("Texture file", texPamP); + if (cmdline.bgcolor && strcmp(colorname, cmdline.bgcolor)) + pm_message("Texture background color: %s (%s)", + cmdline.bgcolor, colorname); + else + pm_message("Texture background color: %s", colorname); + pm_strfree(colorname); + } + + if (texPamP->width != inpamP->width || texPamP->height != inpamP->height) + pm_error("The texture image must have the same width and height " + "as the input image"); + if (cmdline.patfile && + (!streq(texPamP->tuple_type, outpamP->tuple_type) || + texPamP->maxval != outpamP->maxval)) + pm_error("The texture image must be of the same tuple type " + "and maxval as the pattern image"); + + textureP->pam.file = outpamP->file; + + *texturePP = textureP; +} + + + +static unsigned int +totalGuideHeight(struct cmdlineInfo const cmdline) { + + /* Each pair of guides is cmdline.guidesize high, and we add that much + white above and below as well, so the total vertical space is three + times cmdline.giudesize. + */ + + return + (cmdline.guidetop ? 3 * cmdline.guidesize : 0) + + (cmdline.guidebottom ? 3 * cmdline.guidesize : 0); +} + + + +static void createoutputGenerator(struct cmdlineInfo const cmdline, const struct pam * const inPamP, outGenerator ** const outputGeneratorPP) { outGenerator * outGenP; - + MALLOCVAR_NOFAIL(outGenP); outGenP->pam.size = sizeof(struct pam); outGenP->pam.len = PAM_STRUCT_SIZE(tuple_type); outGenP->pam.file = stdout; - outGenP->pam.height = inPamP->height + 3 * abs(cmdline.guidesize); - /* Allow room for guides. */ + outGenP->pam.height = inPamP->height + totalGuideHeight(cmdline); outGenP->pam.width = inPamP->width; - if (cmdline.patFilespec) { + if (cmdline.patfile) { /* Background pixels should come from the pattern file. */ initPatternPixel(outGenP, cmdline); @@ -522,6 +693,12 @@ createoutputGenerator(struct cmdlineInfo const cmdline, outGenP->pam.bytes_per_sample = pnm_bytespersample(outGenP->pam.maxval); + if (cmdline.texfile) { + readTextureImage(cmdline, inPamP, &outGenP->pam, &outGenP->textureP); + outGenP->pam = outGenP->textureP->pam; + } else + outGenP->textureP = NULL; + *outputGeneratorPP = outGenP; } @@ -544,7 +721,7 @@ static void makeWhiteRow(const struct pam * const pamP, tuple * const tuplerow) { - int col; + unsigned int col; for (col = 0; col < pamP->width; ++col) { unsigned int plane; @@ -561,144 +738,205 @@ writeRowCopies(const struct pam * const outPamP, unsigned int const copyCount) { unsigned int i; + for (i = 0; i < copyCount; ++i) pnm_writepamrow(outPamP, outrow); } -/* Draw a pair of guide boxes. */ static void -drawguides(int const guidesize, +writeWhiteRows(const struct pam * const outPamP, + unsigned int const count) { + + tuple * outrow; /* One row of output data */ + + outrow = pnm_allocpamrow(outPamP); + + makeWhiteRow(outPamP, outrow); + + writeRowCopies(outPamP, outrow, count); + + pnm_freerow(outrow); +} + + + +static void +drawguides(unsigned int const guidesize, const struct pam * const outPamP, double const eyesep, unsigned int const dpi, double const depthOfField) { - - int const far = separation(0, eyesep, dpi, depthOfField); +/*---------------------------------------------------------------------------- + Draw a pair of guide boxes, left and right. +-----------------------------------------------------------------------------*/ + unsigned int const far = separation(0, eyesep, dpi, depthOfField); /* Space between the two guide boxes. */ - int const width = outPamP->width; /* Width of the output image */ + unsigned int const width = outPamP->width; /* Width of the output image */ - tuple *outrow; /* One row of output data */ + tuple * outrow; /* One row of output data */ tuple blackTuple; - int col; + unsigned int col; pnm_createBlackTuple(outPamP, &blackTuple); outrow = pnm_allocpamrow(outPamP); - /* Leave some blank rows before the guides. */ + /* Put some white rows before the guides */ + writeWhiteRows(outPamP, guidesize); + + /* Initialize the row buffer to white */ makeWhiteRow(outPamP, outrow); - writeRowCopies(outPamP, outrow, guidesize); - /* Draw the guides. */ - if ((width - far + guidesize)/2 < 0 || - (width + far - guidesize)/2 >= width) + if (far > width + guidesize) pm_message("warning: the guide boxes are completely out of bounds " - "at %d DPI", dpi); - else if ((width - far - guidesize)/2 < 0 || - (width + far + guidesize)/2 >= width) - pm_message("warning: the guide boxes are partially out of bounds " - "at %d DPI", dpi); - - for (col = (width - far - guidesize)/2; - col < (width - far + guidesize)/2; - ++col) - if (col >= 0 && col < width) - pnm_assigntuple(outPamP, outrow[col], blackTuple); + "at %u DPI", dpi); + else { + unsigned int leftBeg, leftEnd, rightBeg, rightEnd; + + assert(far <= width + guidesize); + leftEnd = (width - far + guidesize)/2; + assert(guidesize <= width + far); + rightBeg = (width + far - guidesize)/2; + + if (far + guidesize > width) { + pm_message("warning: the guide boxes are partially out of bounds " + "at %u DPI", dpi); + + leftBeg = 0; + rightEnd = width; + } else { + assert(far + guidesize <= width); + leftBeg = (width - far - guidesize)/2; + rightEnd = (width + far + guidesize)/2; + } - for (col = (width + far - guidesize)/2; - col < (width + far + guidesize)/2; - ++col) - if (col >= 0 && col < width) + /* Draw the left guide black in the buffer */ + assert(leftEnd < outPamP->width); + for (col = leftBeg; col < leftEnd; ++col) pnm_assigntuple(outPamP, outrow[col], blackTuple); - writeRowCopies(outPamP,outrow, guidesize); + /* Draw the right guide black in the buffer */ + assert(rightEnd <= outPamP->width); + for (col = rightBeg; col < rightEnd; ++col) + pnm_assigntuple(outPamP, outrow[col], blackTuple); + } + /* Write out the guide rows */ - /* Leave some blank rows after the guides. */ - makeWhiteRow(outPamP, outrow); writeRowCopies(outPamP, outrow, guidesize); + /* Put some white rows after the guides */ + writeWhiteRows(outPamP, guidesize); + pnm_freerow(outrow); } -/* Do the bulk of the work. See the paper cited above for code - * comments. All I (Scott) did was transcribe the code and make - * minimal changes for Netpbm. And some style changes by Bryan to - * match Netpbm style. - */ static void makeStereoRow(const struct pam * const inPamP, tuple * const inRow, - int * const same, + unsigned int * const sameL, + unsigned int * const sameR, double const depthOfField, double const eyesep, - unsigned int const dpi) { + unsigned int const dpi, + unsigned int const optWidth, + unsigned int const smoothing) { -#define Z(X) (1.0-inRow[X][0]/(double)inPamP->maxval) +/* Given a row of the depth map, compute the sameL and sameR arrays, + * which indicate for each pixel which pixel to its left and right it + * should be colored the same as. + */ +#define Z(X) (inRow[X][0]/(double)inPamP->maxval) - int const width = inPamP->width; - int const pixelEyesep = round2int(eyesep * dpi); - /* Separation in pixels between the viewer's eyes */ + unsigned int col; - int col; + for (col = 0; col < inPamP->width; ++col) { + sameL[col] = col; + sameR[col] = col; + } - for (col = 0; col < width; ++col) - same[col] = col; - - for (col = 0; col < width; ++col) { - int const s = separation(Z(col), eyesep, dpi, depthOfField); - int left, right; - - left = col - s/2; /* initial value */ - right = left + s; /* initial value */ - - if (0 <= left && right < width) { - int visible; - int t; - double zt; - - t = 1; /* initial value */ - - do { - double const dof = depthOfField; - zt = Z(col) + 2.0*(2.0 - dof*Z(col))*t/(dof*pixelEyesep); - visible = Z(col-t) < zt && Z(col+t) < zt; - ++t; - } while (visible && zt < 1); - if (visible) { - int l; - - l = same[left]; - while (l != left && l != right) { - if (l < right) { - left = l; - l = same[left]; - } else { - same[left] = right; - left = right; - l = same[left]; - right = l; - } - } - same[left] = right; + for (col = 0; col < inPamP->width; ++col) { + unsigned int const sep = separation(Z(col), eyesep, dpi, depthOfField); + int const left = col - sep/2; + int const right = left + sep; + + if (left >= 0 && right < inPamP->width) { + bool isVisible; + + if (sameL[right] != right) { + /* Right point already linked */ + if (sameL[right] < left) { + /* Deeper than current */ + sameR[sameL[right]] = sameL[right]; /* Break old links. */ + sameL[right] = right; + isVisible = TRUE; + } else + isVisible = FALSE; + } else + isVisible = TRUE; + + if (sameR[left] != left) { + /* Left point already linked */ + if (sameR[left] > right) { + /* Deeper than current */ + sameL[sameR[left]] = sameR[left]; /* Break old links. */ + sameR[left] = left; + isVisible = TRUE; + } else + isVisible = FALSE; + } else + isVisible = TRUE; + + if (isVisible) { + /* Make a link. */ + sameL[right] = left; + sameR[left] = right; } } } + + /* If smoothing is enabled, replace each non-duplicate pixel with + the pixel adjacent to its right neighbor. + */ + if (smoothing > 0) { + int const baseCol = inPamP->width - optWidth - 1; + + int col; + + for (col = inPamP->width - 1; col >= 0; --col) + sameR[col] = sameR[sameR[col]]; + for (col = baseCol; col >= 0; --col) { + if (sameR[col] == col) + sameR[col] = sameR[col+1] - 1; + } + } } static void -makeMaskRow(const struct pam * const outPamP, - const int * const same, - const tuple * const outRow) { +makeMaskRow(const struct pam * const outPamP, + unsigned int const xbegin, + const unsigned int * const sameL, + const unsigned int * const sameR, + const tuple * const outRow) { int col; - for (col = outPamP->width-1; col >= 0; --col) { - bool const duplicate = (same[col] != col); + for (col = (int)xbegin; col < outPamP->width; ++col) { + bool const duplicate = (sameL[col] != col && sameL[col] >= xbegin); + + unsigned int plane; + + for (plane = 0; plane < outPamP->depth; ++plane) + outRow[col][plane] = duplicate ? outPamP->maxval : 0; + } + + for (col = (int)xbegin - 1; col >= 0; --col) { + bool const duplicate = (sameR[col] != col); + unsigned int plane; for (plane = 0; plane < outPamP->depth; ++plane) @@ -709,37 +947,284 @@ makeMaskRow(const struct pam * const outPamP, static void -makeImageRow(outGenerator * const outGenP, - int const row, - const int * const same, - const tuple * const outRow) { +computeFixedPoint(const unsigned int * const same, + unsigned int * const sameFp, + unsigned int const width) { /*---------------------------------------------------------------------------- - same[N] is one of two things: + Compute the fixed point of same[] (i.e., sameFp[x] is + same[same[same[...[same[x]]...]]]). +-----------------------------------------------------------------------------*/ + int col; - same[N] == N means to generate a value for Column N independent of + for (col = width-1; col >= 0; --col) { + if (same[col] != col) + sameFp[col] = sameFp[same[col]]; + else { + if (col < width-1) + sameFp[col] = sameFp[col + 1] - 1; + else + sameFp[col] = col; + } + } +} + + + +static void +averageFromPattern(struct pam * const pamP, + tuple const bgColor, + const tuple * const textureRow, + const unsigned int * const same, + unsigned int * const sameFp, + const tuple * const outRow, + unsigned int * const tuplesInCol) { +/*---------------------------------------------------------------------------- + Average the color of each non-background pattern tuple to every column that + should have the same color. +-----------------------------------------------------------------------------*/ + int col; + + /* Initialize the tuple sums to zero. */ + + for (col = 0; col < pamP->width; ++col) { + unsigned int plane; + for (plane = 0; plane < pamP->depth; ++plane) + outRow[col][plane] = 0; + tuplesInCol[col] = 0; + } + + /* Accumulate the color of each non-background pattern tuple to + every column that should have the same color. + */ + for (col = pamP->width-1; col >= 0; --col) { + tuple const onetuple = textureRow[(col+same[col])/2]; + unsigned int const targetcol = sameFp[col]; + int eqcol; + + if (!pnm_tupleequal(pamP, onetuple, bgColor)) { + for (eqcol = pamP->width-1; eqcol >= 0; --eqcol) { + if (sameFp[eqcol] == targetcol) { + unsigned int plane; + for (plane = 0; plane < pamP->depth; ++plane) + outRow[eqcol][plane] += onetuple[plane]; + tuplesInCol[eqcol]++; + } + } + } + } + /* Take the average of all colors associated with each column. + Tuples that can be any color are assigned the same color as was + previously assigned to their fixed-point column. + */ + for (col = 0; col < pamP->width; ++col) { + if (tuplesInCol[col] > 0) { + unsigned int plane; + for (plane = 0; plane < pamP->depth; ++plane) + outRow[col][plane] /= tuplesInCol[col]; + } else + pnm_assigntuple(pamP, outRow[col], bgColor); + } +} + + + +static void +smoothOutSpeckles(struct pam * const pamP, + tuple const bgColor, + unsigned int const smoothing, + unsigned int * const tuplesInCol, + tuple * const rowBuffer, + const tuple * const outRow) { +/*---------------------------------------------------------------------------- + Smooth out small speckles of the background color lying between other + colors. +-----------------------------------------------------------------------------*/ + unsigned int i; + + for (i = 0; i < smoothing; ++i) { + int col; + tuple * const scratchrow = rowBuffer; + for (col = pamP->width-2; col >= 1; --col) { + if (tuplesInCol[col] == 0) { + /* Replace a background tuple with the average of its + left and right neighbors. + */ + unsigned int plane; + for (plane = 0; plane < pamP->depth; ++plane) + scratchrow[col][plane] = 0; + if (!pnm_tupleequal(pamP, outRow[col-1], bgColor)) { + for (plane = 0; plane < pamP->depth; ++plane) + scratchrow[col][plane] += outRow[col-1][plane]; + ++tuplesInCol[col]; + } + if (!pnm_tupleequal(pamP, outRow[col+1], bgColor)) { + for (plane = 0; plane < pamP->depth; ++plane) + scratchrow[col][plane] += outRow[col+1][plane]; + ++tuplesInCol[col]; + } + if (tuplesInCol[col] > 0) + for (plane = 0; plane < pamP->depth; ++plane) + scratchrow[col][plane] /= tuplesInCol[col]; + else + pnm_assigntuple(pamP, scratchrow[col], outRow[col]); + } else + pnm_assigntuple(pamP, scratchrow[col], outRow[col]); + } + for (col = 1; col < pamP->width-1; ++col) + pnm_assigntuple(pamP, outRow[col], scratchrow[col]); + } +} + + + +static void +replaceRemainingBackgroundWithPattern(outGenerator * const outGenP, + const unsigned int * const same, + unsigned int const row, + const tuple * const outRow) { + + const struct pam * const pamP = &outGenP->pam; + tuple const bgColor = outGenP->textureP->bgColor; + + if (outGenP->textureP->replaceBgColor) { + int col; + for (col = outGenP->pam.width-1; col >= 0; --col) { + if (pnm_tupleequal(pamP, outRow[col], bgColor)) { + bool const duplicate = (same[col] != col); + + tuple newtuple; + + if (duplicate) { + assert(same[col] > col); + assert(same[col] < outGenP->pam.width); + + newtuple = outRow[same[col]]; + } else + newtuple = outGenP->getTuple(outGenP, col, row); + + pnm_assigntuple(pamP, outRow[col], newtuple); + } + } + } +} + + + +static void +makeImageRowMts(outGenerator * const outGenP, + unsigned int const row, + const unsigned int * const same, + unsigned int * const sameFp, + tuple * const rowBuffer, + const tuple * const outRow) { +/*---------------------------------------------------------------------------- + Make a row of a mapped-texture stereogram. +-----------------------------------------------------------------------------*/ + unsigned int * tuplesInCol; + /* tuplesInCol[C] is the number of tuples averaged together to make + Column C. + */ + MALLOCARRAY(tuplesInCol, outGenP->pam.width); + if (tuplesInCol == NULL) + pm_error("Unable to allocate space for \"tuplesInCol\" array."); + + assert(outGenP->textureP); + /* This is an original algorithm by Scott Pakin. */ + + /* + Compute the fixed point of same[] (i.e., sameFp[x] is + same[same[same[...[same[x]]...]]]). + */ + computeFixedPoint(same, sameFp, outGenP->pam.width); + + /* Average the color of each non-background pattern tuple to + every column that should have the same color. + */ + + averageFromPattern(&outGenP->pam, outGenP->textureP->bgColor, + outGenP->textureP->imageData[row], + same, sameFp, + outRow, tuplesInCol); + + /* Smooth out small speckles of the background color lying between + other colors. + */ + smoothOutSpeckles(&outGenP->pam, outGenP->textureP->bgColor, + outGenP->textureP->smoothing, + tuplesInCol, rowBuffer, outRow); + + /* Replace any remaining background tuples with a pattern tuple. */ + + replaceRemainingBackgroundWithPattern(outGenP, same, row, outRow); + + free(tuplesInCol); +} + + + +static void +makeImageRow(outGenerator * const outGenP, + unsigned int const row, + unsigned int const optWidth, + unsigned int const xbegin, + const unsigned int * const sameL, + const unsigned int * const sameR, + const tuple * const outRow) { +/*---------------------------------------------------------------------------- + sameR[N] is one of two things: + + sameR[N] == N means to generate a value for Column N independent of other columns in the row. - same[N] > N means Column N should be identical to Column same[N]. - - same[N] < N is not allowed. + sameR[N] > N means Column N should be identical to Column sameR[N]. + + sameR[N] < N is not allowed. + + sameL[N] is one of two things: + + sameL[N] == N means to generate a value for Column N independent of + other columns in the row. + + sameL[N] < N means Column N should be identical to Column sameL[N]. + + sameL[N] > N is not allowed. -----------------------------------------------------------------------------*/ int col; - for (col = outGenP->pam.width-1; col >= 0; --col) { - bool const duplicate = (same[col] != col); - + int lastLinked; + + for (col = (int)xbegin, lastLinked = INT_MIN; + col < outGenP->pam.width; + ++col) { + tuple newtuple; - unsigned int plane; - if (duplicate) { - assert(same[col] > col); - assert(same[col] < outGenP->pam.width); + if (sameL[col] == col || sameL[col] < (int)xbegin) { + if (lastLinked == col - 1) + newtuple = outRow[col - 1]; + else + newtuple = outGenP->getTuple(outGenP, col, row); + } else { + newtuple = outRow[sameL[col]]; + lastLinked = col; + /* Keep track of the last pixel to be constrained. */ + } + pnm_assigntuple(&outGenP->pam, outRow[col], newtuple); + } - newtuple = outRow[same[col]]; - } else - newtuple = outGenP->getTuple(outGenP, col, row); + for (col = (int)xbegin - 1, lastLinked = INT_MIN; col >= 0; --col) { + tuple newtuple; - for (plane = 0; plane < outGenP->pam.depth; ++plane) - outRow[col][plane] = newtuple[plane]; + if (sameR[col] == col) { + if (lastLinked == col + 1) + newtuple = outRow[col + 1]; + else + newtuple = outGenP->getTuple(outGenP, col, row); + } else { + newtuple = outRow[sameR[col]]; + lastLinked = col; + /* Keep track of the last pixel to be constrained. */ + } + pnm_assigntuple(&outGenP->pam, outRow[col], newtuple); } } @@ -764,26 +1249,40 @@ makeImageRows(const struct pam * const inPamP, double const eyesep, unsigned int const dpi, bool const crossEyed, - bool const makeMask) { + bool const makeMask, + unsigned int const magnifypat, + unsigned int const smoothing, + unsigned int const xbegin) { tuple * inRow; /* One row of pixels read from the height-map file */ tuple * outRow; /* One row of pixels to write to the height-map file */ - int * same; - /* Array: same[N] is the column number of a pixel to the right forced - to have the same color as the one in column N + unsigned int * sameR; + /* Malloced array: sameR[N] is the column number of a pixel to the + right forced to have the same color as the one in column N + */ + unsigned int * sameL; + /* Malloced array: sameL[N] is the column number of a pixel to the + left forced to have the same color as the one in column N */ - int row; /* Current row in the input and output files */ + unsigned int * sameRfp; + /* Malloced array: Fixed point of sameR[] */ + tuple * rowBuffer; /* Scratch row needed for texture manipulation */ + unsigned int row; /* Current row in the input and output files */ inRow = pnm_allocpamrow(inPamP); outRow = pnm_allocpamrow(&outputGeneratorP->pam); - MALLOCARRAY(same, inPamP->width); - if (same == NULL) - pm_error("Unable to allocate space for \"same\" array."); - - /* See the paper cited above for code comments. All I (Scott) did was - * transcribe the code and make minimal changes for Netpbm. And some - * style changes by Bryan to match Netpbm style. - */ + MALLOCARRAY(sameR, inPamP->width); + if (sameR == NULL) + pm_error("Unable to allocate space for \"sameR\" array."); + MALLOCARRAY(sameL, inPamP->width); + if (sameL == NULL) + pm_error("Unable to allocate space for \"sameL\" array."); + + MALLOCARRAY(sameRfp, inPamP->width); + if (sameRfp == NULL) + pm_error("Unable to allocate space for \"sameRfp\" array."); + rowBuffer = pnm_allocpamrow(&outputGeneratorP->pam); + for (row = 0; row < inPamP->height; ++row) { pnm_readpamrow(inPamP, inRow); if (crossEyed) @@ -793,17 +1292,29 @@ makeImageRows(const struct pam * const inPamP, invertHeightRow(inPamP, inRow); /* Determine color constraints. */ - makeStereoRow(inPamP, inRow, same, depthOfField, eyesep, dpi); + makeStereoRow(inPamP, inRow, sameL, sameR, depthOfField, eyesep, dpi, + ROUNDU(eyesep * dpi)/(magnifypat * 2), + smoothing); if (makeMask) - makeMaskRow(&outputGeneratorP->pam, same, outRow); - else - makeImageRow(outputGeneratorP, row, same, outRow); - + makeMaskRow(&outputGeneratorP->pam, xbegin, sameL, sameR, outRow); + else { + if (outputGeneratorP->textureP) + makeImageRowMts(outputGeneratorP, row, sameR, sameRfp, + rowBuffer, outRow); + else + makeImageRow(outputGeneratorP, row, + ROUNDU(eyesep * dpi)/(magnifypat * 2), + xbegin, sameL, sameR, outRow); + } /* Write the resulting row. */ pnm_writepamrow(&outputGeneratorP->pam, outRow); } - free(same); + + pnm_freepamrow(rowBuffer); + free(sameRfp); + free(sameL); + free(sameR); pnm_freepamrow(outRow); pnm_freepamrow(inRow); } @@ -817,7 +1328,9 @@ produceStereogram(FILE * const ifP, struct pam inPam; /* PAM information for the height-map file */ outGenerator * outputGeneratorP; /* Handle of an object that generates background pixels */ - + unsigned int xbegin; + /* x coordinate separating left-to-right from right-to-left coloring */ + pnm_readpaminit(ifP, &inPam, PAM_STRUCT_SIZE(tuple_type)); createoutputGenerator(cmdline, &inPam, &outputGeneratorP); @@ -832,21 +1345,34 @@ produceStereogram(FILE * const ifP, pnm_writepaminit(&outputGeneratorP->pam); - /* Draw guide boxes at the top, if desired. */ - if (cmdline.guidesize < 0) - drawguides(-cmdline.guidesize, &outputGeneratorP->pam, + if (cmdline.xbeginSpec == 0) + xbegin = outputGeneratorP->pam.width/2; + else { + xbegin = cmdline.xbegin; + if (xbegin >= outputGeneratorP->pam.width) + pm_error("-xbegin must be less than the image width (%d)", + outputGeneratorP->pam.width); + } + + if (cmdline.guidetop) + drawguides(cmdline.guidesize, &outputGeneratorP->pam, cmdline.eyesep, cmdline.dpi, cmdline.depth); makeImageRows(&inPam, outputGeneratorP, cmdline.depth, cmdline.eyesep, cmdline.dpi, - cmdline.crosseyed, cmdline.makemask); + cmdline.crosseyed, cmdline.makemask, cmdline.magnifypat, + cmdline.smoothing, xbegin); - /* Draw guide boxes at the bottom, if desired. */ - if (cmdline.guidesize > 0) + if (cmdline.guidebottom) drawguides(cmdline.guidesize, &outputGeneratorP->pam, cmdline.eyesep, cmdline.dpi, cmdline.depth); + if (cmdline.texfile) { + pnm_freepamarray(outputGeneratorP->textureP->imageData, + &outputGeneratorP->textureP->pam); + free(outputGeneratorP->textureP); + } destroyoutputGenerator(outputGeneratorP); } @@ -855,7 +1381,12 @@ produceStereogram(FILE * const ifP, static void reportParameters(struct cmdlineInfo const cmdline) { - unsigned int const pixelEyesep = round2int(cmdline.eyesep * cmdline.dpi); + unsigned int const pixelEyesep = + ROUNDU(cmdline.eyesep * cmdline.dpi); + unsigned int const sep0 = + separation(0, cmdline.eyesep, cmdline.dpi, cmdline.depth); + unsigned int const sep1 = + separation(1, cmdline.eyesep, cmdline.dpi, cmdline.depth); pm_message("Eye separation: %.4g inch * %d DPI = %u pixels", cmdline.eyesep, cmdline.dpi, pixelEyesep); @@ -863,40 +1394,42 @@ reportParameters(struct cmdlineInfo const cmdline) { if (cmdline.magnifypat > 1) pm_message("Background magnification: %uX * %uX", cmdline.magnifypat, cmdline.magnifypat); - pm_message("\"Optimal\" pattern width: %u / (%u * 2) = %u pixels", + pm_message("\"Optimal\" (far) pattern width: %u / (%u * 2) = %u pixels", pixelEyesep, cmdline.magnifypat, pixelEyesep/(cmdline.magnifypat * 2)); - pm_message("Unique 3-D depth levels possible: %u", - separation(0, cmdline.eyesep, cmdline.dpi, cmdline.depth) - - separation(1, cmdline.eyesep, cmdline.dpi, cmdline.depth) + 1); - if (cmdline.patFilespec && (cmdline.xshift || cmdline.yshift)) - pm_message("Pattern shift: (%u, %u)", cmdline.xshift, cmdline.yshift); + pm_message("Near pattern width: %u / %u = %u pixels", + sep1, cmdline.magnifypat, sep1 / cmdline.magnifypat); + pm_message("Unique 3-D depth levels possible: %u", sep0 - sep1 + 1); + if (cmdline.patfile && (cmdline.xshift || cmdline.yshift)) + pm_message("Pattern shift: (%d, %d)", cmdline.xshift, cmdline.yshift); } int -main(int argc, char *argv[]) { +main(int argc, const char *argv[]) { struct cmdlineInfo cmdline; /* Parsed command line */ FILE * ifP; - - /* Parse the command line. */ - pnm_init(&argc, argv); + + pm_proginit(&argc, argv); parseCommandLine(argc, argv, &cmdline); - + if (cmdline.verbose) reportParameters(cmdline); - - srand(cmdline.randomseed); + + srand(cmdline.randomseedSpec ? cmdline.randomseed : pm_randseed()); ifP = pm_openr(cmdline.inputFilespec); - + /* Produce a stereogram. */ produceStereogram(ifP, cmdline); pm_close(ifP); - + return 0; } + + + |