diff options
Diffstat (limited to 'editor/pammixmulti.c')
-rw-r--r-- | editor/pammixmulti.c | 543 |
1 files changed, 543 insertions, 0 deletions
diff --git a/editor/pammixmulti.c b/editor/pammixmulti.c new file mode 100644 index 00000000..d063d96c --- /dev/null +++ b/editor/pammixmulti.c @@ -0,0 +1,543 @@ +/************************************************* + * Blend multiple Netpbm files into a single one * + * * + * By Scott Pakin <scott+pbm@pakin.org> * + *************************************************/ + +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <float.h> +#include <math.h> +#include "pam.h" +#include "shhopt.h" +#include "mallocvar.h" +#include "nstring.h" + +typedef enum { + BLEND_AVERAGE, /* Take the average color of all pixels */ + BLEND_RANDOM, /* Take each pixel color from a randomly selected image */ + BLEND_MASK /* Take each pixel color from the image indicated by a mask */ +} BlendType; + +static unsigned int const randSamples = 1024; + /* Random samples to draw per file */ + +struct ProgramState { + unsigned int inFileCt; /* Number of input files */ + struct pam * inPam; /* List of input-file PAM structures */ + tuple ** inTupleRows; /* Current row from each input file */ + struct pam outPam; /* Output-file PAM structure */ + tuple * outTupleRow; /* Row to write to the output file */ + const char * maskFileName; /* Name of the image-mask file */ + struct pam maskPam; /* PAM structure for the image mask */ + tuple * maskTupleRow; /* Row to read from the mask file */ + double sigma; + /* Standard deviation when selecting images via a mask */ + unsigned long ** imageWeights; + /* Per-image weights as a function of grayscale level */ +}; + + + +struct CmdlineInfo { + /* All the information the user supplied in the command line, + in a form easy for the program to use. + */ + BlendType blend; + const char * maskfile; + float stdev; + unsigned int randomseed; + unsigned int randomseedSpec; + unsigned int inFileNameCt; /* Number of input files */ + const char ** inFileName; /* Name of each input file */ +}; + + + +static void +freeCmdline(struct CmdlineInfo * const cmdlineP) { + + free(cmdlineP->inFileName); +} + + + +static void +parseCommandLine(int argc, const char ** argv, + struct CmdlineInfo * const cmdlineP) { + + optStruct3 opt; + unsigned int option_def_index = 0; + optEntry * option_def; + unsigned int blendSpec, maskfileSpec, stdevSpec; + const char * blendOpt; + + /* Define the allowed command-line options. */ + MALLOCARRAY(option_def, 100); + OPTENT3(0, "blend", OPT_STRING, &blendOpt, + &blendSpec, 0); + OPTENT3(0, "maskfile", OPT_STRING, &cmdlineP->maskfile, + &maskfileSpec, 0); + OPTENT3(0, "stdev", OPT_FLOAT, &cmdlineP->stdev, + &stdevSpec, 0); + OPTENT3(0, "randomseed", OPT_UINT, &cmdlineP->randomseed, + &cmdlineP->randomseedSpec, 0); + + opt.opt_table = option_def; + opt.short_allowed = 0; + opt.allowNegNum = 0; + + pm_optParseOptions3(&argc, (char **) argv, opt, sizeof(opt), 0); + if (blendSpec) { + if (streq(blendOpt, "average")) + cmdlineP->blend = BLEND_AVERAGE; + else if (streq(blendOpt, "random")) + cmdlineP->blend = BLEND_RANDOM; + else if (streq(blendOpt, "mask")) + cmdlineP->blend = BLEND_MASK; + else + pm_error("Unrecognized -blend value '%s'. " + "We recognize 'average', 'random', and 'mask'", blendOpt); + } else + cmdlineP->blend = BLEND_AVERAGE; + + if (cmdlineP->blend == BLEND_MASK) { + if (!maskfileSpec) + pm_error("Because you specified -blend=mask, " + "you must also specify -maskfile"); + } else { + if (maskfileSpec) + pm_message("Ignoring -maskfile because -blend=mask " + "is not specified"); + if (stdevSpec) + pm_message("Ignoring -stdev because -blend=mask " + "is not specified"); + } + if (!stdevSpec) + cmdlineP->stdev = 0.25; + + if (argc-1 < 1) + pm_error("You must specify the names of the files to blend together " + "as arguments"); + + cmdlineP->inFileNameCt = argc-1; + MALLOCARRAY(cmdlineP->inFileName, argc-1); + if (!cmdlineP->inFileName) + pm_error("Unable to allocate space for %u file names", argc-1); + { + unsigned int i; + for (i = 0; i < argc-1; ++i) + cmdlineP->inFileName[i] = argv[1+i]; + } + free(option_def); +} + +static void +openInputFiles(unsigned int const inFileCt, + const char ** const inFileName, + struct ProgramState * const stateP) { +/*---------------------------------------------------------------------------- + Open all of the input files. + + Abort if the input files don't all have the same size and format. +-----------------------------------------------------------------------------*/ + struct pam * inPam; + unsigned int i; + + MALLOCARRAY(inPam, inFileCt); + if (!inPam) + pm_error("Failed to allocated memory for PAM structures for %u " + "input files", inFileCt); + MALLOCARRAY(stateP->inTupleRows, inFileCt); + if (!stateP->inTupleRows) + pm_error("Failed to allocated memory for PAM structures for %u " + "input rasters", inFileCt); + + for (i = 0; i < inFileCt; ++i) { + FILE * const ifP = pm_openr(inFileName[i]); + pnm_readpaminit(ifP, &inPam[i], PAM_STRUCT_SIZE(tuple_type)); + if (inPam[i].width != inPam[0].width || + inPam[i].height != inPam[0].height) + pm_error("Input image %u has different dimensions from " + "earlier input images", i); + if (inPam[i].depth != inPam[0].depth) + pm_error("Input image %u has different depth from " + "earlier input images", i); + if (inPam[i].maxval != inPam[0].maxval) + pm_error("Input image %u has different maxval from " + "earlier input images", i); + if (!streq(inPam[i].tuple_type, inPam[0].tuple_type)) + pm_error("Input image %u has different tuple type from " + "earlier input images", i); + } + + for (i = 0; i < inFileCt; ++i) + stateP->inTupleRows[i] = pnm_allocpamrow(&inPam[i]); + + stateP->inPam = inPam; + stateP->inFileCt = inFileCt; +} + + +static void +initMask(const char * const maskFileName, + struct ProgramState * const stateP) { + + struct pam * const maskPamP = &stateP->maskPam; + + FILE * const mfP = pm_openr(maskFileName); + + pnm_readpaminit(mfP, maskPamP, PAM_STRUCT_SIZE(tuple_type)); + + if (maskPamP->width != stateP->inPam[0].width || + maskPamP->height != stateP->inPam[0].height) { + + pm_error("The mask image does not have have the same dimensions " + "as the input images"); + } + if (maskPamP->depth > 1) + pm_message("Ignoring all but the first channel of the mask image"); + + stateP->maskTupleRow = pnm_allocpamrow(maskPamP); +} + + + +static void +termMask(struct ProgramState * const stateP) { + + unsigned int i; + + for (i = 0; i <= stateP->maskPam.maxval; ++i) + free(stateP->imageWeights[i]); + + free(stateP->imageWeights); + + pnm_freepamrow(stateP->maskTupleRow); + + pm_close(stateP->maskPam.file); +} + + + +static void +initOutput(FILE * const ofP, + struct ProgramState * const stateP) { + + stateP->outPam = stateP->inPam[0]; + stateP->outPam.file = ofP; + stateP->outTupleRow = pnm_allocpamrow(&stateP->outPam); + + pnm_writepaminit(&stateP->outPam); +} + + + +static void +blendTuplesRandom(struct ProgramState * const stateP, + unsigned int const col, + sample * const outSamps) { +/*---------------------------------------------------------------------------- + Blend one tuple of the input images into a new tuple by selecting a tuple + from a random input image. +-----------------------------------------------------------------------------*/ + unsigned int const depth = stateP->inPam[0].depth; + unsigned int const img = (unsigned int) (random() % stateP->inFileCt); + + unsigned int samp; + + for (samp = 0; samp < depth; ++samp) + outSamps[samp] = ((sample *)stateP->inTupleRows[img][col])[samp]; +} + + + +static void +blendTuplesAverage(struct ProgramState * const stateP, + unsigned int const col, + sample * const outSamps) { +/*---------------------------------------------------------------------------- + Blend one tuple of the input images into a new tuple by averaging all input + tuples. +-----------------------------------------------------------------------------*/ + unsigned int const depth = stateP->inPam[0].depth; + + unsigned int samp; + + for (samp = 0; samp < depth; ++samp) { + unsigned int img; + + for (img = 0, outSamps[samp] = 0; img < stateP->inFileCt; ++img) + outSamps[samp] += ((sample *)stateP->inTupleRows[img][col])[samp]; + outSamps[samp] /= stateP->inFileCt; + } +} + + + +static void +randomNormal2(double * const r1P, + double * const r2P) { +/*---------------------------------------------------------------------------- + Return two normally distributed random numbers. +-----------------------------------------------------------------------------*/ + double u1, u2; + + do { + u1 = drand48(); + u2 = drand48(); + } + while (u1 <= DBL_EPSILON); + + *r1P = sqrt(-2.0*log(u1)) * cos(2.0*M_PI*u2); + *r2P = sqrt(-2.0*log(u1)) * sin(2.0*M_PI*u2); +} + + + +static void +precomputeImageWeights(struct ProgramState * const stateP, + double const sigma) { +/*---------------------------------------------------------------------------- + Precompute the weight to give to each image as a function of grayscale + level. +-----------------------------------------------------------------------------*/ + unsigned int const maxGray = (unsigned int) stateP->maskPam.maxval; + + unsigned int i; + + MALLOCARRAY(stateP->imageWeights, maxGray + 1); + if (!stateP->imageWeights) + pm_error("Unable to allocate memory for image weights for %u " + "gray levels", maxGray); + + for (i = 0; i <= maxGray; ++i) { + unsigned int j; + MALLOCARRAY(stateP->imageWeights[i], stateP->inFileCt); + if (!stateP->imageWeights[i]) + pm_error("Unable to allocate memory for image weights for %u " + "images for gray level %u", stateP->inFileCt, i); + for (j = 0; j < stateP->inFileCt; ++j) + stateP->imageWeights[i][j] = 0; + } + + /* Populate the image-weight arrays. */ + for (i = 0; i <= maxGray; ++i) { + double const pctGray = i / (double)maxGray; + + unsigned int j; + + for (j = 0; j < stateP->inFileCt * randSamples; ) { + double r[2]; + unsigned int k; + + randomNormal2(&r[0], &r[1]); + for (k = 0; k < 2; ++k) { + int const img = + r[k] * sigma + pctGray * stateP->inFileCt * 0.999999; + /* Scale [0, 1] to [0, 1) (sort of). */ + if (img >= 0 && img < (int)stateP->inFileCt) { + ++stateP->imageWeights[i][img]; + ++j; + } + } + } + } +} + + + +static void +blendTuplesMask(struct ProgramState * const stateP, + unsigned int const col, + sample * const outSamps) { +/*---------------------------------------------------------------------------- + Blend one tuple of the input images into a new tuple according to the gray + levels specified in a mask file. +-----------------------------------------------------------------------------*/ + unsigned int const depth = stateP->inPam[0].depth; + sample const grayLevel = ((sample *)stateP->maskTupleRow[col])[0]; + + unsigned int img; + + /* Initialize outSamps[] to zeroes */ + { + unsigned int samp; + + for (samp = 0; samp < depth; ++samp) + outSamps[samp] = 0; + } + + /* Accumulate to outSamps[] */ + for (img = 0; img < stateP->inFileCt; ++img) { + unsigned long weight = stateP->imageWeights[grayLevel][img]; + + if (weight != 0) { + unsigned int samp; + + for (samp = 0; samp < depth; ++samp) + outSamps[samp] += + ((sample *)stateP->inTupleRows[img][col])[samp] * weight; + } + } + /* Scale all outSamps[] */ + { + unsigned int samp; + + for (samp = 0; samp < depth; ++samp) + outSamps[samp] /= randSamples * stateP->inFileCt; + } +} + + + +static void +blendImageRow(BlendType const blend, + struct ProgramState * const stateP) { +/*---------------------------------------------------------------------------- + Blend one row of input images into a new row. +-----------------------------------------------------------------------------*/ + unsigned int const width = stateP->inPam[0].width; + + unsigned int col; + + for (col = 0; col < width; ++col) { + sample * const outSamps = stateP->outTupleRow[col]; + + switch (blend) { + case BLEND_RANDOM: + /* Take each pixel from a different, randomly selected image. */ + blendTuplesRandom(stateP, col, outSamps); + break; + + case BLEND_AVERAGE: + /* Average each sample across all the images. */ + blendTuplesAverage(stateP, col, outSamps); + break; + + case BLEND_MASK: + /* Take each pixel from the image specified by the mask image. */ + blendTuplesMask(stateP, col, outSamps); + break; + } + } +} + + + +static void +blendImages(BlendType const blend, + struct ProgramState * const stateP) { +/*---------------------------------------------------------------------------- + Blend the images row-by-row into a new image. +-----------------------------------------------------------------------------*/ + unsigned int const nRows = stateP->inPam[0].height; + + unsigned int row; + + for (row = 0; row < nRows; ++row) { + unsigned int img; + + for (img = 0; img < stateP->inFileCt; ++img) + pnm_readpamrow(&stateP->inPam[img], stateP->inTupleRows[img]); + + if (blend == BLEND_MASK) + pnm_readpamrow(&stateP->maskPam, stateP->maskTupleRow); + + blendImageRow(blend, stateP); + + pnm_writepamrow(&stateP->outPam, stateP->outTupleRow); + } +} + + + +static void +termState(struct ProgramState * const stateP) { +/*---------------------------------------------------------------------------- + Deallocate all of the resources we allocated. +-----------------------------------------------------------------------------*/ + unsigned int i; + + for (i = 0; i < stateP->inFileCt; ++i) { + pnm_freepamrow(stateP->inTupleRows[i]); + pm_close(stateP->inPam[i].file); + } + + free(stateP->outTupleRow); + free(stateP->inTupleRows); + free(stateP->inPam); + pm_close(stateP->outPam.file); +} + + + +int +main(int argc, const char * argv[]) { + + struct CmdlineInfo cmdline; + struct ProgramState state; + + pm_proginit(&argc, argv); + + parseCommandLine(argc, argv, &cmdline); + + srand(cmdline.randomseedSpec ? cmdline.randomseed : pm_randseed()); + + openInputFiles(cmdline.inFileNameCt, cmdline.inFileName, &state); + + if (cmdline.blend == BLEND_MASK) + initMask(cmdline.maskfile, &state); + + initOutput(stdout, &state); + + if (cmdline.blend == BLEND_MASK) + precomputeImageWeights(&state, cmdline.stdev); + + blendImages(cmdline.blend, &state); + + if (cmdline.blend == BLEND_MASK) + termMask(&state); + + termState(&state); + + freeCmdline(&cmdline); + + return 0; +} + + +/* COPYRIGHT LICENSE and WARRANTY DISCLAIMER + +Copyright (c) 2018 Scott Pakin +All rights reserved + +Redistribution and use in source and binary forms, with or without +modification, are permitted (subject to the limitations in the disclaimer +below) provided that the following conditions are met: + + Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + Neither the names of the oopyright owners nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + + NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY + THIS LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT + NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ |