tifdiff.c 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708
  1. //---------------------------------------------------------------------------------
  2. //
  3. // Little Color Management System
  4. // Copyright (c) 1998-2022 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. #include "utils.h"
  27. #include "tiffio.h"
  28. // ------------------------------------------------------------------------
  29. static TIFF *Tiff1, *Tiff2, *TiffDiff;
  30. static const char* TiffDiffFilename;
  31. static const char* CGATSout;
  32. typedef struct {
  33. double n, x, x2;
  34. double Min, Peak;
  35. } STAT, *LPSTAT;
  36. static STAT ColorantStat[4];
  37. static STAT EuclideanStat;
  38. static STAT ColorimetricStat;
  39. static uint16 Channels;
  40. static cmsHPROFILE hLab;
  41. static
  42. void ConsoleWarningHandler(const char* module, const char* fmt, va_list ap)
  43. {
  44. char e[512] = { '\0' };
  45. if (module != NULL)
  46. strcat(strcpy(e, module), ": ");
  47. vsprintf(e+strlen(e), fmt, ap);
  48. strcat(e, ".");
  49. if (Verbose) {
  50. fprintf(stderr, "\nWarning");
  51. fprintf(stderr, " %s\n", e);
  52. fflush(stderr);
  53. }
  54. }
  55. static
  56. void ConsoleErrorHandler(const char* module, const char* fmt, va_list ap)
  57. {
  58. char e[512] = { '\0' };
  59. if (module != NULL)
  60. strcat(strcpy(e, module), ": ");
  61. vsprintf(e+strlen(e), fmt, ap);
  62. strcat(e, ".");
  63. fprintf(stderr, "\nError");
  64. fprintf(stderr, " %s\n", e);
  65. fflush(stderr);
  66. }
  67. static
  68. void Help()
  69. {
  70. fprintf(stderr, "Little CMS TIFF compare utility. v1.1\n\n");
  71. fprintf(stderr, "usage: tiffdiff [flags] input.tif output.tif\n");
  72. fprintf(stderr, "\nflags:\n\n");
  73. fprintf(stderr, "-o<tiff> - Output TIFF file\n");
  74. fprintf(stderr, "-g<CGATS> - Output results in CGATS file\n");
  75. fprintf(stderr, "\n");
  76. fprintf(stderr, "-v - Verbose (show warnings)\n");
  77. fprintf(stderr, "-h - This help\n");
  78. fflush(stderr);
  79. exit(0);
  80. }
  81. // The toggles stuff
  82. static
  83. void HandleSwitches(int argc, char *argv[])
  84. {
  85. int s;
  86. while ((s=xgetopt(argc,argv,"o:O:hHvVg:G:")) != EOF) {
  87. switch (s) {
  88. case 'v':
  89. case 'V':
  90. Verbose = TRUE;
  91. break;
  92. case 'o':
  93. case 'O':
  94. TiffDiffFilename = xoptarg;
  95. break;
  96. case 'H':
  97. case 'h':
  98. Help();
  99. break;
  100. case 'g':
  101. case 'G':
  102. CGATSout = xoptarg;
  103. break;
  104. default:
  105. FatalError("Unknown option - run without args to see valid ones");
  106. }
  107. }
  108. }
  109. static
  110. void ClearStatistics(LPSTAT st)
  111. {
  112. st ->n = st ->x = st->x2 = st->Peak = 0;
  113. st ->Min = 1E10;
  114. }
  115. static
  116. void AddOnePixel(LPSTAT st, double dE)
  117. {
  118. st-> x += dE; st ->x2 += (dE * dE); st->n += 1.0;
  119. if (dE > st ->Peak) st ->Peak = dE;
  120. if (dE < st ->Min) st ->Min= dE;
  121. }
  122. static
  123. double Std(LPSTAT st)
  124. {
  125. return sqrt((st->n * st->x2 - st->x * st->x) / (st->n*(st->n-1)));
  126. }
  127. static
  128. double Mean(LPSTAT st)
  129. {
  130. return st ->x/st ->n;
  131. }
  132. // Build up the pixeltype descriptor
  133. static
  134. cmsUInt32Number GetInputPixelType(TIFF *Bank)
  135. {
  136. uint16 Photometric, bps, spp, extra, PlanarConfig, *info;
  137. uint16 Compression, reverse = 0;
  138. int ColorChannels, IsPlanar = 0, pt = 0;
  139. TIFFGetField(Bank, TIFFTAG_PHOTOMETRIC, &Photometric);
  140. TIFFGetFieldDefaulted(Bank, TIFFTAG_BITSPERSAMPLE, &bps);
  141. if (bps == 1)
  142. FatalError("Sorry, bilevel TIFFs has nothig to do with ICC profiles");
  143. if (bps != 8 && bps != 16)
  144. FatalError("Sorry, 8 or 16 bits per sample only");
  145. TIFFGetFieldDefaulted(Bank, TIFFTAG_SAMPLESPERPIXEL, &spp);
  146. TIFFGetFieldDefaulted(Bank, TIFFTAG_PLANARCONFIG, &PlanarConfig);
  147. switch (PlanarConfig)
  148. {
  149. case PLANARCONFIG_CONTIG: IsPlanar = 0; break;
  150. case PLANARCONFIG_SEPARATE: FatalError("Planar TIFF are not supported");
  151. default:
  152. FatalError("Unsupported planar configuration (=%d) ", (int) PlanarConfig);
  153. }
  154. // If Samples per pixel == 1, PlanarConfiguration is irrelevant and need
  155. // not to be included.
  156. if (spp == 1) IsPlanar = 0;
  157. // Any alpha?
  158. TIFFGetFieldDefaulted(Bank, TIFFTAG_EXTRASAMPLES, &extra, &info);
  159. ColorChannels = spp - extra;
  160. switch (Photometric) {
  161. case PHOTOMETRIC_MINISWHITE:
  162. reverse = 1;
  163. case PHOTOMETRIC_MINISBLACK:
  164. pt = PT_GRAY;
  165. break;
  166. case PHOTOMETRIC_RGB:
  167. pt = PT_RGB;
  168. break;
  169. case PHOTOMETRIC_PALETTE:
  170. FatalError("Sorry, palette images not supported (at least on this version)");
  171. case PHOTOMETRIC_SEPARATED:
  172. pt = PixelTypeFromChanCount(ColorChannels);
  173. break;
  174. case PHOTOMETRIC_YCBCR:
  175. TIFFGetField(Bank, TIFFTAG_COMPRESSION, &Compression);
  176. {
  177. uint16 subx, suby;
  178. pt = PT_YCbCr;
  179. TIFFGetFieldDefaulted(Bank, TIFFTAG_YCBCRSUBSAMPLING, &subx, &suby);
  180. if (subx != 1 || suby != 1)
  181. FatalError("Sorry, subsampled images not supported");
  182. }
  183. break;
  184. case 9:
  185. case PHOTOMETRIC_CIELAB:
  186. pt = PT_Lab;
  187. break;
  188. case PHOTOMETRIC_LOGLUV: /* CIE Log2(L) (u',v') */
  189. TIFFSetField(Bank, TIFFTAG_SGILOGDATAFMT, SGILOGDATAFMT_16BIT);
  190. pt = PT_YUV; // *ICCSpace = icSigLuvData;
  191. bps = 16; // 16 bits forced by LibTiff
  192. break;
  193. default:
  194. FatalError("Unsupported TIFF color space (Photometric %d)", Photometric);
  195. }
  196. // Convert bits per sample to bytes per sample
  197. bps >>= 3;
  198. return (COLORSPACE_SH(pt)|PLANAR_SH(IsPlanar)|EXTRA_SH(extra)|CHANNELS_SH(ColorChannels)|BYTES_SH(bps)|FLAVOR_SH(reverse));
  199. }
  200. static
  201. cmsUInt32Number OpenEmbedded(TIFF* tiff, cmsHPROFILE* PtrProfile, cmsHTRANSFORM* PtrXform)
  202. {
  203. cmsUInt32Number EmbedLen, dwFormat = 0;
  204. cmsUInt8Number* EmbedBuffer;
  205. *PtrProfile = NULL;
  206. *PtrXform = NULL;
  207. if (TIFFGetField(tiff, TIFFTAG_ICCPROFILE, &EmbedLen, &EmbedBuffer)) {
  208. *PtrProfile = cmsOpenProfileFromMem(EmbedBuffer, EmbedLen);
  209. if (Verbose) {
  210. fprintf(stdout, "Embedded profile found:\n");
  211. PrintProfileInformation(*PtrProfile);
  212. }
  213. dwFormat = GetInputPixelType(tiff);
  214. *PtrXform = cmsCreateTransform(*PtrProfile, dwFormat,
  215. hLab, TYPE_Lab_DBL, INTENT_RELATIVE_COLORIMETRIC, 0);
  216. }
  217. return dwFormat;
  218. }
  219. static
  220. size_t PixelSize(cmsUInt32Number dwFormat)
  221. {
  222. return T_BYTES(dwFormat) * (T_CHANNELS(dwFormat) + T_EXTRA(dwFormat));
  223. }
  224. static
  225. int CmpImages(TIFF* tiff1, TIFF* tiff2, TIFF* diff)
  226. {
  227. cmsUInt8Number* buf1, *buf2, *buf3=NULL;
  228. int row, cols, imagewidth = 0, imagelength = 0;
  229. uint16 Photometric;
  230. double dE = 0;
  231. double dR, dG, dB, dC, dM, dY, dK;
  232. int rc = 0;
  233. cmsHPROFILE hProfile1 = 0, hProfile2 = 0;
  234. cmsHTRANSFORM xform1 = 0, xform2 = 0;
  235. cmsUInt32Number dwFormat1, dwFormat2;
  236. TIFFGetField(tiff1, TIFFTAG_PHOTOMETRIC, &Photometric);
  237. TIFFGetField(tiff1, TIFFTAG_IMAGEWIDTH, &imagewidth);
  238. TIFFGetField(tiff1, TIFFTAG_IMAGELENGTH, &imagelength);
  239. TIFFGetField(tiff1, TIFFTAG_SAMPLESPERPIXEL, &Channels);
  240. dwFormat1 = OpenEmbedded(tiff1, &hProfile1, &xform1);
  241. dwFormat2 = OpenEmbedded(tiff2, &hProfile2, &xform2);
  242. buf1 = (cmsUInt8Number*)_TIFFmalloc(TIFFScanlineSize(tiff1));
  243. buf2 = (cmsUInt8Number*)_TIFFmalloc(TIFFScanlineSize(tiff2));
  244. if (diff) {
  245. TIFFSetField(diff, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_MINISBLACK);
  246. TIFFSetField(diff, TIFFTAG_COMPRESSION, COMPRESSION_NONE);
  247. TIFFSetField(diff, TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG);
  248. TIFFSetField(diff, TIFFTAG_IMAGEWIDTH, imagewidth);
  249. TIFFSetField(diff, TIFFTAG_IMAGELENGTH, imagelength);
  250. TIFFSetField(diff, TIFFTAG_SAMPLESPERPIXEL, 1);
  251. TIFFSetField(diff, TIFFTAG_BITSPERSAMPLE, 8);
  252. buf3 = (cmsUInt8Number*)_TIFFmalloc(TIFFScanlineSize(diff));
  253. }
  254. for (row = 0; row < imagelength; row++) {
  255. if (TIFFReadScanline(tiff1, buf1, row, 0) < 0) goto Error;
  256. if (TIFFReadScanline(tiff2, buf2, row, 0) < 0) goto Error;
  257. for (cols = 0; cols < imagewidth; cols++) {
  258. switch (Photometric) {
  259. case PHOTOMETRIC_MINISWHITE:
  260. case PHOTOMETRIC_MINISBLACK:
  261. dE = fabs(buf2[cols] - buf1[cols]);
  262. AddOnePixel(&ColorantStat[0], dE);
  263. AddOnePixel(&EuclideanStat, dE);
  264. break;
  265. case PHOTOMETRIC_RGB:
  266. {
  267. int index = 3 * cols;
  268. dR = fabs(buf2[index+0] - buf1[index+0]);
  269. dG = fabs(buf2[index+1] - buf1[index+1]);
  270. dB = fabs(buf2[index+2] - buf1[index+2]);
  271. dE = sqrt(dR * dR + dG * dG + dB * dB) / sqrt(3.);
  272. }
  273. AddOnePixel(&ColorantStat[0], dR);
  274. AddOnePixel(&ColorantStat[1], dG);
  275. AddOnePixel(&ColorantStat[2], dB);
  276. AddOnePixel(&EuclideanStat, dE);
  277. break;
  278. case PHOTOMETRIC_SEPARATED:
  279. {
  280. int index = 4 * cols;
  281. dC = fabs(buf2[index+0] - buf1[index+0]);
  282. dM = fabs(buf2[index+1] - buf1[index+1]);
  283. dY = fabs(buf2[index+2] - buf1[index+2]);
  284. dK = fabs(buf2[index+3] - buf1[index+3]);
  285. dE = sqrt(dC * dC + dM * dM + dY * dY + dK * dK) / 2.;
  286. }
  287. AddOnePixel(&ColorantStat[0], dC);
  288. AddOnePixel(&ColorantStat[1], dM);
  289. AddOnePixel(&ColorantStat[2], dY);
  290. AddOnePixel(&ColorantStat[3], dK);
  291. AddOnePixel(&EuclideanStat, dE);
  292. break;
  293. default:
  294. FatalError("Unsupported channels: %d", Channels);
  295. }
  296. if (xform1 && xform2) {
  297. cmsCIELab Lab1, Lab2;
  298. size_t index1 = cols * PixelSize(dwFormat1);
  299. size_t index2 = cols * PixelSize(dwFormat2);
  300. cmsDoTransform(xform1, &buf1[index1], &Lab1, 1);
  301. cmsDoTransform(xform2, &buf2[index2], &Lab2, 1);
  302. dE = cmsDeltaE(&Lab1, &Lab2);
  303. AddOnePixel(&ColorimetricStat, dE);
  304. }
  305. if (diff) {
  306. buf3[cols] = (cmsUInt8Number) floor(dE + 0.5);
  307. }
  308. }
  309. if (diff) {
  310. if (TIFFWriteScanline(diff, buf3, row, 0) < 0) goto Error;
  311. }
  312. }
  313. rc = 1;
  314. Error:
  315. if (hProfile1) cmsCloseProfile(hProfile1);
  316. if (hProfile2) cmsCloseProfile(hProfile2);
  317. if (xform1) cmsDeleteTransform(xform1);
  318. if (xform2) cmsDeleteTransform(xform2);
  319. _TIFFfree(buf1); _TIFFfree(buf2);
  320. if (diff) {
  321. TIFFWriteDirectory(diff);
  322. if (buf3 != NULL) _TIFFfree(buf3);
  323. }
  324. return rc;
  325. }
  326. static
  327. void AssureShortTagIs(TIFF* tif1, TIFF* tiff2, int tag, int Val, const char* Error)
  328. {
  329. uint16 v1;
  330. if (!TIFFGetField(tif1, tag, &v1)) goto Err;
  331. if (v1 != Val) goto Err;
  332. if (!TIFFGetField(tiff2, tag, &v1)) goto Err;
  333. if (v1 != Val) goto Err;
  334. return;
  335. Err:
  336. FatalError("%s is not proper", Error);
  337. }
  338. static
  339. int CmpShortTag(TIFF* tif1, TIFF* tif2, int tag)
  340. {
  341. uint16 v1, v2;
  342. if (!TIFFGetField(tif1, tag, &v1)) return 0;
  343. if (!TIFFGetField(tif2, tag, &v2)) return 0;
  344. return v1 == v2;
  345. }
  346. static
  347. int CmpLongTag(TIFF* tif1, TIFF* tif2, int tag)
  348. {
  349. uint32 v1, v2;
  350. if (!TIFFGetField(tif1, tag, &v1)) return 0;
  351. if (!TIFFGetField(tif2, tag, &v2)) return 0;
  352. return v1 == v2;
  353. }
  354. static
  355. void EqualShortTag(TIFF* tif1, TIFF* tif2, int tag, const char* Error)
  356. {
  357. if (!CmpShortTag(tif1, tif2, tag))
  358. FatalError("%s is different", Error);
  359. }
  360. static
  361. void EqualLongTag(TIFF* tif1, TIFF* tif2, int tag, const char* Error)
  362. {
  363. if (!CmpLongTag(tif1, tif2, tag))
  364. FatalError("%s is different", Error);
  365. }
  366. static
  367. void AddOneCGATSRow(cmsHANDLE hIT8, char *Name, LPSTAT st)
  368. {
  369. double Per100 = 100.0 * ((255.0 - Mean(st)) / 255.0);
  370. cmsIT8SetData(hIT8, Name, "SAMPLE_ID", Name);
  371. cmsIT8SetDataDbl(hIT8, Name, "PER100_EQUAL", Per100);
  372. cmsIT8SetDataDbl(hIT8, Name, "MEAN_DE", Mean(st));
  373. cmsIT8SetDataDbl(hIT8, Name, "STDEV_DE", Std(st));
  374. cmsIT8SetDataDbl(hIT8, Name, "MIN_DE", st ->Min);
  375. cmsIT8SetDataDbl(hIT8, Name, "MAX_DE", st ->Peak);
  376. }
  377. static
  378. void CreateCGATS(const char* TiffName1, const char* TiffName2)
  379. {
  380. cmsHANDLE hIT8 = cmsIT8Alloc(0);
  381. time_t ltime;
  382. char Buffer[256];
  383. cmsIT8SetSheetType(hIT8, "TIFFDIFF");
  384. sprintf(Buffer, "Differences between %s and %s", TiffName1, TiffName2);
  385. cmsIT8SetComment(hIT8, Buffer);
  386. cmsIT8SetPropertyStr(hIT8, "ORIGINATOR", "TIFFDIFF");
  387. time( &ltime );
  388. strcpy(Buffer, ctime(&ltime));
  389. Buffer[strlen(Buffer)-1] = 0; // Remove the nasty "\n"
  390. cmsIT8SetPropertyStr(hIT8, "CREATED", Buffer);
  391. cmsIT8SetComment(hIT8, " ");
  392. cmsIT8SetPropertyDbl(hIT8, "NUMBER_OF_FIELDS", 6);
  393. cmsIT8SetDataFormat(hIT8, 0, "SAMPLE_ID");
  394. cmsIT8SetDataFormat(hIT8, 1, "PER100_EQUAL");
  395. cmsIT8SetDataFormat(hIT8, 2, "MEAN_DE");
  396. cmsIT8SetDataFormat(hIT8, 3, "STDEV_DE");
  397. cmsIT8SetDataFormat(hIT8, 4, "MIN_DE");
  398. cmsIT8SetDataFormat(hIT8, 5, "MAX_DE");
  399. switch (Channels) {
  400. case 1:
  401. cmsIT8SetPropertyDbl(hIT8, "NUMBER_OF_SETS", 3);
  402. AddOneCGATSRow(hIT8, "GRAY_PLANE", &ColorantStat[0]);
  403. break;
  404. case 3:
  405. cmsIT8SetPropertyDbl(hIT8, "NUMBER_OF_SETS", 5);
  406. AddOneCGATSRow(hIT8, "R_PLANE", &ColorantStat[0]);
  407. AddOneCGATSRow(hIT8, "G_PLANE", &ColorantStat[1]);
  408. AddOneCGATSRow(hIT8, "B_PLANE", &ColorantStat[2]);
  409. break;
  410. case 4:
  411. cmsIT8SetPropertyDbl(hIT8, "NUMBER_OF_SETS", 6);
  412. AddOneCGATSRow(hIT8, "C_PLANE", &ColorantStat[0]);
  413. AddOneCGATSRow(hIT8, "M_PLANE", &ColorantStat[1]);
  414. AddOneCGATSRow(hIT8, "Y_PLANE", &ColorantStat[2]);
  415. AddOneCGATSRow(hIT8, "K_PLANE", &ColorantStat[3]);
  416. break;
  417. default: FatalError("Internal error: Bad ColorSpace");
  418. }
  419. AddOneCGATSRow(hIT8, "EUCLIDEAN", &EuclideanStat);
  420. AddOneCGATSRow(hIT8, "COLORIMETRIC", &ColorimetricStat);
  421. cmsIT8SaveToFile(hIT8, CGATSout);
  422. cmsIT8Free(hIT8);
  423. }
  424. int main(int argc, char* argv[])
  425. {
  426. int i;
  427. Tiff1 = Tiff2 = TiffDiff = NULL;
  428. InitUtils("tiffdiff");
  429. HandleSwitches(argc, argv);
  430. if ((argc - xoptind) != 2) {
  431. Help();
  432. }
  433. TIFFSetErrorHandler(ConsoleErrorHandler);
  434. TIFFSetWarningHandler(ConsoleWarningHandler);
  435. Tiff1 = TIFFOpen(argv[xoptind], "r");
  436. if (Tiff1 == NULL) FatalError("Unable to open '%s'", argv[xoptind]);
  437. Tiff2 = TIFFOpen(argv[xoptind+1], "r");
  438. if (Tiff2 == NULL) FatalError("Unable to open '%s'", argv[xoptind+1]);
  439. if (TiffDiffFilename) {
  440. TiffDiff = TIFFOpen(TiffDiffFilename, "w");
  441. if (TiffDiff == NULL) FatalError("Unable to create '%s'", TiffDiffFilename);
  442. }
  443. AssureShortTagIs(Tiff1, Tiff2, TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG, "Planar Config");
  444. AssureShortTagIs(Tiff1, Tiff2, TIFFTAG_BITSPERSAMPLE, 8, "8 bit per sample");
  445. EqualLongTag(Tiff1, Tiff2, TIFFTAG_IMAGEWIDTH, "Image width");
  446. EqualLongTag(Tiff1, Tiff2, TIFFTAG_IMAGELENGTH, "Image length");
  447. EqualShortTag(Tiff1, Tiff2, TIFFTAG_SAMPLESPERPIXEL, "Samples per pixel");
  448. hLab = cmsCreateLab4Profile(NULL);
  449. ClearStatistics(&EuclideanStat);
  450. for (i=0; i < 4; i++)
  451. ClearStatistics(&ColorantStat[i]);
  452. if (!CmpImages(Tiff1, Tiff2, TiffDiff))
  453. FatalError("Error comparing images");
  454. if (CGATSout) {
  455. CreateCGATS(argv[xoptind], argv[xoptind+1]);
  456. }
  457. else {
  458. double Per100 = 100.0 * ((255.0 - Mean(&EuclideanStat)) / 255.0);
  459. printf("Digital counts %g%% equal. mean %g, min %g, max %g, Std %g\n", Per100, Mean(&EuclideanStat),
  460. EuclideanStat.Min,
  461. EuclideanStat.Peak,
  462. Std(&EuclideanStat));
  463. if (ColorimetricStat.n > 0) {
  464. Per100 = 100.0 * ((255.0 - Mean(&ColorimetricStat)) / 255.0);
  465. printf("dE Colorimetric %g%% equal. mean %g, min %g, max %g, Std %g\n", Per100, Mean(&ColorimetricStat),
  466. ColorimetricStat.Min,
  467. ColorimetricStat.Peak,
  468. Std(&ColorimetricStat));
  469. }
  470. }
  471. if (hLab) cmsCloseProfile(hLab);
  472. if (Tiff1) TIFFClose(Tiff1);
  473. if (Tiff2) TIFFClose(Tiff2);
  474. if (TiffDiff) TIFFClose(TiffDiff);
  475. return 0;
  476. }