summaryrefslogtreecommitdiff
path: root/ipl/cfuncs/ppm.c
blob: b9652e1ed31d681016820b6924ce1566df8b16ce (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
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;
   }