about summary refs log tree commit diff
path: root/editor/pammixmulti.c
diff options
context:
space:
mode:
Diffstat (limited to 'editor/pammixmulti.c')
-rw-r--r--editor/pammixmulti.c543
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.
+*/