ViewVC Help
View File | Revision Log | Show Annotations | Download File | Root Listing
root/radiance/ray/src/util/rmatrix.c
Revision: 2.35
Committed: Wed Aug 14 18:20:02 2019 UTC (4 years, 8 months ago) by greg
Content type: text/plain
Branch: MAIN
Changes since 2.34: +16 -2 lines
Log Message:
Added explicit byte-swap checks in headers using BigEndian= line

File Contents

# Content
1 #ifndef lint
2 static const char RCSid[] = "$Id: rmatrix.c,v 2.34 2019/08/12 20:38:19 greg Exp $";
3 #endif
4 /*
5 * General matrix operations.
6 */
7
8 #include <stdio.h>
9 #include <stdlib.h>
10 #include <string.h>
11 #include <fcntl.h>
12 #include <errno.h>
13 #include "rtio.h"
14 #include "platform.h"
15 #include "resolu.h"
16 #include "paths.h"
17 #include "rmatrix.h"
18
19 static char rmx_mismatch_warn[] = "WARNING: data type mismatch\n";
20
21 /* Allocate a nr x nc matrix with n components */
22 RMATRIX *
23 rmx_alloc(int nr, int nc, int n)
24 {
25 RMATRIX *dnew;
26
27 if ((nr <= 0) | (nc <= 0) | (n <= 0))
28 return(NULL);
29 dnew = (RMATRIX *)malloc(sizeof(RMATRIX)-sizeof(dnew->mtx) +
30 sizeof(dnew->mtx[0])*(n*nr*nc));
31 if (!dnew)
32 return(NULL);
33 dnew->nrows = nr; dnew->ncols = nc; dnew->ncomp = n;
34 dnew->dtype = DTdouble;
35 dnew->info = NULL;
36 return(dnew);
37 }
38
39 /* Free a RMATRIX array */
40 void
41 rmx_free(RMATRIX *rm)
42 {
43 if (!rm) return;
44 if (rm->info)
45 free(rm->info);
46 free(rm);
47 }
48
49 /* Resolve data type based on two input types (returns 0 for mismatch) */
50 int
51 rmx_newtype(int dtyp1, int dtyp2)
52 {
53 if ((dtyp1==DTxyze) | (dtyp1==DTrgbe) |
54 (dtyp2==DTxyze) | (dtyp2==DTrgbe)
55 && dtyp1 != dtyp2)
56 return(0);
57 if (dtyp1 < dtyp2)
58 return(dtyp1);
59 return(dtyp2);
60 }
61
62 /* Append header information associated with matrix data */
63 int
64 rmx_addinfo(RMATRIX *rm, const char *info)
65 {
66 if (!rm || !info || !*info)
67 return(0);
68 if (!rm->info) {
69 rm->info = (char *)malloc(strlen(info)+1);
70 if (rm->info) rm->info[0] = '\0';
71 } else
72 rm->info = (char *)realloc(rm->info,
73 strlen(rm->info)+strlen(info)+1);
74 if (!rm->info)
75 return(0);
76 strcat(rm->info, info);
77 return(1);
78 }
79
80 static int
81 get_dminfo(char *s, void *p)
82 {
83 RMATRIX *ip = (RMATRIX *)p;
84 char fmt[MAXFMTLEN];
85 int i;
86
87 if (headidval(fmt, s))
88 return(0);
89 if (!strncmp(s, "NCOMP=", 6)) {
90 ip->ncomp = atoi(s+6);
91 return(0);
92 }
93 if (!strncmp(s, "NROWS=", 6)) {
94 ip->nrows = atoi(s+6);
95 return(0);
96 }
97 if (!strncmp(s, "NCOLS=", 6)) {
98 ip->ncols = atoi(s+6);
99 return(0);
100 }
101 if ((i = isbigendian(s)) >= 0) {
102 ip->swapin = (nativebigendian() != i);
103 return(0);
104 }
105 if (!formatval(fmt, s)) {
106 rmx_addinfo(ip, s);
107 return(0);
108 }
109 for (i = 1; i < DTend; i++)
110 if (!strcmp(fmt, cm_fmt_id[i])) {
111 ip->dtype = i;
112 return(0);
113 }
114 return(-1);
115 }
116
117 static int
118 rmx_load_ascii(RMATRIX *rm, FILE *fp)
119 {
120 int i, j, k;
121
122 for (i = 0; i < rm->nrows; i++)
123 for (j = 0; j < rm->ncols; j++)
124 for (k = 0; k < rm->ncomp; k++)
125 if (fscanf(fp, "%lf", &rmx_lval(rm,i,j,k)) != 1)
126 return(0);
127 return(1);
128 }
129
130 static int
131 rmx_load_float(RMATRIX *rm, FILE *fp)
132 {
133 int i, j, k;
134 float val[100];
135
136 if (rm->ncomp > 100) {
137 fputs("Unsupported # components in rmx_load_float()\n", stderr);
138 exit(1);
139 }
140 for (i = 0; i < rm->nrows; i++)
141 for (j = 0; j < rm->ncols; j++) {
142 if (getbinary(val, sizeof(val[0]), rm->ncomp, fp) != rm->ncomp)
143 return(0);
144 if (rm->swapin)
145 swap32((char *)val, rm->ncomp);
146 for (k = rm->ncomp; k--; )
147 rmx_lval(rm,i,j,k) = val[k];
148 }
149 return(1);
150 }
151
152 static int
153 rmx_load_double(RMATRIX *rm, FILE *fp)
154 {
155 int i, j;
156
157 for (i = 0; i < rm->nrows; i++)
158 for (j = 0; j < rm->ncols; j++) {
159 if (getbinary(&rmx_lval(rm,i,j,0), sizeof(double), rm->ncomp, fp) != rm->ncomp)
160 return(0);
161 if (rm->swapin)
162 swap64((char *)&rmx_lval(rm,i,j,0), rm->ncomp);
163 }
164 return(1);
165 }
166
167 static int
168 rmx_load_rgbe(RMATRIX *rm, FILE *fp)
169 {
170 COLOR *scan = (COLOR *)malloc(sizeof(COLOR)*rm->ncols);
171 int i, j;
172
173 if (!scan)
174 return(0);
175 for (i = 0; i < rm->nrows; i++) {
176 if (freadscan(scan, rm->ncols, fp) < 0) {
177 free(scan);
178 return(0);
179 }
180 for (j = rm->ncols; j--; ) {
181 rmx_lval(rm,i,j,0) = colval(scan[j],RED);
182 rmx_lval(rm,i,j,1) = colval(scan[j],GRN);
183 rmx_lval(rm,i,j,2) = colval(scan[j],BLU);
184 }
185 }
186 free(scan);
187 return(1);
188 }
189
190 /* Load matrix from supported file type */
191 RMATRIX *
192 rmx_load(const char *inspec)
193 {
194 FILE *fp = stdin;
195 RMATRIX dinfo;
196 RMATRIX *dnew;
197
198 if (!inspec) { /* reading from stdin? */
199 inspec = "<stdin>";
200 SET_FILE_BINARY(stdin);
201 } else if (inspec[0] == '!') {
202 if (!(fp = popen(inspec+1, "r")))
203 return(NULL);
204 SET_FILE_BINARY(stdin);
205 } else {
206 const char *sp = inspec; /* check suffix */
207 while (*sp)
208 ++sp;
209 while (sp > inspec && sp[-1] != '.')
210 --sp;
211 if (!strcasecmp(sp, "XML")) { /* assume it's a BSDF */
212 CMATRIX *cm = cm_loadBTDF((char *)inspec);
213 if (!cm)
214 return(NULL);
215 dnew = rmx_from_cmatrix(cm);
216 cm_free(cm);
217 dnew->dtype = DTascii;
218 return(dnew);
219 }
220 /* else open it ourselves */
221 if (!(fp = fopen(inspec, "rb")))
222 return(NULL);
223 }
224 #ifdef getc_unlocked
225 flockfile(fp);
226 #endif
227 dinfo.nrows = dinfo.ncols = dinfo.ncomp = 0;
228 dinfo.dtype = DTascii; /* assumed w/o FORMAT */
229 dinfo.swapin = 0;
230 dinfo.info = NULL;
231 if (getheader(fp, get_dminfo, &dinfo) < 0) {
232 fclose(fp);
233 return(NULL);
234 }
235 if ((dinfo.nrows <= 0) | (dinfo.ncols <= 0)) {
236 if (!fscnresolu(&dinfo.ncols, &dinfo.nrows, fp)) {
237 fclose(fp);
238 return(NULL);
239 }
240 if (dinfo.ncomp <= 0)
241 dinfo.ncomp = 3;
242 else if ((dinfo.dtype == DTrgbe) | (dinfo.dtype == DTxyze) &&
243 dinfo.ncomp != 3) {
244 fclose(fp);
245 return(NULL);
246 }
247 }
248 dnew = rmx_alloc(dinfo.nrows, dinfo.ncols, dinfo.ncomp);
249 if (!dnew) {
250 fclose(fp);
251 return(NULL);
252 }
253 dnew->info = dinfo.info;
254 switch (dinfo.dtype) {
255 case DTascii:
256 SET_FILE_TEXT(stdin);
257 if (!rmx_load_ascii(dnew, fp))
258 goto loaderr;
259 dnew->dtype = DTascii; /* should leave double? */
260 break;
261 case DTfloat:
262 dnew->swapin = dinfo.swapin;
263 if (!rmx_load_float(dnew, fp))
264 goto loaderr;
265 dnew->dtype = DTfloat;
266 break;
267 case DTdouble:
268 dnew->swapin = dinfo.swapin;
269 if (!rmx_load_double(dnew, fp))
270 goto loaderr;
271 dnew->dtype = DTdouble;
272 break;
273 case DTrgbe:
274 case DTxyze:
275 if (!rmx_load_rgbe(dnew, fp))
276 goto loaderr;
277 dnew->dtype = dinfo.dtype;
278 break;
279 default:
280 goto loaderr;
281 }
282 if (fp != stdin) {
283 if (inspec[0] == '!')
284 pclose(fp);
285 else
286 fclose(fp);
287 }
288 #ifdef getc_unlocked
289 else
290 funlockfile(fp);
291 #endif
292 return(dnew);
293 loaderr: /* should report error? */
294 if (inspec[0] == '!')
295 pclose(fp);
296 else
297 fclose(fp);
298 rmx_free(dnew);
299 return(NULL);
300 }
301
302 static int
303 rmx_write_ascii(const RMATRIX *rm, FILE *fp)
304 {
305 const char *fmt = (rm->dtype == DTfloat) ? " %.7e" :
306 (rm->dtype == DTrgbe) | (rm->dtype == DTxyze) ? " %.3e" :
307 " %.15e" ;
308 int i, j, k;
309
310 for (i = 0; i < rm->nrows; i++) {
311 for (j = 0; j < rm->ncols; j++) {
312 for (k = 0; k < rm->ncomp; k++)
313 fprintf(fp, fmt, rmx_lval(rm,i,j,k));
314 fputc('\t', fp);
315 }
316 fputc('\n', fp);
317 }
318 return(1);
319 }
320
321 static int
322 rmx_write_float(const RMATRIX *rm, FILE *fp)
323 {
324 int i, j, k;
325 float val[100];
326
327 if (rm->ncomp > 100) {
328 fputs("Unsupported # components in rmx_write_float()\n", stderr);
329 exit(1);
330 }
331 for (i = 0; i < rm->nrows; i++)
332 for (j = 0; j < rm->ncols; j++) {
333 for (k = rm->ncomp; k--; )
334 val[k] = (float)rmx_lval(rm,i,j,k);
335 if (putbinary(val, sizeof(val[0]), rm->ncomp, fp) != rm->ncomp)
336 return(0);
337 }
338 return(1);
339 }
340
341 static int
342 rmx_write_double(const RMATRIX *rm, FILE *fp)
343 {
344 int i, j;
345
346 for (i = 0; i < rm->nrows; i++)
347 for (j = 0; j < rm->ncols; j++)
348 if (putbinary(&rmx_lval(rm,i,j,0), sizeof(double), rm->ncomp, fp) != rm->ncomp)
349 return(0);
350 return(1);
351 }
352
353 static int
354 rmx_write_rgbe(const RMATRIX *rm, FILE *fp)
355 {
356 COLR *scan = (COLR *)malloc(sizeof(COLR)*rm->ncols);
357 int i, j;
358
359 if (!scan)
360 return(0);
361 for (i = 0; i < rm->nrows; i++) {
362 for (j = rm->ncols; j--; )
363 setcolr(scan[j], rmx_lval(rm,i,j,0),
364 rmx_lval(rm,i,j,1),
365 rmx_lval(rm,i,j,2) );
366 if (fwritecolrs(scan, rm->ncols, fp) < 0) {
367 free(scan);
368 return(0);
369 }
370 }
371 free(scan);
372 return(1);
373 }
374
375 /* Write matrix to file type indicated by dtype */
376 int
377 rmx_write(const RMATRIX *rm, int dtype, FILE *fp)
378 {
379 RMATRIX *mydm = NULL;
380 int ok = 1;
381
382 if (!rm | !fp)
383 return(0);
384 #ifdef getc_unlocked
385 flockfile(fp);
386 #endif
387 /* complete header */
388 if (rm->info)
389 fputs(rm->info, fp);
390 if (dtype == DTfromHeader)
391 dtype = rm->dtype;
392 else if ((dtype == DTrgbe) & (rm->dtype == DTxyze))
393 dtype = DTxyze;
394 else if ((dtype == DTxyze) & (rm->dtype == DTrgbe))
395 dtype = DTrgbe;
396 if ((dtype != DTrgbe) & (dtype != DTxyze)) {
397 fprintf(fp, "NROWS=%d\n", rm->nrows);
398 fprintf(fp, "NCOLS=%d\n", rm->ncols);
399 fprintf(fp, "NCOMP=%d\n", rm->ncomp);
400 } else if (rm->ncomp != 3) { /* wrong # components? */
401 double cmtx[3];
402 if (rm->ncomp != 1) /* only convert grayscale */
403 return(0);
404 cmtx[0] = cmtx[1] = cmtx[2] = 1;
405 mydm = rmx_transform(rm, 3, cmtx);
406 if (!mydm)
407 return(0);
408 rm = mydm;
409 }
410 if ((dtype == DTfloat) | (dtype == DTdouble))
411 fputendian(fp); /* important to record */
412 fputformat((char *)cm_fmt_id[dtype], fp);
413 fputc('\n', fp);
414 switch (dtype) { /* write data */
415 case DTascii:
416 ok = rmx_write_ascii(rm, fp);
417 break;
418 case DTfloat:
419 ok = rmx_write_float(rm, fp);
420 break;
421 case DTdouble:
422 ok = rmx_write_double(rm, fp);
423 break;
424 case DTrgbe:
425 case DTxyze:
426 fprtresolu(rm->ncols, rm->nrows, fp);
427 ok = rmx_write_rgbe(rm, fp);
428 break;
429 default:
430 return(0);
431 }
432 ok &= (fflush(fp) == 0);
433 #ifdef getc_unlocked
434 funlockfile(fp);
435 #endif
436 if (mydm)
437 rmx_free(mydm);
438 return(ok);
439 }
440
441 /* Allocate and assign square identity matrix with n components */
442 RMATRIX *
443 rmx_identity(const int dim, const int n)
444 {
445 RMATRIX *rid = rmx_alloc(dim, dim, n);
446 int i, k;
447
448 if (!rid)
449 return(NULL);
450 memset(rid->mtx, 0, sizeof(rid->mtx[0])*n*dim*dim);
451 for (i = dim; i--; )
452 for (k = n; k--; )
453 rmx_lval(rid,i,i,k) = 1;
454 return(rid);
455 }
456
457 /* Duplicate the given matrix */
458 RMATRIX *
459 rmx_copy(const RMATRIX *rm)
460 {
461 RMATRIX *dnew;
462
463 if (!rm)
464 return(NULL);
465 dnew = rmx_alloc(rm->nrows, rm->ncols, rm->ncomp);
466 if (!dnew)
467 return(NULL);
468 rmx_addinfo(dnew, rm->info);
469 dnew->dtype = rm->dtype;
470 memcpy(dnew->mtx, rm->mtx,
471 sizeof(rm->mtx[0])*rm->ncomp*rm->nrows*rm->ncols);
472 return(dnew);
473 }
474
475 /* Allocate and assign transposed matrix */
476 RMATRIX *
477 rmx_transpose(const RMATRIX *rm)
478 {
479 RMATRIX *dnew;
480 int i, j, k;
481
482 if (!rm)
483 return(0);
484 if ((rm->nrows == 1) | (rm->ncols == 1)) {
485 dnew = rmx_copy(rm);
486 if (!dnew)
487 return(NULL);
488 dnew->nrows = rm->ncols;
489 dnew->ncols = rm->nrows;
490 return(dnew);
491 }
492 dnew = rmx_alloc(rm->ncols, rm->nrows, rm->ncomp);
493 if (!dnew)
494 return(NULL);
495 if (rm->info) {
496 rmx_addinfo(dnew, rm->info);
497 rmx_addinfo(dnew, "Transposed rows and columns\n");
498 }
499 dnew->dtype = rm->dtype;
500 for (i = dnew->nrows; i--; )
501 for (j = dnew->ncols; j--; )
502 for (k = dnew->ncomp; k--; )
503 rmx_lval(dnew,i,j,k) = rmx_lval(rm,j,i,k);
504 return(dnew);
505 }
506
507 /* Multiply (concatenate) two matrices and allocate the result */
508 RMATRIX *
509 rmx_multiply(const RMATRIX *m1, const RMATRIX *m2)
510 {
511 RMATRIX *mres;
512 int i, j, k, h;
513
514 if (!m1 | !m2 || (m1->ncomp != m2->ncomp) | (m1->ncols != m2->nrows))
515 return(NULL);
516 mres = rmx_alloc(m1->nrows, m2->ncols, m1->ncomp);
517 if (!mres)
518 return(NULL);
519 i = rmx_newtype(m1->dtype, m2->dtype);
520 if (i)
521 mres->dtype = i;
522 else
523 rmx_addinfo(mres, rmx_mismatch_warn);
524 for (i = mres->nrows; i--; )
525 for (j = mres->ncols; j--; )
526 for (k = mres->ncomp; k--; ) {
527 long double d = 0;
528 for (h = m1->ncols; h--; )
529 d += rmx_lval(m1,i,h,k) * rmx_lval(m2,h,j,k);
530 rmx_lval(mres,i,j,k) = (double)d;
531 }
532 return(mres);
533 }
534
535 /* Element-wise multiplication (or division) of m2 into m1 */
536 int
537 rmx_elemult(RMATRIX *m1, const RMATRIX *m2, int divide)
538 {
539 int zeroDivides = 0;
540 int i, j, k;
541
542 if (!m1 | !m2 || (m1->ncols != m2->ncols) | (m1->nrows != m2->nrows))
543 return(0);
544 if ((m2->ncomp > 1) & (m2->ncomp != m1->ncomp))
545 return(0);
546 i = rmx_newtype(m1->dtype, m2->dtype);
547 if (i)
548 m1->dtype = i;
549 else
550 rmx_addinfo(m1, rmx_mismatch_warn);
551 for (i = m1->nrows; i--; )
552 for (j = m1->ncols; j--; )
553 if (divide) {
554 double d;
555 if (m2->ncomp == 1) {
556 d = rmx_lval(m2,i,j,0);
557 if (d == 0) {
558 ++zeroDivides;
559 for (k = m1->ncomp; k--; )
560 rmx_lval(m1,i,j,k) = 0;
561 } else {
562 d = 1./d;
563 for (k = m1->ncomp; k--; )
564 rmx_lval(m1,i,j,k) *= d;
565 }
566 } else
567 for (k = m1->ncomp; k--; ) {
568 d = rmx_lval(m2,i,j,k);
569 if (d == 0) {
570 ++zeroDivides;
571 rmx_lval(m1,i,j,k) = 0;
572 } else
573 rmx_lval(m1,i,j,k) /= d;
574 }
575 } else {
576 if (m2->ncomp == 1) {
577 const double d = rmx_lval(m2,i,j,0);
578 for (k = m1->ncomp; k--; )
579 rmx_lval(m1,i,j,k) *= d;
580 } else
581 for (k = m1->ncomp; k--; )
582 rmx_lval(m1,i,j,k) *= rmx_lval(m2,i,j,k);
583 }
584 if (zeroDivides) {
585 rmx_addinfo(m1, "WARNING: zero divide(s) corrupted results\n");
586 errno = ERANGE;
587 }
588 return(1);
589 }
590
591 /* Sum second matrix into first, applying scale factor beforehand */
592 int
593 rmx_sum(RMATRIX *msum, const RMATRIX *madd, const double sf[])
594 {
595 double *mysf = NULL;
596 int i, j, k;
597
598 if (!msum | !madd ||
599 (msum->nrows != madd->nrows) |
600 (msum->ncols != madd->ncols) |
601 (msum->ncomp != madd->ncomp))
602 return(0);
603 if (!sf) {
604 mysf = (double *)malloc(sizeof(double)*msum->ncomp);
605 if (!mysf)
606 return(0);
607 for (k = msum->ncomp; k--; )
608 mysf[k] = 1;
609 sf = mysf;
610 }
611 i = rmx_newtype(msum->dtype, madd->dtype);
612 if (i)
613 msum->dtype = i;
614 else
615 rmx_addinfo(msum, rmx_mismatch_warn);
616 for (i = msum->nrows; i--; )
617 for (j = msum->ncols; j--; )
618 for (k = msum->ncomp; k--; )
619 rmx_lval(msum,i,j,k) += sf[k] * rmx_lval(madd,i,j,k);
620 if (mysf)
621 free(mysf);
622 return(1);
623 }
624
625 /* Scale the given matrix by the indicated scalar component vector */
626 int
627 rmx_scale(RMATRIX *rm, const double sf[])
628 {
629 int i, j, k;
630
631 if (!rm | !sf)
632 return(0);
633 for (i = rm->nrows; i--; )
634 for (j = rm->ncols; j--; )
635 for (k = rm->ncomp; k--; )
636 rmx_lval(rm,i,j,k) *= sf[k];
637
638 if (rm->info)
639 rmx_addinfo(rm, "Applied scalar\n");
640 return(1);
641 }
642
643 /* Allocate new matrix and apply component transformation */
644 RMATRIX *
645 rmx_transform(const RMATRIX *msrc, int n, const double cmat[])
646 {
647 int i, j, ks, kd;
648 RMATRIX *dnew;
649
650 if (!msrc | (n <= 0) | !cmat)
651 return(NULL);
652 dnew = rmx_alloc(msrc->nrows, msrc->ncols, n);
653 if (!dnew)
654 return(NULL);
655 if (msrc->info) {
656 char buf[128];
657 sprintf(buf, "Applied %dx%d matrix transform\n",
658 dnew->ncomp, msrc->ncomp);
659 rmx_addinfo(dnew, msrc->info);
660 rmx_addinfo(dnew, buf);
661 }
662 dnew->dtype = msrc->dtype;
663 for (i = dnew->nrows; i--; )
664 for (j = dnew->ncols; j--; )
665 for (kd = dnew->ncomp; kd--; ) {
666 double d = 0;
667 for (ks = msrc->ncomp; ks--; )
668 d += cmat[kd*msrc->ncomp + ks] * rmx_lval(msrc,i,j,ks);
669 rmx_lval(dnew,i,j,kd) = d;
670 }
671 return(dnew);
672 }
673
674 /* Convert a color matrix to newly allocated RMATRIX buffer */
675 RMATRIX *
676 rmx_from_cmatrix(const CMATRIX *cm)
677 {
678 int i, j;
679 RMATRIX *dnew;
680
681 if (!cm)
682 return(NULL);
683 dnew = rmx_alloc(cm->nrows, cm->ncols, 3);
684 if (!dnew)
685 return(NULL);
686 dnew->dtype = DTfloat;
687 for (i = dnew->nrows; i--; )
688 for (j = dnew->ncols; j--; ) {
689 const COLORV *cv = cm_lval(cm,i,j);
690 rmx_lval(dnew,i,j,0) = cv[0];
691 rmx_lval(dnew,i,j,1) = cv[1];
692 rmx_lval(dnew,i,j,2) = cv[2];
693 }
694 return(dnew);
695 }
696
697 /* Convert general matrix to newly allocated CMATRIX buffer */
698 CMATRIX *
699 cm_from_rmatrix(const RMATRIX *rm)
700 {
701 int i, j;
702 CMATRIX *cnew;
703
704 if (!rm || rm->ncomp != 3)
705 return(NULL);
706 cnew = cm_alloc(rm->nrows, rm->ncols);
707 if (!cnew)
708 return(NULL);
709 for (i = cnew->nrows; i--; )
710 for (j = cnew->ncols; j--; ) {
711 COLORV *cv = cm_lval(cnew,i,j);
712 cv[0] = (COLORV)rmx_lval(rm,i,j,0);
713 cv[1] = (COLORV)rmx_lval(rm,i,j,1);
714 cv[2] = (COLORV)rmx_lval(rm,i,j,2);
715 }
716 return(cnew);
717 }