ViewVC Help
View File | Revision Log | Show Annotations | Download File | Root Listing
root/Development/ray/src/util/pvsum.c
(Generate patch)

Comparing ray/src/util/pvsum.c (file contents):
Revision 2.7 by greg, Fri Oct 24 16:31:18 2025 UTC vs.
Revision 2.13 by greg, Thu Oct 30 20:54:27 2025 UTC

# Line 32 | Line 32 | int    xres=0, yres=0;                 /* input image dimensions */
32   char    viewspec[128] = "";             /* VIEW= line from first header */
33   char    pixasp[48] = "";                /* PIXASPECT= line from header */
34  
35 < RMATRIX *cmtx = NULL;                   /* coefficient matrix */
35 > int     gargc;                          /* global argc */
36 > char    **gargv;                        /* global argv */
37  
38 + RMATRIX *cmtx;                          /* coefficient matrix */
39 + int     row0, rowN;                     /* rows for current pass */
40 +
41   /* does the given spec contain integer format? */
42   int
43   hasFormat(const char *s)
# Line 122 | Line 126 | get_iotypes(void)
126                  long    data_start = ftell(fp);         /* make sure input flat */
127                  off_t   dend = lseek(fileno(fp), 0, SEEK_END);
128                  if (dend < data_start + 4L*xres*yres) {
129 <                        fputs("Warning - multi-processing requires flat input files\n",
130 <                                        stderr);
129 >                        fprintf(stderr, "%s: warning - multi-processing requires flat input files\n",
130 >                                        gargv[0]);
131                          nprocs = 1;
132                  }
133          }
# Line 140 | Line 144 | get_iotypes(void)
144                  rmx_free(cmtx);
145                  cmtx = nmtx;
146          } else if (cmtx->ncomp != ncomp) {
147 <                fprintf(stderr, "operation %s needs %d components, has %d\n",
148 <                                cmtx->ncols == 1 ? "vector" : "matrix",
147 >                fprintf(stderr, "%s: operation %s needs %d components, has %d\n",
148 >                                gargv[0], cmtx->ncols == 1 ? "vector" : "matrix",
149                                  ncomp, cmtx->ncomp);
150                  return(0);
151          }
152          if ((in_type != DTrgbe) & (in_type != DTxyze) & (in_type != DTspec) &
153                          (in_type != DTfloat)) {
154 <                fprintf(stderr, "%s unsupported input data type: %s\n",
154 >                fprintf(stderr, "%s: unsupported input data type '%s'\n",
155                                  fbuf, cm_fmt_id[in_type]);
156                  return(0);
157          }
# Line 159 | Line 163 | get_iotypes(void)
163          return(1);
164   }
165  
166 + struct hdata {
167 +        int     xr, yr;         /* resolution */
168 +        int     fno;            /* frame # */
169 +        char    fmt[MAXFMTLEN]; /* format */
170 + };
171 +
172   /* check subsequent headers match initial file */
173   int
174   checkline(char *s, void *p)
175   {
176          static int      exposWarned = 0;
177 <        int             *xyres = (int *)p;
168 <        char            fmt[MAXFMTLEN];
177 >        struct hdata    *hp = (struct hdata *)p;
178  
179          if (!strncmp(s, "NCOLS=", 6)) {
180 <                xyres[0] = atoi(s+6);
181 <                if (xyres[0] <= 0)
180 >                hp->xr = atoi(s+6);
181 >                if (hp->xr <= 0)
182                          return(-1);
183                  return(1);
184          }
185          if (!strncmp(s, "NROWS=", 6)) {
186 <                xyres[1] = atoi(s+6);
187 <                if (xyres[1] <= 0)
186 >                hp->yr = atoi(s+6);
187 >                if (hp->yr <= 0)
188                          return(-1);
189                  return(1);
190          }
191 +        if (!strncmp(s, "FRAME=", 6)) {
192 +                hp->fno = atoi(s+6);
193 +                return(1);
194 +        }
195          if (isncomp(s)) {
196                  if (ncompval(s) != ncomp)
197                          return(-1);
# Line 186 | Line 199 | checkline(char *s, void *p)
199          }
200          if (isexpos(s)) {
201                  if (!exposWarned && fabs(1. - exposval(s)) > 0.04) {
202 <                        fputs("Warning - ignoring EXPOSURE setting(s)\n",
203 <                                        stderr);
202 >                        fprintf(stderr, "%s: warning - ignoring EXPOSURE setting(s)\n",
203 >                                        gargv[0]);
204                          exposWarned++;
205                  }
206                  return(1);
207          }
208 <        if (formatval(fmt, s)) {
196 <                if (strcmp(fmt, cm_fmt_id[in_type]))
197 <                        return(-1);
208 >        if (formatval(hp->fmt, s))
209                  return(1);
210 <        }
210 >
211          return(0);
212   }
213  
214 < /* open and check input file */
214 > /* open and check input/output file, read/write mode if fno >= 0 */
215   FILE *
216 < open_input(char *fname)
216 > open_iofile(char *fname, int fno)
217   {
218 <        int     xyres[2];
219 <        FILE    *fp = fopen(fname, "rb");
218 >        struct hdata    hd;
219 >        FILE            *fp = fopen(fname, fno>=0 ? "r+b" : "rb");
220  
221          if (!fp) {
222 <                fprintf(stderr, "%s: cannot open for reading\n", fname);
222 >                fprintf(stderr, "%s: cannot open for reading%s\n",
223 >                                fname, fno>=0 ? "/writing" : "");
224                  return(NULL);
225          }
226 <        xyres[0] = xyres[1] = 0;
227 <        if (getheader(fp, checkline, xyres) < 0) {
226 >        hd.xr = hd.yr = 0;
227 >        hd.fno = -1;
228 >        hd.fmt[0] = '\0';
229 >        if (getheader(fp, checkline, &hd) < 0) {
230                  fprintf(stderr, "%s: bad/inconsistent header\n", fname);
231                  fclose(fp);
232                  return(NULL);
233          }
234 <        if ((xyres[0] <= 0) | (xyres[1] <= 0) &&
235 <                        !fscnresolu(&xyres[0], &xyres[1], fp)) {
234 >        if ((hd.fno >= 0) & (fno >= 0) & (hd.fno != fno)) {
235 >                fprintf(stderr, "%s: unexpected frame number (%d != %d)\n",
236 >                                fname, hd.fno, fno);
237 >                fclose(fp);
238 >                return(NULL);
239 >        }
240 >        if (strcmp(hd.fmt, cm_fmt_id[fno>=0 ? out_type : in_type])) {
241 >                fprintf(stderr, "%s: wrong format\n", fname);
242 >                fclose(fp);
243 >                return(NULL);
244 >        }
245 >        if ((hd.xr <= 0) | (hd.yr <= 0) &&
246 >                        !fscnresolu(&hd.xr, &hd.yr, fp)) {
247                  fprintf(stderr, "%s: missing resolution\n", fname);
248                  fclose(fp);
249                  return(NULL);
250          }
251 <        if ((xyres[0] != xres) | (xyres[1] != yres)) {
251 >        if ((hd.xr != xres) | (hd.yr != yres)) {
252                  fprintf(stderr, "%s: mismatched resolution\n", fname);
253                  fclose(fp);
254                  return(NULL);
# Line 231 | Line 256 | open_input(char *fname)
256          return(fp);
257   }
258  
259 + /* read in previous pixel data from output and rewind to data start */
260 + int
261 + reload_data(float *osum, FILE *fp)
262 + {
263 +        long    dstart;
264 +
265 +        if (!osum | !fp)
266 +                return(0);
267 +        if ((dstart = ftell(fp)) < 0) {
268 +                fprintf(stderr, "%s: ftell() error in reload_data()\n",
269 +                                gargv[0]);
270 +                return(0);
271 +        }
272 +        if (out_type == DTfloat) {
273 +                if (fread(osum, sizeof(float)*ncomp, (size_t)xres*yres, fp) !=
274 +                                (size_t)xres*yres) {
275 +                        fprintf(stderr, "%s: fread() error\n", gargv[0]);
276 +                        return(0);
277 +                }
278 +        } else {
279 +                int     y;
280 +                for (y = 0; y < yres; y++, osum += ncomp*xres)
281 +                        if (freadsscan(osum, ncomp, xres, fp) < 0) {
282 +                                fprintf(stderr, "%s: freadsscan() error\n", gargv[0]);
283 +                                return(0);
284 +                        }
285 +        }
286 +        if (fseek(fp, dstart, SEEK_SET) < 0) {
287 +                fprintf(stderr, "%s: fseek() error in reload_data()\n",
288 +                                gargv[0]);
289 +                return(0);
290 +        }
291 +        return(1);
292 + }
293 +
294   /* open output file or command (if !NULL) and write info header */
295   FILE *
296   open_output(char *ospec, int fno)
# Line 242 | Line 302 | open_output(char *ospec, int fno)
302                  fp = stdout;
303          } else if (ospec[0] == '!') {
304                  if (!(fp = popen(ospec+1, "w"))) {
305 <                        fprintf(stderr, "Cannot start: %s\n", ospec);
305 >                        fprintf(stderr, "%s: cannot start: %s\n", gargv[0], ospec);
306                          return(NULL);
307                  }
308          } else if (!(fp = fopen(ospec, "w"))) {
# Line 255 | Line 315 | open_output(char *ospec, int fno)
315                  fputs(cmtx->info, fp);
316          else
317                  fputnow(fp);
318 +        printargs(gargc, gargv, fp);    /* this command */
319          if (fno >= 0)
320                  fprintf(fp, "FRAME=%d\n", fno);
321          if (viewspec[0])
# Line 288 | Line 349 | open_output(char *ospec, int fno)
349                  fprtresolu(xres, yres, fp);
350                  break;
351          default:
352 <                fputs("Unsupported output type!\n", stderr);
352 >                fprintf(stderr, "%s: unsupported output type!\n", gargv[0]);
353                  return(NULL);
354          }
355          if (fflush(fp) < 0) {
# Line 309 | Line 370 | solo_process(void)
370          int     c;
371  
372          if (!osum | !iscan) {
373 <                fprintf(stderr, "Cannot allocate %dx%d %d-component accumulator\n",
374 <                                xres, yres, ncomp);
373 >                fprintf(stderr, "%s: annot allocate %dx%d %d-component accumulator\n",
374 >                                gargv[0], xres, yres, ncomp);
375                  return(0);
376          }
377          if (sizeof(float) != sizeof(COLORV)) {
378 <                fputs("Code Error 1 in solo_process()\n", stderr);
378 >                fprintf(stderr, "%s: Code Error 1 in solo_process()\n", gargv[0]);
379                  return(0);
380          }
381 <        for (c = 0; c < cmtx->ncols; c++) {     /* run through each column/output */
381 >                                        /* run through each column/output */
382 >        for (c = 0; c < cmtx->ncols; c++) {
383 >                int     rc = rowN - row0;
384                  FILE    *fout;
385                  int     y;
386 <                int     rc = cmtx->nrows;
387 <                if (c > 0)              /* clear accumulator? */
386 >                                        /* open output (load if multipass) */
387 >                if (out_spec) {         /* file or command */
388 >                        if (cmtx->ncols > 1 && !hasFormat(out_spec)) {
389 >                                fprintf(stderr, "%s: sequential result must go to stdout\n",
390 >                                                gargv[0]);
391 >                                return(0);
392 >                        }
393 >                        sprintf(fbuf, out_spec, c);
394 >                        if (row0) {     /* another pass -- get prev. data */
395 >                                fout = open_iofile(fbuf, c);
396 >                                if (!reload_data(osum, fout))
397 >                                        return(0);
398 >                        } else          /* else new output (clobbers prev. file) */
399 >                                fout = open_output(fbuf, c-(cmtx->ncols==1));
400 >                } else {                        /* else stdout */
401 >                        if ((out_type == DTfloat) & (cmtx->ncols > 1)) {
402 >                                fprintf(stderr, "%s: float outputs must have separate destinations\n",
403 >                                                gargv[0]);
404 >                                return(0);
405 >                        }
406 >                        strcpy(fbuf, "<stdout>");
407 >                        fout = open_output(NULL, c-(cmtx->ncols==1));
408 >                }
409 >                if (!fout)
410 >                        return(0);      /* assume error was reported */
411 >                if (!row0 & (c > 0))    /* clear accumulator? */
412                          memset(osum, 0, sizeof(float)*ncomp*xres*yres);
413                  while (rc-- > 0) {      /* run through each input file */
414 <                        const int       r = c&1 ? rc : cmtx->nrows-1 - rc;
414 >                        const int       r = c&1 ? row0 + rc : rowN-1 - rc;
415                          const rmx_dtype *cval = rmx_val(cmtx, r, c);
416                          FILE            *finp;
417                          int             i, x;
# Line 333 | Line 420 | solo_process(void)
420                          if (i < 0)      /* this coefficient is zero, skip */
421                                  continue;
422                          sprintf(fbuf, in_spec, r);
423 <                        finp = open_input(fbuf);
423 >                        finp = open_iofile(fbuf, -1);
424                          if (!finp)
425                                  return(0);
426                          for (y = 0; y < yres; y++) {
# Line 349 | Line 436 | solo_process(void)
436                                                  dst[i] += cval[i]*iscan[x*ncomp + i];
437                          }
438                          fclose(finp);
439 <                }                       /* write out accumulated column result... */
353 <                if (out_spec) {         /* ...to file or command */
354 <                        if (cmtx->ncols > 1 && !hasFormat(out_spec)) {
355 <                                fputs("Sequential result must go to stdout\n", stderr);
356 <                                return(0);
357 <                        }
358 <                        sprintf(fbuf, out_spec, c);
359 <                        fout = open_output(fbuf, c-(cmtx->ncols==1));
360 <                } else {                /* ...to stdout */
361 <                        if ((out_type == DTfloat) & (cmtx->ncols > 1)) {
362 <                                fputs("Float outputs must have separate destinations\n",
363 <                                                stderr);
364 <                                return(0);
365 <                        }
366 <                        strcpy(fbuf, "<stdout>");
367 <                        fout = open_output(NULL, c-(cmtx->ncols==1));
368 <                }
369 <                if (!fout)
370 <                        return(0);      /* assume error was reported */
439 >                }                       /* write accumulated picture */
440                  if (out_type != DTfloat) {
441                          for (y = 0; y < yres; y++)
442                                  if (fwritesscan(osum + (size_t)y*xres*ncomp,
# Line 379 | Line 448 | solo_process(void)
448  
449                  if (fbuf[0] == '!') {
450                          if (pclose(fout) != 0) {
451 <                                fprintf(stderr, "Bad status from: %s\n", fbuf);
451 >                                fprintf(stderr, "%s: bad status from: %s\n", gargv[0], fbuf);
452                                  return(0);
453                          }
454                  } else if (fout != stdout && fclose(fout) == EOF)
# Line 408 | Line 477 | scramble(int n)
477          int     i;
478  
479          if (!scarr) {
480 <                fprintf(stderr, "Out of memory in scramble(%d)\n", n);
480 >                fprintf(stderr, "%s: out of memory in scramble(%d)\n", gargv[0], n);
481                  exit(1);
482          }
483          for (i = n; i--; )
# Line 429 | Line 498 | multi_process(void)
498   {
499          int     coff = nprocs;
500          int     odd = 0;
501 +        float   *osum = NULL;
502 +        int     *syarr = NULL;
503          char    fbuf[512];
433        float   *osum;
434        int     *syarr;
504          int     c;
505                                          /* sanity check */
506          if (sizeof(float) != sizeof(COLORV)) {
507 <                fputs("Code Error 1 in multi_process()\n", stderr);
507 >                fprintf(stderr, "%s: code Error 1 in multi_process()\n", gargv[0]);
508                  return(0);
509          }
510 <        while (--coff > 0) {            /* parent births children */
510 >        fflush(NULL);                   /* parent births helper subprocs */
511 >        while (--coff > 0) {
512                  int     pid = fork();
513                  if (pid < 0) {
514 <                        fputs("fork() call failed!\n", stderr);
514 >                        fprintf(stderr, "%s: fork() call failed!\n", gargv[0]);
515                          return(0);
516                  }
517 <                if (pid == 0) break;    /* child gets to work */
517 >                if (!pid) break;        /* new child gets to work */
518          }
519          osum = (float *)calloc((size_t)xres*yres, sizeof(float)*ncomp);
520          if (!osum) {
521 <                fprintf(stderr, "Cannot allocate %dx%d %d-component accumulator\n",
522 <                                xres, yres, ncomp);
521 >                fprintf(stderr, "%s: cannot allocate %dx%d %d-component accumulator\n",
522 >                                gargv[0], xres, yres, ncomp);
523                  return(0);
524          }
525          srandom(113*coff + 5669);       /* randomize row access for this process */
526          syarr = scramble(yres);
527                                          /* run through our unique set of columns */
528          for (c = coff; c < cmtx->ncols; c += nprocs) {
529 +                int     rc = rowN - row0;
530                  FILE    *fout;
531                  int     y;
532 <                int     rc = cmtx->nrows;
533 <                if (c > coff)           /* clear accumulator? */
534 <                        memset(osum, 0, sizeof(float)*ncomp*xres*yres);
532 >                                        /* create/load output */
533 >                sprintf(fbuf, out_spec, c);
534 >                if (row0) {             /* making another pass? */
535 >                        fout = open_iofile(fbuf, c);
536 >                        if (!reload_data(osum, fout))
537 >                                return(0);
538 >                } else {                /* else new output (clobbers prev. file) */
539 >                        fout = open_output(fbuf, c);
540 >                        if (!fout) return(0);
541 >                        if (c > coff)   /* clear accumulator? */
542 >                                memset(osum, 0, sizeof(float)*ncomp*xres*yres);
543 >                }
544                  while (rc-- > 0) {      /* map & sum each input file */
545 <                        const int       r = odd ? rc : cmtx->nrows-1 - rc;
545 >                        const int       r = odd ? row0 + rc : rowN-1 - rc;
546                          const rmx_dtype *cval = rmx_val(cmtx, r, c);
547                          long            dstart;
548 <                        size_t          maplen;
548 >                        size_t          imaplen;
549                          void            *imap;
550                          FILE            *finp;
551                          float           *dst;
# Line 475 | Line 555 | multi_process(void)
555                          if (i < 0)      /* this coefficient is zero, skip */
556                                  continue;
557                          sprintf(fbuf, in_spec, r);
558 <                        finp = open_input(fbuf);
558 >                        finp = open_iofile(fbuf, -1);
559                          if (!finp)
560                                  return(0);
561                          dstart = ftell(finp);
# Line 488 | Line 568 | multi_process(void)
568                                  return(0);
569                          }
570                          i = in_type==DTfloat ? ncomp*(int)sizeof(float) : ncomp+1;
571 <                        maplen = dstart + (size_t)yres*xres*i;
572 <                        imap = mmap(NULL, maplen, PROT_READ,
571 >                        imaplen = dstart + (size_t)yres*xres*i;
572 >                        imap = mmap(NULL, imaplen, PROT_READ,
573                                          MAP_FILE|MAP_SHARED, fileno(finp), 0);
574                          fclose(finp);           /* will read from map (randomly) */
575                          if (imap == MAP_FAILED) {
# Line 516 | Line 596 | multi_process(void)
596                                          dst[i] += cval[i]*(cvp[i]+(rmx_dtype).5)*fe;
597                                  }
598                              }
599 <                        munmap(imap, maplen);
600 <                }                       /* write accumulated column picture/matrix */
521 <                sprintf(fbuf, out_spec, c);
522 <                fout = open_output(fbuf, c);
523 <                if (!fout)
524 <                        return(0);      /* assume error was reported */
599 >                        munmap(imap, imaplen);
600 >                }                       /* write accumulated column picture */
601                  if (out_type != DTfloat) {
602                          for (y = 0; y < yres; y++)
603                                  if (fwritesscan(osum + (size_t)y*xres*ncomp,
# Line 533 | Line 609 | multi_process(void)
609  
610                  if (fbuf[0] == '!') {
611                          if (pclose(fout) != 0) {
612 <                                fprintf(stderr, "Bad status from: %s\n", fbuf);
612 >                                fprintf(stderr, "%s: bad status from: %s\n", gargv[0], fbuf);
613                                  return(0);
614                          }
615                  } else if (fclose(fout) == EOF)
616                          goto writerr;
617                  odd = !odd;             /* go back & forth to milk page cache */
618          }
619 +        if (coff) _exit(0);             /* child exits here */
620 +                                        /* but parent waits for children */
621          free(osum);
622          free(syarr);
623 <        if (coff)                       /* child processes return here... */
546 <                return(1);
547 <        c = 0;                          /* ...but parent waits for children */
623 >        c = 0;
624          while (++coff < nprocs) {
625                  int     st;
626 <                if (wait(&st) < 0)
626 >                if (wait(&st) < 0) {
627 >                        fprintf(stderr, "%s: warning - child disappeared\n", gargv[0]);
628                          break;
629 <                if (st) c = st;
629 >                }
630 >                if (st) {
631 >                        fprintf(stderr, "%s: bad exit status from child\n", gargv[0]);
632 >                        c = st;
633 >                }
634          }
635          return(c == 0);
636   writerr:
# Line 562 | Line 643 | writerr:
643   int
644   main(int argc, char *argv[])
645   {
646 +        double  cacheGB = 0;
647 +        int     rintvl;
648          int     a;
649  
650 +        gargc = argc;                   /* for header output */
651 +        gargv = argv;
652 +
653          for (a = 1; a < argc-1 && argv[a][0] == '-'; a++)
654                  switch (argv[a][1]) {
655                  case 'o':               /* output spec/format */
# Line 581 | Line 667 | main(int argc, char *argv[])
667                                  goto badopt;
668                          }
669                          break;
670 +                case 'm':               /* cache size in GigaBytes */
671 +                        cacheGB = atof(argv[++a]);
672 +                        break;
673                  case 'N':               /* number of desired processes */
674                  case 'n':               /* quietly supported alternate */
675                          nprocs = atoi(argv[++a]);
# Line 598 | Line 687 | badopt:                        fprintf(stderr, "%s: bad option: %s\n", argv
687          cmtx = rmx_load(argv[a+1]);     /* loads from stdin if a+1==argc */
688          if (cmtx == NULL)
689                  return(1);              /* error reported */
690 +        cacheGB *= (cmtx->ncols > 1);
691 +        if (cacheGB > 0 && (!out_spec || *out_spec == '!')) {
692 +                fprintf(stderr, "%s: -m option incompatible with output to %s\n",
693 +                                argv[0], out_spec ? "command" : "stdout");
694 +                return(1);
695 +        }
696          if (nprocs > cmtx->ncols)
697                  nprocs = cmtx->ncols;
698   #if defined(_WIN32) || defined(_WIN64)
# Line 624 | Line 719 | badopt:                        fprintf(stderr, "%s: bad option: %s\n", argv
719          }
720          if (!get_iotypes())
721                  return(1);
722 <        if (!(nprocs == 1 ? solo_process() : multi_process()))
723 <                return(1);
722 >        if (cacheGB > 1e-4) {           /* figure out # of passes => rintvl */
723 >                size_t  inp_bytes = (in_type==DTfloat ? sizeof(float)*ncomp
724 >                                                : (size_t)(ncomp+1)) * xres*yres;
725 >                size_t  over_bytes = rmx_array_size(cmtx) +
726 >                                        sizeof(float)*ncomp*xres*yres +
727 >                                        2*(out_type==DTfloat ? sizeof(float)*ncomp
728 >                                                : (size_t)(ncomp+1)) * xres*yres;
729 >                int     npasses = (double)inp_bytes*cmtx->nrows /
730 >                                (cacheGB*(1L<<30) - (double)over_bytes*nprocs) + 1;
731 >                if ((npasses <= 0) | (npasses*6 >= cmtx->nrows)) {
732 >                        fprintf(stderr,
733 >                            "%s: warning - insufficient cache space for multi-pass\n",
734 >                                        argv[0]);
735 >                        npasses = 1;
736 >                }
737 >                rintvl = cmtx->nrows / npasses;
738 >                rintvl += (rintvl*npasses < cmtx->nrows);
739 >        } else
740 >                rintvl = cmtx->nrows;
741 >                                        /* make our output accumulation passes */
742 >        for (row0 = 0; row0 < cmtx->nrows; row0 += rintvl) {
743 >                if ((rowN = row0 + rintvl) > cmtx->nrows)
744 >                        rowN = cmtx->nrows;
745 >                if (nprocs==1 ? !solo_process() : !multi_process())
746 >                        return(1);
747 >        }
748          return(0);
749   userr:
750 <        fprintf(stderr, "Usage: %s [-oc | -of][-o ospec][-N nproc] inpspec [mtx]\n",
750 >        fprintf(stderr, "Usage: %s [-oc | -of][-o ospec][-N nproc][-m cacheGB] inpspec [mtx]\n",
751                                  argv[0]);
752          return(1);
753   }

Diff Legend

Removed lines
+ Added lines
< Changed lines (old)
> Changed lines (new)