--- OLD/pnm/pnmscale.c Thu Jan 1 00:00:00 1970 +++ NEW/pnm/pnmscale.c Thu Jan 1 00:00:00 1970 @@ -1,464 +1,1881 @@ -/* pnmscale.c - read a portable anymap and scale it -** -** Copyright (C) 1989, 1991 by Jef Poskanzer. -** -** Permission to use, copy, modify, and distribute this software and its -** documentation for any purpose and without fee is hereby granted, provided -** that the above copyright notice appear in all copies and that both that -** copyright notice and this permission notice appear in supporting -** documentation. This software is provided "as is" without express or -** implied warranty. -*/ +/* + * Rewriting of pnmscale. The pbmplus pnmscale is buggy: I found a + * 1000x100 image that when pnmscaled by .01 produces a fairly + * drastically different result from when rotated by 180°, pnmscaled + * by .01, and rotated back. I conjecture this is because of the + * peculiar power-of-two scaling that code does, presumably in an + * attempt to use only integer arithmetic - but which I suspect + * introduces roundoff errors that really shouldn't be there. (I + * suspect, for example, that scaling by .01 actually scales by not + * 1/100 but 1/102.4, appalling as that is; this could explain the + * errors I saw.) + * + * This version also lets you specify exact pixel scaling ratios as + * rational fractions, which are obeyed exactly even if they're + * something like 5/13 that can't be represented in machine floating + * point. It also supports pam files, and supports scaling that does + * something even though it doesn't change the pixel dimensions of the + * image, like scaling a 10x10 image to 95% (I think the other + * pnmscale will "optimize" such scaling into a no-op). + * + * The interface is like the stock pnmscale, except that scaling + * factors can be rational fractions (integer/integer) as well as + * integers or floats. It also accepts an integer or a float followed + * by a %, which effectively appends /100 to integers and divides + * floats by 100. (When using floats, this version uses C doubles to + * do the arithmetic, to avoid the sort of roundoff error that the + * other version suffers from.) Also, this version does not promote + * pbm to pgm unless it needs to, and never is noisy about promoting. + * + * Specifically, the argument list can have + * + * A simple scaling factor, S + * + * A scaling factor or new size for one dimension, or one for each + * dimension, where "scaling factor" means "-[xy]scale S" and "new + * size" means "-[xy]size|-width|-height N" + * + * One of the special forms "-xysize N N", "-xyshrink N N" or + * "-pixels N" + * + * where N is an integer pixel count and S can be an integer, a float, + * two integers with a slash between them, or an integer or float with + * a % after it. In any case, the above may be followed by a filename + * (if omitted, the input comes from stdin). + * + * The arglist can also include "-bg VAL", to supply a background value + * for use when an output pixel does not completely fall within the + * input picture. VAL in this case can be an X11-style hex colour + * spec (#RGB, #RRGGBB, #RRRGGGBBB, or #RRRRGGGGBBBB), a single + * floating-point value giving a grey level (from 0=black to 1=white), + * three floating-point values with slashes between them, R/G/B, or + * any of the previous three possibilities followed by a slash and an + * alpha value (also a float from 0=opaque to 1=transparent). If not + * specified, the default is opaque black (0/0). Using this option + * causes the output file format to be promoted as far as necessary to + * handle the specified value, even if it never actually turns out to + * be used. + * + * The arglist can also include "-xshift VAL" and/or "-yshift VAL", + * where VAL is either a fraction or a floating-point number; if a + * float, it must be in [0,1), and if a fraction, the numerator must + * be less than the denominator. These shift the input picture to the + * right or down by the specified fraction of an output pixel. Using + * a floating-point shift causes any scaling on that axis to be + * converted to floating-point style as well. + */ #include -#include "pnm.h" - -#define SCALE 4096 -#define HALFSCALE 2048 - -int -main( argc, argv ) - int argc; - char* argv[]; - { - FILE* ifp; - xel* xelrow; - xel* tempxelrow; - xel* newxelrow; - register xel* xP; - register xel* nxP; - int argn, specxscale, specyscale, specxsize, specysize, specxysize; - int rows, cols, format, newformat, rowsread, newrows, newcols, newpixels; - register int row, col, needtoreadrow; - xelval maxval; - float xscale, yscale; - long sxscale, syscale; - register long fracrowtofill, fracrowleft; - long* rs; - long* gs; - long* bs; - char* usage = " [pnmfile]\n \ - -xsize|width|-ysize|-height [pnmfile]\n \ - -xscale|-yscale [pnmfile]\n \ - -xscale|-xsize|-width -yscale|-ysize|-height [pnmfile]\n \ - -xysize [pnmfile]\n \ - -pixels [pnmfile]"; - - pnm_init( &argc, argv ); - - argn = 1; - specxscale = specyscale = specxsize = specysize = specxysize = newpixels = 0; - - while ( argn < argc && argv[argn][0] == '-' && argv[argn][1] != '\0' ) - { - if ( pm_keymatch( argv[argn], "-xscale", 4 ) ) - { - if ( specxscale ) - pm_error( "already specified an x scale" ); - if ( specxsize ) - pm_error( - "only one of -xsize/-width and -xscale may be specified" ); - ++argn; - if ( argn == argc || sscanf( argv[argn], "%f", &xscale ) != 1 ) - pm_usage( usage ); - if ( xscale <= 0.0 ) - pm_error( "x scale must be greater than 0" ); - specxscale = 1; - } - else if ( pm_keymatch( argv[argn], "-yscale", 4 ) ) - { - if ( specyscale ) - pm_error( "already specified a y scale" ); - if ( specysize ) - pm_error( - "only one of -ysize/-height and -yscale may be specified" ); - ++argn; - if ( argn == argc || sscanf( argv[argn], "%f", &yscale ) != 1 ) - pm_usage( usage ); - if ( yscale <= 0.0 ) - pm_error( "y scale must be greater than 0" ); - specyscale = 1; - } - else if ( pm_keymatch( argv[argn], "-xsize", 4 ) || - pm_keymatch( argv[argn], "-width", 2 ) ) - { - if ( specxsize ) - pm_error( "already specified a width" ); - if ( specxscale ) - pm_error( - "only one of -xscale and -xsize/-width may be specified" ); - ++argn; - if ( argn == argc || sscanf( argv[argn], "%d", &newcols ) != 1 ) - pm_usage( usage ); - if ( newcols <= 0 ) - pm_error( "new width must be greater than 0" ); - specxsize = 1; - } - else if ( pm_keymatch( argv[argn], "-ysize", 4 ) || - pm_keymatch( argv[argn], "-height", 2 ) ) - { - if ( specysize ) - pm_error( "already specified a height" ); - if ( specyscale ) - pm_error( - "only one of -yscale and -ysize/-height may be specified" ); - ++argn; - if ( argn == argc || sscanf( argv[argn], "%d", &newrows ) != 1 ) - pm_usage( usage ); - if ( newrows <= 0 ) - pm_error( "new height must be greater than 0" ); - specysize = 1; - } - else if ( pm_keymatch( argv[argn], "-xysize", 3 ) ) - { - if ( specxsize || specysize || specxscale || specyscale || newpixels ) - pm_error( "can't use -xysize with any other specifiers" ); - ++argn; - if ( argn == argc || sscanf( argv[argn], "%d", &newcols ) != 1 ) - pm_usage( usage ); - ++argn; - if ( argn == argc || sscanf( argv[argn], "%d", &newrows ) != 1 ) - pm_usage( usage ); - if ( newcols <= 0 || newrows <= 0 ) - pm_error( "new width and height must be greater than 0" ); - specxsize = 1; - specysize = 1; - specxysize = 1; - } - else if ( pm_keymatch( argv[argn], "-pixels", 2 ) ) - { - if ( specxsize || specysize || specxscale || specyscale ) - pm_error( "can't use -pixels with any other specifiers" ); - ++argn; - if ( argn == argc || sscanf( argv[argn], "%d", &newpixels ) != 1 ) - pm_usage( usage ); - if ( newpixels <= 0 ) - pm_error( "number of pixels must be greater than 0" ); - } - else - pm_usage( usage ); - ++argn; +#include +#include +#include +#include +#include +#include + +extern const char *__progname; + +typedef unsigned short int RGBVAL; +typedef unsigned short int ALPHAVAL; + +typedef struct scl SCL; + +struct scl { + unsigned int type; +#define SCLT_RATIO 1 +#define SCLT_FLOAT 2 + union { + struct { + int num; + int den; + } r; + double f; + } u; + } ; +#define SCL_RATIO(n,d) (SCL){.type=SCLT_RATIO,.u={.r={.num=(n),.den=(d)}}} + +static unsigned int have; +#define HAVE_XSCALE 0x00000001 +#define HAVE_YSCALE 0x00000002 +#define HAVE_XSIZE 0x00000004 +#define HAVE_YSIZE 0x00000008 +#define HAVE_SCALE 0x00000010 +#define HAVE_XYSIZE 0x00000020 +#define HAVE_XYSHRINK 0x00000040 +#define HAVE_PIXELS 0x00000080 +#define HAVE_BG 0x00000100 +#define HAVE_XSHIFT 0x00000200 +#define HAVE_YSHIFT 0x00000400 + +static SCL xscale; +static SCL yscale; +static SCL xshift; +static SCL yshift; +static int xsize; +static int ysize; +static SCL scale; +static int xys_x; +static int xys_y; +static int pixels; + +static const char *ifilename = 0; + +typedef struct pixel PIXEL; +typedef struct fpixel FPIXEL; +typedef struct lpixel LPIXEL; +typedef struct reader READER; +typedef struct writer WRITER; + +struct pixel { + RGBVAL r; + RGBVAL g; + RGBVAL b; + ALPHAVAL a; + } ; + +struct fpixel { + double r; + double g; + double b; + double a; + } ; + +struct lpixel { + unsigned long long int r; + unsigned long long int g; + unsigned long long int b; + unsigned long long int a; + } ; + +struct reader { + void *(*init)(FILE *, int *, int *, int *, int *); + PIXEL (*pixel)(void *); + void (*endrow)(void *); + void (*done)(void *); + } ; + +struct writer { + void *(*init)(FILE *, int, int, int, int); + void (*pixel)(void *, PIXEL); + void (*endrow)(void *); + void (*done)(void *); + } ; + +static READER *rdr; +static void *rpriv; +static WRITER *wtr; +static void *wpriv; +static char imc; +#define MC_PBM '1' +#define MC_PGM '2' +#define MC_PPM '3' +#define MC_PAM '7' +#define MC_RPBM '4' +#define MC_RPGM '5' +#define MC_RPPM '6' +#define MC_RPAM '8' +static int ixsize; +static int iysize; +static int imv; +static int imv2; +static int omv; +static int omv2; +static int oxsize; +static int oysize; +static PIXEL bg; +static FPIXEL bg_f; +static LPIXEL bg_l; + +static void badfile(const char *, ...) + __attribute__((__format__(__printf__,1,2),__noreturn__)); +static void badfile(const char *fmt, ...) +{ + va_list ap; + + fprintf(stderr,"%s: bad input file: ",__progname); + va_start(ap,fmt); + vfprintf(stderr,fmt,ap); + va_end(ap); + fprintf(stderr,"\n"); + exit(1); +} + +typedef struct rprivate RPRIV; +typedef struct rbitbuf RBITBUF; + +struct rbitbuf { + unsigned char vmask; + unsigned char bits; + } ; + +struct rprivate { + FILE *f; + unsigned int mv; + unsigned int mv2; + RBITBUF bb; + RBITBUF bb2; + } ; + +#define digitval(c) (((int)(c))-'0') + +static int xdigitval(unsigned char) __attribute__((__const__)); +static int xdigitval(unsigned char c) +{ + switch (c) + { case '0': return(0); break; + case '1': return(1); break; + case '2': return(2); break; + case '3': return(3); break; + case '4': return(4); break; + case '5': return(5); break; + case '6': return(6); break; + case '7': return(7); break; + case '8': return(8); break; + case '9': return(9); break; + case 'a': case 'A': return(10); break; + case 'b': case 'B': return(11); break; + case 'c': case 'C': return(12); break; + case 'd': case 'D': return(13); break; + case 'e': case 'E': return(14); break; + case 'f': case 'F': return(15); break; + } + return(-1); +} + +static int pnmskipcomments(FILE *f) +{ + int c; + + while (1) + { c = getc(f); + if (isspace(c)) continue; + if (c == '#') + { do c = getc(f); while ((c != '\n') && (c != EOF)); + continue; + } + return(c); + } +} + +static void pnmskip1space(FILE *f) +{ + int c; + + c = getc(f); + if ((c == EOF) || isspace(c)) return; + ungetc(c,f); +} + +static unsigned int pnmgetnum(FILE *f, const char *what) +{ + int c; + unsigned int v; + + v = 0; + c = pnmskipcomments(f); + if (c == EOF) badfile("EOF while reading %s",what); + if (! isdigit(c)) badfile("non-digit when reading %s",what); + v = digitval(c); + while (1) + { c = getc(f); + if (! isdigit(c)) break; + v = (v * 10) + digitval(c); + } + ungetc(c,f); + return(v); +} + +static void rbb_init(RBITBUF *bb) +{ + bb->vmask = 0; +} + +static int rbb_bit(RPRIV *p, RBITBUF *bb) +{ + int v; + + if (! bb->vmask) + { v = getc(p->f); + if (v == EOF) badfile("EOF while reading pixel value"); + bb->bits = v; + bb->vmask = 0xff; + } + v = bb->bits >> 7; + bb->vmask <<= 1; + bb->bits <<= 1; + return(v); +} + +static void rbb_endrow(RBITBUF *bb) +{ + bb->vmask = 0; +} + +static int pnmget1(FILE *f) +{ + int c; + + c = getc(f); + if (c == EOF) badfile("EOF while reading pixel value"); + return(c); +} + +static void nil_endrow(void *vp __attribute__((__unused__))) +{ +} + +static void *pbm_rinit(FILE *f, int *xp, int *yp, int *mvp, int *mavp) +{ + RPRIV *p; + + *xp = pnmgetnum(f,"X size"); + *yp = pnmgetnum(f,"Y size"); + p = malloc(sizeof(*p)); + p->f = f; + *mvp = 1; + *mavp = 0; + return(p); +} + +static PIXEL pbm_rpixel(void *pv) +{ + FILE *f; + int c; + + f = ((RPRIV *)pv)->f; + c = pnmskipcomments(f); + if (c == EOF) badfile("EOF while reading pixel value"); + switch (c) + { case '0': return((PIXEL){omv,omv,omv,0}); break; + case '1': return((PIXEL){0,0,0,0}); break; + } + badfile("invalid character %c reading pixel value",c); +} + +#define pbm_rendrow nil_endrow + +static void *pgm_rinit(FILE *f, int *xp, int *yp, int *mvp, int *mavp) +{ + RPRIV *p; + + *xp = pnmgetnum(f,"X size"); + *yp = pnmgetnum(f,"Y size"); + p = malloc(sizeof(*p)); + p->f = f; + p->mv = pnmgetnum(f,"maxval"); + *mvp = p->mv; + *mavp = 0; + return(p); +} + +static PIXEL pgm_rpixel(void *pv) +{ + RPRIV *p; + PIXEL v; + + p = pv; + v.r = pnmgetnum(p->f,"pixel value"); + if (imv != omv) v.r = (v.r * omv) / imv; + v.g = v.r; + v.b = v.r; + v.a = 0; + return(v); +} + +#define pgm_rendrow nil_endrow + +static void *ppm_rinit(FILE *f, int *xp, int *yp, int *mvp, int *mavp) +{ + RPRIV *p; + + *xp = pnmgetnum(f,"X size"); + *yp = pnmgetnum(f,"Y size"); + p = malloc(sizeof(*p)); + p->f = f; + p->mv = pnmgetnum(f,"maxval"); + *mvp = p->mv; + *mavp = 0; + return(p); +} + +static PIXEL ppm_rpixel(void *pv) +{ + RPRIV *p; + PIXEL v; + + p = pv; + v.r = pnmgetnum(p->f,"pixel value"); + v.g = pnmgetnum(p->f,"pixel value"); + v.b = pnmgetnum(p->f,"pixel value"); + if (imv != omv) + { v.r = (v.r * omv) / imv; + v.g = (v.g * omv) / imv; + v.b = (v.b * omv) / imv; + } + v.a = 0; + return(v); +} + +#define ppm_rendrow nil_endrow + +static void *pam_rinit(FILE *f, int *xp, int *yp, int *mvp, int *mavp) +{ + RPRIV *p; + + *xp = pnmgetnum(f,"X size"); + *yp = pnmgetnum(f,"Y size"); + p = malloc(sizeof(*p)); + p->f = f; + p->mv = pnmgetnum(f,"maxval"); + p->mv2 = pnmgetnum(f,"maxval"); + *mvp = p->mv; + *mavp = p->mv2; + return(p); +} + +static PIXEL pam_rpixel(void *pv) +{ + RPRIV *p; + PIXEL v; + + p = pv; + v.r = pnmgetnum(p->f,"pixel value"); + v.g = pnmgetnum(p->f,"pixel value"); + v.b = pnmgetnum(p->f,"pixel value"); + if (imv != omv) + { v.r = (v.r * omv) / imv; + v.g = (v.g * omv) / imv; + v.b = (v.b * omv) / imv; + } + v.a = pnmgetnum(p->f,"pixel value"); + if (imv2 != omv2) v.a = (v.a * omv2) / imv2; + return(v); +} + +#define pam_rendrow nil_endrow + +static void *rawpbm_rinit(FILE *f, int *xp, int *yp, int *mvp, int *mavp) +{ + RPRIV *p; + + *xp = pnmgetnum(f,"X size"); + *yp = pnmgetnum(f,"Y size"); + pnmskip1space(f); + p = malloc(sizeof(*p)); + p->f = f; + rbb_init(&p->bb); + *mvp = 1; + *mavp = 0; + return(p); +} + +static PIXEL rawpbm_rpixel(void *pv) +{ + return(rbb_bit(pv,&((RPRIV *)pv)->bb)?(PIXEL){0,0,0,0}:(PIXEL){omv,omv,omv,0}); +} + +static void rawpbm_rendrow(void *pv) +{ + rbb_endrow(&((RPRIV *)pv)->bb); +} + +static void *rawpgm_rinit(FILE *f, int *xp, int *yp, int *mvp, int *mavp) +{ + RPRIV *p; + + *xp = pnmgetnum(f,"X size"); + *yp = pnmgetnum(f,"Y size"); + p = malloc(sizeof(*p)); + p->f = f; + p->mv = pnmgetnum(f,"maxval"); + if (p->mv > 255) badfile("invalid maxval %u for raw pgm",p->mv); + pnmskip1space(f); + *mvp = p->mv; + *mavp = 0; + return(p); +} + +static PIXEL rawpgm_rpixel(void *pv) +{ + RPRIV *p; + PIXEL v; + + p = pv; + v.r = pnmget1(p->f); + if (imv != omv) v.r = (v.r * omv) / imv; + v.g = v.r; + v.b = v.r; + v.a = 0; + return(v); +} + +#define rawpgm_rendrow nil_endrow + +static void *rawppm_rinit(FILE *f, int *xp, int *yp, int *mvp, int *mavp) +{ + RPRIV *p; + + *xp = pnmgetnum(f,"X size"); + *yp = pnmgetnum(f,"Y size"); + p = malloc(sizeof(*p)); + p->f = f; + p->mv = pnmgetnum(f,"maxval"); + if (p->mv > 255) badfile("invalid maxval %u for raw ppm",p->mv); + pnmskip1space(f); + *mvp = p->mv; + *mavp = 0; + return(p); +} + +static PIXEL rawppm_rpixel(void *pv) +{ + RPRIV *p; + PIXEL v; + + p = pv; + v.r = pnmget1(p->f); + v.g = pnmget1(p->f); + v.b = pnmget1(p->f); + if (imv != omv) + { v.r = (v.r * omv) / imv; + v.g = (v.g * omv) / imv; + v.b = (v.b * omv) / imv; + } + v.a = 0; + return(v); +} + +#define rawppm_rendrow nil_endrow + +static void *rawpam_rinit(FILE *f, int *xp, int *yp, int *mvp, int *mavp) +{ + RPRIV *p; + + *xp = pnmgetnum(f,"X size"); + *yp = pnmgetnum(f,"Y size"); + p = malloc(sizeof(*p)); + p->f = f; + p->mv = pnmgetnum(f,"pixel maxval"); + p->mv2 = pnmgetnum(f,"alpha maxval"); + if (p->mv > 255) badfile("invalid pixel maxval %u for raw pam",p->mv); + if (p->mv2 > 255) badfile("invalid alpha maxval %u for raw pam",p->mv2); + pnmskip1space(f); + if (p->mv == 1) rbb_init(&p->bb); + if (p->mv2 == 1) rbb_init(&p->bb2); + *mvp = p->mv; + *mavp = p->mv2; + return(p); +} + +static PIXEL rawpam_rpixel(void *pv) +{ + RPRIV *p; + PIXEL v; + + p = pv; + if (p->mv == 1) + { v.r = rbb_bit(p,&p->bb) ? omv : 0; + v.g = rbb_bit(p,&p->bb) ? omv : 0; + v.b = rbb_bit(p,&p->bb) ? omv : 0; + } + else + { v.r = pnmget1(p->f); + v.g = pnmget1(p->f); + v.b = pnmget1(p->f); + if (imv != omv) + { v.r = (v.r * omv) / imv; + v.g = (v.g * omv) / imv; + v.b = (v.b * omv) / imv; + } + } + if (p->mv2 == 1) + { v.a = rbb_bit(p,&p->bb2) ? omv2 : 0; + } + else + { v.a = pnmget1(p->f); + if (imv2 != omv2) v.a = (v.a * omv2) / imv2; + } + return(v); +} + +static void rawpam_rendrow(void *pv) +{ + RPRIV *p; + + p = pv; + if (p->mv == 1) rbb_endrow(&p->bb); + if (p->mv2 == 1) rbb_endrow(&p->bb2); +} + +static void rdone(void *pv) +{ + free(pv); +} + +static READER reader_pbm = { pbm_rinit, pbm_rpixel, pbm_rendrow, rdone }; +static READER reader_pgm = { pgm_rinit, pgm_rpixel, pgm_rendrow, rdone }; +static READER reader_ppm = { ppm_rinit, ppm_rpixel, ppm_rendrow, rdone }; +static READER reader_pam = { pam_rinit, pam_rpixel, pam_rendrow, rdone }; +static READER reader_rawpbm = { rawpbm_rinit, rawpbm_rpixel, rawpbm_rendrow, rdone }; +static READER reader_rawpgm = { rawpgm_rinit, rawpgm_rpixel, rawpgm_rendrow, rdone }; +static READER reader_rawppm = { rawppm_rinit, rawppm_rpixel, rawppm_rendrow, rdone }; +static READER reader_rawpam = { rawpam_rinit, rawpam_rpixel, rawpam_rendrow, rdone }; + +typedef struct wprivate WPRIV; +typedef struct wbitbuf WBITBUF; + +struct wbitbuf { + unsigned char bit; + unsigned char val; + off_t obx; + } ; + +struct wprivate { + FILE *f; + int np; + int na; + int raw; + int mv; + int mv2; + off_t ob0; + unsigned char obuf[32]; + unsigned int obfilled; + off_t obat; + WBITBUF bb; + WBITBUF bb2; + } ; + +static void *pbm_winit(FILE *f, int w, int h, int mv, int mv2) +{ + WPRIV *p; + + if ((mv != 1) || (mv2 != 0)) + { fprintf(stderr,"%s: writing bad pbm file (maxvals %d %d)\n",__progname,mv,mv2); + abort(); + } + p = malloc(sizeof(*p)); + p->f = f; + p->np = 0; + p->obuf[0] = 0; + fprintf(f,"P%c\n%d %d\n",MC_RPBM,w,h); + return(p); +} + +static void pbm_wpixel(void *pv, PIXEL pix) +{ + WPRIV *p; + + p = pv; + if (! pix.r) p->obuf[0] |= 0x80 >> p->np; + p->np ++; + if (p->np >= 8) + { putc(p->obuf[0],p->f); + p->obuf[0] = 0; + p->np = 0; + } +} + +static void pbm_wendrow(void *pv) +{ + WPRIV *p; + + p = pv; + if (p->np > 0) + { putc(p->obuf[0],p->f); + p->obuf[0] = 0; + p->np = 0; + } +} + +static void *pgm_winit(FILE *f, int w, int h, int mv, int mv2) +{ + WPRIV *p; + + if (mv2 != 0) + { fprintf(stderr,"%s: writing bad pgm file (maxvals %d %d)\n",__progname,mv,mv2); + abort(); + } + p = malloc(sizeof(*p)); + p->f = f; + if (mv <= 255) + { fprintf(f,"P%c\n%d %d\n%d\n",MC_RPGM,w,h,mv); + p->raw = 1; + } + else + { fprintf(f,"P%c\n%d %d\n%d\n",MC_PGM,w,h,mv); + p->raw = 0; + } + return(p); +} + +static void pgm_wpixel(void *pv, PIXEL pix) +{ + WPRIV *p; + + p = pv; + if (p->raw) + { putc(pix.r,p->f); + } + else + { fprintf(p->f,"%d\n",pix.r); + } +} + +static void *ppm_winit(FILE *f, int w, int h, int mv, int mv2) +{ + WPRIV *p; + + if (mv2 != 0) + { fprintf(stderr,"%s: writing bad ppm file (maxvals %d %d)\n",__progname,mv,mv2); + abort(); + } + p = malloc(sizeof(*p)); + p->f = f; + if (mv <= 255) + { fprintf(f,"P%c\n%d %d\n%d\n",MC_RPPM,w,h,mv); + p->raw = 1; + } + else + { fprintf(f,"P%c\n%d %d\n%d\n",MC_PPM,w,h,mv); + p->raw = 0; + } + return(p); +} + +static void ppm_wpixel(void *pv, PIXEL pix) +{ + WPRIV *p; + + p = pv; + if (p->raw) + { putc(pix.r,p->f); + putc(pix.g,p->f); + putc(pix.b,p->f); + } + else + { fprintf(p->f,"%d %d %d\n",pix.r,pix.g,pix.b); + } +} + +static void pwp_putc(WPRIV *p, unsigned char c, off_t at) +{ + int i; + + if ((at == p->ob0) && (p->obat == at+1)) + { p->ob0 ++; + putc(c,p->f); + return; + } + i = at - p->ob0; + if (i >= sizeof(p->obuf)) abort(); + p->obuf[i] = c; + p->obfilled |= 1U << i; + if (p->obfilled & (p->obfilled+1)) return; + if (p->obat-p->ob0 > 30) abort(); + i = p->obat - p->ob0; + if (p->obfilled != (1U<obuf[0],1,i,p->f); + p->ob0 = p->obat; + p->obfilled = 0; +} + +static void wbb_init(WBITBUF *bb) +{ + bb->bit = 0; +} + +static void wbb_put(WPRIV *p, WBITBUF *bb, int v) +{ + if (bb->bit == 0) + { bb->obx = p->obat++; + bb->bit = 0x80; + bb->val = 0; + } + if (v) bb->val |= bb->bit; + bb->bit >>= 1; + if (bb->bit == 0) pwp_putc(p,bb->val,bb->obx); +} + +static void wbb_endrow(WPRIV *p, WBITBUF *bb) +{ + if (bb->bit) + { pwp_putc(p,bb->val,bb->obx); + bb->bit = 0; + } +} + +static void *pam_winit(FILE *f, int w, int h, int mv, int mv2) +{ + WPRIV *p; + + p = malloc(sizeof(*p)); + p->f = f; + p->np = 0; + p->na = 0; + if ((mv > 255) || (mv2 > 255)) + { fprintf(f,"P%c\n%d %d\n%d %d\n",MC_PAM,w,h,mv,mv2); + p->raw = 0; + } + else + { fprintf(f,"P%c\n%d %d\n%d %d\n",MC_RPAM,w,h,mv,mv2); + p->raw = 1; + if (mv == 1) wbb_init(&p->bb); + if (mv2 == 1) wbb_init(&p->bb2); + p->ob0 = 0; + p->obat = 0; + p->obfilled = 0; + } + p->mv = mv; + p->mv2 = mv2; + return(p); +} + +static void pam_wpixel(void *pv, PIXEL pix) +{ + WPRIV *p; + + p = pv; + if (p->raw) + { if (p->mv == 1) + { wbb_put(p,&p->bb,pix.r); + wbb_put(p,&p->bb,pix.g); + wbb_put(p,&p->bb,pix.b); + } + else + { pwp_putc(p,pix.r,p->obat++); + pwp_putc(p,pix.g,p->obat++); + pwp_putc(p,pix.b,p->obat++); + } + if (p->mv2 == 1) + { wbb_put(p,&p->bb2,pix.a); + } + else + { pwp_putc(p,pix.a,p->obat++); + } + } + else + { fprintf(p->f,"%d %d %d %d\n",pix.r,pix.g,pix.b,pix.a); + } +} + +static void pam_wendrow(void *pv) +{ + WPRIV *p; + + p = pv; + if (p->raw) + { if (p->mv == 1) wbb_endrow(p,&p->bb); + if (p->mv2 == 1) wbb_endrow(p,&p->bb2); + if (p->obat != p->ob0) abort(); + } +} + +static void wdone(void *pv) +{ + free(pv); +} + +static WRITER writer_pbm = { pbm_winit, pbm_wpixel, pbm_wendrow, wdone }; +static WRITER writer_pgm = { pgm_winit, pgm_wpixel, nil_endrow, wdone }; +static WRITER writer_ppm = { ppm_winit, ppm_wpixel, nil_endrow, wdone }; +static WRITER writer_pam = { pam_winit, pam_wpixel, pam_wendrow, wdone }; + +static int argmatch(const char *arg, const char *key, int minlen) +{ + int al; + + al = strlen(arg); + if (al < minlen) return(0); + return(!strncmp(arg,key,al)); +} + +static const char *havename(unsigned int bit) +{ + switch (bit) + { case HAVE_XSCALE: return("X scale"); break; + case HAVE_YSCALE: return("Y scale"); break; + case HAVE_XSIZE: return("X size"); break; + case HAVE_YSIZE: return("Y size"); break; + case HAVE_XYSIZE: return("-xysize"); break; + case HAVE_XYSHRINK: return("-xyshrink"); break; + case HAVE_PIXELS: return("-pixels"); break; + } + abort(); +} + +static int arg_int(const char *flag, const char *valstr, int *vp, unsigned int havebit) +{ + unsigned long int uli; + int i; + char *ep; + + if (have & havebit) + { fprintf(stderr,"%s: %s already given\n",__progname,havename(havebit)); + return(1); + } + have |= havebit; + uli = strtoul(valstr,&ep,0); + i = uli; + if (*ep || (ep == valstr) || (i < 0) || (i != uli)) + { fprintf(stderr,"%s: invalid %s argument\n",__progname,flag); + return(1); + } + *vp = i; + return(0); +} + +static int arg_scale(const char *flag, const char *valstr, SCL *vp, unsigned int havebit) +{ + unsigned long int uli; + int i; + const char *sp; + char *ep; + double d; + + if (have & havebit) + { fprintf(stderr,"%s: %s already given\n",__progname,havename(havebit)); + return(1); + } + have |= havebit; + do <"invalid"> + { do <"interr"> + { uli = strtoul(valstr,&ep,0); + i = uli; + if ((ep == valstr) || (i < 0) || (i != uli)) break <"interr">; + vp->type = SCLT_RATIO; + vp->u.r.num = i; + switch (*ep) + { case '\0': + vp->u.r.den = 1; + return(0); + break; + case '%': + if (! ep[1]) + { vp->u.r.den = 100; + return(0); + } + break; + case '/': + sp = ep + 1; + uli = strtoul(sp,&ep,0); + i = uli; + if (*ep || (ep == valstr) || (i < 0) || (i != uli)) break <"invalid">; + vp->u.r.den = i; + return(0); + break; } - - if ( ! ( specxscale || specyscale || specxsize || specysize || newpixels ) ) - { - /* No flags specified, so a single scale factor is required. */ - if ( argn == argc ) - pm_usage( usage ); - if ( sscanf( argv[argn], "%f", &xscale ) != 1 ) - pm_usage( usage ); - if ( xscale <= 0.0 ) - pm_error( "scale must be greater than 0" ); - ++argn; - yscale = xscale; - specxscale = specyscale = 1; + } while (0); + vp->type = SCLT_FLOAT; + d = strtod(valstr,&ep); + if ((ep == valstr) || (d < 0)) break <"invalid">; + switch (*ep) + { case '\0': + vp->u.f = d; + return(0); + break; + case '%': + if (! ep[1]) + { vp->u.f = d / 100; + return(0); + } + break; + } + } while (0); + fprintf(stderr,"%s: invalid %s argument\n",__progname,flag); + return(1); +} + +static int arg_pixel(const char *flag, const char *valstr, PIXEL *vp, unsigned int havebit) +{ + const char *vsp; + char *esp; + PIXEL v; + int i; + double f; + int parts; + unsigned short int vals[4]; + + if (have & havebit) + { fprintf(stderr,"%s: %s already given\n",__progname,havename(havebit)); + return(1); + } + have |= havebit; + vsp = valstr; + if (*vsp == '#') + { int m; + int s; + RGBVAL xpart(const char *str) + { int j; + RGBVAL v; + v = 0; + for (j=0;j>s); + } + for (i=0;i<12;i++) if (xdigitval(vsp[i+1]) < 0) break; + switch (vsp[i+1]) + { case '\0': case '/': + break; + default: + fprintf(stderr,"%s: %s %s: invalid X11-style specifier character\n",__progname,flag,valstr); + return(1); + break; + } + switch (i) + { case 3: + m = 0x1111; + s = 0; + i = 1; + break; + case 6: + m = 0x101; + s = 0; + i = 2; + break; + case 9: + m = 0x1001; + s = 8; + i = 3; + break; + case 12: + m = 1; + s = 0; + i = 4; + break; + default: + fprintf(stderr,"%s: %s %s: invalid X11-style specifier length\n",__progname,flag,valstr); + return(1); + break; + } + v.r = xpart(vsp+1); + v.g = xpart(vsp+1+i); + v.b = xpart(vsp+1+i+i); + v.a = 0; + vsp += i * 3; + if (*vsp == '/') + { vsp ++; + f = strtod(vsp,&esp); + if (vsp == esp) + { fprintf(stderr,"%s: %s %s: missing/bad alpha value\n",__progname,flag,valstr); + return(1); } - - /* Now get input file. */ - if ( argn != argc ) - { - ifp = pm_openr( argv[argn] ); - ++argn; + if (*esp) + { fprintf(stderr,"%s: %s %s: junk after alpha value\n",__progname,flag,valstr); + return(1); } - else - ifp = stdin; - - if ( argn != argc ) - pm_usage( usage ); - - pnm_pbmmaxval = PNM_MAXMAXVAL; /* use larger value for better results */ - pnm_readpnminit( ifp, &cols, &rows, &maxval, &format ); - - /* Promote PBM files to PGM. */ - if ( PNM_FORMAT_TYPE(format) == PBM_TYPE ) + if ((f < 0) || (f > 1)) + { fprintf(stderr,"%s: %s %s: alpha value out of range\n",__progname,flag,valstr); + return(1); + } + i = f * 65536; + if (i > 65535) i = 65535; + v.a = i; + } + *vp = v; + return(0); + } + parts = 0; + while <"parts"> (1) + { f = strtod(vsp,&esp); + if (esp == vsp) + { fprintf(stderr,"%s: %s %s: bad value\n",__progname,flag,valstr); + return(1); + } + if ((f < 0) || (f > 1)) + { fprintf(stderr,"%s: %s %s: value %g out of range\n",__progname,flag,valstr,f); + return(1); + } + i = f * 65536; + if (i > 65535) i = 65535; + vals[parts++] = i; + switch (*esp) + { case '\0': + break <"parts">; + break; + case '/': + vsp = esp + 1; + break; + default: + fprintf(stderr,"%s: %s %s: invalid separator\n",__progname,flag,valstr); + return(1); + break; + } + if (parts > 3) + { fprintf(stderr,"%s: %s %s: too many slashes\n",__progname,flag,valstr); + return(1); + } + } + switch (parts) + { case 1: + v.r = vals[0]; + v.g = vals[0]; + v.b = vals[0]; + v.a = 0; + break; + case 2: + v.r = vals[0]; + v.g = vals[0]; + v.b = vals[0]; + v.a = vals[1]; + break; + case 3: + v.r = vals[0]; + v.g = vals[1]; + v.b = vals[2]; + v.a = 0; + break; + case 4: + v.r = vals[0]; + v.g = vals[1]; + v.b = vals[2]; + v.a = vals[3]; + break; + default: + fprintf(stderr,"%s: %s %s: bad part count\n",__progname,flag,valstr); + return(1); + break; + } + return(0); +} + +static void check_shift(SCL *sh, const char *axis) +{ + switch (sh->type) + { case SCLT_RATIO: + if (sh->u.r.num >= sh->u.r.den) + { fprintf(stderr,"%s: %s shift value numerator > denominator\n",__progname,axis); + exit(1); + } + break; + case SCLT_FLOAT: + if ((sh->u.f < 0) || (sh->u.f >= 1)) + { fprintf(stderr,"%s: %s shift value must be in [0,1)\n",__progname,axis); + exit(1); + } + break; + default: + abort(); + break; + } +} + +static void usage(void) __attribute__((__noreturn__)); +static void usage(void) +{ + fprintf(stderr,"Usage: %s [inputfile]\n",__progname); + fprintf(stderr," %s -xsize|-width|-ysize|-height [inputfile]\n",__progname); + fprintf(stderr," %s -xscale|-yscale [inputfile]\n",__progname); + fprintf(stderr,"or one of -xscale/-xsize/-width combined with one of -yscale/-ysize/-height\n"); + fprintf(stderr,"or one of the special forms\n"); + fprintf(stderr," %s -xysize [inputfile]\n",__progname); + fprintf(stderr," %s -xyshrink [inputfile]\n",__progname); + fprintf(stderr," %s -pixels [inputfile]\n",__progname); + fprintf(stderr,"Arguments may also include\n"); + fprintf(stderr," -bg VAL\n"); + fprintf(stderr," -xshift FRACTION\n"); + fprintf(stderr," -yshift FRACTION\n"); + exit(1); +} + +static void handleargs(int ac, char **av) +{ + int skip; + int errs; + + have = 0; + skip = 0; + errs = 0; + for (ac--,av++;ac;ac--,av++) + { if (skip > 0) + { skip --; + continue; + } + if (**av != '-') break; + if (0) + { +needarg:; + fprintf(stderr,"%s: missing argument following %sn",__progname,*av); + errs ++; + continue; + } +#define WANTARG() do { if (++skip >= ac) goto needarg; } while (0) + if (argmatch(*av,"-xscale",4)) + { WANTARG(); + errs += arg_scale(*av,av[skip],&xscale,HAVE_XSCALE); + continue; + } + if (argmatch(*av,"-yscale",4)) + { WANTARG(); + errs += arg_scale(*av,av[skip],&yscale,HAVE_YSCALE); + continue; + } + if (argmatch(*av,"-xsize",4) || argmatch(*av,"-width",2)) + { WANTARG(); + errs += arg_int(*av,av[skip],&xsize,HAVE_XSIZE); + continue; + } + if (argmatch(*av,"-ysize",4) || argmatch(*av,"-height",2)) + { WANTARG(); + errs += arg_int(*av,av[skip],&ysize,HAVE_YSIZE); + continue; + } + if (argmatch(*av,"-xysize",4)) + { WANTARG(); + errs += arg_int(*av,av[skip],&xys_x,HAVE_XYSIZE); + WANTARG(); + errs += arg_int(0,av[skip],&xys_y,0); + continue; + } + if (argmatch(*av,"-xyshrink",4)) + { WANTARG(); + errs += arg_int(*av,av[skip],&xys_x,HAVE_XYSHRINK); + WANTARG(); + errs += arg_int(0,av[skip],&xys_y,0); + continue; + } + if (argmatch(*av,"-pixels",2)) + { WANTARG(); + errs += arg_int(*av,av[skip],&pixels,HAVE_PIXELS); + continue; + } + if (argmatch(*av,"-bg",2)) + { WANTARG(); + errs += arg_pixel(*av,av[skip],&bg,HAVE_BG); + continue; + } + if (argmatch(*av,"-xshift",4)) + { WANTARG(); + errs += arg_scale(*av,av[skip],&xshift,HAVE_XSHIFT); + continue; + } + if (argmatch(*av,"-yshift",4)) + { WANTARG(); + errs += arg_scale(*av,av[skip],&yshift,HAVE_YSHIFT); + continue; + } +#undef WANTARG + fprintf(stderr,"%s: unrecognized option `%s'\n",__progname,*av); + errs ++; + } + switch (ac) + { case 0: + if (have == 0) + { fprintf(stderr,"%s: no transformation specified\n",__progname); + errs ++; + } + break; + case 1: + if (have & ( HAVE_XSCALE | HAVE_YSCALE | + HAVE_XSIZE | HAVE_YSIZE | + HAVE_SCALE | + HAVE_XYSIZE | HAVE_XYSHRINK | HAVE_PIXELS )) + { ifilename = av[0]; + } + else + { errs += arg_scale("scale factor",av[0],&scale,HAVE_SCALE); + } + break; + case 2: + if (have & ( HAVE_XSCALE | HAVE_YSCALE | + HAVE_XSIZE | HAVE_YSIZE | + HAVE_SCALE | + HAVE_XYSIZE | HAVE_XYSHRINK | HAVE_PIXELS )) { - newformat = PGM_TYPE; - pm_message( "promoting from PBM to PGM" ); + default: + fprintf(stderr,"%s: extra argument\n",__progname); + errs ++; + break; } + errs += arg_scale("scale factor",av[0],&scale,HAVE_SCALE); + ifilename = av[1]; + break; + } + switch (have & ( HAVE_XSCALE | HAVE_YSCALE | + HAVE_XSIZE | HAVE_YSIZE | + HAVE_SCALE | + HAVE_XYSIZE | HAVE_XYSHRINK | HAVE_PIXELS )) + { case HAVE_SCALE: + case HAVE_XSCALE: + case HAVE_XSIZE: + case HAVE_YSCALE: + case HAVE_YSIZE: + case HAVE_XSCALE|HAVE_YSCALE: + case HAVE_XSCALE|HAVE_YSIZE: + case HAVE_XSIZE|HAVE_YSCALE: + case HAVE_XSIZE|HAVE_YSIZE: + case HAVE_XYSIZE: + case HAVE_XYSHRINK: + case HAVE_PIXELS: + break; + default: + fprintf(stderr,"%s: conflicting scaling specifications\n",__progname); + errs ++; + break; + } + if (have & HAVE_XSHIFT) check_shift(&xshift,"X"); + if (have & HAVE_YSHIFT) check_shift(&yshift,"Y"); + if (errs) usage(); +} + +static void open_input(void) +{ + FILE *f; + int c; + + if (ifilename) + { f = fopen(ifilename,"r"); + if (f == 0) + { fprintf(stderr,"%s: can't open %s: %s\n",__progname,ifilename,strerror(errno)); + exit(1); + } + } + else + { f = stdin; + } + c = getc(f); + if (c != 'P') badfile("bad magic number"); + imc = getc(f); + switch (imc) + { case MC_PBM: rdr = &reader_pbm; break; + case MC_PGM: rdr = &reader_pgm; break; + case MC_PPM: rdr = &reader_ppm; break; + case MC_PAM: rdr = &reader_pam; break; + case MC_RPBM: rdr = &reader_rawpbm; break; + case MC_RPGM: rdr = &reader_rawpgm; break; + case MC_RPPM: rdr = &reader_rawppm; break; + case MC_RPAM: rdr = &reader_rawpam; break; + default: + badfile("bad magic number"); + break; + } + rpriv = (*rdr->init)(f,&ixsize,&iysize,&imv,&imv2); +} + +static __inline__ int ceilscale(int, int, int, int) + __attribute__((__const__)); +static __inline__ int ceilscale(int v, int n, int d, int a) +{ + return(((v*(long long int)n)+a+d-1)/d); +} + +static void set_pixelcount_scale(void) +{ + double f; + int nx; + int ny; + int isx; + + f = sqrt(pixels/(double)(ixsize*iysize)); + if (ixsize > iysize) + { nx = f * ixsize; + ny = ceilscale(iysize,nx,ixsize,0); + isx = 1; + } + else + { ny = f * iysize; + nx = ceilscale(ixsize,ny,iysize,0); + isx = 0; + } + while (nx*ny < pixels) + { if (isx) + { if ((nx+1)*iysize < ny*ixsize) + { nx ++; + } + else + { isx = 0; + nx = ceilscale(ixsize,ny,iysize,0); + } + } else - newformat = format; - - /* Compute all sizes and scales. */ - if ( newpixels ) - if ( rows * cols <= newpixels ) - { - newrows = rows; - newcols = cols; - xscale = yscale = 1.0; - } - else - { - xscale = yscale = - sqrt( (float) newpixels / (float) cols / (float) rows ); - specxscale = specyscale = 1; - } - - if ( specxysize ) - if ( (float) newcols / (float) newrows > (float) cols / (float) rows ) - specxsize = 0; - else - specysize = 0; - - if ( specxsize ) - xscale = (float) newcols / (float) cols; - else if ( specxscale ) - newcols = cols * xscale + 0.999; - - if ( specysize ) - yscale = (float) newrows / (float) rows; - else if ( specyscale ) - newrows = rows * yscale + 0.999; - else - if ( specxsize ) - { - yscale = xscale; - newrows = rows * yscale + 0.999; - } - else - { - yscale = 1.0; - newrows = rows; - } - - if ( ! ( specxsize || specxscale ) ) - if ( specysize ) - { - xscale = yscale; - newcols = cols * xscale + 0.999; - } - else - { - xscale = 1.0; - newcols = cols; - } - - sxscale = xscale * SCALE; - syscale = yscale * SCALE; - - xelrow = pnm_allocrow( cols ); - if ( newrows == rows ) /* shortcut Y scaling if possible */ - tempxelrow = xelrow; + { if ((ny+1)*ixsize < nx*iysize) + { ny ++; + } + else + { isx = 1; + ny = ceilscale(iysize,nx,ixsize,0); + } + } + } + while (nx*ny > pixels) + { if ((nx-1)*iysize > (ny-1)*ixsize) + { nx --; + ny = ceilscale(iysize,nx,ixsize,0); + isx = 1; + } else - tempxelrow = pnm_allocrow( cols ); - rs = (long*) pm_allocrow( cols, sizeof(long) ); - gs = (long*) pm_allocrow( cols, sizeof(long) ); - bs = (long*) pm_allocrow( cols, sizeof(long) ); - rowsread = 0; - fracrowleft = syscale; - needtoreadrow = 1; - for ( col = 0; col < cols; ++col ) - rs[col] = gs[col] = bs[col] = HALFSCALE; - fracrowtofill = SCALE; - - pnm_writepnminit( stdout, newcols, newrows, maxval, newformat, 0 ); - newxelrow = pnm_allocrow( newcols ); - - for ( row = 0; row < newrows; ++row ) - { - /* First scale Y from xelrow into tempxelrow. */ - if ( newrows == rows ) /* shortcut Y scaling if possible */ - { - pnm_readpnmrow( ifp, xelrow, cols, maxval, format ); - } - else - { - while ( fracrowleft < fracrowtofill ) - { - if ( needtoreadrow ) - if ( rowsread < rows ) - { - pnm_readpnmrow( ifp, xelrow, cols, maxval, format ); - ++rowsread; - /* needtoreadrow = 0; */ - } - switch ( PNM_FORMAT_TYPE(format) ) - { - case PPM_TYPE: - for ( col = 0, xP = xelrow; col < cols; ++col, ++xP ) - { - rs[col] += fracrowleft * PPM_GETR( *xP ); - gs[col] += fracrowleft * PPM_GETG( *xP ); - bs[col] += fracrowleft * PPM_GETB( *xP ); - } - break; - - default: - for ( col = 0, xP = xelrow; col < cols; ++col, ++xP ) - gs[col] += fracrowleft * PNM_GET1( *xP ); - break; - } - fracrowtofill -= fracrowleft; - fracrowleft = syscale; - needtoreadrow = 1; - } - /* Now fracrowleft is >= fracrowtofill, so we can produce a row. */ - if ( needtoreadrow ) - if ( rowsread < rows ) - { - pnm_readpnmrow( ifp, xelrow, cols, maxval, format ); - ++rowsread; - needtoreadrow = 0; - } - switch ( PNM_FORMAT_TYPE(format) ) - { - case PPM_TYPE: - for ( col = 0, xP = xelrow, nxP = tempxelrow; - col < cols; ++col, ++xP, ++nxP ) - { - register long r, g, b; - - r = rs[col] + fracrowtofill * PPM_GETR( *xP ); - g = gs[col] + fracrowtofill * PPM_GETG( *xP ); - b = bs[col] + fracrowtofill * PPM_GETB( *xP ); - r /= SCALE; - if ( r > maxval ) r = maxval; - g /= SCALE; - if ( g > maxval ) g = maxval; - b /= SCALE; - if ( b > maxval ) b = maxval; - PPM_ASSIGN( *nxP, r, g, b ); - rs[col] = gs[col] = bs[col] = HALFSCALE; - } - break; - - default: - for ( col = 0, xP = xelrow, nxP = tempxelrow; - col < cols; ++col, ++xP, ++nxP ) - { - register long g; - - g = gs[col] + fracrowtofill * PNM_GET1( *xP ); - g /= SCALE; - if ( g > maxval ) g = maxval; - PNM_ASSIGN1( *nxP, g ); - gs[col] = HALFSCALE; - } - break; - } - fracrowleft -= fracrowtofill; - if ( fracrowleft == 0 ) - { - fracrowleft = syscale; - needtoreadrow = 1; - } - fracrowtofill = SCALE; - } - - /* Now scale X from tempxelrow into newxelrow and write it out. */ - if ( newcols == cols ) /* shortcut X scaling if possible */ - pnm_writepnmrow( stdout, tempxelrow, newcols, maxval, newformat, 0 ); - else - { - register long r, g, b; - register long fraccoltofill, fraccolleft; - register int needcol; - - nxP = newxelrow; - fraccoltofill = SCALE; - r = g = b = HALFSCALE; - needcol = 0; - for ( col = 0, xP = tempxelrow; col < cols; ++col, ++xP ) - { - fraccolleft = sxscale; - while ( fraccolleft >= fraccoltofill ) - { - if ( needcol ) - { - ++nxP; - r = g = b = HALFSCALE; - } - switch ( PNM_FORMAT_TYPE(format) ) - { - case PPM_TYPE: - r += fraccoltofill * PPM_GETR( *xP ); - g += fraccoltofill * PPM_GETG( *xP ); - b += fraccoltofill * PPM_GETB( *xP ); - r /= SCALE; - if ( r > maxval ) r = maxval; - g /= SCALE; - if ( g > maxval ) g = maxval; - b /= SCALE; - if ( b > maxval ) b = maxval; - PPM_ASSIGN( *nxP, r, g, b ); - break; - - default: - g += fraccoltofill * PNM_GET1( *xP ); - g /= SCALE; - if ( g > maxval ) g = maxval; - PNM_ASSIGN1( *nxP, g ); - break; - } - fraccolleft -= fraccoltofill; - fraccoltofill = SCALE; - needcol = 1; - } - if ( fraccolleft > 0 ) - { - if ( needcol ) - { - ++nxP; - r = g = b = HALFSCALE; - needcol = 0; - } - switch ( PNM_FORMAT_TYPE(format) ) - { - case PPM_TYPE: - r += fraccolleft * PPM_GETR( *xP ); - g += fraccolleft * PPM_GETG( *xP ); - b += fraccolleft * PPM_GETB( *xP ); - break; - - default: - g += fraccolleft * PNM_GET1( *xP ); - break; - } - fraccoltofill -= fraccolleft; - } - } - if ( fraccoltofill > 0 ) - { - --xP; - switch ( PNM_FORMAT_TYPE(format) ) - { - case PPM_TYPE: - r += fraccoltofill * PPM_GETR( *xP ); - g += fraccoltofill * PPM_GETG( *xP ); - b += fraccoltofill * PPM_GETB( *xP ); - break; - - default: - g += fraccoltofill * PNM_GET1( *xP ); - break; - } - } - if ( ! needcol ) - { - switch ( PNM_FORMAT_TYPE(format) ) - { - case PPM_TYPE: - r /= SCALE; - if ( r > maxval ) r = maxval; - g /= SCALE; - if ( g > maxval ) g = maxval; - b /= SCALE; - if ( b > maxval ) b = maxval; - PPM_ASSIGN( *nxP, r, g, b ); - break; - - default: - g /= SCALE; - if ( g > maxval ) g = maxval; - PNM_ASSIGN1( *nxP, g ); - break; - } - } - pnm_writepnmrow( stdout, newxelrow, newcols, maxval, newformat, 0 ); - } + { ny --; + nx = ceilscale(ixsize,ny,iysize,0); + isx = 0; + } + } + xscale = isx ? SCL_RATIO(nx,ixsize) : SCL_RATIO(ny,iysize); + yscale = xscale; +} + +static unsigned int gcd(unsigned int a, unsigned int b) +{ + unsigned int t; + + while (b) + { t = a % b; + a = b; + b = t; + } + return(a); +} + +static void gcd_scale(SCL *s) +{ + if (s->type == SCLT_RATIO) + { int d; + d = gcd(s->u.r.num,s->u.r.den); + s->u.r.num /= d; + s->u.r.den /= d; + } +} + +/* arrange that scale->den == shift->den */ +static void scale_and_shift(SCL *scale, SCL *shift) +{ + int d; + + if ((scale->type != SCLT_RATIO) || (shift->type != SCLT_RATIO)) abort(); + d = gcd(scale->u.r.den,shift->u.r.den); + scale->u.r.num *= shift->u.r.den / d; + shift->u.r.num *= scale->u.r.den / d; + shift->u.r.den *= scale->u.r.den / d; + scale->u.r.den = shift->u.r.den; +} + +static void fixup_scales(void) +{ + switch (have & ( HAVE_XSCALE | HAVE_YSCALE | + HAVE_XSIZE | HAVE_YSIZE | + HAVE_SCALE | + HAVE_XYSIZE | HAVE_XYSHRINK | HAVE_PIXELS )) + { case HAVE_SCALE: + xscale = scale; + yscale = scale; + break; + case HAVE_XSCALE: + yscale = SCL_RATIO(1,1); + break; + case HAVE_XSIZE: + xscale = SCL_RATIO(xsize,ixsize); + yscale = xscale; + break; + case HAVE_YSCALE: + xscale = SCL_RATIO(1,1); + break; + case HAVE_YSIZE: + yscale = SCL_RATIO(ysize,iysize); + xscale = yscale; + break; + case HAVE_XSCALE|HAVE_YSCALE: + break; + case HAVE_XSCALE|HAVE_YSIZE: + yscale = SCL_RATIO(ysize,iysize); + break; + case HAVE_XSIZE|HAVE_YSCALE: + xscale = SCL_RATIO(xsize,ixsize); + break; + case HAVE_XSIZE|HAVE_YSIZE: + xscale = SCL_RATIO(xsize,ixsize); + yscale = SCL_RATIO(ysize,iysize); + break; + case HAVE_XYSIZE: + if (xys_y*ixsize < xys_x*iysize) + { xscale = SCL_RATIO(xys_y,iysize); } + else + { xscale = SCL_RATIO(xys_x,ixsize); + } + yscale = xscale; + break; + case HAVE_XYSHRINK: + if ((ixsize <= xys_x) && (iysize <= xys_y)) + { xscale = SCL_RATIO(1,1); + } + else if (xys_y*ixsize < xys_x*iysize) + { xscale = SCL_RATIO(xys_y,iysize); + } + else + { xscale = SCL_RATIO(xys_x,ixsize); + } + yscale = xscale; + break; + case HAVE_PIXELS: + if (pixels < 1) + { fprintf(stderr,"%s: -pixels argument must be at least 1\n",__progname); + exit(1); + break; + } + if (pixels >= ixsize*iysize) + { xscale = SCL_RATIO(1,1); + yscale = xscale; + } + else + { set_pixelcount_scale(); + } + break; + default: + fprintf(stderr,"%s: overspecified scaling specifications\n",__progname); + exit(1); + break; + } + gcd_scale(&xscale); + gcd_scale(&yscale); + if (! (have & HAVE_XSHIFT)) xshift = SCL_RATIO(0,1); + gcd_scale(&xshift); + if (xshift.type != xscale.type) + { if (xshift.type == SCLT_RATIO) + { xshift.type = SCLT_FLOAT; + xshift.u.f = xshift.u.r.num / (double)xshift.u.r.den; + } + if (xscale.type == SCLT_RATIO) + { xscale.type = SCLT_FLOAT; + xscale.u.f = xscale.u.r.num / (double)xscale.u.r.den; + } + } + if (xscale.type == SCLT_RATIO) scale_and_shift(&xscale,&xshift); + if (! (have & HAVE_YSHIFT)) yshift = SCL_RATIO(0,1); + gcd_scale(&yshift); + if (yshift.type != yscale.type) + { if (yshift.type == SCLT_RATIO) + { yshift.type = SCLT_FLOAT; + yshift.u.f = yshift.u.r.num / (double)yshift.u.r.den; + } + if (yscale.type == SCLT_RATIO) + { yscale.type = SCLT_FLOAT; + yscale.u.f = yscale.u.r.num / (double)yscale.u.r.den; + } + } + if (yscale.type == SCLT_RATIO) scale_and_shift(&yscale,&yshift); +} + +/* +scale to 5/13: +in aaaaabbbbbcccccdddddeeeeefffffggggghhhhhiiiiijjjjjkkkkklllllmmmmm +out AAAAAAAAAAAAABBBBBBBBBBBBBCCCCCCCCCCCCCDDDDDDDDDDDDDEEEEEEEEEEEEE + +scale to 13/5: +in aaaaaaaaaaaaabbbbbbbbbbbbbcccccccccccccdddddddddddddeeeeeeeeeeeee +out AAAAABBBBBCCCCCDDDDDEEEEEFFFFFGGGGGHHHHHIIIIIJJJJJKKKKKLLLLLMMMMM +*/ - pm_close( ifp ); - pm_close( stdout ); - - exit( 0 ); - } +static LPIXEL muladd_lpix_pix(LPIXEL lp, PIXEL p, unsigned long long int mul) +{ + return((LPIXEL){ .r = lp.r+(p.r*mul), + .g = lp.g+(p.g*mul), + .b = lp.b+(p.b*mul), + .a = lp.a+(p.a*mul) }); +} + +static LPIXEL muladd_lpix_lpix(LPIXEL lp, LPIXEL p, unsigned long long int mul) +{ + return((LPIXEL){ .r = lp.r+(p.r*mul), + .g = lp.g+(p.g*mul), + .b = lp.b+(p.b*mul), + .a = lp.a+(p.a*mul) }); +} + +static FPIXEL muladd_fpix_pix(FPIXEL fp, PIXEL p, double mul) +{ + return((FPIXEL){ .r = fp.r+(p.r*mul), + .g = fp.g+(p.g*mul), + .b = fp.b+(p.b*mul), + .a = fp.a+(p.a*mul) }); +} + +static FPIXEL muladd_fpix_lpix(FPIXEL fp, LPIXEL p, double mul) +{ + return((FPIXEL){ .r = fp.r+(p.r*mul), + .g = fp.g+(p.g*mul), + .b = fp.b+(p.b*mul), + .a = fp.a+(p.a*mul) }); +} + +static FPIXEL muladd_fpix_fpix(FPIXEL fp, FPIXEL p, double mul) +{ + return((FPIXEL){ .r = fp.r+(p.r*mul), + .g = fp.g+(p.g*mul), + .b = fp.b+(p.b*mul), + .a = fp.a+(p.a*mul) }); +} + +#define ROWBODY(ITYPE,OTYPE,UTYPE,sctyp,numv,shfv,denv,mafn,osfn,osarg,ibg) \ +{ \ + OTYPE op; \ + UTYPE haveu; \ + UTYPE leftu; \ + ITYPE ip; \ + int x; \ + int ix; \ + \ + if (xscale.type != sctyp) abort(); \ + haveu = shfv; \ + leftu = denv; \ + op = (OTYPE){0,0,0,0}; \ + ip = ibg; \ + x = 0; \ + ix = 0; \ + while (x < oxsize) \ + { if (haveu >= leftu) \ + { op = mafn(op,ip,leftu); \ + haveu -= leftu; \ + (*wtr->pixel)(wpriv,(PIXEL){ .r = osfn(op.r,osarg), \ + .g = osfn(op.g,osarg), \ + .b = osfn(op.b,osarg), \ + .a = osfn(op.a,osarg) }); \ + x ++; \ + op = (OTYPE){0,0,0,0}; \ + leftu = denv; \ + } \ + else if (haveu == 0) \ + { ip = (ix >= ixsize) ? ibg : r[ix]; \ + ix ++; \ + haveu = numv; \ + } \ + else \ + { op = mafn(op,ip,haveu); \ + leftu -= haveu; \ + haveu = 0; \ + } \ + } \ + (*wtr->endrow)(wpriv); \ +} + +static int irdiv(unsigned long long int a, unsigned long long int b) +{ + return((a+(b/2))/b); +} + +#define fdiv(x,y) rint(x/y) +#define fnil(x,y) rint(x) + +static void scale_row_l_ratio(LPIXEL *r, unsigned long long int div) +ROWBODY( LPIXEL, LPIXEL, int, SCLT_RATIO, + xscale.u.r.num, xshift.u.r.num, xscale.u.r.den, + muladd_lpix_lpix, irdiv, div*xscale.u.r.den, bg_l + ) + +static void scale_row_l_float(LPIXEL *r, unsigned long long int div) +ROWBODY( LPIXEL, FPIXEL, double, SCLT_FLOAT, + xscale.u.f, xshift.u.f, 1, + muladd_fpix_lpix, fdiv, div, bg_l + ) + +static void scale_row_f_ratio(FPIXEL *r, double div __attribute__((__unused__))) +ROWBODY( FPIXEL, FPIXEL, int, SCLT_RATIO, + xscale.u.r.num, xshift.u.r.num, xscale.u.r.den, + muladd_fpix_fpix, fdiv, xscale.u.r.den, bg_f + ) + +static void scale_row_f_float(FPIXEL *r, double div __attribute__((__unused__))) +ROWBODY( FPIXEL, FPIXEL, double, SCLT_FLOAT, + xscale.u.f, xshift.u.f, 1, + muladd_fpix_fpix, fnil, 0, bg_f + ) + +#undef fdiv +#undef fnil + +#undef ROWBODY + +#define SCALEBODY(RTYPE,UTYPE,scltype,numv,shfv,denv,amfn,rowpref) \ +{ \ + PIXEL *irow; \ + RTYPE *orow; \ + UTYPE haveu; \ + UTYPE leftu; \ + int y; \ + int x; \ + int iy; \ + \ + if ((yscale.type != scltype) || (yshift.type != scltype)) abort(); \ + irow = malloc(ixsize*sizeof(PIXEL)); \ + orow = malloc(ixsize*sizeof(RTYPE)); \ + haveu = shfv; \ + leftu = denv; \ + for (x=0;x= leftu) \ + { for (x=0;xpixel)(rpriv); \ + (*rdr->endrow)(rpriv); \ + } \ + iy ++; \ + haveu = numv; \ + } \ + else \ + { for (x=0;xtype) + { case SCLT_RATIO: + return(s->u.r.num==0); + break; + case SCLT_FLOAT: + return(s->u.f==0); + break; + } + abort(); +} + +static PIXEL opaque(PIXEL a) +{ + a.a = 0; + return(a); +} + +int main(int, char **); +int main(int ac, char **av) +{ + handleargs(ac,av); + open_input(); + fixup_scales(); + if (! (have & HAVE_BG)) bg = (PIXEL){ .r=0, .g=0, .b=0, .a=0 }; + omv = imv; + omv2 = imv2; + switch (imc) + { case MC_PBM: case MC_RPBM: + if (pbm_compatible(bg,0xffff) && zeroscale(&xshift) && zeroscale(&yshift)) + { if ( (xscale.type == SCLT_RATIO) && + (xscale.u.r.den == 1) && + (yscale.type == SCLT_RATIO) && + (yscale.u.r.den == 1) ) + { wtr = &writer_pbm; + } + else + { omv = 255; + wtr = &writer_pgm; + } + break; + } + /* fall through */ + case MC_PGM: case MC_RPGM: + if (pgm_compatible(bg)) + { if (omv < 255) omv = 255; + wtr = &writer_pgm; + break; + } + /* fall through */ + case MC_PPM: case MC_RPPM: + if (ppm_compatible(bg)) + { if (omv < 255) omv = 255; + wtr = &writer_ppm; + break; + } + /* fall through */ + case MC_PAM: case MC_RPAM: + if ((omv > 1) && (omv < 255)) omv = 255; + if ((omv2 > 1) && (omv2 < 255)) omv2 = 255; + if ((omv == 1) && !pbm_compatible(opaque(bg),0xffff)) omv = 255; + if ((omv2 == 1) && (bg.a != 0) && (bg.a != 0xffff)) omv2 = 255; + wtr = &writer_pam; + break; + default: + abort(); + break; + } + bg.r = (bg.r * (unsigned long long int)omv) / 0xffff; + bg.g = (bg.g * (unsigned long long int)omv) / 0xffff; + bg.b = (bg.b * (unsigned long long int)omv) / 0xffff; + bg.a = (bg.a * (unsigned long long int)omv2) / 0xffff; + switch (xscale.type) + { case SCLT_RATIO: + oxsize = ceilscale(ixsize,xscale.u.r.num,xscale.u.r.den,xshift.u.r.num); + break; + case SCLT_FLOAT: + oxsize = ceil(ixsize*xscale.u.f); + break; + default: + abort(); + break; + } + switch (yscale.type) + { case SCLT_RATIO: + oysize = ceilscale(iysize,yscale.u.r.num,yscale.u.r.den,yshift.u.r.num); + break; + case SCLT_FLOAT: + oysize = ceil(iysize*yscale.u.f); + break; + default: + abort(); + break; + } + wpriv = (*wtr->init)(stdout,oxsize,oysize,omv,omv2); + switch (yscale.type) + { case SCLT_RATIO: + bg_l = muladd_lpix_pix((LPIXEL){0,0,0,0},bg,yscale.u.r.den); + scale_y_ratio(); + break; + case SCLT_FLOAT: + bg_f = muladd_fpix_pix((FPIXEL){0,0,0,0},bg,1); + scale_y_float(); + break; + } + (*rdr->done)(rpriv); + (*wtr->done)(wpriv); + return(0); +}