/* ############################################################################ # # File: ppm.c # # Subject: Functions to manipulate PPM files in memory # # Author: Gregg M. Townsend # # Date: November 17, 1997 # ############################################################################ # # This file is in the public domain. # ############################################################################ # # These functions manipulate raw (P6) PPM image files in memory. # The images must not contain comment strings. # # ppmwidth(s) -- return width of PPM image. # ppmheight(s) -- return height of PPM image. # ppmmax(s) -- return maximum value in PPM header. # ppmdata(s) -- return data portion of PPM image. # # ppmimage(s,p,f) -- quantify image s using palette p, with flags f. # Returns an Icon image string. Flag "o" selects ordered dithering. # Defaults: p="c6", f="o" # # ppmstretch(s,lo,hi,max) -- apply contrast stretch operation # Returns a PPM string image that results from setting all # values <= lo to zero, all values >= hi to max, with values # between scaling linearly. If hi = lo + 1, this becomes a # simple threshold operation. If lo=0 and hi=ppmmax(s), this # simply scales an image to a new maximum. # # Requirements: 0 <= lo < hi <= ppmmax(s), 1 <= max <= 255. # Defaults: lo=0, hi=ppmmax(s), max=255. # # ppm3x3(s,a,b,c,d,e,f,g,h,i) -- apply 3x3 convolution to PPM image. # The matrix of real numbers [[a,b,c],[d,e,f],[g,h,i]] is used # as a transformation matrix applied independently to the three # color components of the image. # ############################################################################ # # Requires: Dynamic loading # ############################################################################ */ #include "icall.h" #include #include #include int palnum(descriptor *d); char *rgbkey(int p, double r, double g, double b); typedef struct { /* ppminfo: struct describing a ppm image */ int w, h; /* width and height */ int max; /* maximum value */ long npixels; /* total number of pixels */ long nbytes; /* total number of pixels */ char *data; /* pointer to start of raw data; null indicates error */ } ppminfo; static ppminfo ppmcrack(descriptor d); static descriptor ppmalc(int w, int h, int max); static char *rowextend(char *dst, char *src, int w, int nbr); static int ppmrows(ppminfo hdr, int nbr, int (*func) (), long arg); static int sharpenrow(char *a[], int w, int i, long max); static int convrow(char *a[], int w, int i, long max); static char *out; /* general purpose global output pointer */ /* macros */ /* ArgPPM(int n, ppminfo hdr) -- validate arg n, init hdr */ #define ArgPPM(n,hdr) do {\ ArgString(n); \ hdr = ppmcrack(argv[n]); \ if (!hdr.data) Fail; \ } while(0) /* AlcResult(int w, h, max, ppminfo hdr) -- alc result string, init hdr */ /* WARNING -- can move other strings; refresh addresses from descriptors. */ #define AlcResult(w, h, max, hdr) do {\ descriptor d = ppmalc(w, h, max); \ if (d.vword == 0) Error(306); \ hdr = ppmcrack(argv[0] = d); \ } while(0) /* ppm info functions */ int ppmwidth(int argc, descriptor *argv) /*: extract width of PPM string */ { ppminfo hdr; ArgPPM(1, hdr); RetInteger(hdr.w); } int ppmheight(int argc, descriptor *argv) /*: extract height of PPM string */ { ppminfo hdr; ArgPPM(1, hdr); RetInteger(hdr.h); } int ppmmax(int argc, descriptor *argv) /*: extract max of PPM string */ { ppminfo hdr; ArgPPM(1, hdr); RetInteger(hdr.max); } int ppmdata(int argc, descriptor *argv) /*: extract data from PPM string */ { ppminfo hdr; ArgPPM(1, hdr); RetAlcString(hdr.data, hdr.nbytes); } /* ppmstretch(s,lo,hi) -- apply contrast stretch operation */ int ppmstretch(int argc, descriptor *argv) /*: stretch contrast of PPM string */ { ppminfo src, dst; int lo, hi, max, i, v; float m; char *d, *s; ArgPPM(1, src); if (argc < 2 || IconType(argv[2]) == 'n') lo = 0; else { ArgInteger(2); lo = IntegerVal(argv[2]); if (lo < 0 || lo >= src.max) ArgError(2, 205); } if (argc < 3 || IconType(argv[3]) == 'n') hi = src.max; else { ArgInteger(3); hi = IntegerVal(argv[3]); if (hi <= lo || hi > src.max) ArgError(3, 205); } if (argc < 4 || IconType(argv[4]) == 'n') max = 255; else { ArgInteger(4); max = IntegerVal(argv[4]); if (max < 1 || max > 255) ArgError(4, 205); } m = (float)(max + 1) / (hi - lo); AlcResult(src.w, src.h, max, dst); src = ppmcrack(argv[1]); /* may have moved */ d = dst.data; s = src.data; for (i = 0; i < dst.nbytes; i++) { v = m * ((*s++ & 0xFF) - lo); if (v < 0) v = 0; else if (v > dst.max) v = dst.max; *d++ = v; } Return; } /* ppmsharpen(s) -- apply fixed sharpening convolution */ int ppmsharpen(int argc, descriptor *argv) /*: sharpen a PPM string */ { int rv; ppminfo src, dst; ArgPPM(1, src); AlcResult(src.w, src.h, src.max, dst); src = ppmcrack(argv[1]); /* may have moved */ out = dst.data; rv = ppmrows(src, 1, sharpenrow, src.max); if (rv == 0) Return; argv[0] = nulldesc; return rv; } static int sharpenrow(char *a[], int w, int i, long max) { unsigned char *prev, *curr, *next; int v; prev = (unsigned char *) a[-1]; curr = (unsigned char *) a[0]; next = (unsigned char *) a[1]; w *= 3; while (w--) { v = 2.0 * curr[0] - .10 * (prev[-3] + prev[3] + next[-3] + next[3]) - .15 * (prev[0] + curr[-3] + curr[3] + next[0]); if (v < 0) v = 0; else if (v > max) v = max; *out++ = v; prev++; curr++; next++; } return 0; } /* ppm3x3(s,a,b,c,d,e,f,g,h,i) -- apply 3x3 convolution matrix */ static float cells[9]; int ppm3x3(int argc, descriptor *argv) /*: convolve PPM with matrix */ { int rv, i; ppminfo src, dst; ArgPPM(1, src); for (i = 0; i < 9; i++) { ArgReal(i + 2); cells[i] = RealVal(argv[i + 2]); } AlcResult(src.w, src.h, src.max, dst); src = ppmcrack(argv[1]); /* may have moved */ out = dst.data; rv = ppmrows(src, 1, convrow, src.max); if (rv == 0) Return; argv[0] = nulldesc; return rv; } static int convrow(char *a[], int w, int i, long max) { unsigned char *prev, *curr, *next; int v; prev = (unsigned char *) a[-1]; curr = (unsigned char *) a[0]; next = (unsigned char *) a[1]; w *= 3; while (w--) { v = cells[0] * prev[-3] + cells[1] * prev[0] + cells[2] * prev[3] + cells[3] * curr[-3] + cells[4] * curr[0] + cells[5] * curr[3] + cells[6] * next[-3] + cells[7] * next[0] + cells[8] * next[3]; if (v < 0) v = 0; else if (v > max) v = max; *out++ = v; prev++; curr++; next++; } return 0; } /* ppmimage(s,p,f) -- quantify image s using palette p, returning Icon image. */ #define MDIM 16 /* dither matrix dimension */ #define MSIZE (MDIM * MDIM) /* total size */ int ppmimage(int argc, descriptor *argv) /*: dither PPM to Icon image */ { int i, p, row, col, ir, ig, ib; double m, gd, r, g, b, dither[MSIZE], *dp, d; char *pname, *flags, *s, *t, *rv; ppminfo hdr; static double dmults[7] = {0., 1./3., 1./1., 1./2., 1./3., 1./4., 1./5.}; static double gmults[7] = {0., 3./6., 1./2., 1./3., 1./4., 1./5., 1./6.}; static unsigned char dfactor[MSIZE] = { 0,128, 32,160, 8,136, 40,168, 2,130, 34,162, 10,138, 42,170, 192, 64,224, 96,200, 72,232,104,194, 66,226, 98,202, 74,234,106, 48,176, 16,144, 56,184, 24,152, 50,178, 18,146, 58,186, 26,154, 240,112,208, 80,248,120,216, 88,242,114,210, 82,250,122,218, 90, 12,140, 44,172, 4,132, 36,164, 14,142, 46,174, 6,134, 38,166, 204, 76,236,108,196, 68,228,100,206, 78,238,110,198, 70,230,102, 60,188, 28,156, 52,180, 20,148, 62,190, 30,158, 54,182, 22,150, 252,124,220, 92,244,116,212, 84,254,126,222, 94,246,118,214, 86, 3,131, 35,163, 11,139, 43,171, 1,129, 33,161, 9,137, 41,169, 195, 67,227, 99,203, 75,235,107,193, 65,225, 97,201, 73,233,105, 51,179, 19,147, 59,187, 27,155, 49,177, 17,145, 57,185, 25,153, 243,115,211, 83,251,123,219, 91,241,113,209, 81,249,121,217, 89, 15,143, 47,175, 7,135, 39,167, 13,141, 45,173, 5,133, 37,165, 207, 79,239,111,199, 71,231,103,205, 77,237,109,197, 69,229,101, 63,191, 31,159, 55,183, 23,151, 61,189, 29,157, 53,181, 21,149, 255,127,223, 95,247,119,215, 87,253,125,221, 93,245,117,213, 85, }; ArgString(1); if (argc < 2 || IconType(argv[2]) == 'n') { p = 6; pname = "c6"; } else { ArgString(2); p = palnum(&argv[2]); if (p == 0) Fail; if (p == -1) ArgError(1, 103); pname = StringVal(argv[2]); } if (argc < 3 || IconType(argv[3]) == 'n') flags = "o"; else { ArgString(3); flags = StringVal(argv[3]); } hdr = ppmcrack(argv[1]); if (!hdr.data) Fail; /* PPM format error */ if (!strchr(flags, 'o')) m = gd = 0.0; /* no dithering */ else if (p > 0) { m = dmults[p] - .0001; /* color dithering magnitude */ gd = gmults[p]; /* correction factor if gray input */ } else { m = 1.0 / (-p - .9999); /* grayscale dithering magnitude */ gd = 1.0; /* no correction needed */ } for (i = 0; i < MSIZE; i++) /* build dithering table */ dither[i] = m * (dfactor[i] / (double)(MSIZE)- 0.5); rv = alcstr(NULL, 10 + hdr.npixels); /* allocate room for output string */ if (!rv) Error(306); hdr = ppmcrack(argv[1]); /* get addr again -- may have moved */ sprintf(rv, "%d,%s,", hdr.w, pname); t = rv + strlen(rv); m = 1.0 / hdr.max; s = hdr.data; for (row = hdr.h; row > 0; row--) { dp = &dither[MDIM * (row & (MDIM - 1))]; for (col = hdr.w; col > 0; col--) { d = dp[col & (MDIM - 1)]; ir = *s++ & 0xFF; ig = *s++ & 0xFF; ib = *s++ & 0xFF; if (ir == ig && ig == ib) { g = m * ig + gd * d; if (g < 0) g = 0; else if (g > 1) g = 1; r = b = g; } else { r = m * ir + d; if (r < 0) r = 0; else if (r > 1) r = 1; g = m * ig + d; if (g < 0) g = 0; else if (g > 1) g = 1; b = m * ib + d; if (b < 0) b = 0; else if (b > 1) b = 1; } *t++ = *(rgbkey(p, r, g, b)); } } RetAlcString(rv, t - rv); } /************************* internal functions *************************/ /* * ppmalc(w, h, max) -- allocate new ppm image and initialize header * * If allocation fails, the address in the returned descriptor is NULL. */ static descriptor ppmalc(int w, int h, int max) { char buf[32]; descriptor d; sprintf(buf, "P6\n%d %d\n%d\n", w, h, max); d.dword = strlen(buf) + 3 * w * h; d.vword = (word)alcstr(NULL, d.dword); if (d.vword != 0) strcpy((void *)d.vword, buf); return d; } /* ppmcrack(d) -- crack PPM header, setting max=0 on error */ static ppminfo ppmcrack(descriptor d) { int n; char *s; ppminfo info; static ppminfo zeroes; s = StringAddr(d); if (sscanf(s, "P6 %d %d %n", &info.w, &info.h, &n) < 2) return zeroes; /* not a raw PPM file */ /* can't scanf for "max" because it consumes too much trailing whitespace */ info.max = 0; for (s += n; isspace(*s); s++) ; while (isdigit(*s)) info.max = 10 * info.max + *s++ - '0'; if (info.max == 0 || info.max > 255) return zeroes; /* illegal max value for raw PPM */ /* now consume exactly one more whitespace character */ if (isspace(*s)) s++; info.npixels = (long)info.w * (long)info.h; info.nbytes = 3 * info.npixels; if (s + info.nbytes > StringAddr(d) + StringLen(d)) return zeroes; /* file was truncated */ info.data = s; return info; } /* * ppmrows(hdr, nbr, func, arg) -- extend rows and call driver function * * Calls func(a, w, i, arg) for each row of the PPM image identified by hdr, * where * a is a pointer to a pointer to the first byte of the row (see below) * w is the width of a row, in pixels * i is the row number * arg is passed along from the call to ppmrows * * When nbr > 0, this indicates that func() needs to read up to nbr pixels * above, below, left, and/or right of each source pixel; ppmrows copies * and extends the rows to make this easy. The argument "a" passed to func * is a pointer to the center of an array of row pointers that extends by * nbr rows in each direction. That is, a[0] points to the current row; * a[-1] points to the previous row, a[1] to the next row, and so on. * * Each row is extended by nbr additional pixels in each direction by the * duplication of the first and last pixels. The pointers in the array "a" * skip past the initial duplicates. Thus a[0][0] is the first byte * (the red byte) of the first pixel, a[0][-3] is its duplicate, and * a[0][3] is the first byte of the second pixel of the row. * * The idea behind all this complication is to make it easy to perform * neighborhood operations. See any caller of ppmrows for an example. * * If ppmrows cannot allocate memory, it returns error code 305. * If func returns nonzero, ppmrows returns that value immediately. * Otherwise, ppmrows returns zero. */ static int ppmrows(ppminfo hdr, int nbr, int (*func) (), long arg) { char **a, *s; void *buf; int i, rv, np, row, rowlen; /* process nbr=0 without any copying */ if (nbr <= 0) { s = hdr.data; for (row = 0; row < hdr.h; row++) { rv = func(&s, hdr.w, row, arg); if (rv != 0) return rv; s += 3 * hdr.w; } return 0; } /* allocate memory for pointers and data */ np = 2 * nbr + 1; /* number of pointers */ rowlen = 3 * (nbr + hdr.w + nbr); /* length of one extended row */ a = buf = malloc(np * sizeof(char *) + np * rowlen); if (buf == NULL) return 305; /* set pointers to row buffers */ s = (char *)buf + np * sizeof(char *) + 3 * nbr; for (i = 0; i < np; i++) { *a++ = s; s += rowlen; } a -= nbr + 1; /* point to center row */ /* initialize buffers */ for (i = -nbr; i < 0; i++) /* duplicates of first row */ rowextend(a[i], hdr.data, hdr.w, nbr); for (i = 0; i <= nbr; i++) /* first nbr+1 rows */ rowextend(a[i], hdr.data + 3 * i * hdr.w, hdr.w, nbr); /* iterate through rows */ for (row = 0; row < hdr.h; row++) { /* call function for this row */ rv = func(a, hdr.w, row, arg); if (rv != 0) { free(buf); return rv; } /* rotate row pointers */ s = a[-nbr]; for (i = -nbr; i < nbr; i++) a[i] = a[i+1]; a[nbr] = s; /* replace oldest with new row */ if (row + nbr < hdr.h) rowextend(s, hdr.data + 3 * (row + nbr) * hdr.w, hdr.w, nbr); else rowextend(s, hdr.data + 3 * (hdr.h - 1) * hdr.w, hdr.w, nbr); } free(buf); return 0; } /* * rowextend(dst, src, w, nbr) -- extend row on both ends * * Copy w bytes from src to dst, extending both ends by nbr copies of * the first/last 3-byte pixel. w is the row width in pixels. * Returns unextended dst pointer. */ static char *rowextend(char *dst, char *src, int w, int nbr) { char *s1, *s2, *d1, *d2; memcpy(dst, src, 3 * w); d1 = dst; d2 = dst + 3 * w; s1 = d1 + 3; s2 = d2 - 3; nbr *= 3; while (nbr--) { *--d1 = *--s1; *d2++ = *s2++; } return dst; }