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