Blame editor/pnmpaste.c

Packit 78deda
/* pnmpaste.c - paste a rectangle into a PNM image
Packit 78deda
**
Packit 78deda
** Copyright (C) 1989 by Jef Poskanzer.
Packit 78deda
**
Packit 78deda
** Permission to use, copy, modify, and distribute this software and its
Packit 78deda
** documentation for any purpose and without fee is hereby granted, provided
Packit 78deda
** that the above copyright notice appear in all copies and that both that
Packit 78deda
** copyright notice and this permission notice appear in supporting
Packit 78deda
** documentation.  This software is provided "as is" without express or
Packit 78deda
** implied warranty.
Packit 78deda
*/
Packit 78deda
Packit 78deda
#include <assert.h>
Packit 78deda
Packit 78deda
#include "pm_c_util.h"
Packit 78deda
#include "mallocvar.h"
Packit 78deda
#include "nstring.h"
Packit 78deda
#include "shhopt.h"
Packit 78deda
#include "pnm.h"
Packit 78deda
Packit 78deda
Packit 78deda
enum boolOp {REPLACE, AND, OR, XOR /*, NAND, NOR, NXOR */ };
Packit 78deda
Packit 78deda
struct CmdlineInfo {
Packit 78deda
    /* All the information the user supplied in the command line,
Packit 78deda
       in a form easy for the program to use.
Packit 78deda
    */
Packit 78deda
    const char * baseFilename;
Packit 78deda
    const char * insetFilename;
Packit 78deda
    int insertCol;  /* Negative means from right edge */
Packit 78deda
    int insertRow;  /* Negative means from bottom edge */
Packit 78deda
    enum boolOp operation;
Packit 78deda
};
Packit 78deda
Packit 78deda
Packit 78deda
Packit 78deda
static void
Packit 78deda
parseCommandLine(int argc, const char ** argv,
Packit 78deda
                 struct CmdlineInfo * const cmdlineP) {
Packit 78deda
/*----------------------------------------------------------------------------
Packit 78deda
   Note that the file spec array we return is stored in the storage that
Packit 78deda
   was passed to us as the argv array.
Packit 78deda
-----------------------------------------------------------------------------*/
Packit 78deda
    optEntry *option_def;
Packit 78deda
        /* Instructions to OptParseOptions3 on how to parse our options.
Packit 78deda
         */
Packit 78deda
    optStruct3 opt;
Packit 78deda
Packit 78deda
    unsigned int option_def_index;
Packit 78deda
    unsigned int replaceOpt, andOpt, orOpt, xorOpt;
Packit 78deda
Packit 78deda
    MALLOCARRAY_NOFAIL(option_def, 100);
Packit 78deda
Packit 78deda
    option_def_index = 0;   /* incremented by OPTENT3 */
Packit 78deda
    OPTENT3(0,   "replace",     OPT_FLAG,    NULL,
Packit 78deda
            &replaceOpt,           0);
Packit 78deda
    OPTENT3(0,   "and",         OPT_FLAG,    NULL,
Packit 78deda
            &andOpt,               0);
Packit 78deda
    OPTENT3(0,   "or",          OPT_FLAG,    NULL,
Packit 78deda
            &orOpt,                0);
Packit 78deda
    OPTENT3(0,   "xor",         OPT_FLAG,    NULL,
Packit 78deda
            &xorOpt,               0);
Packit 78deda
Packit 78deda
    opt.opt_table = option_def;
Packit 78deda
    opt.short_allowed = FALSE;  /* We have no short (old-fashioned) options */
Packit 78deda
    opt.allowNegNum = TRUE;  /* We have parms that are negative numbers */
Packit 78deda
Packit 78deda
    pm_optParseOptions3(&argc, (char **)argv, opt, sizeof opt, 0);
Packit 78deda
        /* Uses and sets argc, argv, and some of *cmdlineP and others. */
Packit 78deda
Packit 78deda
    if (replaceOpt + andOpt + orOpt + xorOpt > 1)
Packit 78deda
        pm_error("You may specify only one of -replace, -and, -or, and -xor");
Packit 78deda
Packit 78deda
    cmdlineP->operation =
Packit 78deda
        replaceOpt ? REPLACE :
Packit 78deda
        andOpt     ? AND     :
Packit 78deda
        orOpt      ? OR      :
Packit 78deda
        xorOpt     ? XOR     :
Packit 78deda
        replaceOpt;
Packit 78deda
        
Packit 78deda
Packit 78deda
    if (argc-1 >= 3) {
Packit 78deda
        cmdlineP->insetFilename = argv[1];
Packit 78deda
        cmdlineP->insertCol     = atoi(argv[2]);
Packit 78deda
        cmdlineP->insertRow     = atoi(argv[3]);
Packit 78deda
Packit 78deda
        if (argc-1 >= 4) {
Packit 78deda
            cmdlineP->baseFilename = argv[4];
Packit 78deda
Packit 78deda
            if (argc-1 > 4)
Packit 78deda
                pm_error("Too many arguments: %u.  This program takes "
Packit 78deda
                         "at most 4", argc-1);
Packit 78deda
        } else
Packit 78deda
            cmdlineP->baseFilename = "-";
Packit 78deda
    } else
Packit 78deda
        pm_error("You must specify at least 3 arguments: \"from\" file "
Packit 78deda
                 "name, insert-at column, and insert-at row.  "
Packit 78deda
                 "You specified %u", argc-1);
Packit 78deda
Packit 78deda
    if (streq(cmdlineP->baseFilename, "-") &&
Packit 78deda
        streq(cmdlineP->insetFilename, "-"))
Packit 78deda
        pm_error("You can't use Standard Input for both the input images");
Packit 78deda
}
Packit 78deda
Packit 78deda
Packit 78deda
Packit 78deda
static unsigned char
Packit 78deda
leftBits(unsigned char const x,
Packit 78deda
         unsigned int  const n){
Packit 78deda
/*----------------------------------------------------------------------------
Packit 78deda
  'x' with the leftmost (high) n bits retained and the rest cleared to zero.
Packit 78deda
-----------------------------------------------------------------------------*/
Packit 78deda
    assert(n <= 8);
Packit 78deda
Packit 78deda
    return (x >> (8-n)) << (8-n);
Packit 78deda
}
Packit 78deda
Packit 78deda
Packit 78deda
Packit 78deda
static unsigned char
Packit 78deda
rightBits(unsigned char const x,
Packit 78deda
          unsigned int  const n){
Packit 78deda
/*----------------------------------------------------------------------------
Packit 78deda
  The rightmost 'n' bits of 'x'.
Packit 78deda
-----------------------------------------------------------------------------*/
Packit 78deda
    assert(n <= 8);
Packit 78deda
Packit 78deda
    return ((unsigned char)(x << (8-n))) >> (8-n);
Packit 78deda
}
Packit 78deda
Packit 78deda
Packit 78deda
Packit 78deda
static void
Packit 78deda
insertDirect(FILE *          const ifP,
Packit 78deda
             unsigned char * const destrow,
Packit 78deda
             unsigned int    const cols,
Packit 78deda
             int             const format,
Packit 78deda
             enum boolOp     const operation,
Packit 78deda
             unsigned char * const buffer) {
Packit 78deda
/*----------------------------------------------------------------------------
Packit 78deda
   Read the next row from PBM file 'ifP' and merge it according to
Packit 78deda
   'operation' into 'destrow', flush left in packed PBM format.
Packit 78deda
Packit 78deda
   'cols' and 'format' describe the 'ifP' image.
Packit 78deda
Packit 78deda
   'buffer' is a scratch buffer for our use, at least wide enough to hold
Packit 78deda
   a packed PBM row of 'ifP'.
Packit 78deda
-----------------------------------------------------------------------------*/
Packit 78deda
    /* We use pbm_readpbmrow_packed() to read whole bytes rounded up and merge
Packit 78deda
       those into 'destrow', which means we update more than we're supposed to
Packit 78deda
       if the image is not a multiple of 8 columns.  In that case, we then fix
Packit 78deda
       up the last byte by replacing the bits from the original image that we
Packit 78deda
       messed up.
Packit 78deda
    */
Packit 78deda
    unsigned int  const colBytes  = pbm_packed_bytes(cols);
Packit 78deda
    unsigned int  const last      = colBytes - 1;
Packit 78deda
    unsigned char const origRight = destrow[last];
Packit 78deda
Packit 78deda
    if (operation == REPLACE)
Packit 78deda
        pbm_readpbmrow_packed(ifP, destrow, cols, format);
Packit 78deda
    else {
Packit 78deda
        unsigned int i;
Packit 78deda
Packit 78deda
        pbm_readpbmrow_packed(ifP, buffer, cols, format);
Packit 78deda
Packit 78deda
        for (i = 0; i < colBytes; ++i) {
Packit 78deda
            switch (operation) {
Packit 78deda
            case AND: destrow[i] |= buffer[i]; break;
Packit 78deda
            case OR : destrow[i] &= buffer[i]; break;
Packit 78deda
            case XOR: destrow[i]  = ~( destrow[i] ^ buffer[i] ) ; break;
Packit 78deda
            /*
Packit 78deda
            case NAND: destrow[i] = ~( destrow[i] | buffer[i] ) ; break;
Packit 78deda
            case NOR : destrow[i] = ~( destrow[i] & buffer[i] ) ; break;
Packit 78deda
            case NXOR: destrow[i] ^= buffer[i]  ; break;
Packit 78deda
            */
Packit 78deda
            case REPLACE: assert(false); break;
Packit 78deda
            }
Packit 78deda
        }
Packit 78deda
    }
Packit 78deda
Packit 78deda
    /* destrow[] now contains garbage in all but the cols % 8 leftmost bits of
Packit 78deda
       the last byte we touched.  Those are supposed to be unchanged from the
Packit 78deda
       input, so we restore them now.
Packit 78deda
    */
Packit 78deda
    if (cols % 8 > 0)
Packit 78deda
        destrow[last] = leftBits(destrow[last], cols % 8)
Packit 78deda
            | rightBits(origRight, 8 - cols % 8);
Packit 78deda
}
Packit 78deda
Packit 78deda
Packit 78deda
Packit 78deda
static void
Packit 78deda
insertShift(FILE *          const ifP,
Packit 78deda
            unsigned char * const destrow,
Packit 78deda
            unsigned int    const cols,
Packit 78deda
            unsigned int    const format,
Packit 78deda
            unsigned int    const offset,
Packit 78deda
            enum boolOp     const operation,
Packit 78deda
            unsigned char * const buffer) {
Packit 78deda
/*----------------------------------------------------------------------------
Packit 78deda
   Same as insertDirect(), but start merging 'offset' bits from the left
Packit 78deda
   end of 'destrow'.  'offset' is less than 8.
Packit 78deda
Packit 78deda
   buffer[] is wide enough to hold a packed PBM row of *ifP plus two
Packit 78deda
   bytes of margin.
Packit 78deda
-----------------------------------------------------------------------------*/
Packit 78deda
    unsigned int  const shiftByteCt = pbm_packed_bytes(cols + offset);
Packit 78deda
    unsigned int  const last        = shiftByteCt - 1;
Packit 78deda
    unsigned char const origLeft    = destrow[0];
Packit 78deda
    unsigned char const origRight   = destrow[last];
Packit 78deda
Packit 78deda
    unsigned int const padOffset = (cols + offset) % 8;
Packit 78deda
Packit 78deda
    unsigned int i;
Packit 78deda
Packit 78deda
    assert(offset < 8);
Packit 78deda
Packit 78deda
    pbm_readpbmrow_packed(ifP, &buffer[1], cols, format);
Packit 78deda
Packit 78deda
    /* Note that buffer[0] is undefined. */
Packit 78deda
Packit 78deda
    for (i = 0; i < shiftByteCt; ++i) {
Packit 78deda
        unsigned int  const rsh = offset;
Packit 78deda
        unsigned int  const lsh = 8-rsh;
Packit 78deda
        unsigned char const t = buffer[i] << lsh | buffer[i+1] >> rsh;
Packit 78deda
Packit 78deda
        switch (operation) {
Packit 78deda
        case REPLACE: destrow[i] = t; break;
Packit 78deda
        case AND:     destrow[i] |= t; break;
Packit 78deda
        case OR :     destrow[i] &= t; break;
Packit 78deda
        case XOR:     destrow[i] = ~ (destrow[i] ^ t); break;
Packit 78deda
        /*
Packit 78deda
        case NAND:    destrow[i] = ~ (destrow[i] | t); break;
Packit 78deda
        case NOR :    destrow[i] = ~ (destrow[i] & t); break;
Packit 78deda
        case NXOR:    destrow[i] ^= t; break;
Packit 78deda
        */
Packit 78deda
        }
Packit 78deda
    }
Packit 78deda
Packit 78deda
    /* destrow[] now contains garbage in the 'offset' leftmost bits and
Packit 78deda
       8-offset rightmost bits of the last byte we touched.  Those are
Packit 78deda
       supposed to be unchanged from the input, so we restore them now.
Packit 78deda
    */
Packit 78deda
Packit 78deda
    destrow[0] = leftBits(origLeft, offset) |
Packit 78deda
        rightBits(destrow[0], 8-offset);
Packit 78deda
   
Packit 78deda
    if (padOffset % 8 > 0)
Packit 78deda
        destrow[last] = leftBits(destrow[last], padOffset) |
Packit 78deda
            rightBits(origRight , 8-padOffset);
Packit 78deda
}
Packit 78deda
Packit 78deda
Packit 78deda
Packit 78deda
static void
Packit 78deda
pastePbm(FILE *       const fpInset,
Packit 78deda
         FILE *       const fpBase,
Packit 78deda
         int          const insetFormat,
Packit 78deda
         int          const baseFormat,
Packit 78deda
         unsigned int const insetRows,
Packit 78deda
         unsigned int const baseRows,
Packit 78deda
         unsigned int const insetCols,
Packit 78deda
         unsigned int const baseCols,
Packit 78deda
         unsigned int const insertCol,
Packit 78deda
         unsigned int const insertRow,
Packit 78deda
         enum boolOp  const operation) {
Packit 78deda
/*----------------------------------------------------------------------------
Packit 78deda
  Fast paste for PBM
Packit 78deda
-----------------------------------------------------------------------------*/
Packit 78deda
    unsigned char * const baserow       = pbm_allocrow_packed(baseCols);
Packit 78deda
    unsigned char * const buffer        = pbm_allocrow_packed(insetCols+16);
Packit 78deda
    unsigned int    const shiftByteCt   = insertCol / 8;
Packit 78deda
    unsigned int    const shiftOffset   = insertCol % 8;
Packit 78deda
    unsigned int    const baseColByteCt = pbm_packed_bytes(baseCols);
Packit 78deda
Packit 78deda
    unsigned int row;
Packit 78deda
Packit 78deda
    pbm_writepbminit(stdout, baseCols, baseRows, 0);
Packit 78deda
Packit 78deda
    for (row = 0; row < baseRows; ++row) {
Packit 78deda
        pbm_readpbmrow_packed(fpBase, baserow, baseCols, baseFormat);
Packit 78deda
        
Packit 78deda
        if (row >= insertRow && row < insertRow + insetRows) {
Packit 78deda
            if (shiftOffset == 0)
Packit 78deda
                insertDirect(fpInset, &baserow[shiftByteCt], insetCols,
Packit 78deda
                             insetFormat, operation, buffer);
Packit 78deda
            else
Packit 78deda
                insertShift(fpInset, &baserow[shiftByteCt], insetCols,
Packit 78deda
                            insetFormat, shiftOffset, operation, buffer);
Packit 78deda
        }
Packit 78deda
Packit 78deda
        if (baseCols % 8 > 0)
Packit 78deda
            baserow[baseColByteCt-1]
Packit 78deda
                = leftBits(baserow[baseColByteCt-1] , baseCols % 8);
Packit 78deda
Packit 78deda
        pbm_writepbmrow_packed(stdout, baserow, baseCols, 0);
Packit 78deda
    }
Packit 78deda
    pbm_freerow_packed(buffer);
Packit 78deda
    pbm_freerow_packed(baserow);
Packit 78deda
}
Packit 78deda
Packit 78deda
Packit 78deda
Packit 78deda
static void
Packit 78deda
pasteNonPbm(FILE *       const fpInset,
Packit 78deda
            FILE *       const fpBase,
Packit 78deda
            int          const formatInset,
Packit 78deda
            int          const formatBase,
Packit 78deda
            int          const newformat,
Packit 78deda
            xelval       const maxvalInset,
Packit 78deda
            xelval       const maxvalBase,
Packit 78deda
            unsigned int const rowsInset,
Packit 78deda
            unsigned int const rowsBase,
Packit 78deda
            unsigned int const colsInset,
Packit 78deda
            unsigned int const colsBase,
Packit 78deda
            unsigned int const insertCol,
Packit 78deda
            unsigned int const insertRow) {
Packit 78deda
Packit 78deda
    /* Logic works for PBM, but cannot do bitwise operations */             
Packit 78deda
Packit 78deda
    xelval const newmaxval = MAX(maxvalInset, maxvalBase);
Packit 78deda
Packit 78deda
    xel * const xelrowInset = pnm_allocrow(colsInset);
Packit 78deda
    xel * const xelrowBase  = pnm_allocrow(colsBase);
Packit 78deda
Packit 78deda
    unsigned int row;
Packit 78deda
Packit 78deda
    pnm_writepnminit(stdout, colsBase, rowsBase, newmaxval, newformat, 0);
Packit 78deda
Packit 78deda
    for (row = 0; row < rowsBase; ++row) {
Packit 78deda
        pnm_readpnmrow(fpBase, xelrowBase, colsBase, maxvalBase, formatBase);
Packit 78deda
        pnm_promoteformatrow(xelrowBase, colsBase, maxvalBase, formatBase,
Packit 78deda
                             newmaxval, newformat);
Packit 78deda
Packit 78deda
        if (row >= insertRow && row < insertRow + rowsInset) {
Packit 78deda
            unsigned int colInset;
Packit 78deda
Packit 78deda
            pnm_readpnmrow(fpInset, xelrowInset, colsInset, maxvalInset,
Packit 78deda
                           formatInset);
Packit 78deda
            pnm_promoteformatrow(xelrowInset, colsInset, maxvalInset,
Packit 78deda
                                 formatInset, newmaxval, newformat );
Packit 78deda
            for (colInset = 0; colInset < colsInset; ++colInset)
Packit 78deda
                xelrowBase[insertCol + colInset] = xelrowInset[colInset];
Packit 78deda
        }
Packit 78deda
        pnm_writepnmrow(stdout, xelrowBase, colsBase, newmaxval, newformat, 0);
Packit 78deda
    }
Packit 78deda
    
Packit 78deda
    pnm_freerow(xelrowBase);
Packit 78deda
    pnm_freerow(xelrowInset);
Packit 78deda
}
Packit 78deda
Packit 78deda
Packit 78deda
Packit 78deda
int
Packit 78deda
main(int argc, const char ** argv) {
Packit 78deda
Packit 78deda
    struct CmdlineInfo cmdline;
Packit 78deda
    FILE * fpInset;
Packit 78deda
    FILE * fpBase;
Packit 78deda
    xelval maxvalInset, maxvalBase;
Packit 78deda
    int rowsInset, colsInset;
Packit 78deda
    int formatInset;
Packit 78deda
    int rowsBase, colsBase;
Packit 78deda
    int formatBase;
Packit 78deda
    int newformat;
Packit 78deda
    unsigned int insertRow, insertCol;
Packit 78deda
Packit 78deda
    pm_proginit(&argc, argv);
Packit 78deda
Packit 78deda
    parseCommandLine(argc, argv, &cmdline);
Packit 78deda
Packit 78deda
    fpInset = pm_openr(cmdline.insetFilename);
Packit 78deda
    fpBase  = pm_openr(cmdline.baseFilename);
Packit 78deda
Packit 78deda
    pnm_readpnminit(fpInset, &colsInset, &rowsInset,
Packit 78deda
                    &maxvalInset, &formatInset);
Packit 78deda
    pnm_readpnminit(fpBase, &colsBase, &rowsBase, &maxvalBase, &formatBase);
Packit 78deda
Packit 78deda
    if (colsBase < colsInset)
Packit 78deda
        pm_error(
Packit 78deda
            "Image to paste is wider than base image by %u cols",
Packit 78deda
            colsInset - colsBase);
Packit 78deda
    else if (cmdline.insertCol <= -colsBase)
Packit 78deda
        pm_error(
Packit 78deda
            "x is too negative -- the second image has only %u cols",
Packit 78deda
            colsBase);
Packit 78deda
    else if (cmdline.insertCol >= colsBase)
Packit 78deda
        pm_error(
Packit 78deda
            "x is too large -- the second image has only %u cols",
Packit 78deda
            colsBase);
Packit 78deda
Packit 78deda
    if (rowsBase < rowsInset)
Packit 78deda
        pm_error(
Packit 78deda
            "Image to paste is taller than base image by %u rows",
Packit 78deda
            rowsInset - rowsBase);
Packit 78deda
    else if (cmdline.insertRow <= -rowsBase)
Packit 78deda
        pm_error(
Packit 78deda
            "y is too negative -- the second image has only %u rows",
Packit 78deda
            rowsBase);
Packit 78deda
    else if (cmdline.insertRow >= rowsBase)
Packit 78deda
        pm_error(
Packit 78deda
            "y is too large -- the second image has only %d rows",
Packit 78deda
            rowsBase);
Packit 78deda
Packit 78deda
    insertCol = cmdline.insertCol < 0 ?
Packit 78deda
        colsBase + cmdline.insertCol : cmdline.insertCol;
Packit 78deda
    insertRow = cmdline.insertRow < 0 ?
Packit 78deda
        rowsBase + cmdline.insertRow : cmdline.insertRow;
Packit 78deda
Packit 78deda
    if (insertCol + colsInset > colsBase)
Packit 78deda
        pm_error("Extends over right edge by %u pixels",
Packit 78deda
                 (insertCol + colsInset) - colsBase);
Packit 78deda
    if (insertRow + rowsInset > rowsBase)
Packit 78deda
        pm_error("Extends over bottom edge by %u pixels",
Packit 78deda
                 (insertRow + rowsInset) - rowsBase);
Packit 78deda
Packit 78deda
    newformat = MAX(PNM_FORMAT_TYPE(formatInset), PNM_FORMAT_TYPE(formatBase));
Packit 78deda
Packit 78deda
    if (cmdline.operation != REPLACE && newformat != PBM_TYPE)
Packit 78deda
        pm_error("no logical operations allowed for a non-PBM image");
Packit 78deda
Packit 78deda
    if (newformat == PBM_TYPE)
Packit 78deda
        pastePbm(fpInset, fpBase, formatInset, formatBase,
Packit 78deda
                 rowsInset, rowsBase, colsInset, colsBase,
Packit 78deda
                 insertCol, insertRow, cmdline.operation);
Packit 78deda
    else
Packit 78deda
        pasteNonPbm(fpInset, fpBase,
Packit 78deda
                    formatInset, formatBase, newformat,
Packit 78deda
                    maxvalInset, maxvalBase,
Packit 78deda
                    rowsInset, rowsBase, colsInset, colsBase,
Packit 78deda
                    insertCol, insertRow);
Packit 78deda
Packit 78deda
    pm_close(fpInset);
Packit 78deda
    pm_close(fpBase);
Packit 78deda
    pm_close(stdout);
Packit 78deda
Packit 78deda
    return 0;
Packit 78deda
}