ViewVC Help
View File | Revision Log | Show Annotations | Download File | Root Listing
root/radiance/ray/src/util/rmtxop.c
Revision: 2.36
Committed: Fri Mar 28 00:06:36 2025 UTC (2 months, 4 weeks ago) by greg
Content type: text/plain
Branch: MAIN
Changes since 2.35: +3 -1 lines
Log Message:
refactor: Made MAXCOMP redefinable and common between rmatrix tools

File Contents

# Content
1 #ifndef lint
2 static const char RCSid[] = "$Id: rmtxop.c,v 2.35 2024/11/08 17:52:26 greg Exp $";
3 #endif
4 /*
5 * General component matrix operations.
6 */
7
8 #include <errno.h>
9 #include "rtio.h"
10 #include "rmatrix.h"
11 #include "platform.h"
12
13 #ifndef MAXCOMP
14 #define MAXCOMP MAXCSAMP /* #components we support */
15 #endif
16
17 /* Unary matrix operation(s) */
18 typedef struct {
19 double cmat[MAXCOMP*MAXCOMP]; /* component transformation */
20 double sca[MAXCOMP]; /* scalar coefficients */
21 const char *csym; /* symbolic coefs or file */
22 short clen; /* number of coefficients */
23 short nsf; /* number of scalars */
24 short transpose; /* do transpose? */
25 } RUNARYOP;
26
27 /* Matrix input source and requested operation(s) */
28 typedef struct {
29 const char *inspec; /* input specification */
30 RMPref rmp; /* matrix preference */
31 RUNARYOP preop; /* unary operation(s) */
32 RMATRIX *mtx; /* original matrix if loaded */
33 int binop; /* binary op with next (or 0) */
34 } ROPMAT;
35
36 int verbose = 0; /* verbose reporting? */
37
38 /* Load matrix */
39 int
40 loadmatrix(ROPMAT *rop)
41 {
42 if (rop->mtx != NULL) /* already loaded? */
43 return(0);
44
45 rop->mtx = rmx_load(rop->inspec, rop->rmp);
46
47 return(!rop->mtx ? -1 : 1);
48 }
49
50 extern int checksymbolic(ROPMAT *rop);
51
52 /* Check/set transform based on a reference input file */
53 int
54 checkreffile(ROPMAT *rop)
55 {
56 static const char *curRF = NULL;
57 static RMATRIX refm;
58 const int nc = rop->mtx->ncomp;
59 int i;
60
61 if (!curRF || strcmp(rop->preop.csym, curRF)) {
62 FILE *fp = fopen(rop->preop.csym, "rb");
63 if (!rmx_load_header(&refm, fp)) {
64 fprintf(stderr, "%s: cannot read info header\n",
65 rop->preop.csym);
66 curRF = NULL;
67 if (fp) fclose(fp);
68 return(-1);
69 }
70 fclose(fp);
71 curRF = rop->preop.csym;
72 }
73 if (refm.ncomp == 3) {
74 rop->preop.csym = (refm.dtype == DTxyze) ? "XYZ" : "RGB";
75 return(checksymbolic(rop));
76 }
77 if (refm.ncomp == 2) {
78 fprintf(stderr, "%s: cannot convert to 2 components\n",
79 curRF);
80 return(-1);
81 }
82 if (refm.ncomp == 1) {
83 rop->preop.csym = "Y"; /* XXX big assumption */
84 return(checksymbolic(rop));
85 }
86 if (refm.ncomp == nc &&
87 !memcmp(refm.wlpart, rop->mtx->wlpart, sizeof(refm.wlpart)))
88 return(0); /* nothing to do */
89
90 if ((nc <= 3) | (nc > MAXCSAMP) | (refm.ncomp > MAXCSAMP)) {
91 fprintf(stderr, "%s: cannot resample from %d to %d components\n",
92 curRF, nc, refm.ncomp);
93 return(-1);
94 }
95 rop->preop.clen = refm.ncomp * nc; /* compute spec to ref */
96
97 for (i = 0; i < nc; i++) {
98 SCOLOR scstim, scresp;
99 int j;
100 memset(scstim, 0, sizeof(COLORV)*nc);
101 scstim[i] = 1.f;
102 convertscolor(scresp, refm.ncomp, refm.wlpart[0], refm.wlpart[3],
103 scstim, nc, rop->mtx->wlpart[0], rop->mtx->wlpart[3]);
104 for (j = refm.ncomp; j-- > 0; )
105 rop->preop.cmat[j*nc + i] = scresp[j];
106 }
107 memcpy(rop->mtx->wlpart, refm.wlpart, sizeof(rop->mtx->wlpart));
108 return(0);
109 }
110
111 /* Compute conversion row from spectrum to one channel of RGB */
112 void
113 rgbrow(ROPMAT *rop, int r, int p)
114 {
115 const int nc = rop->mtx->ncomp;
116 const float * wlp = rop->mtx->wlpart;
117 int i;
118
119 for (i = nc; i--; ) {
120 int nmEnd = wlp[0] + (wlp[3] - wlp[0])*i/nc;
121 int nmStart = wlp[0] + (wlp[3] - wlp[0])*(i+1)/nc;
122 COLOR crgb;
123 spec_rgb(crgb, nmStart, nmEnd);
124 rop->preop.cmat[r*nc+i] = crgb[p];
125 }
126 }
127
128 /* Compute conversion row from spectrum to one channel of XYZ */
129 void
130 xyzrow(ROPMAT *rop, int r, int p)
131 {
132 const int nc = rop->mtx->ncomp;
133 const float * wlp = rop->mtx->wlpart;
134 int i;
135
136 for (i = nc; i--; ) {
137 int nmEnd = wlp[0] + (wlp[3] - wlp[0])*i/nc;
138 int nmStart = wlp[0] + (wlp[3] - wlp[0])*(i+1)/nc;
139 COLOR cxyz;
140 spec_cie(cxyz, nmStart, nmEnd);
141 rop->preop.cmat[r*nc+i] = cxyz[p];
142 }
143 }
144
145 /* Use the spectral sensitivity function to compute matrix coefficients */
146 void
147 sensrow(ROPMAT *rop, int r, double (*sf)(const SCOLOR sc, int ncs, const float wlpt[4]))
148 {
149 const int nc = rop->mtx->ncomp;
150 int i;
151
152 for (i = nc; i--; ) {
153 SCOLOR sclr;
154 memset(sclr, 0, sizeof(COLORV)*nc);
155 sclr[i] = 1.f;
156 rop->preop.cmat[r*nc+i] = (*sf)(sclr, nc, rop->mtx->wlpart);
157 }
158 }
159
160 /* Check/set symbolic transform */
161 int
162 checksymbolic(ROPMAT *rop)
163 {
164 const int nc = rop->mtx->ncomp;
165 const int dt = rop->mtx->dtype;
166 double cf = 1;
167 int i, j;
168 /* check suffix => reference file */
169 if (strchr(rop->preop.csym, '.') > rop->preop.csym)
170 return(checkreffile(rop));
171
172 if (nc < 3) {
173 fprintf(stderr, "%s: -c '%s' requires at least 3 components\n",
174 rop->inspec, rop->preop.csym);
175 return(-1);
176 }
177 rop->preop.clen = strlen(rop->preop.csym) * nc;
178 if (rop->preop.clen > MAXCOMP*MAXCOMP) {
179 fprintf(stderr, "%s: -c '%s' results in too many components\n",
180 rop->inspec, rop->preop.csym);
181 return(-1);
182 }
183 for (j = 0; rop->preop.csym[j]; j++) {
184 int comp = 0;
185 switch (rop->preop.csym[j]) {
186 case 'B':
187 case 'b':
188 ++comp;
189 /* fall through */
190 case 'G':
191 case 'g':
192 ++comp;
193 /* fall through */
194 case 'R':
195 case 'r':
196 if (rop->preop.csym[j] <= 'Z')
197 cf = 1./WHTEFFICACY;
198 if (dt == DTxyze) {
199 for (i = 3; i--; )
200 rop->preop.cmat[j*nc+i] = cf*xyz2rgbmat[comp][i];
201 } else if (nc == 3)
202 rop->preop.cmat[j*nc+comp] = 1.;
203 else
204 rgbrow(rop, j, comp);
205 break;
206 case 'Z':
207 case 'z':
208 ++comp;
209 /* fall through */
210 case 'Y':
211 case 'y':
212 ++comp;
213 /* fall through */
214 case 'X':
215 case 'x':
216 if ((rop->preop.csym[j] <= 'Z') & (dt != DTxyze))
217 cf = WHTEFFICACY;
218 if (dt == DTxyze) {
219 rop->preop.cmat[j*nc+comp] = 1.;
220 } else if (nc == 3) {
221 for (i = 3; i--; )
222 rop->preop.cmat[j*nc+i] =
223 rgb2xyzmat[comp][i];
224 } else if (comp == CIEY)
225 sensrow(rop, j, scolor2photopic);
226 else
227 xyzrow(rop, j, comp);
228
229 for (i = nc*(cf != 1); i--; )
230 rop->preop.cmat[j*nc+i] *= cf;
231 break;
232 case 'S': /* scotopic (il)luminance */
233 cf = WHTSCOTOPIC;
234 /* fall through */
235 case 's':
236 sensrow(rop, j, scolor2scotopic);
237 for (i = nc*(cf != 1); i--; )
238 rop->preop.cmat[j*nc+i] *= cf;
239 break;
240 case 'M': /* melanopic (il)luminance */
241 cf = WHTMELANOPIC;
242 /* fall through */
243 case 'm':
244 sensrow(rop, j, scolor2melanopic);
245 for (i = nc*(cf != 1); i--; )
246 rop->preop.cmat[j*nc+i] *= cf;
247 break;
248 case 'A': /* average component */
249 case 'a':
250 for (i = nc; i--; )
251 rop->preop.cmat[j*nc+i] = 1./(double)nc;
252 break;
253 default:
254 fprintf(stderr, "%s: -c '%c' unsupported\n",
255 rop->inspec, rop->preop.csym[j]);
256 return(-1);
257 }
258 }
259 /* return recommended output type */
260 if (!strcasecmp(rop->preop.csym, "XYZ")) {
261 if (dt <= DTspec)
262 return(DTxyze);
263 } else if (!strcasecmp(rop->preop.csym, "RGB")) {
264 if (dt <= DTspec)
265 return(DTrgbe);
266 } else if (dt == DTspec)
267 return(DTfloat); /* probably not actual spectrum */
268 return(0);
269 }
270
271 /* Get matrix and perform unary operations */
272 RMATRIX *
273 loadop(ROPMAT *rop)
274 {
275 int outtype = 0;
276 RMATRIX *mres;
277 int i, j;
278
279 if (loadmatrix(rop) < 0) /* make sure we're loaded */
280 return(NULL);
281
282 if (rop->preop.csym && /* symbolic transform? */
283 (outtype = checksymbolic(rop)) < 0)
284 goto failure;
285 if (rop->preop.clen > 0) { /* apply component transform? */
286 if (rop->preop.clen % rop->mtx->ncomp) {
287 fprintf(stderr, "%s: -c must have N x %d coefficients\n",
288 rop->inspec, rop->mtx->ncomp);
289 goto failure;
290 }
291 if (rop->preop.nsf > 0) { /* scale transform, first */
292 if (rop->preop.nsf == 1) {
293 for (i = rop->preop.clen; i--; )
294 rop->preop.cmat[i] *= rop->preop.sca[0];
295 } else if (rop->preop.nsf*rop->mtx->ncomp != rop->preop.clen) {
296 fprintf(stderr, "%s: -s must have one or %d factors\n",
297 rop->inspec,
298 rop->preop.clen/rop->mtx->ncomp);
299 goto failure;
300 } else {
301 for (i = rop->preop.nsf; i--; )
302 for (j = rop->mtx->ncomp; j--; )
303 rop->preop.cmat[i*rop->mtx->ncomp+j]
304 *= rop->preop.sca[i];
305 }
306 }
307 mres = rmx_transform(rop->mtx, rop->preop.clen/rop->mtx->ncomp,
308 rop->preop.cmat);
309 if (mres == NULL) {
310 fprintf(stderr, "%s: matrix transform failed\n",
311 rop->inspec);
312 goto failure;
313 }
314 if (verbose)
315 fprintf(stderr, "%s: applied %d x %d transform%s\n",
316 rop->inspec, mres->ncomp,
317 rop->mtx->ncomp,
318 rop->preop.nsf ? " (* scalar)" : "");
319 rop->preop.nsf = 0; /* now folded in */
320 if ((mres->ncomp > 3) & (mres->dtype <= DTspec))
321 outtype = DTfloat; /* probably not actual spectrum */
322 rmx_free(rop->mtx);
323 rop->mtx = mres;
324 }
325 if (rop->preop.nsf > 0) { /* apply scalar(s)? */
326 if (rop->preop.nsf == 1) {
327 for (i = rop->mtx->ncomp; --i; )
328 rop->preop.sca[i] = rop->preop.sca[0];
329 } else if (rop->preop.nsf != rop->mtx->ncomp) {
330 fprintf(stderr, "%s: -s must have one or %d factors\n",
331 rop->inspec, rop->mtx->ncomp);
332 goto failure;
333 }
334 if (!rmx_scale(rop->mtx, rop->preop.sca)) {
335 fputs(rop->inspec, stderr);
336 fputs(": scalar operation failed\n", stderr);
337 goto failure;
338 }
339 if (verbose) {
340 fputs(rop->inspec, stderr);
341 fputs(": applied scalar (", stderr);
342 for (i = 0; i < rop->preop.nsf; i++)
343 fprintf(stderr, " %f", rop->preop.sca[i]);
344 fputs(" )\n", stderr);
345 }
346 }
347 if (rop->preop.transpose) { /* transpose matrix? */
348 mres = rmx_transpose(rop->mtx);
349 if (mres == NULL) {
350 fputs(rop->inspec, stderr);
351 fputs(": transpose failed\n", stderr);
352 goto failure;
353 }
354 if (verbose) {
355 fputs(rop->inspec, stderr);
356 fputs(": transposed rows and columns\n", stderr);
357 }
358 rmx_free(rop->mtx);
359 rop->mtx = mres;
360 }
361 mres = rop->mtx;
362 rop->mtx = NULL;
363 if (outtype)
364 mres->dtype = outtype;
365 return(mres);
366 failure:
367 rmx_free(rop->mtx);
368 return(rop->mtx = NULL);
369 }
370
371 /* Execute binary operation, free matrix arguments and return new result */
372 RMATRIX *
373 binaryop(const char *inspec, RMATRIX *mleft, int op, RMATRIX *mright)
374 {
375 RMATRIX *mres = NULL;
376 int i;
377
378 if ((mleft == NULL) | (mright == NULL))
379 return(NULL);
380 switch (op) {
381 case '.': /* concatenate */
382 if (mleft->ncomp != mright->ncomp) {
383 fputs(inspec, stderr);
384 fputs(": # components do not match\n", stderr);
385 } else if (mleft->ncols != mright->nrows) {
386 fputs(inspec, stderr);
387 fputs(": mismatched dimensions\n",
388 stderr);
389 } else
390 mres = rmx_multiply(mleft, mright);
391 rmx_free(mleft);
392 rmx_free(mright);
393 if (mres == NULL) {
394 fputs(inspec, stderr);
395 fputs(": concatenation failed\n", stderr);
396 return(NULL);
397 }
398 if (verbose) {
399 fputs(inspec, stderr);
400 fputs(": concatenated matrix\n", stderr);
401 }
402 break;
403 case '+':
404 if (!rmx_sum(mleft, mright, NULL)) {
405 fputs(inspec, stderr);
406 fputs(": matrix sum failed\n", stderr);
407 rmx_free(mleft);
408 rmx_free(mright);
409 return(NULL);
410 }
411 if (verbose) {
412 fputs(inspec, stderr);
413 fputs(": added in matrix\n", stderr);
414 }
415 rmx_free(mright);
416 mres = mleft;
417 break;
418 case '*':
419 case '/': {
420 const char * tnam = (op == '/') ?
421 "division" : "multiplication";
422 errno = 0;
423 if (!rmx_elemult(mleft, mright, (op == '/'))) {
424 fprintf(stderr, "%s: element-wise %s failed\n",
425 inspec, tnam);
426 rmx_free(mleft);
427 rmx_free(mright);
428 return(NULL);
429 }
430 if (errno)
431 fprintf(stderr,
432 "%s: warning - error during element-wise %s\n",
433 inspec, tnam);
434 else if (verbose)
435 fprintf(stderr, "%s: element-wise %s\n", inspec, tnam);
436 rmx_free(mright);
437 mres = mleft;
438 } break;
439 default:
440 fprintf(stderr, "%s: unknown operation '%c'\n", inspec, op);
441 rmx_free(mleft);
442 rmx_free(mright);
443 return(NULL);
444 }
445 return(mres);
446 }
447
448 /* Perform matrix operations from left to right */
449 RMATRIX *
450 op_left2right(ROPMAT *mop)
451 {
452 RMATRIX *mleft = loadop(mop);
453
454 while (mop->binop) {
455 if (mleft == NULL)
456 break;
457 mleft = binaryop(mop[1].inspec,
458 mleft, mop->binop, loadop(mop+1));
459 mop++;
460 }
461 return(mleft);
462 }
463
464 /* Perform matrix operations from right to left */
465 RMATRIX *
466 op_right2left(ROPMAT *mop)
467 {
468 RMATRIX *mright;
469 int rpos = 0;
470 /* find end of list */
471 while (mop[rpos].binop)
472 if (mop[rpos++].binop != '.') {
473 fputs(
474 "Right-to-left evaluation only for matrix multiplication!\n",
475 stderr);
476 return(NULL);
477 }
478 mright = loadop(mop+rpos);
479 while (rpos-- > 0) {
480 if (mright == NULL)
481 break;
482 mright = binaryop(mop[rpos+1].inspec,
483 loadop(mop+rpos), mop[rpos].binop, mright);
484 }
485 return(mright);
486 }
487
488 #define t_nrows(mop) ((mop)->preop.transpose ? (mop)->mtx->ncols \
489 : (mop)->mtx->nrows)
490 #define t_ncols(mop) ((mop)->preop.transpose ? (mop)->mtx->nrows \
491 : (mop)->mtx->ncols)
492
493 /* Should we prefer concatenating from rightmost matrix towards left? */
494 int
495 prefer_right2left(ROPMAT *mop)
496 {
497 int mri = 0;
498
499 while (mop[mri].binop) /* find rightmost matrix */
500 if (mop[mri++].binop != '.')
501 return(0); /* pre-empt reversal for other ops */
502
503 if (mri <= 1)
504 return(0); /* won't matter */
505
506 if (loadmatrix(mop+mri) < 0) /* load rightmost cat */
507 return(1); /* fail will bail in a moment */
508
509 if (t_ncols(mop+mri) == 1)
510 return(1); /* definitely better R->L */
511
512 if (t_ncols(mop+mri) >= t_nrows(mop+mri))
513 return(0); /* ...probably worse */
514
515 if (loadmatrix(mop) < 0) /* load leftmost */
516 return(0); /* fail will bail in a moment */
517
518 return(t_ncols(mop+mri) < t_nrows(mop));
519 }
520
521 int
522 get_factors(double da[], int n, char *av[])
523 {
524 int ac;
525
526 for (ac = 0; ac < n && isflt(av[ac]); ac++)
527 da[ac] = atof(av[ac]);
528 return(ac);
529 }
530
531 ROPMAT *
532 resize_moparr(ROPMAT *mop, int n2alloc)
533 {
534 int nmats = 0;
535 int i;
536
537 while (mop[nmats++].binop)
538 ;
539 for (i = nmats; i >= n2alloc; i--)
540 rmx_free(mop[i].mtx);
541 mop = (ROPMAT *)realloc(mop, n2alloc*sizeof(ROPMAT));
542 if (mop == NULL) {
543 fputs("Out of memory in resize_moparr()\n", stderr);
544 exit(1);
545 }
546 if (n2alloc > nmats)
547 memset(mop+nmats, 0, (n2alloc-nmats)*sizeof(ROPMAT));
548 return(mop);
549 }
550
551 /* Load one or more matrices and operate on them, sending results to stdout */
552 int
553 main(int argc, char *argv[])
554 {
555 int outfmt = DTfromHeader;
556 const char *defCsym = NULL;
557 int nall = 2;
558 ROPMAT *mop = (ROPMAT *)calloc(nall, sizeof(ROPMAT));
559 int nmats = 0;
560 RMATRIX *mres = NULL;
561 int stdin_used = 0;
562 int i;
563 /* get options and arguments */
564 for (i = 1; i < argc; i++) {
565 if (argv[i][0] && !argv[i][1] &&
566 strchr(".+*/", argv[i][0]) != NULL) {
567 if (!nmats || mop[nmats-1].binop) {
568 fprintf(stderr,
569 "%s: missing matrix argument before '%c' operation\n",
570 argv[0], argv[i][0]);
571 return(1);
572 }
573 mop[nmats-1].binop = argv[i][0];
574 } else if (argv[i][0] != '-' || !argv[i][1]) {
575 if (argv[i][0] == '-') {
576 if (stdin_used++) {
577 fprintf(stderr,
578 "%s: standard input used for more than one matrix\n",
579 argv[0]);
580 return(1);
581 }
582 mop[nmats].inspec = stdin_name;
583 } else
584 mop[nmats].inspec = argv[i];
585 if (!mop[nmats].preop.csym)
586 mop[nmats].preop.csym = defCsym;
587 if (nmats > 0 && !mop[nmats-1].binop)
588 mop[nmats-1].binop = '.';
589 nmats++;
590 } else {
591 int n = argc-1 - i;
592 switch (argv[i][1]) { /* get option */
593 case 'v':
594 verbose++;
595 break;
596 case 'f':
597 switch (argv[i][2]) {
598 case 'd':
599 outfmt = DTdouble;
600 break;
601 case 'f':
602 outfmt = DTfloat;
603 break;
604 case 'a':
605 outfmt = DTascii;
606 break;
607 case 'c':
608 outfmt = DTrgbe;
609 break;
610 default:
611 goto userr;
612 }
613 break;
614 case 't':
615 mop[nmats].preop.transpose = 1;
616 break;
617 case 's':
618 if (n > MAXCOMP) n = MAXCOMP;
619 i += mop[nmats].preop.nsf =
620 get_factors(mop[nmats].preop.sca,
621 n, argv+i+1);
622 if (mop[nmats].preop.nsf <= 0) {
623 fprintf(stderr, "%s: -s missing arguments\n",
624 argv[0]);
625 goto userr;
626 }
627 break;
628 case 'C':
629 if (!n || isflt(argv[i+1]))
630 goto userr;
631 defCsym = mop[nmats].preop.csym = argv[++i];
632 mop[nmats].preop.clen = 0;
633 break;
634 case 'c':
635 if (n && !isflt(argv[i+1])) {
636 mop[nmats].preop.csym = argv[++i];
637 mop[nmats].preop.clen = 0;
638 break;
639 }
640 if (n > MAXCOMP*MAXCOMP) n = MAXCOMP*MAXCOMP;
641 i += mop[nmats].preop.clen =
642 get_factors(mop[nmats].preop.cmat,
643 n, argv+i+1);
644 if (mop[nmats].preop.clen <= 0) {
645 fprintf(stderr, "%s: -c missing arguments\n",
646 argv[0]);
647 goto userr;
648 }
649 mop[nmats].preop.csym = NULL;
650 break;
651 case 'r':
652 if (argv[i][2] == 'f')
653 mop[nmats].rmp = RMPreflF;
654 else if (argv[i][2] == 'b')
655 mop[nmats].rmp = RMPreflB;
656 else
657 goto userr;
658 break;
659 default:
660 fprintf(stderr, "%s: unknown operation '%s'\n",
661 argv[0], argv[i]);
662 goto userr;
663 }
664 }
665 if (nmats >= nall)
666 mop = resize_moparr(mop, nall += 2);
667 }
668 if (mop[0].inspec == NULL) /* nothing to do? */
669 goto userr;
670 if (mop[nmats-1].binop) {
671 fprintf(stderr,
672 "%s: missing matrix argument after '%c' operation\n",
673 argv[0], mop[nmats-1].binop);
674 return(1);
675 }
676 /* favor quicker concatenation */
677 mop[nmats].mtx = prefer_right2left(mop) ? op_right2left(mop)
678 : op_left2right(mop);
679 if (mop[nmats].mtx == NULL)
680 return(1);
681 /* apply trailing unary operations */
682 mop[nmats].inspec = "trailing_ops";
683 mres = loadop(mop+nmats);
684 if (mres == NULL)
685 return(1);
686 if (outfmt == DTfromHeader) /* check data type */
687 outfmt = mres->dtype;
688 if (outfmt == DTrgbe) {
689 if (mres->ncomp > 3)
690 outfmt = DTspec;
691 else if (mres->dtype == DTxyze)
692 outfmt = DTxyze;
693 }
694 newheader("RADIANCE", stdout); /* write result to stdout */
695 printargs(argc, argv, stdout);
696 return(rmx_write(mres, outfmt, stdout) ? 0 : 1);
697 userr:
698 fprintf(stderr,
699 "Usage: %s [-v][-f{adfc}][-t][-s sf .. | -c ce ..][-rf|-rb] m1 [.+*/] .. > mres\n",
700 argv[0]);
701 return(1);
702 }