1 //---------------------------------------------------------------------------------
2 //
3 //  Little Color Management System
4 //  Copyright (c) 1998-2020 Marti Maria Saguer
5 //
6 // Permission is hereby granted, free of charge, to any person obtaining
7 // a copy of this software and associated documentation files (the "Software"),
8 // to deal in the Software without restriction, including without limitation
9 // the rights to use, copy, modify, merge, publish, distribute, sublicense,
10 // and/or sell copies of the Software, and to permit persons to whom the Software
11 // is furnished to do so, subject to the following conditions:
12 //
13 // The above copyright notice and this permission notice shall be included in
14 // all copies or substantial portions of the Software.
15 //
16 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO
18 // THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23 //
24 //---------------------------------------------------------------------------------
25 //
26 
27 #include "utils.h"
28 #include "tiffio.h"
29 
30 
31 // ------------------------------------------------------------------------
32 
33 static TIFF *Tiff1, *Tiff2, *TiffDiff;
34 static const char* TiffDiffFilename;
35 static const char* CGATSout;
36 
37 typedef struct {
38                 double  n, x, x2;
39                 double  Min, Peak;
40 
41     } STAT, *LPSTAT;
42 
43 
44 static STAT ColorantStat[4];
45 static STAT EuclideanStat;
46 static STAT ColorimetricStat;
47 
48 static uint16 Channels;
49 
50 static cmsHPROFILE hLab;
51 
52 
53 static
ConsoleWarningHandler(const char * module,const char * fmt,va_list ap)54 void ConsoleWarningHandler(const char* module, const char* fmt, va_list ap)
55 {
56         char e[512] = { '\0' };
57         if (module != NULL)
58               strcat(strcpy(e, module), ": ");
59 
60         vsprintf(e+strlen(e), fmt, ap);
61         strcat(e, ".");
62         if (Verbose) {
63 
64               fprintf(stderr, "\nWarning");
65               fprintf(stderr, " %s\n", e);
66               fflush(stderr);
67               }
68 }
69 
70 static
ConsoleErrorHandler(const char * module,const char * fmt,va_list ap)71 void ConsoleErrorHandler(const char* module, const char* fmt, va_list ap)
72 {
73        char e[512] = { '\0' };
74 
75        if (module != NULL)
76               strcat(strcpy(e, module), ": ");
77 
78        vsprintf(e+strlen(e), fmt, ap);
79        strcat(e, ".");
80        fprintf(stderr, "\nError");
81        fprintf(stderr, " %s\n", e);
82        fflush(stderr);
83 }
84 
85 
86 
87 static
Help()88 void Help()
89 {
90     fprintf(stderr, "Little cms TIFF compare utility. v1.0\n\n");
91 
92     fprintf(stderr, "usage: tiffdiff [flags] input.tif output.tif\n");
93 
94     fprintf(stderr, "\nflags:\n\n");
95 
96 
97     fprintf(stderr, "%co<tiff>   - Output TIFF file\n", SW);
98     fprintf(stderr, "%cg<CGATS>  - Output results in CGATS file\n", SW);
99 
100     fprintf(stderr, "\n");
101 
102     fprintf(stderr, "%cv - Verbose (show warnings)\n", SW);
103     fprintf(stderr, "%ch - This help\n", SW);
104 
105 
106     fflush(stderr);
107     exit(0);
108 }
109 
110 
111 
112 // The toggles stuff
113 
114 static
HandleSwitches(int argc,char * argv[])115 void HandleSwitches(int argc, char *argv[])
116 {
117        int s;
118 
119        while ((s=xgetopt(argc,argv,"o:O:hHvVg:G:")) != EOF) {
120 
121        switch (s) {
122 
123 
124        case 'v':
125        case 'V':
126             Verbose = TRUE;
127             break;
128 
129        case 'o':
130        case 'O':
131            TiffDiffFilename  = xoptarg;
132            break;
133 
134 
135         case 'H':
136         case 'h':
137             Help();
138             break;
139 
140         case 'g':
141         case 'G':
142             CGATSout = xoptarg;
143             break;
144 
145   default:
146 
147        FatalError("Unknown option - run without args to see valid ones");
148     }
149     }
150 }
151 
152 
153 static
ClearStatistics(LPSTAT st)154 void ClearStatistics(LPSTAT st)
155 {
156 
157     st ->n = st ->x = st->x2 = st->Peak = 0;
158     st ->Min = 1E10;
159 
160 }
161 
162 
163 static
AddOnePixel(LPSTAT st,double dE)164 void AddOnePixel(LPSTAT st, double dE)
165 {
166 
167     st-> x += dE; st ->x2 += (dE * dE); st->n  += 1.0;
168     if (dE > st ->Peak) st ->Peak = dE;
169     if (dE < st ->Min)  st ->Min= dE;
170 }
171 
172 static
Std(LPSTAT st)173 double Std(LPSTAT st)
174 {
175     return sqrt((st->n * st->x2 - st->x * st->x) / (st->n*(st->n-1)));
176 }
177 
178 static
Mean(LPSTAT st)179 double Mean(LPSTAT st)
180 {
181     return st ->x/st ->n;
182 }
183 
184 
185 // Build up the pixeltype descriptor
186 
187 static
GetInputPixelType(TIFF * Bank)188 cmsUInt32Number GetInputPixelType(TIFF *Bank)
189 {
190      uint16 Photometric, bps, spp, extra, PlanarConfig, *info;
191      uint16 Compression, reverse = 0;
192      int ColorChannels, IsPlanar = 0, pt = 0;
193 
194      TIFFGetField(Bank,           TIFFTAG_PHOTOMETRIC,   &Photometric);
195      TIFFGetFieldDefaulted(Bank,  TIFFTAG_BITSPERSAMPLE, &bps);
196 
197      if (bps == 1)
198        FatalError("Sorry, bilevel TIFFs has nothig to do with ICC profiles");
199 
200      if (bps != 8 && bps != 16)
201               FatalError("Sorry, 8 or 16 bits per sample only");
202 
203      TIFFGetFieldDefaulted(Bank, TIFFTAG_SAMPLESPERPIXEL, &spp);
204      TIFFGetFieldDefaulted(Bank, TIFFTAG_PLANARCONFIG, &PlanarConfig);
205 
206      switch (PlanarConfig)
207      {
208      case PLANARCONFIG_CONTIG: IsPlanar = 0; break;
209      case PLANARCONFIG_SEPARATE: FatalError("Planar TIFF are not supported");
210      default:
211 
212      FatalError("Unsupported planar configuration (=%d) ", (int) PlanarConfig);
213      }
214 
215      // If Samples per pixel == 1, PlanarConfiguration is irrelevant and need
216      // not to be included.
217 
218      if (spp == 1) IsPlanar = 0;
219 
220 
221      // Any alpha?
222 
223      TIFFGetFieldDefaulted(Bank, TIFFTAG_EXTRASAMPLES, &extra, &info);
224 
225 
226      ColorChannels = spp - extra;
227 
228      switch (Photometric) {
229 
230      case PHOTOMETRIC_MINISWHITE:
231 
232             reverse = 1;
233 
234      case PHOTOMETRIC_MINISBLACK:
235 
236             pt = PT_GRAY;
237             break;
238 
239      case PHOTOMETRIC_RGB:
240 
241             pt = PT_RGB;
242             break;
243 
244 
245      case PHOTOMETRIC_PALETTE:
246 
247             FatalError("Sorry, palette images not supported (at least on this version)");
248 
249      case PHOTOMETRIC_SEPARATED:
250            pt = PixelTypeFromChanCount(ColorChannels);
251            break;
252 
253      case PHOTOMETRIC_YCBCR:
254            TIFFGetField(Bank, TIFFTAG_COMPRESSION, &Compression);
255            {
256                   uint16 subx, suby;
257 
258                   pt = PT_YCbCr;
259                   TIFFGetFieldDefaulted(Bank, TIFFTAG_YCBCRSUBSAMPLING, &subx, &suby);
260                   if (subx != 1 || suby != 1)
261                          FatalError("Sorry, subsampled images not supported");
262 
263            }
264            break;
265 
266      case 9:
267      case PHOTOMETRIC_CIELAB:
268            pt = PT_Lab;
269            break;
270 
271 
272      case PHOTOMETRIC_LOGLUV:      /* CIE Log2(L) (u',v') */
273 
274            TIFFSetField(Bank, TIFFTAG_SGILOGDATAFMT, SGILOGDATAFMT_16BIT);
275            pt = PT_YUV;             // *ICCSpace = icSigLuvData;
276            bps = 16;               // 16 bits forced by LibTiff
277            break;
278 
279      default:
280            FatalError("Unsupported TIFF color space (Photometric %d)", Photometric);
281      }
282 
283      // Convert bits per sample to bytes per sample
284 
285      bps >>= 3;
286 
287      return (COLORSPACE_SH(pt)|PLANAR_SH(IsPlanar)|EXTRA_SH(extra)|CHANNELS_SH(ColorChannels)|BYTES_SH(bps)|FLAVOR_SH(reverse));
288 }
289 
290 
291 
292 static
OpenEmbedded(TIFF * tiff,cmsHPROFILE * PtrProfile,cmsHTRANSFORM * PtrXform)293 cmsUInt32Number OpenEmbedded(TIFF* tiff, cmsHPROFILE* PtrProfile, cmsHTRANSFORM* PtrXform)
294 {
295 
296     cmsUInt32Number EmbedLen, dwFormat = 0;
297     cmsUInt8Number* EmbedBuffer;
298 
299     *PtrProfile = NULL;
300     *PtrXform   = NULL;
301 
302     if (TIFFGetField(tiff, TIFFTAG_ICCPROFILE, &EmbedLen, &EmbedBuffer)) {
303 
304               *PtrProfile = cmsOpenProfileFromMem(EmbedBuffer, EmbedLen);
305 
306               if (Verbose) {
307 
308 				  fprintf(stdout, "Embedded profile found:\n");
309 				  PrintProfileInformation(NULL, *PtrProfile);
310 
311               }
312 
313               dwFormat  = GetInputPixelType(tiff);
314               *PtrXform = cmsCreateTransform(*PtrProfile, dwFormat,
315                                           hLab, TYPE_Lab_DBL, INTENT_RELATIVE_COLORIMETRIC, 0);
316 
317       }
318 
319     return dwFormat;
320 }
321 
322 
323 static
PixelSize(cmsUInt32Number dwFormat)324 size_t PixelSize(cmsUInt32Number dwFormat)
325 {
326     return T_BYTES(dwFormat) * (T_CHANNELS(dwFormat) + T_EXTRA(dwFormat));
327 }
328 
329 
330 static
CmpImages(TIFF * tiff1,TIFF * tiff2,TIFF * diff)331 int CmpImages(TIFF* tiff1, TIFF* tiff2, TIFF* diff)
332 {
333     cmsUInt8Number* buf1, *buf2, *buf3=NULL;
334     int row, cols, imagewidth = 0, imagelength = 0;
335     uint16   Photometric;
336     double dE = 0;
337     double dR, dG, dB, dC, dM, dY, dK;
338     int rc = 0;
339     cmsHPROFILE hProfile1 = 0, hProfile2 = 0;
340     cmsHTRANSFORM xform1 = 0, xform2 = 0;
341     cmsUInt32Number dwFormat1, dwFormat2;
342 
343 
344 
345       TIFFGetField(tiff1, TIFFTAG_PHOTOMETRIC, &Photometric);
346       TIFFGetField(tiff1, TIFFTAG_IMAGEWIDTH,  &imagewidth);
347       TIFFGetField(tiff1, TIFFTAG_IMAGELENGTH, &imagelength);
348       TIFFGetField(tiff1, TIFFTAG_SAMPLESPERPIXEL, &Channels);
349 
350       dwFormat1 = OpenEmbedded(tiff1, &hProfile1, &xform1);
351       dwFormat2 = OpenEmbedded(tiff2, &hProfile2, &xform2);
352 
353 
354 
355       buf1 = (cmsUInt8Number*)_TIFFmalloc(TIFFScanlineSize(tiff1));
356       buf2 = (cmsUInt8Number*)_TIFFmalloc(TIFFScanlineSize(tiff2));
357 
358       if (diff) {
359 
360            TIFFSetField(diff, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_MINISBLACK);
361            TIFFSetField(diff, TIFFTAG_COMPRESSION, COMPRESSION_NONE);
362            TIFFSetField(diff, TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG);
363 
364            TIFFSetField(diff, TIFFTAG_IMAGEWIDTH,  imagewidth);
365            TIFFSetField(diff, TIFFTAG_IMAGELENGTH, imagelength);
366 
367            TIFFSetField(diff, TIFFTAG_SAMPLESPERPIXEL, 1);
368            TIFFSetField(diff, TIFFTAG_BITSPERSAMPLE, 8);
369 
370            buf3 = (cmsUInt8Number*)_TIFFmalloc(TIFFScanlineSize(diff));
371       }
372 
373 
374 
375       for (row = 0; row < imagelength; row++) {
376 
377         if (TIFFReadScanline(tiff1, buf1, row, 0) < 0) goto Error;
378         if (TIFFReadScanline(tiff2, buf2, row, 0) < 0) goto Error;
379 
380 
381         for (cols = 0; cols < imagewidth; cols++) {
382 
383 
384             switch (Photometric) {
385 
386             case PHOTOMETRIC_MINISWHITE:
387             case PHOTOMETRIC_MINISBLACK:
388 
389                     dE = fabs(buf2[cols] - buf1[cols]);
390 
391                     AddOnePixel(&ColorantStat[0], dE);
392                     AddOnePixel(&EuclideanStat, dE);
393                     break;
394 
395             case PHOTOMETRIC_RGB:
396 
397                     {
398                         int index = 3 * cols;
399 
400                         dR = fabs(buf2[index+0] - buf1[index+0]);
401                         dG = fabs(buf2[index+1] - buf1[index+1]);
402                         dB = fabs(buf2[index+2] - buf1[index+2]);
403 
404                         dE = sqrt(dR * dR + dG * dG + dB * dB) / sqrt(3.);
405                     }
406 
407                     AddOnePixel(&ColorantStat[0], dR);
408                     AddOnePixel(&ColorantStat[1], dG);
409                     AddOnePixel(&ColorantStat[2], dB);
410                     AddOnePixel(&EuclideanStat,   dE);
411                     break;
412 
413             case PHOTOMETRIC_SEPARATED:
414 
415                 {
416                         int index = 4 * cols;
417 
418                         dC = fabs(buf2[index+0] - buf1[index+0]);
419                         dM = fabs(buf2[index+1] - buf1[index+1]);
420                         dY = fabs(buf2[index+2] - buf1[index+2]);
421                         dK = fabs(buf2[index+3] - buf1[index+3]);
422 
423                         dE = sqrt(dC * dC + dM * dM + dY * dY + dK * dK) / 2.;
424                     }
425                     AddOnePixel(&ColorantStat[0], dC);
426                     AddOnePixel(&ColorantStat[1], dM);
427                     AddOnePixel(&ColorantStat[2], dY);
428                     AddOnePixel(&ColorantStat[3], dK);
429                     AddOnePixel(&EuclideanStat,   dE);
430                     break;
431 
432             default:
433                     FatalError("Unsupported channels: %d", Channels);
434             }
435 
436 
437             if (xform1 && xform2) {
438 
439 
440                 cmsCIELab Lab1, Lab2;
441                 size_t index1 = cols * PixelSize(dwFormat1);
442                 size_t index2 = cols * PixelSize(dwFormat2);
443 
444                 cmsDoTransform(NULL, xform1, &buf1[index1], &Lab1,  1);
445                 cmsDoTransform(NULL, xform2, &buf2[index2], &Lab2,  1);
446 
447                 dE = cmsDeltaE(NULL, &Lab1, &Lab2);
448                 AddOnePixel(&ColorimetricStat, dE);
449             }
450 
451 
452             if (diff) {
453                 buf3[cols] = (cmsUInt8Number) floor(dE + 0.5);
454         }
455 
456         }
457 
458         if (diff) {
459 
460                 if (TIFFWriteScanline(diff, buf3, row, 0) < 0) goto Error;
461         }
462 
463 
464       }
465 
466      rc = 1;
467 
468 Error:
469 
470      if (hProfile1) cmsCloseProfile(NULL, hProfile1);
471      if (hProfile2) cmsCloseProfile(NULL, hProfile2);
472      if (xform1) cmsDeleteTransform(NULL, xform1);
473      if (xform2) cmsDeleteTransform(NULL, xform2);
474       _TIFFfree(buf1); _TIFFfree(buf2);
475       if (diff) {
476            TIFFWriteDirectory(diff);
477           if (buf3 != NULL) _TIFFfree(buf3);
478       }
479       return rc;
480 }
481 
482 
483 static
AssureShortTagIs(TIFF * tif1,TIFF * tiff2,int tag,int Val,const char * Error)484 void AssureShortTagIs(TIFF* tif1, TIFF* tiff2, int tag, int Val, const char* Error)
485 {
486         uint16 v1;
487 
488 
489         if (!TIFFGetField(tif1, tag, &v1)) goto Err;
490         if (v1 != Val) goto Err;
491 
492         if (!TIFFGetField(tiff2, tag, &v1)) goto Err;
493         if (v1 != Val) goto Err;
494 
495         return;
496 Err:
497         FatalError("%s is not proper", Error);
498 }
499 
500 
501 static
CmpShortTag(TIFF * tif1,TIFF * tif2,int tag)502 int CmpShortTag(TIFF* tif1, TIFF* tif2, int tag)
503 {
504         uint16 v1, v2;
505 
506         if (!TIFFGetField(tif1, tag, &v1)) return 0;
507         if (!TIFFGetField(tif2, tag, &v2)) return 0;
508 
509         return v1 == v2;
510 }
511 
512 static
CmpLongTag(TIFF * tif1,TIFF * tif2,int tag)513 int CmpLongTag(TIFF* tif1, TIFF* tif2, int tag)
514 {
515         uint32 v1, v2;
516 
517         if (!TIFFGetField(tif1, tag, &v1)) return 0;
518         if (!TIFFGetField(tif2, tag, &v2)) return 0;
519 
520         return v1 == v2;
521 }
522 
523 
524 static
EqualShortTag(TIFF * tif1,TIFF * tif2,int tag,const char * Error)525 void EqualShortTag(TIFF* tif1, TIFF* tif2, int tag, const char* Error)
526 {
527     if (!CmpShortTag(tif1, tif2, tag))
528         FatalError("%s is different", Error);
529 }
530 
531 
532 
533 static
EqualLongTag(TIFF * tif1,TIFF * tif2,int tag,const char * Error)534 void EqualLongTag(TIFF* tif1, TIFF* tif2, int tag, const char* Error)
535 {
536     if (!CmpLongTag(tif1, tif2, tag))
537         FatalError("%s is different", Error);
538 }
539 
540 
541 
542 static
AddOneCGATSRow(cmsHANDLE hIT8,char * Name,LPSTAT st)543 void AddOneCGATSRow(cmsHANDLE hIT8, char *Name, LPSTAT st)
544 {
545 
546     double Per100 = 100.0 * ((255.0 - Mean(st)) / 255.0);
547 
548     cmsIT8SetData(NULL, hIT8,    Name, "SAMPLE_ID", Name);
549     cmsIT8SetDataDbl(NULL, hIT8, Name, "PER100_EQUAL", Per100);
550     cmsIT8SetDataDbl(NULL, hIT8, Name, "MEAN_DE", Mean(st));
551     cmsIT8SetDataDbl(NULL, hIT8, Name, "STDEV_DE", Std(st));
552     cmsIT8SetDataDbl(NULL, hIT8, Name, "MIN_DE", st ->Min);
553     cmsIT8SetDataDbl(NULL, hIT8, Name, "MAX_DE", st ->Peak);
554 
555 }
556 
557 
558 static
CreateCGATS(const char * TiffName1,const char * TiffName2)559 void CreateCGATS(const char* TiffName1, const char* TiffName2)
560 {
561     cmsHANDLE hIT8 = cmsIT8Alloc(0);
562     time_t ltime;
563     char Buffer[256];
564 
565     cmsIT8SetSheetType(NULL, hIT8, "TIFFDIFF");
566 
567 
568     sprintf(Buffer, "Differences between %s and %s", TiffName1, TiffName2);
569 
570     cmsIT8SetComment(NULL, hIT8, Buffer);
571 
572     cmsIT8SetPropertyStr(NULL, hIT8, "ORIGINATOR", "TIFFDIFF");
573     time( &ltime );
574     strcpy(Buffer, ctime(&ltime));
575     Buffer[strlen(Buffer)-1] = 0;     // Remove the nasty "\n"
576 
577     cmsIT8SetPropertyStr(NULL, hIT8, "CREATED", Buffer);
578 
579     cmsIT8SetComment(NULL, hIT8, " ");
580 
581     cmsIT8SetPropertyDbl(NULL, hIT8, "NUMBER_OF_FIELDS", 6);
582 
583 
584     cmsIT8SetDataFormat(NULL, hIT8, 0, "SAMPLE_ID");
585     cmsIT8SetDataFormat(NULL, hIT8, 1, "PER100_EQUAL");
586     cmsIT8SetDataFormat(NULL, hIT8, 2, "MEAN_DE");
587     cmsIT8SetDataFormat(NULL, hIT8, 3, "STDEV_DE");
588     cmsIT8SetDataFormat(NULL, hIT8, 4, "MIN_DE");
589     cmsIT8SetDataFormat(NULL, hIT8, 5, "MAX_DE");
590 
591 
592     switch (Channels) {
593 
594     case 1:
595             cmsIT8SetPropertyDbl(NULL, hIT8, "NUMBER_OF_SETS", 3);
596             AddOneCGATSRow(hIT8, "GRAY_PLANE", &ColorantStat[0]);
597             break;
598 
599     case 3:
600             cmsIT8SetPropertyDbl(NULL, hIT8, "NUMBER_OF_SETS", 5);
601             AddOneCGATSRow(hIT8, "R_PLANE", &ColorantStat[0]);
602             AddOneCGATSRow(hIT8, "G_PLANE", &ColorantStat[1]);
603             AddOneCGATSRow(hIT8, "B_PLANE", &ColorantStat[2]);
604             break;
605 
606 
607     case 4:
608             cmsIT8SetPropertyDbl(NULL, hIT8, "NUMBER_OF_SETS", 6);
609             AddOneCGATSRow(hIT8, "C_PLANE", &ColorantStat[0]);
610             AddOneCGATSRow(hIT8, "M_PLANE", &ColorantStat[1]);
611             AddOneCGATSRow(hIT8, "Y_PLANE", &ColorantStat[2]);
612             AddOneCGATSRow(hIT8, "K_PLANE", &ColorantStat[3]);
613             break;
614 
615     default: FatalError("Internal error: Bad ColorSpace");
616 
617     }
618 
619     AddOneCGATSRow(hIT8, "EUCLIDEAN",    &EuclideanStat);
620     AddOneCGATSRow(hIT8, "COLORIMETRIC", &ColorimetricStat);
621 
622     cmsIT8SaveToFile(NULL, hIT8, CGATSout);
623     cmsIT8Free(NULL, hIT8);
624 }
625 
main(int argc,char * argv[])626 int main(int argc, char* argv[])
627 {
628       int i;
629 
630       Tiff1 = Tiff2 = TiffDiff = NULL;
631 
632       InitUtils(NULL, "tiffdiff");
633 
634       HandleSwitches(argc, argv);
635 
636       if ((argc - xoptind) != 2) {
637 
638               Help();
639               }
640 
641       TIFFSetErrorHandler(ConsoleErrorHandler);
642       TIFFSetWarningHandler(ConsoleWarningHandler);
643 
644       Tiff1 = TIFFOpen(argv[xoptind], "r");
645       if (Tiff1 == NULL) FatalError("Unable to open '%s'", argv[xoptind]);
646 
647       Tiff2 = TIFFOpen(argv[xoptind+1], "r");
648       if (Tiff2 == NULL) FatalError("Unable to open '%s'", argv[xoptind+1]);
649 
650       if (TiffDiffFilename) {
651 
652           TiffDiff = TIFFOpen(TiffDiffFilename, "w");
653           if (TiffDiff == NULL) FatalError("Unable to create '%s'", TiffDiffFilename);
654 
655       }
656 
657 
658       AssureShortTagIs(Tiff1, Tiff2, TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG, "Planar Config");
659       AssureShortTagIs(Tiff1, Tiff2, TIFFTAG_BITSPERSAMPLE, 8, "8 bit per sample");
660 
661       EqualLongTag(Tiff1, Tiff2, TIFFTAG_IMAGEWIDTH,  "Image width");
662       EqualLongTag(Tiff1, Tiff2, TIFFTAG_IMAGELENGTH, "Image length");
663 
664       EqualShortTag(Tiff1, Tiff2, TIFFTAG_SAMPLESPERPIXEL, "Samples per pixel");
665 
666 
667       hLab = cmsCreateLab4Profile(NULL);
668 
669       ClearStatistics(&EuclideanStat);
670       for (i=0; i < 4; i++)
671             ClearStatistics(&ColorantStat[i]);
672 
673       if (!CmpImages(Tiff1, Tiff2, TiffDiff))
674                 FatalError("Error comparing images");
675 
676       if (CGATSout) {
677             CreateCGATS(argv[xoptind], argv[xoptind+1]);
678       }
679       else {
680 
681         double  Per100 = 100.0 * ((255.0 - Mean(&EuclideanStat)) / 255.0);
682 
683         printf("Digital counts  %g%% equal. mean %g, min %g, max %g, Std %g\n", Per100, Mean(&EuclideanStat),
684                                                                                 EuclideanStat.Min,
685                                                                                 EuclideanStat.Peak,
686                                                                                 Std(&EuclideanStat));
687 
688         if (ColorimetricStat.n > 0) {
689 
690             Per100 = 100.0 * ((255.0 - Mean(&ColorimetricStat)) / 255.0);
691 
692             printf("dE Colorimetric %g%% equal. mean %g, min %g, max %g, Std %g\n", Per100, Mean(&ColorimetricStat),
693                                                                                     ColorimetricStat.Min,
694                                                                                     ColorimetricStat.Peak,
695                                                                                     Std(&ColorimetricStat));
696         }
697 
698       }
699 
700       if (hLab)     cmsCloseProfile(NULL, hLab);
701       if (Tiff1)    TIFFClose(Tiff1);
702       if (Tiff2)    TIFFClose(Tiff2);
703       if (TiffDiff) TIFFClose(TiffDiff);
704 
705       return 0;
706 }
707 
708 
709