diff options
author | giraffedata <giraffedata@9d0c8265-081b-0410-96cb-a4ca84ce46f8> | 2016-06-26 18:15:09 +0000 |
---|---|---|
committer | giraffedata <giraffedata@9d0c8265-081b-0410-96cb-a4ca84ce46f8> | 2016-06-26 18:15:09 +0000 |
commit | aab4792db8e0adcaf63cf57a1f5e0b18666dae47 (patch) | |
tree | bbfa9c4e12783d2dfd785c4e10964366550e9a60 /editor/pbmreduce.c | |
parent | fe14f983ade44baa0794e4ce58a1a5334e46cf68 (diff) | |
download | netpbm-mirror-aab4792db8e0adcaf63cf57a1f5e0b18666dae47.tar.gz netpbm-mirror-aab4792db8e0adcaf63cf57a1f5e0b18666dae47.tar.xz netpbm-mirror-aab4792db8e0adcaf63cf57a1f5e0b18666dae47.zip |
Promote Development to Advanced as Release 10.75.00
git-svn-id: http://svn.code.sf.net/p/netpbm/code/advanced@2803 9d0c8265-081b-0410-96cb-a4ca84ce46f8
Diffstat (limited to 'editor/pbmreduce.c')
-rw-r--r-- | editor/pbmreduce.c | 514 |
1 files changed, 328 insertions, 186 deletions
diff --git a/editor/pbmreduce.c b/editor/pbmreduce.c index ee4a4fbd..3a0968fe 100644 --- a/editor/pbmreduce.c +++ b/editor/pbmreduce.c @@ -10,203 +10,345 @@ ** implied warranty. */ -#include <limits.h> +#include "pm_c_util.h" #include "pbm.h" #include "mallocvar.h" +#include "shhopt.h" +#include <assert.h> -int -main( argc, argv ) - int argc; - char* argv[]; - { - FILE* ifp; - register bit** bitslice; - register bit* newbitrow; - register bit* nbP; - int argn, n, rows, cols, format, newrows, newcols; - int row, col, limitcol, subrow, subcol, count, direction; - const char* const usage = "[-floyd|-fs | -threshold] [-value <val>] N [pbmfile]"; - int halftone; -#define QT_FS 1 -#define QT_THRESH 2 #define SCALE 1024 #define HALFSCALE 512 - long threshval, sum; - long* thiserr; /* used for Floyd-Steinberg stuff */ - long* nexterr; /* used for Floyd-Steinberg stuff */ - long* temperr; - - - pbm_init( &argc, argv ); - - argn = 1; - halftone = QT_FS; - threshval = HALFSCALE; - - while ( argn < argc && argv[argn][0] == '-' && argv[argn][1] != '\0' ) - { - if ( pm_keymatch( argv[argn], "-fs", 2 ) || - pm_keymatch( argv[argn], "-floyd", 2 ) ) - halftone = QT_FS; - else if ( pm_keymatch( argv[argn], "-threshold", 2 ) ) - halftone = QT_THRESH; - else if ( pm_keymatch( argv[argn], "-value", 2 ) ) - { - float f; - - ++argn; - if ( argn == argc || sscanf( argv[argn], "%f", &f ) != 1 || - f < 0.0 || f > 1.0 ) - pm_usage( usage ); - threshval = f * SCALE; - } - else - pm_usage( usage ); - ++argn; - } - - if ( argn == argc ) - pm_usage( usage ); - if ( sscanf( argv[argn], "%d", &n ) != 1 ) - pm_usage( usage ); - if ( n < 2 ) - pm_error( "N must be greater than 1" ); - if (n > INT_MAX / n) - pm_error("Scale argument too large. You specified %d", n); - ++argn; - - if ( argn == argc ) - ifp = stdin; - else - { - ifp = pm_openr( argv[argn] ); - ++argn; - } - - if ( argn != argc ) - pm_usage( usage ); - - pbm_readpbminit( ifp, &cols, &rows, &format ); - bitslice = pbm_allocarray( cols, n ); - - newrows = rows / n; - newcols = cols / n; + + +enum Halftone {QT_FS, QT_THRESH}; + +struct CmdlineInfo { + /* All the information the user supplied in the command line, + in a form easy for the program to use. + */ + const char * inputFilespec; + enum Halftone halftone; + int value; + unsigned int randomseed; + unsigned int randomseedSpec; + int scale; +}; + + + +static void +parseCommandLine(int argc, const char ** argv, + struct CmdlineInfo *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 pm_optParseOptions3 on how to parse our options. + */ + optStruct3 opt; + + unsigned int option_def_index; + unsigned int floydOpt, thresholdOpt; + unsigned int valueSpec; + float value; + + MALLOCARRAY_NOFAIL(option_def, 100); + + option_def_index = 0; /* incremented by OPTENTRY */ + OPTENT3(0, "floyd", OPT_FLAG, NULL, + &floydOpt, 0); + OPTENT3(0, "fs", OPT_FLAG, NULL, + &floydOpt, 0); + OPTENT3(0, "threshold", OPT_FLAG, NULL, + &thresholdOpt, 0); + OPTENT3(0, "value", OPT_FLOAT, &value, + &valueSpec, 0); + OPTENT3(0, "randomseed", OPT_UINT, &cmdlineP->randomseed, + &cmdlineP->randomseedSpec, 0); + + opt.opt_table = option_def; + opt.short_allowed = FALSE; /* We have no short (old-fashioned) options */ + opt.allowNegNum = FALSE; /* We may have 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. */ + + if (floydOpt + thresholdOpt == 0) + cmdlineP->halftone = QT_FS; + else if (!!floydOpt + !!thresholdOpt > 1) + pm_error("Cannot specify both floyd and threshold"); + else { + if (floydOpt) + cmdlineP->halftone = QT_FS; + else { + cmdlineP->halftone = QT_THRESH; + if (cmdlineP->randomseedSpec) + pm_message("-randomseed value has no effect with -threshold"); + } + } + + if (!valueSpec) + cmdlineP->value = HALFSCALE; + else { + if (value < 0.0) + pm_error("-value cannot be negative. You specified %f", value); + if (value > 1.0) + pm_error("-value cannot be greater than one. You specified %f", + value); + else + cmdlineP->value = value * SCALE; + } + + if (argc-1 > 0) { + char * endptr; /* ptr to 1st invalid character in scale arg */ + unsigned int scale; + + scale = strtol(argv[1], &endptr, 10); + if (*argv[1] == '\0') + pm_error("Scale argument is a null string. Must be a number."); + else if (*endptr != '\0') + pm_error("Scale argument contains non-numeric character '%c'.", + *endptr); + else if (scale < 2) + pm_error("Scale argument must be at least 2. " + "You specified %d", scale); + else if (scale > INT_MAX / scale) + pm_error("Scale argument too large. You specified %d", scale); + else + cmdlineP->scale = scale; + + if (argc-1 > 1) { + cmdlineP->inputFilespec = argv[2]; + + if (argc-1 > 2) + pm_error("Too many arguments (%d). There are at most two " + "non-option arguments: " + "scale factor and the file name", + argc-1); + } else + cmdlineP->inputFilespec = "-"; + } else + pm_error("You must specify the scale factor as an argument"); + + free(option_def); +} + + + +struct FS { + int * thiserr; + int * nexterr; +}; + + +static void +initializeFloydSteinberg(struct FS * const fsP, + int const newcols, + unsigned int const seed, + bool const seedSpec) { + + unsigned int col; + + MALLOCARRAY(fsP->thiserr, newcols + 2); + MALLOCARRAY(fsP->nexterr, newcols + 2); + + if (fsP->thiserr == NULL || fsP->nexterr == NULL) + pm_error("out of memory"); + + srand(seedSpec ? seed : pm_randseed()); + + for (col = 0; col < newcols + 2; ++col) + fsP->thiserr[col] = (rand() % SCALE - HALFSCALE) / 4; + /* (random errors in [-SCALE/8 .. SCALE/8]) */ +} + + + +/* + Scanning method + + In Floyd-Steinberg dithering mode horizontal direction of scan alternates + between rows; this is called "serpentine scanning". + + Example input (14 x 7), N=3: + + 111222333444xx Fractional pixels on the right edge and bottom edge (x) + 111222333444xx are ignored; their values do not influence output. + 111222333444xx + 888777666555xx + 888777666555xx + 888777666555xx + xxxxxxxxxxxxxx + + Output (4 x 2): + + 1234 + 8765 + +*/ + + + +enum Direction { RIGHT_TO_LEFT, LEFT_TO_RIGHT }; + + +static enum Direction +oppositeDir(enum Direction const arg) { + + switch (arg) { + case LEFT_TO_RIGHT: return RIGHT_TO_LEFT; + case RIGHT_TO_LEFT: return LEFT_TO_RIGHT; + } + assert(false); /* All cases handled above */ +} + + + +int +main(int argc, const char * argv[]) { + + FILE * ifP; + struct CmdlineInfo cmdline; + bit ** bitslice; + bit * newbitrow; + int rows, cols; + int format; + unsigned int newrows, newcols; + unsigned int row; + enum Direction direction; + struct FS fs; + + pm_proginit(&argc, argv); + + parseCommandLine(argc, argv, &cmdline); + + ifP = pm_openr(cmdline.inputFilespec); + + pbm_readpbminit(ifP, &cols, &rows, &format); + + bitslice = pbm_allocarray(cols, cmdline.scale); + + if (rows < cmdline.scale || cols < cmdline.scale) + pm_error("Scale argument (%u) too large for image", cmdline.scale); + else { + newrows = rows / cmdline.scale; + newcols = cols / cmdline.scale; + } pbm_writepbminit( stdout, newcols, newrows, 0 ); - newbitrow = pbm_allocrow( newcols ); + newbitrow = pbm_allocrow_packed( newcols ); - if (halftone == QT_FS) { - unsigned int col; - /* Initialize Floyd-Steinberg. */ - MALLOCARRAY(thiserr, newcols + 2); - MALLOCARRAY(nexterr, newcols + 2); - if (thiserr == NULL || nexterr == NULL) - pm_error("out of memory"); - - srand(pm_randseed()); - for (col = 0; col < newcols + 2; ++col) - thiserr[col] = (rand() % SCALE - HALFSCALE) / 4; - /* (random errors in [-SCALE/8 .. SCALE/8]) */ - } else { + if (cmdline.halftone == QT_FS) + initializeFloydSteinberg(&fs, newcols, + cmdline.randomseed, cmdline.randomseedSpec); + else { /* These variables are meaningless in this case, and the values should never be used. */ - thiserr = NULL; - nexterr = NULL; + fs.thiserr = NULL; + fs.nexterr = NULL; } - direction = 1; - - for ( row = 0; row < newrows; ++row ) - { - for ( subrow = 0; subrow < n; ++subrow ) - pbm_readpbmrow( ifp, bitslice[subrow], cols, format ); - - if ( halftone == QT_FS ) - for ( col = 0; col < newcols + 2; ++col ) - nexterr[col] = 0; - if ( direction ) - { - col = 0; - limitcol = newcols; - nbP = newbitrow; - } - else - { - col = newcols - 1; - limitcol = -1; - nbP = &(newbitrow[col]); - } - - do - { - sum = 0; - count = 0; - for ( subrow = 0; subrow < n; ++subrow ) - for ( subcol = 0; subcol < n; ++subcol ) - if ( row * n + subrow < rows && col * n + subcol < cols ) - { - count += 1; - if ( bitslice[subrow][col * n + subcol] == PBM_WHITE ) - sum += 1; - } - sum = ( sum * SCALE ) / count; - - if ( halftone == QT_FS ) - sum += thiserr[col + 1]; - - if ( sum >= threshval ) - { - *nbP = PBM_WHITE; - if ( halftone == QT_FS ) - sum = sum - threshval - HALFSCALE; - } - else - *nbP = PBM_BLACK; - - if ( halftone == QT_FS ) - { - if ( direction ) - { - thiserr[col + 2] += ( sum * 7 ) / 16; - nexterr[col ] += ( sum * 3 ) / 16; - nexterr[col + 1] += ( sum * 5 ) / 16; - nexterr[col + 2] += ( sum ) / 16; - } - else - { - thiserr[col ] += ( sum * 7 ) / 16; - nexterr[col + 2] += ( sum * 3 ) / 16; - nexterr[col + 1] += ( sum * 5 ) / 16; - nexterr[col ] += ( sum ) / 16; - } - } - if ( direction ) - { - ++col; - ++nbP; - } - else - { - --col; - --nbP; - } - } - while ( col != limitcol ); - - pbm_writepbmrow( stdout, newbitrow, newcols, 0 ); - - if ( halftone == QT_FS ) - { - temperr = thiserr; - thiserr = nexterr; - nexterr = temperr; - direction = ! direction; - } - } - - pm_close( ifp ); - pm_close( stdout ); - - exit( 0 ); + + for (row = 0, direction = LEFT_TO_RIGHT; row < newrows; ++row) { + unsigned int const colChars = pbm_packed_bytes(newcols); + + unsigned int colChar; + unsigned int subrow; + unsigned int col; + int limitCol; + int startCol; + int step; + + for (colChar = 0; colChar < colChars; ++colChar) + newbitrow[colChar] = 0x00; /* Clear to white */ + + for (subrow = 0; subrow < cmdline.scale; ++subrow) + pbm_readpbmrow(ifP, bitslice[subrow], cols, format); + + if (cmdline.halftone == QT_FS) { + unsigned int col; + for (col = 0; col < newcols + 2; ++col) + fs.nexterr[col] = 0; + } + switch (direction) { + case LEFT_TO_RIGHT: { + startCol = 0; + limitCol = newcols; + step = +1; + } break; + case RIGHT_TO_LEFT: { + startCol = newcols - 1; + limitCol = -1; + step = -1; + } break; + } + + for (col = startCol; col != limitCol; col += step) { + int const n = cmdline.scale; + unsigned int sum; + int sumScaled; + unsigned int subrow; + + for (subrow = 0, sum = 0; subrow < n; ++subrow) { + unsigned int subcol; + for (subcol = 0; subcol < n; ++subcol) { + assert(row * n + subrow < rows); + assert(col * n + subcol < cols); + if (bitslice[subrow][col * n + subcol] == PBM_WHITE) + ++sum; + } + } + + sumScaled = (sum * SCALE) / (SQR(n)); + + if (cmdline.halftone == QT_FS) + sumScaled += fs.thiserr[col + 1]; + + if (sumScaled >= cmdline.value) { + if (cmdline.halftone == QT_FS) + sumScaled = sumScaled - cmdline.value - HALFSCALE; + } else + newbitrow[col/8] |= (PBM_BLACK << (7 - col%8)); + + if (cmdline.halftone == QT_FS) { + switch (direction) { + case LEFT_TO_RIGHT: { + fs.thiserr[col + 2] += ( sumScaled * 7 ) / 16; + fs.nexterr[col ] += ( sumScaled * 3 ) / 16; + fs.nexterr[col + 1] += ( sumScaled * 5 ) / 16; + fs.nexterr[col + 2] += ( sumScaled ) / 16; + break; + } + case RIGHT_TO_LEFT: { + fs.thiserr[col ] += ( sumScaled * 7 ) / 16; + fs.nexterr[col + 2] += ( sumScaled * 3 ) / 16; + fs.nexterr[col + 1] += ( sumScaled * 5 ) / 16; + fs.nexterr[col ] += ( sumScaled ) / 16; + break; + } + } + } + } + + pbm_writepbmrow_packed(stdout, newbitrow, newcols, 0); + + if (cmdline.halftone == QT_FS) { + int * const temperr = fs.thiserr; + fs.thiserr = fs.nexterr; + fs.nexterr = temperr; + direction = oppositeDir(direction); + } } + free(fs.thiserr); + free(fs.nexterr); + + pbm_freerow(newbitrow); + pbm_freearray(bitslice, cmdline.scale); + pm_close(ifP); + pm_close(stdout); + + return 0; +} + |