diff options
author | giraffedata <giraffedata@9d0c8265-081b-0410-96cb-a4ca84ce46f8> | 2023-06-28 17:29:32 +0000 |
---|---|---|
committer | giraffedata <giraffedata@9d0c8265-081b-0410-96cb-a4ca84ce46f8> | 2023-06-28 17:29:32 +0000 |
commit | 23ce26f64c34e30951ad9ade2151552ed77e7357 (patch) | |
tree | d73b31a0c2f7c7be4a69f8a8e84e00dd39c432b5 /editor/pamcat.c | |
parent | 1b6e51a266008348ad93ed8b6ac9ec91b5024fea (diff) | |
download | netpbm-mirror-23ce26f64c34e30951ad9ade2151552ed77e7357.tar.gz netpbm-mirror-23ce26f64c34e30951ad9ade2151552ed77e7357.tar.xz netpbm-mirror-23ce26f64c34e30951ad9ade2151552ed77e7357.zip |
promote Advanced to Stable
git-svn-id: http://svn.code.sf.net/p/netpbm/code/stable@4558 9d0c8265-081b-0410-96cb-a4ca84ce46f8
Diffstat (limited to 'editor/pamcat.c')
-rw-r--r-- | editor/pamcat.c | 1461 |
1 files changed, 1461 insertions, 0 deletions
diff --git a/editor/pamcat.c b/editor/pamcat.c new file mode 100644 index 00000000..7823bdfc --- /dev/null +++ b/editor/pamcat.c @@ -0,0 +1,1461 @@ +/*============================================================================= + pamcat +=============================================================================== + + Concatenate images. + + By Bryan Henderson and Akira Urushibata. Contributed to the public domain + by its authors. + +=============================================================================*/ + +#include <stdio.h> +#include <limits.h> +#include <assert.h> + +#include "pm_c_util.h" +#include "mallocvar.h" +#include "shhopt.h" +#include "bitarith.h" +#include "nstring.h" +#include "pam.h" +#include "pbm.h" + +#define LEFTBITS pm_byteLeftBits +#define RIGHTBITS pm_byteRightBits + +enum PadColorMethod {PAD_BLACK, PAD_WHITE, PAD_AUTO}; + /* The method of determining the color of padding when images are not the + same height or width. Always white (maxval samples) always black (zero + samples) or determined from what looks like background for the image in + question. + */ + + +enum Orientation {TOPBOTTOM, LEFTRIGHT}; + /* Direction of concatenation */ + +enum Justification {JUST_CENTER, JUST_MIN, JUST_MAX}; + /* Justification of images in concatenation */ + +/* FOPEN_MAX is usually defined in stdio.h, PATH_MAX in limits.h + Given below are typical values. Adjust as necessary. + */ + +#ifndef FOPEN_MAX + #define FOPEN_MAX 16 +#endif + +#ifndef PATH_MAX + #define PATH_MAX 255 +#endif + + +static const char ** +copyOfStringList(const char ** const list, + unsigned int const size) { + + const char ** retval; + unsigned int i; + + MALLOCARRAY_NOFAIL(retval, size); + + for (i = 0; i < size; ++i) + retval[i] = pm_strdup(list[i]); + + return retval; +} + + + +struct CmdlineInfo { + /* All the information the user supplied in the command line, + in a form easy for the program to use. + */ + const char ** inputFileName; + unsigned int inputFileCt; + const char * listfile; /* NULL if not specified */ + enum PadColorMethod padColorMethod; + enum Orientation orientation; + enum Justification justification; + unsigned int verbose; +}; + + +static void +parseCommandLine(int argc, const char ** const 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. +-----------------------------------------------------------------------------*/ + optEntry * option_def; + /* Instructions to OptParseOptions3() on how to parse our options. + */ + optStruct3 opt; + + unsigned int option_def_index; + + unsigned int leftright, topbottom; + unsigned int black, white; + unsigned int jtop, jbottom, jleft, jright, jcenter; + unsigned int listfileSpec; + + MALLOCARRAY_NOFAIL(option_def, 100); + + option_def_index = 0; /* incremented by OPTENT3 */ + OPTENT3(0, "leftright", OPT_FLAG, NULL, &leftright, 0); + OPTENT3(0, "lr", OPT_FLAG, NULL, &leftright, 0); + OPTENT3(0, "topbottom", OPT_FLAG, NULL, &topbottom, 0); + OPTENT3(0, "tb", OPT_FLAG, NULL, &topbottom, 0); + OPTENT3(0, "black", OPT_FLAG, NULL, &black, 0); + OPTENT3(0, "white", OPT_FLAG, NULL, &white, 0); + OPTENT3(0, "jtop", OPT_FLAG, NULL, &jtop, 0); + OPTENT3(0, "jbottom", OPT_FLAG, NULL, &jbottom, 0); + OPTENT3(0, "jleft", OPT_FLAG, NULL, &jleft, 0); + OPTENT3(0, "jright", OPT_FLAG, NULL, &jright, 0); + OPTENT3(0, "jcenter", OPT_FLAG, NULL, &jcenter, 0); + OPTENT3(0, "listfile", OPT_STRING, &cmdlineP->listfile, + &listfileSpec, 0); + OPTENT3(0, "verbose", OPT_FLAG, NULL, &cmdlineP->verbose, 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. */ + + free(option_def); + + if (leftright + topbottom > 1) + pm_error("You may specify only one of -topbottom (-tb) and " + "-leftright (-lr)"); + else if (leftright) + cmdlineP->orientation = LEFTRIGHT; + else if (topbottom) + cmdlineP->orientation = TOPBOTTOM; + else + pm_error("You must specify either -leftright or -topbottom"); + + if (black + white > 1) + pm_error("You may specify only one of -black and -white"); + else if (black) + cmdlineP->padColorMethod = PAD_BLACK; + else if (white) + cmdlineP->padColorMethod = PAD_WHITE; + else + cmdlineP->padColorMethod = PAD_AUTO; + + if (jtop + jbottom + jleft + jright + jcenter > 1) + pm_error("You may specify only one of -jtop, -jbottom, " + "-jleft, and -jright"); + else { + switch (cmdlineP->orientation) { + case LEFTRIGHT: + if (jleft) + pm_error("-jleft is invalid with -leftright"); + if (jright) + pm_error("-jright is invalid with -leftright"); + if (jtop) + cmdlineP->justification = JUST_MIN; + else if (jbottom) + cmdlineP->justification = JUST_MAX; + else if (jcenter) + cmdlineP->justification = JUST_CENTER; + else + cmdlineP->justification = JUST_CENTER; + break; + case TOPBOTTOM: + if (jtop) + pm_error("-jtop is invalid with -topbottom"); + if (jbottom) + pm_error("-jbottom is invalid with -topbottom"); + if (jleft) + cmdlineP->justification = JUST_MIN; + else if (jright) + cmdlineP->justification = JUST_MAX; + else if (jcenter) + cmdlineP->justification = JUST_CENTER; + else + cmdlineP->justification = JUST_CENTER; + break; + } + } + + if (listfileSpec) { + if (argc-1 > 0) + pm_error ("You can not specify files on the command line and " + "also -listfile."); + } else { + cmdlineP->listfile = NULL; + + if (argc-1 < 1) { + MALLOCARRAY_NOFAIL(cmdlineP->inputFileName, 1); + cmdlineP->inputFileName[0] = "-"; + cmdlineP->inputFileCt = 1; + } else { + unsigned int i; + unsigned int stdinCt; + /* Number of input files user specified as Standard Input */ + + MALLOCARRAY_NOFAIL(cmdlineP->inputFileName, argc-1); + + for (i = 0, stdinCt = 0; i < argc-1; ++i) { + cmdlineP->inputFileName[i] = argv[1+i]; + if (streq(argv[1+i], "-")) + ++stdinCt; + } + cmdlineP->inputFileCt = argc-1; + if (stdinCt > 1) + pm_error("At most one input image can come from " + "Standard Input. You specified %u", stdinCt); + } + } +} + + + +static void +freeCmdLine(struct CmdlineInfo const cmdline) { + + if (!cmdline.listfile) + free(cmdline.inputFileName); +} + + + +static void +createInFileListFmFile(const char * const listFileNm, + bool const verbose, + const char *** const inputFileNmP, + unsigned int * const inputFileCtP) { + + FILE * const lfP = pm_openr(listFileNm); + + const char ** inputFileNm; + unsigned int inputFileCt; + unsigned int emptyLineCt; + unsigned int stdinCt; + int eof; + + MALLOCARRAY_NOFAIL(inputFileNm, FOPEN_MAX); + + for (inputFileCt = emptyLineCt = stdinCt = eof = 0; !eof; ) { + + size_t lineLen; + char * buf; + size_t bufferSz; + + buf = NULL; /* initial value */ + bufferSz = 0; /* initial value */ + + pm_getline(lfP, &buf, &bufferSz, &eof, &lineLen); + + if (!eof) { + if (lineLen == 0) + ++emptyLineCt; + else if (lineLen > PATH_MAX) + pm_error("Path/file name in list file is too long " + "(%u bytes). Maximum is %u bytes", + (unsigned)lineLen, PATH_MAX); + else /* 0 < lineLen < PATH_MAX */ { + if (inputFileCt >= FOPEN_MAX) + pm_error("Too many files in list file. Maximum is %u", + FOPEN_MAX); + else { + inputFileNm[inputFileCt] = buf; + ++inputFileCt; + if (streq(buf, "-")) + ++stdinCt; + } + } + } + } + + pm_close(lfP); + + if (stdinCt > 1) + pm_error("At most one input image can come from Standard Input. " + "You specified %u", stdinCt); + + if (inputFileCt == 0) + pm_error("No files specified in list file."); + + if (verbose) { + pm_message("%u files specified and %u blank lines in list file", + inputFileCt, emptyLineCt); + } + + *inputFileCtP = inputFileCt; + *inputFileNmP = inputFileNm; +} + + + +static void +createInFileList(struct CmdlineInfo const cmdline, + bool const verbose, + const char *** const inputFileNmP, + unsigned int * const inputFileCtP) { + + if (cmdline.listfile) + createInFileListFmFile(cmdline.listfile, verbose, + inputFileNmP, inputFileCtP); + else { + *inputFileCtP = cmdline.inputFileCt; + *inputFileNmP = copyOfStringList(cmdline.inputFileName, + cmdline.inputFileCt); + } +} + + + +static void +freeInFileList(const char ** const inputFileNm, + unsigned int const inputFileCt) { + + unsigned int i; + + for (i = 0; i < inputFileCt; ++i) + pm_strfree(inputFileNm[i]); + + free(inputFileNm); +} + + + +static const char * +tupletypeX(bool const allVisual, + unsigned int const colorDepth, + sample const maxMaxval, + bool const haveOpacity) { + + const char * retval; + + if (allVisual) { + switch (colorDepth) { + case 1: + if (maxMaxval == 1) + retval = haveOpacity ? "BLACKANDWHITE_ALPHA" : "BLACKANDWHITE"; + else + retval = haveOpacity ? "GRAYSCALE_ALPHA" : "GRAYSCALE"; + break; + case 3: + retval = haveOpacity ? "RGB_ALPHA" : "RGB"; + break; + default: + assert(false); + } + } else + retval = ""; + + return retval; +} + + + +typedef struct { + /* This describes a transformation from one tuple type to another, + e.g. from BLACKANDWHITE to GRAY_ALPHA. + + For transformations bewteen the defined ones for visual images, + only the "up" transformations are covered. + */ + bool mustPromoteColor; + /* Plane 0, which is the black/white or grayscale plane and also + the red plane must be copied as the red, green, and blue planes + (0, 1, and 2). + */ + bool mustPromoteOpacity; + /* Plane 1, which is the opacity plane for black and white or + grayscale tuples, must be copied as the RGB opacity plane (3). + */ + bool mustCreateOpacity; + /* The opacity plane value must be set to opaque */ + + bool mustPadZero; + /* Where the target tuple type is deeper than the source tuple + type, all higher numbered planes must be cleared to zero. + + This is mutually exclusive with the rest of the musts. + */ + +} TtTransform; + + + +static TtTransform +ttXformForImg(const struct pam * const inpamP, + const struct pam * const outpamP) { +/*---------------------------------------------------------------------------- + The transform required to transform tuples of the kind described by *inpamP + to tuples of the kind described by *outpamP (e.g. from grayscale to RGB, + which involves replicating one plane into three). + + We assume *outpamP tuples are of a type that is at least as expressive as + *inpamP tuples. So e.g. outpamP->tuple_type cannot be "GRAYSCALE" if + inpamP->tuple_type is "RGB". +-----------------------------------------------------------------------------*/ + TtTransform retval; + + if (inpamP->visual && outpamP->visual) { + retval.mustPromoteColor = + (outpamP->color_depth > inpamP->color_depth); + retval.mustPromoteOpacity = + (outpamP->color_depth > inpamP->color_depth && + (outpamP->have_opacity && inpamP->have_opacity)); + retval.mustCreateOpacity = + (outpamP->have_opacity && !inpamP->have_opacity); + retval.mustPadZero = false; + } else { + retval.mustPromoteColor = false; + retval.mustPromoteOpacity = false; + retval.mustCreateOpacity = false; + retval.mustPadZero = true; + } + return retval; +} + + + +static void +reportPlans(unsigned int const fileCt, + const struct pam * const outpamP) { + + pm_message("Concatenating %u input images", fileCt); + + pm_message("Output width, height, depth: %u x %u x %u", + outpamP->width, outpamP->height, outpamP->depth); + + if (outpamP->format == RPBM_FORMAT) + pm_message("Using PBM fast path and producing raw PBM output"); + else if (outpamP->format == PBM_FORMAT) + pm_message("Output format: Plain PBM"); + else { + pm_message("Output maxval (max of all inputs): %lu", outpamP->maxval); + + switch (outpamP->format) { + case PGM_FORMAT: + pm_message("Output format: Plain PGM"); + break; + case RPGM_FORMAT: + pm_message("Output format: Raw PGM"); + break; + case PPM_FORMAT: + pm_message("Output format: Plain PPM"); + break; + case RPPM_FORMAT: + pm_message("Output format: Raw PPM"); + break; + case PAM_FORMAT: + pm_message("Output format: PAM"); + + if (strlen(outpamP->tuple_type) > 0) + pm_message("Output tuple type: '%s'", outpamP->tuple_type); + else + pm_message("Output tuple type is null string because " + "input images have various non-visual tuple types"); + break; + } + } +} + + + +static void +computeOutputParms(unsigned int const fileCt, + enum Orientation const orientation, + const struct pam * const inpam, /* array */ + bool const verbose, + struct pam * const outpamP) { + + double newCols, newRows; + unsigned int maxDepth; + sample maxMaxval; + int newFormat; + const char * firstTupletype; + bool allSameTt; + bool allVisual; + unsigned int maxColorDepth; + bool haveOpacity; + unsigned int fileSeq; + + for (fileSeq = 0, newCols = 0, newRows = 0, maxDepth = 0, maxMaxval = 0, + newFormat = 0, + allVisual = true, maxColorDepth = 0, haveOpacity = false, + firstTupletype = NULL, allSameTt = true; + fileSeq < fileCt; + ++fileSeq) { + + const struct pam * const inpamP = &inpam[fileSeq]; + + switch (orientation) { + case LEFTRIGHT: + newCols += inpamP->width; + newRows = MAX(newRows, inpamP->height); + break; + case TOPBOTTOM: + newRows += inpamP->height; + newCols = MAX(newCols, inpamP->width); + break; + } + + if (!firstTupletype) + firstTupletype = inpamP->tuple_type; + if (inpamP->tuple_type != firstTupletype) + allSameTt = false; + + if (inpamP->visual) { + maxColorDepth = MAX(maxColorDepth, inpamP->color_depth); + + if (inpamP->have_opacity) + haveOpacity = true; + } else + allVisual = false; + + maxDepth = MAX(maxDepth, inpamP->depth); + maxMaxval = MAX(maxMaxval, inpamP->maxval); + + if (PAM_FORMAT_TYPE(inpamP->format) > PAM_FORMAT_TYPE(newFormat)) + newFormat = inpamP->format; + } + assert(newCols > 0); + assert(newRows > 0); + assert(maxMaxval > 0); + assert(newFormat > 0); + + if (newCols > INT_MAX) + pm_error("Output width too large: %.0f.", newCols); + if (newRows > INT_MAX) + pm_error("Output height too large: %.0f.", newRows); + + outpamP->size = sizeof(*outpamP); + outpamP->len = PAM_STRUCT_SIZE(tuple_type); + + /* Note that while 'double' is not in general a precise numerical type, + in the case of a sum of integers which is less than INT_MAX, it + is exact, because double's precision is greater than int's. + */ + outpamP->height = (unsigned int)newRows; + outpamP->width = (unsigned int)newCols; + if (allVisual) + outpamP->depth = MAX(maxDepth, + maxColorDepth + (haveOpacity ? 1 : 0)); + else + outpamP->depth = maxDepth; + outpamP->allocation_depth = 0; /* This means same as depth */ + outpamP->maxval = maxMaxval; + outpamP->format = newFormat; + if (allSameTt) + STRSCPY(outpamP->tuple_type, firstTupletype); + else + STRSCPY(outpamP->tuple_type, + tupletypeX(allVisual, maxColorDepth, maxMaxval, haveOpacity)); + outpamP->comment_p = NULL; + outpamP->plainformat = false; + + if (verbose) + reportPlans(fileCt, outpamP); +} + + + +static void +copyBitrow(const unsigned char * const source, + unsigned char * const destBitrow, + unsigned int const cols, + unsigned int const offset) { +/*---------------------------------------------------------------------------- + Copy from source to destBitrow, without shifting. Preserve + surrounding image data. +-----------------------------------------------------------------------------*/ + unsigned char * const dest = & destBitrow[ offset/8 ]; + /* Copy destination, with leading full bytes ignored. */ + unsigned int const rs = offset % 8; + /* The "little offset", as measured from start of dest. Source + is already shifted by this value. + */ + unsigned int const trs = (cols + rs) % 8; + /* The number of partial bits in the final char. */ + unsigned int const colByteCnt = pbm_packed_bytes(cols + rs); + /* # bytes to process, including partial ones on both ends. */ + unsigned int const last = colByteCnt - 1; + + unsigned char const origHead = dest[0]; + unsigned char const origEnd = dest[last]; + + unsigned int i; + + assert(colByteCnt >= 1); + + for (i = 0; i < colByteCnt; ++i) + dest[i] = source[i]; + + if (rs > 0) + dest[0] = LEFTBITS(origHead, rs) | RIGHTBITS(dest[0], 8-rs); + + if (trs > 0) + dest[last] = LEFTBITS(dest[last], trs) | RIGHTBITS(origEnd, 8-trs); +} + + + +static void +padFillBitrow(unsigned char * const destBitrow, + unsigned char const padColor, + unsigned int const cols, + unsigned int const offset) { +/*---------------------------------------------------------------------------- + Fill destBitrow, starting at offset, with padColor. padColor is a + byte -- 0x00 or 0xff -- not a single bit. +-----------------------------------------------------------------------------*/ + unsigned char * const dest = &destBitrow[offset/8]; + unsigned int const rs = offset % 8; + unsigned int const trs = (cols + rs) % 8; + unsigned int const colByteCnt = pbm_packed_bytes(cols + rs); + unsigned int const last = colByteCnt - 1; + + unsigned char const origHead = dest[0]; + unsigned char const origEnd = dest[last]; + + unsigned int i; + + assert(colByteCnt > 0); + + for (i = 0; i < colByteCnt; ++i) + dest[i] = padColor; + + if (rs > 0) + dest[0] = LEFTBITS(origHead, rs) | RIGHTBITS(dest[0], 8-rs); + + if (trs > 0) + dest[last] = LEFTBITS(dest[last], trs) | RIGHTBITS(origEnd, 8-trs); +} + + + +/* concatenateLeftRightPbm() and concatenateLeftRightGen() + employ almost identical algorithms. + The difference is in the data types and functions. + + Same for concatenateTopBottomPbm() and concatenateTopBottomGen(). +*/ + + +typedef struct { + /* Information about one image */ + unsigned char * proberow; + /* Top row of image, when background color is + auto-determined. + */ + unsigned int offset; + /* start position of image, in bits, counting from left + edge + */ + unsigned char background; + /* Background color. 0x00 means white; 0xff means black */ + unsigned int padtop; + /* Top padding amount */ +} LrImgCtlPbm; + + + +static void +createLrImgCtlPbm(const struct pam * const inpam, /* array */ + unsigned int const fileCt, + unsigned int const outHeight, + enum Justification const justification, + enum PadColorMethod const padColorMethod, + LrImgCtlPbm ** const imgCtlP) { +/*---------------------------------------------------------------------------- + Read the first row of each image in inpam[] and return that and additional + information about images as *imgCtlP. +-----------------------------------------------------------------------------*/ + LrImgCtlPbm * imgCtl; /* array, size 'fileCt' */ + unsigned int fileSeq; + + MALLOCARRAY_NOFAIL(imgCtl, fileCt); + + for (fileSeq = 0; fileSeq < fileCt; ++fileSeq) { + LrImgCtlPbm * const imgCtlP = &imgCtl[fileSeq]; + const struct pam * const inpamP = &inpam[fileSeq]; + + switch (justification) { + case JUST_MIN: + imgCtlP->padtop = 0; + break; + case JUST_MAX: + imgCtlP->padtop = outHeight - inpam[fileSeq].height; + break; + case JUST_CENTER: + imgCtlP->padtop = (outHeight - inpamP->height) / 2; + break; + } + + imgCtlP->offset = + (fileSeq == 0) ? + 0 : imgCtl[fileSeq-1].offset + inpam[fileSeq-1].width; + + if (inpamP->height == outHeight) /* no padding */ + imgCtlP->proberow = NULL; + else { /* determine pad color for image i */ + switch (padColorMethod) { + case PAD_AUTO: { + bit bgBit; + imgCtlP->proberow = + pbm_allocrow_packed((unsigned int)inpamP->width + 7); + pbm_readpbmrow_bitoffset( + inpamP->file, imgCtlP->proberow, + inpamP->width, inpamP->format, imgCtlP->offset % 8); + + bgBit = pbm_backgroundbitrow( + imgCtlP->proberow, inpamP->width, + imgCtlP->offset % 8); + + imgCtlP->background = bgBit == PBM_BLACK ? 0xff : 0x00; + } break; + case PAD_BLACK: + imgCtlP->proberow = NULL; + imgCtlP->background = 0xff; + break; + case PAD_WHITE: + imgCtlP->proberow = NULL; + imgCtlP->background = 0x00; + break; + } + } + } + *imgCtlP = imgCtl; +} + + + +static void +destroyPbmImgCtl(LrImgCtlPbm * const imgCtl, /* array */ + unsigned int const fileCt) { + + unsigned int i; + + for (i = 0; i < fileCt; ++i) { + if (imgCtl[i].proberow) + free(imgCtl[i].proberow); + } + free(imgCtl); +} + + + +static void +concatenateLeftRightPbm(struct pam * const outpamP, + const struct pam * const inpam, /* array */ + unsigned int const fileCt, + enum Justification const justification, + enum PadColorMethod const padColorMethod) { + + unsigned char * const outrow = pbm_allocrow_packed(outpamP->width); + /* We use just one outrow. All padding and image data (with the + exception of following imgCtl.proberow) goes directly into this + packed PBM row. + */ + + LrImgCtlPbm * imgCtl; + /* malloc'ed array, one element per image. Shadows inpam[] */ + unsigned int row; + + createLrImgCtlPbm(inpam, fileCt, outpamP->height, + justification, padColorMethod, + &imgCtl); + + outrow[pbm_packed_bytes(outpamP->width)-1] = 0x00; + + for (row = 0; row < outpamP->height; ++row) { + unsigned int fileSeq; + + for (fileSeq = 0; fileSeq < fileCt; ++fileSeq) { + const LrImgCtlPbm * const imgCtlP = &imgCtl[fileSeq]; + const struct pam * const inpamP = &inpam[fileSeq]; + + if ((row == 0 && imgCtlP->padtop > 0) || + row == imgCtlP->padtop + inpamP->height) { + + /* This row begins a run of padding, either above or below + file 'i', so set 'outrow' to padding. + */ + padFillBitrow(outrow, imgCtlP->background, inpamP->width, + imgCtlP->offset); + } + + if (row == imgCtlP->padtop && imgCtlP->proberow != NULL) { + /* Top row has been read to proberow[] to determine + background. Copy it to outrow[]. + */ + copyBitrow(imgCtlP->proberow, outrow, + inpamP->width, imgCtlP->offset); + } else if (row >= imgCtlP->padtop && + row < imgCtlP->padtop + inpamP->height) { + pbm_readpbmrow_bitoffset( + inpamP->file, outrow, inpamP->width, inpamP->format, + imgCtlP->offset); + } else { + /* It's a row of padding, so outrow[] is already set + appropriately. + */ + } + } + pbm_writepbmrow_packed(outpamP->file, outrow, outpamP->width, 0); + } + + destroyPbmImgCtl(imgCtl, fileCt); + + pbm_freerow_packed(outrow); +} + + + +static void +concatenateTopBottomPbm(const struct pam * const outpamP, + const struct pam * const inpam, /* array */ + unsigned int const fileCt, + enum Justification const justification, + enum PadColorMethod const padColorMethod) { + + unsigned char * const outrow = pbm_allocrow_packed(outpamP->width); + /* Like the left-right PBM case, all padding and image data + goes directly into outrow. There is no proberow. + */ + unsigned char background, backgroundPrev; + /* 0x00 means white; 0xff means black */ + unsigned int padleft; + bool backChange; + /* Background color is different from that of the previous + input image. + */ + + unsigned int fileSeq; + unsigned int row, startRow; + + outrow[pbm_packed_bytes(outpamP->width)-1] = 0x00; + + switch (padColorMethod){ + case PAD_AUTO: /* do nothing */ break; + case PAD_BLACK: background = 0xff; break; + case PAD_WHITE: background = 0x00; break; + } + + for (fileSeq = 0; fileSeq < fileCt; ++fileSeq) { + const struct pam * const inpamP = &inpam[fileSeq]; + + if (inpamP->width == outpamP->width) { + /* No padding */ + startRow = 0; + backChange = FALSE; + padleft = 0; + outrow[pbm_packed_bytes(outpamP->width)-1] = 0x00; + } else { + /* Determine amount of padding and color */ + switch (justification) { + case JUST_MIN: + padleft = 0; + break; + case JUST_MAX: + padleft = outpamP->width - inpamP->width; + break; + case JUST_CENTER: + padleft = (outpamP->width - inpamP->width) / 2; + break; + } + + switch (padColorMethod) { + case PAD_AUTO: { + bit bgBit; + + startRow = 1; + + pbm_readpbmrow_bitoffset( + inpamP->file, outrow, inpamP->width, inpamP->format, + padleft); + + bgBit = pbm_backgroundbitrow(outrow, inpamP->width, padleft); + background = bgBit == PBM_BLACK ? 0xff : 0x00; + + backChange = (fileSeq == 0 || background != backgroundPrev); + } break; + case PAD_WHITE: + case PAD_BLACK: + startRow = 0; + backChange = (fileSeq == 0); + break; + } + + if (backChange || + (fileSeq > 0 && inpam[fileSeq-1].width > inpamP->width)) { + unsigned int const padright = + outpamP->width - padleft - inpamP->width; + + if (padleft > 0) + padFillBitrow(outrow, background, padleft, 0); + + if (padright > 0) + padFillBitrow(outrow, background, padright, + padleft + inpamP->width); + + } + } + + if (startRow == 1) + /* Top row already read for auto background color + determination. Write it out. + */ + pbm_writepbmrow_packed(outpamP->file, outrow, outpamP->width, 0); + + for (row = startRow; row < inpamP->height; ++row) { + pbm_readpbmrow_bitoffset(inpamP->file, outrow, inpamP->width, + inpamP->format, padleft); + pbm_writepbmrow_packed(outpamP->file, outrow, outpamP->width, 0); + } + + backgroundPrev = background; + } + pbm_freerow_packed(outrow); +} + + + +static void +padPlanesRow(const struct pam * const inpamP, + tuple * const outrow, + const struct pam * const outpamP) { +/*---------------------------------------------------------------------------- + Rearrange the planes of *outrow as needed to transform them into tuples + as described by *outpamP from tuples as described by *inpamP. +-----------------------------------------------------------------------------*/ + TtTransform const ttTransform = ttXformForImg(inpamP, outpamP); + + assert(inpamP->allocation_depth >= outpamP->depth); + + if (ttTransform.mustPromoteOpacity) { + unsigned int col; + + assert(outpamP->depth >= PAM_TRN_PLANE); + + for (col = 0; col < inpamP->width; ++col) { + outrow[col][outpamP->opacity_plane] = + outrow[col][inpamP->opacity_plane]; + } + } + if (ttTransform.mustPromoteColor) { + unsigned int col; + + assert(outpamP->depth >= PAM_GRN_PLANE); + assert(outpamP->depth >= PAM_BLU_PLANE); + + for (col = 0; col < inpamP->width; ++col) { + assert(PAM_RED_PLANE == 0); + outrow[col][PAM_GRN_PLANE] = outrow[col][0]; + outrow[col][PAM_BLU_PLANE] = outrow[col][0]; + } + } + + if (ttTransform.mustCreateOpacity) { + unsigned int col; + + for (col = 0; col < inpamP->width; ++col) + outrow[col][outpamP->opacity_plane] = outpamP->maxval; + } + + if (ttTransform.mustPadZero) { + unsigned int plane; + + for (plane = inpamP->depth; plane < outpamP->depth; ++plane) { + unsigned int col; + + for (col = 0; col < inpamP->width; ++col) + outrow[col][plane] = 0; + } + } +} + + + +typedef struct { +/*---------------------------------------------------------------------------- + Parameters and state for placing a row of a particular input image in + the output in a left-right concatenation. +-----------------------------------------------------------------------------*/ + tuple * cachedRow; + /* Contents of the current row of the image, with depth and maxval + adjusted for output, in malloc'ed space belonging to this object. + Input file is positioned past this row. Null if data not present + and input file is positioned to the current row. + */ + tuple * out; + /* Point in output row buffer where the row from this image goes */ + tuple background; + unsigned int padtop; + /* Number of rows of padding that go above this image in the output */ +} LrImgCtl; + + + +static void +createLrImgCtlArray(const struct pam * const inpam, /* array */ + unsigned int const fileCt, + tuple * const newTuplerow, + const struct pam * const outpamP, + enum Justification const justification, + enum PadColorMethod const padColorMethod, + LrImgCtl ** const imgCtlP) { + + LrImgCtl * imgCtl; /* array */ + unsigned int fileSeq; + + MALLOCARRAY_NOFAIL(imgCtl, fileCt); + + for (fileSeq = 0; fileSeq < fileCt; ++fileSeq) { + LrImgCtl * const thisEntryP = &imgCtl[fileSeq]; + const struct pam * const inpamP = &inpam[fileSeq]; + + switch (justification) { /* Determine top padding */ + case JUST_MIN: + thisEntryP->padtop = 0; + break; + case JUST_MAX: + thisEntryP->padtop = outpamP->height - inpamP->height; + break; + case JUST_CENTER: + thisEntryP->padtop = (outpamP->height - inpamP->height) / 2; + break; + } + + thisEntryP->out = + (fileSeq == 0 ? + &newTuplerow[0] : imgCtl[fileSeq-1].out + inpam[fileSeq-1].width); + + if (inpamP->height == outpamP->height) { /* no vertical padding */ + thisEntryP->cachedRow = NULL; + pnm_createBlackTuple(outpamP, &thisEntryP->background); + /* Meaningless because no padding */ + } else { + /* Determine pad color */ + switch (padColorMethod){ + case PAD_AUTO: + thisEntryP->cachedRow = pnm_allocpamrow(inpamP); + pnm_readpamrow(inpamP, thisEntryP->cachedRow); + pnm_scaletuplerow(inpamP, thisEntryP->cachedRow, + thisEntryP->cachedRow, outpamP->maxval); + padPlanesRow(inpamP, thisEntryP->cachedRow, outpamP); + { + struct pam cachedRowPam; + cachedRowPam = *outpamP; + cachedRowPam.width = inpamP->width; + thisEntryP->background = pnm_backgroundtuplerow( + &cachedRowPam, thisEntryP->cachedRow); + } + break; + case PAD_BLACK: + thisEntryP->cachedRow = NULL; + pnm_createBlackTuple(outpamP, &thisEntryP->background); + break; + case PAD_WHITE: + thisEntryP->cachedRow = NULL; + pnm_createWhiteTuple(outpamP, &thisEntryP->background); + break; + } + } + if (outpamP->visual) { + /* Any opacity sample in background color tuple is meaningless at + this point; make it opaque. + */ + if (outpamP->have_opacity) { + thisEntryP->background[outpamP->opacity_plane] = + outpamP->maxval; + } + } + + } + *imgCtlP = imgCtl; +} + + + +static void +destroyLrImgCtlArray(LrImgCtl * const imgCtl, /* array */ + unsigned int const fileCt) { + + unsigned int fileSeq; + + for (fileSeq = 0; fileSeq < fileCt; ++fileSeq) { + LrImgCtl * const thisEntryP = &imgCtl[fileSeq]; + + pnm_freepamtuple(thisEntryP->background); + pnm_freepamrow(thisEntryP->cachedRow); + } + + free(imgCtl); +} + + + +static void +concatenateLeftRightGen(const struct pam * const outpamP, + const struct pam * const inpam, /* array */ + unsigned int const fileCt, + enum Justification const justification, + enum PadColorMethod const padColorMethod) { + + tuple * const outrow = pnm_allocpamrow(outpamP); + + LrImgCtl * imgCtl; + unsigned int row; + + createLrImgCtlArray(inpam, fileCt, outrow, outpamP, + justification, padColorMethod, + &imgCtl); + + for (row = 0; row < outpamP->height; ++row) { + unsigned int fileSeq; + + for (fileSeq = 0; fileSeq < fileCt; ++fileSeq) { + LrImgCtl * const thisEntryP = &imgCtl[fileSeq]; + const struct pam * const inpamP = &inpam[fileSeq]; + + if ((row == 0 && thisEntryP->padtop > 0) || + row == thisEntryP->padtop + inpamP->height) { + /* This row begins a run of padding, either above or below + image 'fileSeq', so set its part of outrow[] to padding. + */ + unsigned int col; + for (col = 0; col < inpamP->width; ++col) { + pnm_assigntuple(outpamP, thisEntryP->out[col], + thisEntryP->background); + } + } + if (row == thisEntryP->padtop && thisEntryP->cachedRow) { + /* We're at the top row of image 'fileSeq', and that row + has already been read to cachedRow[] to determine + background. Copy it to image fileseq's part of outrow[]. + */ + unsigned int col; + for (col = 0; col < inpamP->width; ++col) { + pnm_assigntuple(outpamP, thisEntryP->out[col], + thisEntryP->cachedRow[col]); + } + free(thisEntryP->cachedRow); + thisEntryP->cachedRow = NULL; + } else if (row >= thisEntryP->padtop && + row < thisEntryP->padtop + inpamP->height) { + pnm_readpamrow(inpamP, thisEntryP->out); + pnm_scaletuplerow(inpamP, thisEntryP->out, + thisEntryP->out, outpamP->maxval); + padPlanesRow(inpamP, thisEntryP->out, outpamP); + } else { + /* It's a row of padding, so image filesSeq's part of outrow[] + is already set appropriately. + */ + } + } + /* Note that imgCtl[N].out[] is an alias to part of outrow[], so + outrow[] has been set. + */ + pnm_writepamrow(outpamP, outrow); + } + destroyLrImgCtlArray(imgCtl, fileCt); + + pnm_freepamrow(outrow); +} + + + +static tuple +initialBackgroundColor(const struct pam * const outpamP, + enum PadColorMethod const padColorMethod) { + + tuple retval; + + switch (padColorMethod) { + case PAD_AUTO: + /* Background is different for each input image */ + retval = pnm_allocpamtuple(outpamP); + /* Dummy value; just need something to free */ + break; + case PAD_BLACK: + pnm_createBlackTuple(outpamP, &retval); + break; + case PAD_WHITE: + pnm_createWhiteTuple(outpamP, &retval); + break; + } + + if (outpamP->visual) { + /* Any opacity sample in background color tuple is meaningless at this + point; make it opaque. + */ + if (outpamP->have_opacity) + retval[outpamP->opacity_plane] = outpamP->maxval; + } + + return retval; +} + + + +static unsigned int +leftPadAmount(const struct pam * const outpamP, + const struct pam * const inpamP, + enum Justification const justification) { + + switch (justification) { + case JUST_MIN: return 0; + case JUST_MAX: return outpamP->width - inpamP->width; + case JUST_CENTER: return (outpamP->width - inpamP->width) / 2; + } + assert(false); +} + + + +static void +setHorizPadding(tuple * const newTuplerow, + const struct pam * const outpamP, + bool const backChanged, + const struct pam * const inpam, /* array */ + unsigned int const imageSeq, + unsigned int const padLeft, + tuple const background) { +/*---------------------------------------------------------------------------- + Set the left and right padding for an output row in a top-bottom + concatenation. + + 'newTuplerow' is where we set the padding (and also assumed to hold the + contents of the previous output row). *outpamP describes it. + + 'backChanged' means the background color is different for the current row + from that of the previous row. + + inpam[] is the array of descriptors for all the input images. 'imageSeq' + is the index into this array for the current image. + + 'background' is the background color to set. +-----------------------------------------------------------------------------*/ + if (backChanged || + (imageSeq > 0 && inpam[imageSeq-1].width > inpam[imageSeq].width)) { + unsigned int col; + + for (col = 0; col < padLeft; ++col) + pnm_assigntuple(outpamP, newTuplerow[col], background); + for (col = padLeft + inpam[imageSeq].width; + col < outpamP->width; + ++col) { + pnm_assigntuple(outpamP, newTuplerow[col], background); + } + } else { + /* No need to pad because newTuplerow[] already contains the + correct padding from the previous row. + */ + } +} + + + +static void +readFirstTBRowAndDetermineBackground(const struct pam * const inpamP, + const struct pam * const outpamP, + tuple * const out, + tuple * const backgroundP) { +/*---------------------------------------------------------------------------- + Read the first row of an input image into 'out', adjusting it to conform + to the output depth and maxval described by *outpamP. + + The image is positioned to the first row at entry. + + From this row, determine the background color for the input image and + return it as *backgroundP (a newly malloced tuple). +-----------------------------------------------------------------------------*/ + pnm_readpamrow(inpamP, out); + + pnm_scaletuplerow(inpamP, out, out, outpamP->maxval); + + padPlanesRow(inpamP, out, outpamP); + + { + struct pam partialOutpam; + /* Descriptor for the input image with depth and maxval adjusted to + that of the output image. + */ + tuple background; + + partialOutpam = *outpamP; + partialOutpam.width = inpamP->width; + + background = pnm_backgroundtuplerow(&partialOutpam, out); + + if (outpamP->visual) { + /* Make the background opaque. */ + if (outpamP->have_opacity) + background[outpamP->opacity_plane] = outpamP->maxval; + } + + *backgroundP = background; + } +} + + + +static void +concatenateTopBottomGen(const struct pam * const outpamP, + const struct pam * const inpam, /* array */ + unsigned int const fileCt, + enum Justification const justification, + enum PadColorMethod const padColorMethod) { + + tuple * const newTuplerow = pnm_allocpamrow(outpamP); + tuple * out; + /* The location in newTuplerow[] that the row from the current + input image goes. + */ + unsigned int fileSeq; + tuple background; + tuple backgroundPrev; + + background = initialBackgroundColor(outpamP, padColorMethod); + + for (fileSeq = 0; fileSeq < fileCt; ++fileSeq) { + const struct pam * const inpamP = &inpam[fileSeq]; + + unsigned int row; + unsigned int startRow; + bool backChanged; + /* The background color is different from that of the previous + input image. + */ + + if (inpamP->width == outpamP->width) { + /* no padding */ + startRow = 0; + backChanged = false; + out = &newTuplerow[0]; + } else { + unsigned int const padLeft = + leftPadAmount(outpamP, inpamP, justification); + + if (padColorMethod == PAD_AUTO) { + out = &newTuplerow[padLeft]; + backgroundPrev = background; + readFirstTBRowAndDetermineBackground( + inpamP, outpamP, out, &background); + + backChanged = + fileSeq == 0 || + pnm_tupleequal(outpamP, background, backgroundPrev); + pnm_freepamtuple(backgroundPrev); + + startRow = 1; + } else { + /* Background color is constant: black or white */ + startRow = 0; + out = &newTuplerow[padLeft]; + backChanged = (fileSeq == 0); + } + + setHorizPadding(newTuplerow, outpamP, backChanged, inpam, fileSeq, + padLeft, background); + } + + if (startRow == 1) + /* Top row was already read for auto background color + determination. Write it out. + */ + pnm_writepamrow(outpamP, newTuplerow); + + for (row = startRow; row < inpamP->height; ++row) { + pnm_readpamrow(inpamP, out); + + pnm_scaletuplerow(inpamP, out, out, outpamP->maxval); + + padPlanesRow(inpamP, out, outpamP); + + pnm_writepamrow(outpamP, newTuplerow); + } + } + pnm_freepamtuple(background); + pnm_freepamrow(newTuplerow); +} + + + +int +main(int argc, + const char ** argv) { + + struct CmdlineInfo cmdline; + const char ** inputFileNm; + unsigned int inputFileCt; + struct pam * inpam; /* malloc'ed array */ + struct pam outpam; + unsigned int i; + + pm_proginit(&argc, argv); + + parseCommandLine(argc, argv, &cmdline); + + createInFileList(cmdline, !!cmdline.verbose, &inputFileNm, &inputFileCt); + + MALLOCARRAY_NOFAIL(inpam, inputFileCt); + + for (i = 0; i < inputFileCt; ++i) { + FILE * ifP; + ifP = pm_openr(inputFileNm[i]); + inpam[i].comment_p = NULL; /* Don't want to see the comments */ + pnm_readpaminit(ifP, &inpam[i], PAM_STRUCT_SIZE(opacity_plane)); + } + + computeOutputParms(inputFileCt, cmdline.orientation, inpam, + cmdline.verbose, &outpam); + + outpam.file = stdout; + + for (i = 0; i < inputFileCt; ++i) + pnm_setminallocationdepth(&inpam[i], outpam.depth); + + pnm_writepaminit(&outpam); + + if (outpam.format == RPBM_FORMAT) { + switch (cmdline.orientation) { + case LEFTRIGHT: + concatenateLeftRightPbm(&outpam, inpam, inputFileCt, + cmdline.justification, + cmdline.padColorMethod); + break; + case TOPBOTTOM: + concatenateTopBottomPbm(&outpam, inpam, inputFileCt, + cmdline.justification, + cmdline.padColorMethod); + break; + } + } else { + switch (cmdline.orientation) { + case LEFTRIGHT: + concatenateLeftRightGen(&outpam, inpam, inputFileCt, + cmdline.justification, + cmdline.padColorMethod); + break; + case TOPBOTTOM: + concatenateTopBottomGen(&outpam, inpam, inputFileCt, + cmdline.justification, + cmdline.padColorMethod); + break; + } + } + for (i = 0; i < inputFileCt; ++i) + pm_close(inpam[i].file); + free(inpam); + freeInFileList(inputFileNm, inputFileCt); + freeCmdLine(cmdline); + pm_close(stdout); + + return 0; +} + + + |