/* * pnmtartan - insert strips into, or remove strips from, a pnm file * * pnmmpaste spec [spec ...] * * Reads a pnm file from stdin. Conceptually, applies the specs in * order, producing the result on stdout. A spec can be: * * delx nnn@mmm * delx nnn-mmm * delx nnn- * delx nnn * dely nnn@mmm * dely nnn-mmm * dely nnn- * dely nnn * Deletes a strip. The x or y specifies whether the * strip is horizontal or vertical (x specifies a vertical * strip - ie, the insertion or deletion moves things * along the x axis - and y a horizontal strip). If a * single number follows, only one pixel is deleted. To * delete a strip multiple pixels wide, specify either * nnn-mmm (which deletes pixels nnn through mmm * inclusive) or nnn@mmm (which deletes a strip nnn pixels * wide starting at mmm). The nnn- forms delete from * pixel nnn through the highest existing pixel. In the * nnn@mmm form, nnn can be prefixed with a minus sign, in * which case it has the image's width added to it; if it * is still negative after this, an error occurs. * * insx nnn@mmm colour * insx nnn-mmm colour * insx nnn colour * insy nnn@mmm colour * insy nnn-mmm colour * insy nnn colour * Inserts a strip. The x or y specifies the orientation, * as for delx/dely. The next argument specifies where * the strip is to be placed. The spec is similar to the * way the corresponding syntaxes for the del forms work: * nnn inserts a single-pixel strip at coordinate nnn, * nnn-mmm inserts a strip mmm+1-nnn pixels wide starting * at nnn (ie, running from nnn through mmm), and nnn@mmm * inserts a strip nnn pixels wide starting at mmm. * colour specifies the colour of the inserted pixels. If * the input is a pbm file, colour must be 0 or 1, if pgm, * either an integer from 0 through the maxval for the * file, or a floating-point number from 0 through 1.0, * and if ppm, an r-g-b triple, three values (interpreted * as if they were pgm specs) with slashes or commas * separating them, if pam, same as ppm but with a fourth * component added. A ppm file will also accept a pgm * spec, in which case the single value is used for all * three primaries. * * It is an error to delete pixels that don't exist, or to insert * pixels at a coordinate that is higher than immediately after the * highest existing coordinate. * * Note that the specs are applied in order, and the coordinate system * for each spec is the coordinate system left after the previous * specs have been applied. Thus, for example, * delx 5@10 delx 5@20 * is equivalent to not * delx 5@20 delx 5@10 * but rather * delx 5@25 delx 5@10 * It is therefore usually advisable to apply specs in order of * decreasing coordinates. * * For deletions, or for insertions of the same colour, it makes no * difference whether an X operation is done before or after a Y * operation, but for insertions of different colours order matters. */ #include #include #include #include #include #include extern const char *__progname; typedef enum { OP_INS = 1, OP_DEL } OP; typedef enum { C_X = 1, C_Y } COORD; typedef enum { T_PBM = 1, T_PGM, T_PPM, T_PAM, T_RPBM, T_RPGM, T_RPPM, T_RPAM } PCLASS; typedef struct fdesc FDESC; typedef struct pixval PIXVAL; typedef struct opn OPN; struct fdesc { PCLASS class; const char *fn; FILE *f; unsigned int maxval; unsigned int maxval2; unsigned int w; unsigned int h; unsigned char pbmbuf; unsigned char pbmbit; } ; struct pixval { unsigned int r; unsigned int g; unsigned int b; unsigned int a; } ; struct opn { OPN *link; OP op; COORD coord; int width; int at; const char *colour; PIXVAL pv; const char *locspec; } ; static FDESC odesc; static FDESC idesc; static OPN *ops; static int noraw = 0; #define Cdigit(x) isdigit((unsigned char)(x)) static void usage(const char *, ...) __attribute__((__format__(__printf__,1,2),__noreturn__)); static void usage(const char *fmt, ...) { va_list ap; if (fmt) { fprintf(stderr,"%s: ",__progname); va_start(ap,fmt); vfprintf(stderr,fmt,ap); va_end(ap); fprintf(stderr,"\n"); } fprintf(stderr,"Usage: %s spec [spec ...]\n",__progname); if (fmt) { fprintf(stderr,"`%s -help' for more help\n",__progname); } else { fprintf(stderr,"a spec can be:\n"); fprintf(stderr," delx nnn@mmm\n"); fprintf(stderr," delx nnn-mmm\n"); fprintf(stderr," delx nnn\n"); fprintf(stderr," dely (same)\n"); fprintf(stderr," insx (same) colour\n"); fprintf(stderr," insy (same) colour\n"); fprintf(stderr," noraw\n"); fprintf(stderr,"colour can be:\n"); fprintf(stderr," 0 or 1 (for pbm files)\n"); fprintf(stderr," 0 through maxval (for pgm and ppm files)\n"); fprintf(stderr," 0.0 through 1.0 (for pgm and ppm files)\n"); fprintf(stderr," r/g/b (for ppm files - r, g, b are as for pgm)\n"); fprintf(stderr," r,g,b (like r/g/b)\n"); fprintf(stderr,"Pixel coordinates are after all previous specs have been applied.\n"); } exit(1); } static void badpnm(FDESC *, const char *, ...) __attribute__((__format__(__printf__,2,3),__noreturn__)); static void badpnm(FDESC *d, const char *fmt, ...) { va_list ap; va_start(ap,fmt); fprintf(stderr,"%s: %s: bad pnm file: ",__progname,d->fn); vfprintf(stderr,fmt,ap); fprintf(stderr,"\n"); va_end(ap); exit(1); } #define digitval(c) (((int)(c))-'0') static int pnmskipcomments(FDESC *d) { int c; while (1) { c = getc(d->f); if (isspace(c)) continue; if (c == '#') { do { c = getc(d->f); } while ((c != '\n') && (c != EOF)); continue; } return(c); } } static unsigned int pnmgetnum(FDESC *d, const char *what) { int c; unsigned int v; v = 0; c = pnmskipcomments(d); if (c == EOF) badpnm(d,"EOF while reading %s",what); if (! isdigit(c)) badpnm(d,"non-digit when reading %s",what); v = digitval(c); while (1) { c = getc(d->f); if (! isdigit(c)) break; v = (v * 10) + digitval(c); } ungetc(c,d->f); return(v); } static char class_is_raw(PCLASS class) { switch (class) { case T_PBM: case T_PGM: case T_PPM: case T_PAM: return(0); break; case T_RPBM: case T_RPGM: case T_RPPM: case T_RPAM: return(1); break; } abort(); } static void skip1white(FDESC *d) { int c; c = getc(d->f); if (! isspace(c)) ungetc(c,d->f); } static void init_input(FILE *f, const char *name, FDESC *d) { int c; d->fn = name; d->f = f; c = getc(f); if (c != 'P') badpnm(d,"bad magic number"); c = getc(f); switch (c) { case '1': d->class = T_PBM; break; case '2': d->class = T_PGM; break; case '3': d->class = T_PPM; break; case '7': d->class = T_PAM; break; case '4': d->class = T_RPBM; break; case '5': d->class = T_RPGM; break; case '6': d->class = T_RPPM; break; case '8': d->class = T_RPAM; break; default: badpnm(d,"bad magic number"); break; } d->w = pnmgetnum(d,"width"); d->h = pnmgetnum(d,"height"); d->maxval = 0; d->maxval2 = 0; d->pbmbuf = 0; d->pbmbit = 0; switch (d->class) { case T_PGM: case T_PPM: case T_PAM: case T_RPGM: case T_RPPM: case T_RPAM: d->maxval = pnmgetnum(d,"maxval"); break; default: break; } switch (d->class) { case T_PAM: case T_RPAM: d->maxval2 = pnmgetnum(d,"maxval2"); break; default: break; } if (class_is_raw(d->class)) skip1white(d); } static void parse_location(OPN *o, const char *s) { char *ep; long int liv; int iv; void bad(void) { usage("invalid %s%s spec %s",(o->op==OP_INS)?"ins":"del",(o->coord==C_X)?"x":"y",s); } o->locspec = s; iv = liv = strtol(s,&ep,0); if ((ep == s) || (iv != liv)) bad(); switch (*ep) { case '\0': if (iv < 0) bad(); o->width = 1; o->at = iv; return; case '-': if (iv < 0) bad(); if ((ep[1] == '\0') && (o->op == OP_DEL)) { if (iv >= idesc.w) bad(); o->width = -iv; o->at = iv; break; } o->at = iv; s = ep + 1; iv = liv = strtol(s,&ep,0); if ((ep == s) || (iv != liv) || (iv < o->at)) bad(); o->width = iv + 1 - o->at; break; case '@': if ((iv < 0) && (o->op != OP_DEL)) bad(); o->width = iv; s = ep + 1; iv = liv = strtol(s,&ep,0); if ((ep == s) || (iv != liv) || (iv < 0)) bad(); o->at = iv; break; default: bad(); break; } } static char class_rawform(PCLASS class) { switch (class) { case T_PBM: case T_RPBM: return(T_RPBM); break; case T_PGM: case T_RPGM: return(T_RPGM); break; case T_PPM: case T_RPPM: return(T_RPPM); break; case T_PAM: case T_RPAM: return(T_RPAM); break; } abort(); } static void setup(int ac, char **av) { __label__ restart_; OPN *o; OPN **ot; unsigned int w; unsigned int h; unsigned int *d; const char *dstr; const char *ep; unsigned int pixnum(const char *s, const char **endp) { char *ep; unsigned long int v; v = strtoul(s,&ep,10); if (*ep == '.') { const char *dp; for (dp=ep+1;Cdigit(*dp);dp++) ; switch (*dp) { case '\0': case ' ': case ',': case '/': { double d; d = strtod(s,&ep); *endp = ep; switch (class_rawform(odesc.class)) { case T_RPBM: odesc.class = T_RPGM; odesc.maxval = 255; goto restart_; break; case T_RPGM: case T_RPPM: case T_RPAM: if (odesc.maxval < 255) { odesc.maxval = 255; goto restart_; } break; } v = d * (odesc.maxval + 1); if (v > odesc.maxval) v = odesc.maxval; return(v); } break; } } *endp = ep; return(v); } unsigned int alphanum(const char *s, const char **endp) { char *ep; unsigned long int v; v = strtoul(s,&ep,10); if (*ep == '.') { const char *dp; for (dp=ep+1;Cdigit(*dp);dp++) ; switch (*dp) { case ' ': case ',': case '/': { double d; d = strtod(s,&ep); *endp = ep; if (odesc.maxval2 < 255) { odesc.maxval2 = 255; goto restart_; } v = d * (odesc.maxval + 1); if (v > odesc.maxval) v = odesc.maxval; return(v); } break; } } *endp = ep; return(v); } ac --; av ++; ot = &ops; if ((ac == 1) && !strcmp(av[0],"-help")) { usage(0); } while (ac > 0) { o = malloc(sizeof(OPN)); if (!strcmp(av[0],"delx")) { o->op = OP_DEL; o->coord = C_X; } else if (!strcmp(av[0],"dely")) { o->op = OP_DEL; o->coord = C_Y; } else if (!strcmp(av[0],"insx")) { o->op = OP_INS; o->coord = C_X; } else if (!strcmp(av[0],"insy")) { o->op = OP_INS; o->coord = C_Y; } else if (!strcmp(av[0],"noraw")) { free(o); noraw = 1; ac --; av ++; continue; } else { usage("bad key string"); } switch (o->op) { case OP_INS: switch (ac) { case 1: usage("missing arguments after %s",av[0]); break; case 2: usage("missing argument after %s",av[0]); break; } parse_location(o,av[1]); o->colour = av[2]; ac -= 3; av += 3; break; case OP_DEL: switch (ac) { case 1: usage("missing argument after %s",av[0]); break; } parse_location(o,av[1]); ac -= 2; av += 2; break; default: abort(); break; } *ot = o; ot = &o->link; } *ot = 0; init_input(stdin,"standard input",&idesc); w = idesc.w; h = idesc.h; odesc.class = class_rawform(idesc.class); odesc.fn = "standard output"; odesc.f = stdout; odesc.maxval = idesc.maxval; odesc.maxval2 = idesc.maxval2; restart_:; for (o=ops;o;o=o->link) { switch (o->coord) { case C_X: d = &w; dstr = "x"; break; case C_Y: d = &h; dstr = "y"; break; default: abort(); break; } switch (o->op) { case OP_INS: if (o->at > *d) usage("ins%s at %d out of range 0-%d\n",dstr,o->at,*d); if (o->width+*d < *d) usage("ins%s overflow\n",dstr); *d += o->width; switch (idesc.class) { case T_PBM: case T_RPBM: if (!strcmp(o->colour,"0")) { o->pv.r = 0; o->pv.g = 0; o->pv.b = 0; } else if (!strcmp(o->colour,"1")) { o->pv.r = odesc.maxval; o->pv.g = odesc.maxval; o->pv.b = odesc.maxval; } else { usage("bad colour spec `%s' for PBM input file",o->colour); } o->pv.a = 0; break; case T_PGM: case T_RPGM: o->pv.r = pixnum(o->colour,&ep); if (*ep) usage("bad colour spec `%s' for PGM input file",o->colour); o->pv.g = o->pv.r; o->pv.b = o->pv.r; o->pv.a = 0; break; case T_PPM: case T_RPPM: o->pv.r = pixnum(o->colour,&ep); if (! *ep) { o->pv.g = o->pv.r; o->pv.b = o->pv.r; } else { switch <"green"> (*ep) { case ',': case '/': o->pv.g = pixnum(ep+1,&ep); switch (*ep) { case ',': case '/': o->pv.b = pixnum(ep+1,&ep); if (*ep) { default: default <"green">: usage("bad colour spec `%s' for PPM input file",o->colour); } break; } break; } } o->pv.a = 0; break; case T_PAM: case T_RPAM: o->pv.r = pixnum(o->colour,&ep); if (! *ep) { o->pv.g = o->pv.r; o->pv.b = o->pv.r; o->pv.a = 0; } else { switch <"green"> (*ep) { case ',': case '/': o->pv.g = pixnum(ep+1,&ep); switch <"blue"> (*ep) { case ',': case '/': o->pv.b = pixnum(ep+1,&ep); switch (*ep) { case ',': case '/': o->pv.a = alphanum(ep+1,&ep); if (*ep) { default: default <"blue">: default <"green">: usage("bad colour spec `%s' for PAM input file",o->colour); } break; } break; } break; } } break; } break; case OP_DEL: if (o->width < 0) { o->width += (o->coord == C_X) ? idesc.w : idesc.h; if (o->width < 0) usage("invalid %s%s spec %s",(o->op==OP_INS)?"ins":"del",(o->coord==C_X)?"x":"y",o->locspec); } if ((o->at > *d) || (o->at+o->width > *d)) usage("del%s %d at %d out of range 0-%d\n",dstr,o->width,o->at,*d); *d -= o->width; break; default: abort(); break; } } odesc.w = w; odesc.h = h; } static void init_output(void) { switch (odesc.class) { case T_RPBM: if (noraw) { odesc.class = T_PBM; printf("P1\n%u %u\n",odesc.w,odesc.h); odesc.pbmbit = 0; } else { printf("P4\n%u %u\n",odesc.w,odesc.h); odesc.pbmbuf = 0; odesc.pbmbit = 0x80; } break; case T_RPGM: if (noraw || (odesc.maxval > 255)) { odesc.class = T_PGM; printf("P2\n%u %u\n%u\n",odesc.w,odesc.h,odesc.maxval); } else { printf("P5\n%u %u\n%u\n",odesc.w,odesc.h,odesc.maxval); } break; case T_RPPM: if (noraw || (odesc.maxval > 255)) { odesc.class = T_PPM; printf("P3\n%u %u\n%u\n",odesc.w,odesc.h,odesc.maxval); } else { printf("P6\n%u %u\n%u\n",odesc.w,odesc.h,odesc.maxval); } break; case T_RPAM: if (noraw || (odesc.maxval > 255) || (odesc.maxval2 > 255)) { odesc.class = T_PAM; printf("P7\n%u %u\n%u %u\n",odesc.w,odesc.h,odesc.maxval,odesc.maxval2); } else { printf("P8\n%u %u\n%u %u\n",odesc.w,odesc.h,odesc.maxval,odesc.maxval2); } break; default: abort(); break; } } static void pnmputpixel(FDESC *d, PIXVAL v) { switch (d->class) { default: abort(); break; case T_PBM: if (d->pbmbit >= 70) { putc('\n',d->f); d->pbmbit = 0; } putc(v.r?'0':'1',d->f); d->pbmbit ++; break; case T_RPBM: if (d->pbmbit == 0) { putc(d->pbmbuf,d->f); d->pbmbuf = 0; d->pbmbit = 0x80; } if (! v.r) d->pbmbuf |= d->pbmbit; d->pbmbit >>= 1; break; case T_PGM: fprintf(d->f,"%u\n",v.r); break; case T_RPGM: putc(v.r,d->f); break; case T_PPM: fprintf(d->f,"%u %u %u\n",v.r,v.g,v.b); break; case T_RPPM: putc(v.r,d->f); putc(v.g,d->f); putc(v.b,d->f); break; case T_PAM: fprintf(d->f,"%u %u %u %u\n",v.r,v.g,v.b,v.a); break; case T_RPAM: putc(v.r,d->f); putc(v.g,d->f); putc(v.b,d->f); putc(v.a,d->f); break; } } static void pnmput_endrow(FDESC *d) { switch (d->class) { case T_RPBM: putc(d->pbmbuf,d->f); d->pbmbuf = 0; d->pbmbit = 0x80; break; default: break; } } static int pnmgetbit(FDESC *d) { int c; unsigned int v; v = 0; c = pnmskipcomments(d); if (c == EOF) badpnm(d,"EOF while reading pixel value"); switch (c) { case '0': return(0); break; case '1': return(1); break; } badpnm(d,"invalid pixel character"); } static void dataeof(FDESC *) __attribute__((__noreturn__)); static void dataeof(FDESC *d) { fprintf(stderr,"%s: %s: EOF while reading pixels\n",__progname,d->fn); exit(1); } static unsigned int getrawval(FDESC *d) { int c; c = getc(d->f); if (c == EOF) dataeof(d); return(c); } static PIXVAL pnmgetpixel(FDESC *d) { PIXVAL rv; switch (d->class) { default: abort(); break; case T_PBM: { int v; v = pnmgetbit(d); if (0) { case T_RPBM: if (d->pbmbit < 2) { v = getc(d->f); if (v == EOF) dataeof(d); d->pbmbuf = v; d->pbmbit = 0x80; v &= 0x80; } else { v = d->pbmbuf & (d->pbmbit >>= 1); } } rv.r = ! v; } break; case T_PGM: { unsigned int v; v = pnmgetnum(d,"pixel value"); if (0) { case T_RPGM: v = getrawval(d); } if (d->maxval != odesc.maxval) v = (v * (odesc.maxval+1)) / (d->maxval+1); rv.r = v; } break; case T_PPM: rv.r = pnmgetnum(d,"pixel value"); rv.g = pnmgetnum(d,"pixel value"); rv.b = pnmgetnum(d,"pixel value"); if (0) { case T_RPPM: rv.r = getrawval(d); rv.g = getrawval(d); rv.b = getrawval(d); } if (d->maxval != odesc.maxval) { rv.r = (rv.r * (odesc.maxval+1)) / (d->maxval+1); rv.g = (rv.g * (odesc.maxval+1)) / (d->maxval+1); rv.b = (rv.b * (odesc.maxval+1)) / (d->maxval+1); } break; } return(rv); } static void pnmget_endrow(FDESC *d) { switch (d->class) { case T_RPBM: d->pbmbit = 0; break; default: break; } } static void crank(void) { unsigned int ix; unsigned int iy; PIXVAL cur; unsigned int ox; unsigned int oy; void pass_down(int x, int y, unsigned int w, unsigned int h, PIXVAL pv, OPN *o) { unsigned int i; unsigned int j; if (o == 0) { if ((x >= odesc.w) || (y >= odesc.h)) return; pnmputpixel(&odesc,pv); ox ++; if (ox >= odesc.w) { pnmput_endrow(&odesc); ox = 0; oy ++; } return; } switch (o->op) { case OP_INS: switch (o->coord) { case C_X: if (x == o->at) { for (i=0;iwidth;i++) { pass_down(x+i,y,w+o->width,h,o->pv,o->link); } } if (x < o->at) { pass_down(x,y,w+o->width,h,pv,o->link); } else { pass_down(x+o->width,y,w+o->width,h,pv,o->link); } break; case C_Y: if ((x <= 0) && (y == o->at)) { for (i=0;iwidth;i++) { for (j=0;jwidth,o->pv,o->link); pass_down(w,y+i,w,h+o->width,o->pv,o->link); } } if (y < o->at) { pass_down(x,y,w,h+o->width,pv,o->link); } else { pass_down(x,y+o->width,w,h+o->width,pv,o->link); } break; default: abort(); break; } break; case OP_DEL: switch (o->coord) { case C_X: if (x < o->at) { pass_down(x,y,w-o->width,h,pv,o->link); } else { x -= o->width; if (x >= o->at) pass_down(x,y,w-o->width,h,pv,o->link); } break; case C_Y: if (y < o->at) { pass_down(x,y,w,h-o->width,pv,o->link); } else { y -= o->width; if (y >= o->at) pass_down(x,y,w,h-o->width,pv,o->link); } break; default: abort(); break; } break; default: abort(); break; } } ix = 0; iy = 0; ox = 0; oy = 0; while (1) { cur = pnmgetpixel(&idesc); pass_down(ix,iy,idesc.w,idesc.h,cur,ops); ix ++; if (ix >= idesc.w) { pnmget_endrow(&idesc); pass_down(idesc.w,iy,idesc.w,idesc.h,cur,ops); ix = 0; iy ++; if (iy >= idesc.h) break; } } pass_down(-1,idesc.h,idesc.w,idesc.h,cur,ops); if (ox || (oy != odesc.h)) abort(); } int main(int, char **); int main(int ac, char **av) { setup(ac,av); init_output(); crank(); exit(0); }