1 // Copyright 2008-present Contributors to the OpenImageIO project.
2 // SPDX-License-Identifier: BSD-3-Clause
3 // https://github.com/OpenImageIO/oiio/blob/master/LICENSE.md
4 
5 
6 #include <cmath>
7 #include <cstdio>
8 #include <cstdlib>
9 #include <ctime>
10 #include <iomanip>
11 #include <iostream>
12 #include <iterator>
13 #include <string>
14 #include <vector>
15 
16 #include <OpenImageIO/argparse.h>
17 #include <OpenImageIO/filter.h>
18 #include <OpenImageIO/imagebuf.h>
19 #include <OpenImageIO/imagebufalgo.h>
20 #include <OpenImageIO/imageio.h>
21 
22 #include "oiiotool.h"
23 
24 using namespace OIIO;
25 using namespace OiioTool;
26 using namespace ImageBufAlgo;
27 
28 
29 
30 // function that standardize printing NaN and Inf values on
31 // Windows (where they are in 1.#INF, 1.#NAN format) and all
32 // others platform
33 inline void
safe_double_print(double val)34 safe_double_print(double val)
35 {
36     if (OIIO::isnan(val))
37         std::cout << "nan";
38     else if (OIIO::isinf(val))
39         std::cout << "inf";
40     else
41         std::cout << val;
42     std::cout << '\n';
43 }
44 
45 
46 
47 inline void
print_subimage(ImageRec & img0,int subimage,int miplevel)48 print_subimage(ImageRec& img0, int subimage, int miplevel)
49 {
50     if (img0.subimages() > 1)
51         std::cout << "Subimage " << subimage << ' ';
52     if (img0.miplevels(subimage) > 1)
53         std::cout << " MIP level " << miplevel << ' ';
54     if (img0.subimages() > 1 || img0.miplevels(subimage) > 1)
55         std::cout << ": ";
56     const ImageSpec& spec(*img0.spec(subimage));
57     std::cout << spec.width << " x " << spec.height;
58     if (spec.depth > 1)
59         std::cout << " x " << spec.depth;
60     std::cout << ", " << spec.nchannels << " channel\n";
61 }
62 
63 
64 
65 int
do_action_diff(ImageRec & ir0,ImageRec & ir1,Oiiotool & ot,int perceptual)66 OiioTool::do_action_diff(ImageRec& ir0, ImageRec& ir1, Oiiotool& ot,
67                          int perceptual)
68 {
69     std::cout << "Computing " << (perceptual ? "perceptual " : "")
70               << "diff of \"" << ir0.name() << "\" vs \"" << ir1.name()
71               << "\"\n";
72     ir0.read();
73     ir1.read();
74 
75     int ret = DiffErrOK;
76     for (int subimage = 0; subimage < ir0.subimages(); ++subimage) {
77         if (subimage > 0 && !ot.allsubimages)
78             break;
79         if (subimage >= ir1.subimages())
80             break;
81 
82         for (int m = 0; m < ir0.miplevels(subimage); ++m) {
83             if (m > 0 && !ot.allsubimages)
84                 break;
85             if (m > 0 && ir0.miplevels(subimage) != ir1.miplevels(subimage)) {
86                 std::cout
87                     << "Files do not match in their number of MIPmap levels\n";
88                 ret = DiffErrDifferentSize;
89                 break;
90             }
91 
92             ImageBuf& img0(ir0(subimage, m));
93             ImageBuf& img1(ir1(subimage, m));
94             int npels = img0.spec().width * img0.spec().height
95                         * img0.spec().depth;
96             if (npels == 0)
97                 npels = 1;  // Avoid divide by zero for 0x0 images
98             OIIO_ASSERT(img0.spec().format == TypeDesc::FLOAT);
99 
100             // Compare the two images.
101             //
102             ImageBufAlgo::CompareResults cr;
103             int yee_failures = 0;
104             switch (perceptual) {
105             case 1:
106                 yee_failures = ImageBufAlgo::compare_Yee(img0, img1, cr);
107                 break;
108             default:
109                 ImageBufAlgo::compare(img0, img1, ot.diff_failthresh,
110                                       ot.diff_warnthresh, cr);
111                 break;
112             }
113 
114             if (cr.nfail > (ot.diff_failpercent / 100.0 * npels)
115                 || cr.maxerror > ot.diff_hardfail
116                 || yee_failures > (ot.diff_failpercent / 100.0 * npels)) {
117                 ret = DiffErrFail;
118             } else if (cr.nwarn > (ot.diff_warnpercent / 100.0 * npels)
119                        || cr.maxerror > ot.diff_hardwarn) {
120                 if (ret != DiffErrFail)
121                     ret = DiffErrWarn;
122             }
123 
124             // Print the report
125             //
126             if (ot.verbose || ot.debug || ret != DiffErrOK) {
127                 if (ot.allsubimages)
128                     print_subimage(ir0, subimage, m);
129                 std::cout << "  Mean error = ";
130                 safe_double_print(cr.meanerror);
131                 std::cout << "  RMS error = ";
132                 safe_double_print(cr.rms_error);
133                 std::cout << "  Peak SNR = ";
134                 safe_double_print(cr.PSNR);
135                 std::cout << "  Max error  = " << cr.maxerror;
136                 if (cr.maxerror != 0) {
137                     std::cout << " @ (" << cr.maxx << ", " << cr.maxy;
138                     if (img0.spec().depth > 1)
139                         std::cout << ", " << cr.maxz;
140                     if (cr.maxc < (int)img0.spec().channelnames.size())
141                         std::cout << ", " << img0.spec().channelnames[cr.maxc]
142                                   << ')';
143                     else if (cr.maxc < (int)img1.spec().channelnames.size())
144                         std::cout << ", " << img1.spec().channelnames[cr.maxc]
145                                   << ')';
146                     else
147                         std::cout << ", channel " << cr.maxc << ')';
148                     if (!img0.deep()) {
149                         std::cout << "  values are ";
150                         for (int c = 0; c < img0.spec().nchannels; ++c)
151                             std::cout
152                                 << (c ? ", " : "")
153                                 << img0.getchannel(cr.maxx, cr.maxy, 0, c);
154                         std::cout << " vs ";
155                         for (int c = 0; c < img1.spec().nchannels; ++c)
156                             std::cout
157                                 << (c ? ", " : "")
158                                 << img1.getchannel(cr.maxx, cr.maxy, 0, c);
159                     }
160                 }
161                 std::cout << "\n";
162 
163                 std::streamsize precis = std::cout.precision();
164                 std::cout << "  " << cr.nwarn << " pixels ("
165                           << std::setprecision(3) << (100.0 * cr.nwarn / npels)
166                           << std::setprecision(precis) << "%) over "
167                           << ot.diff_warnthresh << "\n";
168                 std::cout << "  " << cr.nfail << " pixels ("
169                           << std::setprecision(3) << (100.0 * cr.nfail / npels)
170                           << std::setprecision(precis) << "%) over "
171                           << ot.diff_failthresh << "\n";
172                 if (perceptual == 1)
173                     std::cout << "  " << yee_failures << " pixels ("
174                               << std::setprecision(3)
175                               << (100.0 * yee_failures / npels)
176                               << std::setprecision(precis)
177                               << "%) failed the perceptual test\n";
178             }
179         }
180     }
181 
182     if (ot.allsubimages && ir0.subimages() != ir1.subimages()) {
183         std::cout << "Images had differing numbers of subimages ("
184                   << ir0.subimages() << " vs " << ir1.subimages() << ")\n";
185         ret = DiffErrFail;
186     }
187     if (!ot.allsubimages && (ir0.subimages() > 1 || ir1.subimages() > 1)) {
188         std::cout << "Only compared the first subimage (of " << ir0.subimages()
189                   << " and " << ir1.subimages() << ", respectively)\n";
190     }
191 
192     if (ret == DiffErrOK)
193         std::cout << "PASS\n";
194     else if (ret == DiffErrWarn)
195         std::cout << "WARNING\n";
196     else {
197         std::cout << "FAILURE\n";
198         ot.return_value = ret;
199     }
200     return ret;
201 }
202