diff options
author | giraffedata <giraffedata@9d0c8265-081b-0410-96cb-a4ca84ce46f8> | 2022-07-28 17:09:57 +0000 |
---|---|---|
committer | giraffedata <giraffedata@9d0c8265-081b-0410-96cb-a4ca84ce46f8> | 2022-07-28 17:09:57 +0000 |
commit | 1e77fa9f5744744d8775e3aebf97f153fec31f22 (patch) | |
tree | 1ae766d5ec103d3c5b22e3b70af9d5e85a4478c5 /editor | |
parent | 162b555fde209c967c5f29cbc755855cddd6483e (diff) | |
download | netpbm-mirror-1e77fa9f5744744d8775e3aebf97f153fec31f22.tar.gz netpbm-mirror-1e77fa9f5744744d8775e3aebf97f153fec31f22.tar.xz netpbm-mirror-1e77fa9f5744744d8775e3aebf97f153fec31f22.zip |
First draft. Compiles
git-svn-id: http://svn.code.sf.net/p/netpbm/code/trunk@4371 9d0c8265-081b-0410-96cb-a4ca84ce46f8
Diffstat (limited to 'editor')
-rw-r--r-- | editor/Makefile | 3 | ||||
-rw-r--r-- | editor/pamcat.c | 1049 |
2 files changed, 1051 insertions, 1 deletions
diff --git a/editor/Makefile b/editor/Makefile index 8798cf6e..ab7c49f2 100644 --- a/editor/Makefile +++ b/editor/Makefile @@ -16,7 +16,8 @@ SUBDIRS = pamflip specialty # This package is so big, it's useful even when some parts won't # build. -PORTBINARIES = pamaddnoise pamaltsat pambackground pambrighten pamcomp pamcut \ +PORTBINARIES = pamaddnoise pamaltsat pambackground pambrighten \ + pamcat pamcomp pamcut \ pamdice pamditherbw pamedge pamenlarge \ pamfunc pamhomography pamhue pamlevels \ pammasksharpen pammixmulti \ diff --git a/editor/pamcat.c b/editor/pamcat.c new file mode 100644 index 00000000..63fffca3 --- /dev/null +++ b/editor/pamcat.c @@ -0,0 +1,1049 @@ +/*============================================================================= + pamcat +=============================================================================== + + Concatenate images. + + By Bryan Henderson and Akira Urushibata. Contributed to the public domain + by its authors. + +=============================================================================*/ + +#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 PlanePadMethod {PLANEPAD_ZERO, PLANEPAD_EXTEND}; + /* The method for adding additional planes when some images have fewer + planes than others. The additional plane is either all zeroes or + equal to the highest plane in the original image. + */ + +enum Orientation {TOPBOTTOM, LEFTRIGHT}; + /* Direction of concatenation */ + +enum Justification {JUST_CENTER, JUST_MIN, JUST_MAX}; + /* Justification of images in concatenation */ + +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 fileCt; + enum PadColorMethod padColorMethod; + enum PlanePadMethod planePadMethod; + 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, extendplane; + unsigned int jtop, jbottom, jleft, jright, jcenter; + + 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, "extendplane", OPT_FLAG, NULL, &extendplane, 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; + + cmdlineP->planePadMethod = extendplane ? PLANEPAD_EXTEND : PLANEPAD_ZERO; + + 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 (argc-1 < 1) { + MALLOCARRAY_NOFAIL(cmdlineP->inputFileName, 1); + cmdlineP->inputFileName[0] = "-"; + cmdlineP->fileCt = 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->fileCt = argc-1; + if (stdinCt > 1) + pm_error("At most one input image can come from Standard Input. " + "You specified %u", stdinCt); + } +} + + + +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, newDepth; + const char * newTupletype; + sample newMaxval; + bool allPbm; + /* All the input images are raw PBM, so far as we've seen */ + bool tupleTypeVaries; + /* We've seen multiple tuple types among the input images */ + unsigned int i; + + for (i = 0, newCols = 0, newRows = 0, newMaxval = 0, allPbm = true, + newTupletype = NULL, tupleTypeVaries = false; + i < fileCt; + ++i) { + + const struct pam * const inpamP = &inpam[i]; + + if (inpamP->format != RPBM_FORMAT) + allPbm = false; + + newMaxval = MAX(newMaxval, inpamP->maxval); + newDepth = MAX(newDepth, inpamP->depth); + + 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 (newTupletype) { + if (!streq(inpamP->tuple_type, newTupletype)) + tupleTypeVaries = true; + } else + newTupletype = inpamP->tuple_type; + } + assert(newCols > 0); + assert(newRows > 0); + assert(newMaxval > 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(raster_pos); + + /* 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; + outpamP->depth = newDepth; + outpamP->allocation_depth = newDepth; + outpamP->maxval = newMaxval; + outpamP->format = allPbm ? RPBM_FORMAT : PAM_FORMAT; + STRSCPY(outpamP->tuple_type, tupleTypeVaries ? "" : newTupletype); + outpamP->comment_p = NULL; + outpamP->plainformat = false; + + if (verbose) { + 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 { + pm_message("Output maxval (max of all inputs): %lu", + outpamP->maxval); + if (strlen(outpamP->tuple_type) > 0) + pm_message("Output tuple type (same as all inputs): '%s'", + outpamP->tuple_type); + else + pm_message("Output tuple type is null string because input " + "images have various tuple types"); + } + } +} + + + +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 */ +} ImgInfoPbm; + + + +static void +getPbmImageInfo(const struct pam * const inpam, /* array */ + unsigned int const fileCt, + unsigned int const newRows, + enum Justification const justification, + enum PadColorMethod const padColorMethod, + ImgInfoPbm ** const imgInfoP) { +/*---------------------------------------------------------------------------- + Read the first row of each image in inpam[] and return that and additional + information about images as *imgInfoP. +-----------------------------------------------------------------------------*/ + ImgInfoPbm * imgInfo; /* array, size 'fileCt' */ + unsigned int i; + + MALLOCARRAY_NOFAIL(imgInfo, fileCt); + + for (i = 0; i < fileCt; ++i) { + switch (justification) { + case JUST_MIN: + imgInfo[i].padtop = 0; + break; + case JUST_MAX: + imgInfo[i].padtop = newRows - inpam[i].height; + break; + case JUST_CENTER: + imgInfo[i].padtop = (newRows - inpam[i].width) / 2; + break; + } + + imgInfo[i].offset = + (i == 0) ? 0 : imgInfo[i-1].offset + inpam[i-1].width; + + if (inpam[i].height == newRows) /* no padding */ + imgInfo[i].proberow = NULL; + else { /* determine pad color for image i */ + switch (padColorMethod) { + case PAD_AUTO: { + bit bgBit; + imgInfo[i].proberow = + pbm_allocrow_packed((unsigned int)inpam[i].width + 7); + pbm_readpbmrow_bitoffset( + inpam[i].file, imgInfo[i].proberow, + inpam[i].width, inpam[i].format, imgInfo[i].offset % 8); + + bgBit = pbm_backgroundbitrow( + imgInfo[i].proberow, inpam[i].width, + imgInfo[i].offset % 8); + + imgInfo[i].background = bgBit == PBM_BLACK ? 0xff : 0x00; + } break; + case PAD_BLACK: + imgInfo[i].proberow = NULL; + imgInfo[i].background = 0xff; + break; + case PAD_WHITE: + imgInfo[i].proberow = NULL; + imgInfo[i].background = 0x00; + break; + } + } + } + *imgInfoP = imgInfo; +} + + + +static void +destroyPbmImgInfo(ImgInfoPbm * const imgInfo, + unsigned int const fileCt) { + + unsigned int i; + + for (i = 0; i < fileCt; ++i) { + if (imgInfo[i].proberow) + free(imgInfo[i].proberow); + } + free(imgInfo); +} + + + +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 imgInfo.proberow) goes directly into this + packed PBM row. + */ + + ImgInfoPbm * imgInfo; + /* malloc'ed array, one element per image. Shadows inpam[] */ + unsigned int row; + + getPbmImageInfo(inpam, fileCt, outpamP->height, + justification, padColorMethod, + &imgInfo); + + outrow[pbm_packed_bytes(outpamP->width)-1] = 0x00; + + for (row = 0; row < outpamP->width; ++row) { + unsigned int i; + + for (i = 0; i < fileCt; ++i) { + + if ((row == 0 && imgInfo[i].padtop > 0) || + row == imgInfo[i].padtop + inpam[i].height) { + + /* This row begins a run of padding, either above or below + file 'i', so set 'outrow' to padding. + */ + padFillBitrow(outrow, imgInfo[i].background, inpam[i].width, + imgInfo[i].offset); + } + + if (row == imgInfo[i].padtop && imgInfo[i].proberow != NULL) { + /* Top row has been read to proberow[] to determine + background. Copy it to outrow[]. + */ + copyBitrow(imgInfo[i].proberow, outrow, + inpam[i].width, imgInfo[i].offset); + } else if (row >= imgInfo[i].padtop && + row < imgInfo[i].padtop + inpam[i].height) { + pbm_readpbmrow_bitoffset( + inpam[i].file, outrow, inpam[i].width, inpam[i].format, + imgInfo[i].offset); + } else { + /* It's a row of padding, so outrow[] is already set + appropriately. + */ + } + } + pbm_writepbmrow_packed(outpamP->file, outrow, outpamP->width, 0); + } + + destroyPbmImgInfo(imgInfo, 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 i; + 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 (i = 0; i < fileCt; ++i) { + if (inpam[i].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 - inpam[i].width; + break; + case JUST_CENTER: + padleft = (outpamP->width - inpam[i].width) / 2; + break; + } + + switch (padColorMethod) { + case PAD_AUTO: { + bit bgBit; + + startRow = 1; + + pbm_readpbmrow_bitoffset( + inpam[i].file, outrow, inpam[i].width, inpam[i].format, + padleft); + + bgBit = pbm_backgroundbitrow(outrow, inpam[i].width, padleft); + background = bgBit == PBM_BLACK ? 0xff : 0x00; + + backChange = (i == 0 || background != backgroundPrev); + } break; + case PAD_WHITE: + case PAD_BLACK: + startRow = 0; + backChange = (i == 0); + break; + } + + if (backChange || (i > 0 && inpam[i-1].width > inpam[i].width)) { + unsigned int const padright = + outpamP->width - padleft - inpam[i].width; + + if (padleft > 0) + padFillBitrow(outrow, background, padleft, 0); + + if (padright > 0) + padFillBitrow(outrow, background, padright, + padleft + inpam[i].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 < inpam[i].height; ++row) { + pbm_readpbmrow_bitoffset(inpam[i].file, outrow, inpam[i].width, + inpam[i].format, padleft); + pbm_writepbmrow_packed(outpamP->file, outrow, outpamP->width, 0); + } + + backgroundPrev = background; + } + pbm_freerow_packed(outrow); +} + + + +static void +padPlanesRow(enum PlanePadMethod const planePadMethod, + const struct pam * const inpamP, + tuple * const outrow, + const struct pam * const outpamP) { +/*---------------------------------------------------------------------------- + Add additional planes to *outrow as needed to pad out to the depth + indicated in *outpamP from the depth indicated in *inpamP. + + 'planePadMethod' tells what to use for the new planes. +-----------------------------------------------------------------------------*/ + unsigned int plane; + + for (plane = inpamP->depth; plane < outpamP->depth; ++plane) { + unsigned int col; + + for (col = 0; col < inpamP->width; ++col) { + switch (planePadMethod) { + case PLANEPAD_ZERO: + outrow[col][plane] = 0; + break; + case PLANEPAD_EXTEND: + assert(plane >= 1); + /* Because depth is always at least 1 and we started at + inpam[i].depth: + */ + outrow[col][plane] = outrow[col][plane-1]; + break; + } + } + } +} + + + +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; + +/* TODO: free LrImgCtl, including allocated stuff within */ + + +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, + enum PlanePadMethod const planePadMethod, + LrImgCtl ** const imgCtlP) { + + LrImgCtl * imgCtl; /* array */ + unsigned int i; + + MALLOCARRAY_NOFAIL(imgCtl, fileCt); + + for (i = 0; i < fileCt; ++i) { + struct LrImgCtl * const thisEntryP = &imgCtl[i]; + const struct pam * const inpamP = &inpam[i]; + + 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 = + (i == 0 ? &newTuplerow[0] : imgCtl[i-1].out + inpam[i-1].width); + + if (inpamP->height == outpamP->height) /* no vertical padding */ + thisEntryP->cachedRow = NULL; + else { + /* Determine pad color */ + switch (padColorMethod){ + case PAD_AUTO: + thisEntryP->cachedRow = pnm_allocpamrow(&inpam[i]); + pnm_readpamrow(&inpam[i], thisEntryP->cachedRow); + padPlanesRow(planePadMethod, &inpam[i], thisEntryP->cachedRow, + outpamP); + pnm_scaletuplerow(&inpam[i], thisEntryP->cachedRow, + thisEntryP->cachedRow, outpamP->maxval); + thisEntryP->background = pnm_backgroundtuplerow( + thisEntryP->cachedRow, &inpam[i], outpamP); + 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; + } + } + } + *imgCtlP = imgCtl; +} + + + +static void +destroyLrImgCtlArray(LrImgCtl * const imgCtl, /* array */ + unsigned int const fileCt) { + + unsigned int i; + + for (i = 0; i < fileCt; ++i) { + struct LrImgCtl * const thisEntryP = &imgCtl[i]; + + pnm_freepamtuple(thisEntry->background); + pnm_freepamrow(thisEntry->.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, + enum PlanePadMethod const planePadMethod) { + + tuple * const outrow = pnm_allocpamrow(outpamP); + + LrImgCtl * imgCtl; + unsigned int row; + + createLrImgCtlArray(inpam, fileCt, outrow, outpamP, + justification, padColorMethod, planePadMethod, + &imgCtl); + + for (row = 0; row < outpamP->height; ++row) { + unsigned int i; + + for (i = 0; i < fileCt; ++i) { + LrImgCtl * const thisEntryP = &imgCtl[i]; + struct pam * const inpamP = &inpam[i]; + + if ((row == 0 && thisEntryP->padtop > 0) || + row == thisEntryP->padtop + inpamP->height) { + /* This row begins a run of padding, either above or below + image 'i', so set its part of outrow[] to padding. + */ + unsigned int col; + for (col = 0; col < inpamP->width; ++col) + thisEntryP->out[col] = thisEntryP->background; + } + if (row == thisEntryP->padtop && thisEntryP->cachedRow) { + /* We're at the top row of file 'i', and that row + has already been read to cachedRow[] to determine + background. Copy it to image i's part of outrow[]. + */ + unsigned int col; + for (col = 0; col < inpamP->width; ++col) + thisEntryP->out[col] = thisEntryP->cachedRow[col]; + + free(thisEntryP->cachedRow); + thisEntryP->cachedRow = NULL; + } else if (row >= thisEntryP->padtop && + row < thisEntryP->padtop + inpamP->height) { + pnm_readpamrow(&inpam[i], thisEntryP->out); + padPlanesRow(planePadMethod, &inpam[i], thisEntryP->out, + outpamP); + pnm_scaletuplerow(&inpam[i], thisEntryP->out, + thisEntryP->out, outpamP->maxval); + } else { + /* It's a row of padding, so image i'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); + } + pnm_freepamrow(outrow); +} + + + +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 + than for the previous one. + + 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 +concatenateTopBottomGen(const struct pam * const outpamP, + const struct pam * const inpam, /* array */ + unsigned int const fileCt, + enum Justification const justification, + enum PadColorMethod const padColorMethod, + enum PlanePadMethod const planePadMethod) { + + tuple * const newTuplerow = pnm_allocpamrow(outpamP); + tuple * out; + /* The location in newTuplerow[] that the row from the current + input image goes. + */ + unsigned int i; + tuple background; + tuple backgroundPrev; + + switch (padColorMethod) { + case PAD_AUTO: /* do nothing now, determine at start of each image */ + break; + case PAD_BLACK: + pnm_createBlackTuple(outpamP, &background); + break; + case PAD_WHITE: + pnm_createWhiteTuple(outpamP, &background); + break; + } + + for (i = 0; i < fileCt; ++i, backgroundPrev = background) { + 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, &inpam[i], justification); + + if (padColorMethod == PAD_AUTO) { + /* Determine background color */ + + startRow = 1; + out = &newTuplerow[padLeft]; + + pnm_readpamrow(&inpam[i], out); + + padPlanesRow(planePadMethod, &inpam[i], out, outpamP); + + pnm_scaletuplerow(&inpam[i], out, out, outpamP->maxval); + + background = pnm_backgroundtuplerow(out, &inpam[i], outpamP); + + backChanged = + i == 0 || + pnm_tupleequal(outpamP, background, backgroundPrev); + } else { + /* Background color is constant: black or white */ + startRow = 0; + out = &newTuplerow[padLeft]; + backChanged = (i == 0); + } + + setHorizPadding(newTuplerow, outpamP, backChanged, inpam, i, + padLeft, background); + } + + if (startRow == 1) + /* Top row already read for auto background + color determination. Write it out. */ + pnm_writepamrow(outpamP, newTuplerow); + + for (row = startRow; row < inpamP->height; ++row) { + pnm_readpamrow(&inpam[i], out); + + pnm_writepamrow(outpamP, newTuplerow); + } + } + pnm_freepamrow(newTuplerow); +} + + + +int +main(int argc, + const char ** argv) { + + struct CmdlineInfo cmdline; + struct pam * inpam; /* malloc'ed array */ + struct pam outpam; + unsigned int i; + + pm_proginit(&argc, argv); + + parseCommandLine(argc, argv, &cmdline); + + MALLOCARRAY_NOFAIL(inpam, cmdline.fileCt); + + for (i = 0; i < cmdline.fileCt; ++i) { + FILE * const ifP = pm_openr(cmdline.inputFileName[i]); + pnm_readpaminit(ifP, &inpam[i], PAM_STRUCT_SIZE(opacity_plane)); + } + + outpam.file = stdout; + + computeOutputParms(cmdline.fileCt, cmdline.orientation, inpam, + cmdline.verbose, &outpam); + + pnm_writepaminit(&outpam); + + if (outpam.format == RPBM_FORMAT) { + switch (cmdline.orientation) { + case LEFTRIGHT: + concatenateLeftRightPbm(&outpam, inpam, cmdline.fileCt, + cmdline.justification, + cmdline.padColorMethod); + break; + case TOPBOTTOM: + concatenateTopBottomPbm(&outpam, inpam, cmdline.fileCt, + cmdline.justification, + cmdline.padColorMethod); + break; + } + } else { + switch (cmdline.orientation) { + case LEFTRIGHT: + concatenateLeftRightGen(&outpam, inpam, cmdline.fileCt, + cmdline.justification, + cmdline.padColorMethod, + cmdline.planePadMethod); + break; + case TOPBOTTOM: + concatenateTopBottomGen(&outpam, inpam, cmdline.fileCt, + cmdline.justification, + cmdline.padColorMethod, + cmdline.planePadMethod); + break; + } + } + for (i = 0; i < cmdline.fileCt; ++i) + pm_close(inpam[i].file); + free(cmdline.inputFileName); + free(inpam); + pm_close(stdout); + + return 0; +} + + + |