summaryrefslogtreecommitdiff
path: root/ipl/cfuncs/ppm.c
diff options
context:
space:
mode:
Diffstat (limited to 'ipl/cfuncs/ppm.c')
-rw-r--r--ipl/cfuncs/ppm.c581
1 files changed, 581 insertions, 0 deletions
diff --git a/ipl/cfuncs/ppm.c b/ipl/cfuncs/ppm.c
new file mode 100644
index 0000000..b9652e1
--- /dev/null
+++ b/ipl/cfuncs/ppm.c
@@ -0,0 +1,581 @@
+/*
+############################################################################
+#
+# 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 <ctype.h>
+#include <string.h>
+#include <stdlib.h>
+
+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;
+ }