1 #include <cmath>
2 
3 #include "colorspace/gamma.h"
4 #include "gtest/gtest.h"
5 
6 namespace {
7 
test_monotonic(float (* func)(float),float min,float max,unsigned long steps)8 void test_monotonic(float (*func)(float), float min, float max, unsigned long steps)
9 {
10 	zimg::colorspace::EnsureSinglePrecision x87;
11 
12 	float cur = -INFINITY;
13 
14 	for (unsigned long i = 0; i <= steps; ++i) {
15 		float x = min + i * ((max - min) / steps);
16 		float y = func(x);
17 		ASSERT_FALSE(std::isnan(y)) << " x=" << x << " i=" << i;
18 		ASSERT_GE(y, cur) << " x=" << x << " i=" << i;
19 		cur = y;
20 	}
21 }
22 
test_accuracy(float (* f)(float),float (* g)(float),float min,float max,float errthr,float biasthr)23 void test_accuracy(float (*f)(float), float (*g)(float), float min, float max, float errthr, float biasthr)
24 {
25 	zimg::colorspace::EnsureSinglePrecision x87;
26 
27 	const unsigned long STEPS = 1UL << 16;
28 	float err = 0.0f;
29 	float bias = 0.0f;
30 
31 	for (unsigned long i = 0; i <= STEPS; ++i) {
32 		float x = min + i * ((max - min) / STEPS);
33 		float y = f(x);
34 		float x_roundtrip = g(y);
35 
36 		err += std::fabs((x_roundtrip - x) / (x == 0.0f ? FLT_EPSILON : x));
37 		bias += x_roundtrip - x;
38 	}
39 
40 	err /= (STEPS + 1);
41 	bias /= (STEPS + 1);
42 
43 	EXPECT_LT(err, errthr);
44 	EXPECT_LT(std::fabs(bias), biasthr);
45 }
46 
47 } // namespace
48 
49 
TEST(GammaTest,test_rec709)50 TEST(GammaTest, test_rec709)
51 {
52 	EXPECT_EQ(0.0f, zimg::colorspace::rec_709_oetf(0.0f));
53 	EXPECT_EQ(1.0f, zimg::colorspace::rec_709_oetf(1.0f));
54 	EXPECT_EQ(0.0f, zimg::colorspace::rec_709_inverse_oetf(0.0f));
55 	EXPECT_EQ(1.0f, zimg::colorspace::rec_709_inverse_oetf(1.0f));
56 
57 	SCOPED_TRACE("forward");
58 	test_monotonic(zimg::colorspace::rec_709_oetf, 0.0f, 1.0f, 1UL << 16);
59 	SCOPED_TRACE("reverse");
60 	test_monotonic(zimg::colorspace::rec_709_inverse_oetf, 0.0f, 1.0f, 1UL << 16);
61 	SCOPED_TRACE("forward->reverse");
62 	test_accuracy(zimg::colorspace::rec_709_oetf, zimg::colorspace::rec_709_inverse_oetf, 0.0f, 1.0f, 1e-6f, 1e-6f);
63 	SCOPED_TRACE("reverse->forward");
64 	test_accuracy(zimg::colorspace::rec_709_inverse_oetf, zimg::colorspace::rec_709_oetf, 0.0f, 1.0f, 1e-6f, 1e-6f);
65 
66 	SCOPED_TRACE("wtw");
67 	test_monotonic(zimg::colorspace::rec_709_oetf, 1.0f, 2.0f, 1UL << 16);
68 	test_monotonic(zimg::colorspace::rec_709_inverse_oetf, 1.0f, 2.0f, 1UL << 16);
69 
70 	SCOPED_TRACE("btb");
71 	test_monotonic(zimg::colorspace::rec_709_oetf, -1.0f, 0.0f, 1UL << 16);
72 	test_monotonic(zimg::colorspace::rec_709_inverse_oetf, -1.0f, 0.0f, 1UL << 16);
73 }
74 
TEST(GammaTest,test_log100)75 TEST(GammaTest, test_log100)
76 {
77 	EXPECT_EQ(0.0f, zimg::colorspace::log100_oetf(0.01f));
78 	EXPECT_GE(zimg::colorspace::log100_oetf(std::nextafter(0.01f, INFINITY)), 0.0f);
79 	EXPECT_EQ(1.0f, zimg::colorspace::log100_oetf(1.0f));
80 	EXPECT_EQ(0.01f, zimg::colorspace::log100_inverse_oetf(0.0f));
81 	EXPECT_GE(zimg::colorspace::log100_inverse_oetf(std::nextafter(0.0f, INFINITY)), 0.01f);
82 	EXPECT_EQ(1.0f, zimg::colorspace::log100_inverse_oetf(1.0f));
83 
84 	SCOPED_TRACE("forward");
85 	test_monotonic(zimg::colorspace::log100_inverse_oetf, 0.0f, 1.0f, 1UL << 16);
86 	SCOPED_TRACE("reverse");
87 	test_monotonic(zimg::colorspace::log100_oetf, 0.0f, 1.0f, 1UL << 16);
88 	SCOPED_TRACE("forward->reverse");
89 	test_accuracy(zimg::colorspace::log100_oetf, zimg::colorspace::log100_inverse_oetf, 0.01f, 1.0f, 1e-6f, 1e-6f);
90 	SCOPED_TRACE("reverse->forward");
91 	test_accuracy(zimg::colorspace::log100_inverse_oetf, zimg::colorspace::log100_oetf, 0.0f, 1.0f, 1e-6f, 1e-6f);
92 
93 	SCOPED_TRACE("wtw");
94 	test_monotonic(zimg::colorspace::log100_inverse_oetf, 1.0f, 2.0f, 1UL << 16);
95 	test_monotonic(zimg::colorspace::log100_oetf, 1.0f, 2.0f, 1UL << 16);
96 }
97 
TEST(GammaTest,test_log316)98 TEST(GammaTest, test_log316)
99 {
100 	EXPECT_EQ(0.0f, zimg::colorspace::log316_oetf(0.00316227766f));
101 	EXPECT_GE(zimg::colorspace::log316_oetf(std::nextafter(0.00316227766f, INFINITY)), 0.0f);
102 	EXPECT_EQ(1.0f, zimg::colorspace::log316_oetf(1.0f));
103 	EXPECT_EQ(0.00316227766f, zimg::colorspace::log316_inverse_oetf(0.0f));
104 	EXPECT_GE(zimg::colorspace::log316_inverse_oetf(std::nextafterf(0.0f, INFINITY)), 0.00316227766f);
105 	EXPECT_EQ(1.0f, zimg::colorspace::log316_inverse_oetf(1.0f));
106 
107 	SCOPED_TRACE("forward");
108 	test_monotonic(zimg::colorspace::log316_inverse_oetf, 0.0f, 1.0f, 1UL << 16);
109 	SCOPED_TRACE("reverse");
110 	test_monotonic(zimg::colorspace::log316_oetf, 0.0f, 1.0f, 1UL << 16);
111 	SCOPED_TRACE("forward->reverse");
112 	test_accuracy(zimg::colorspace::log316_oetf, zimg::colorspace::log316_inverse_oetf, 0.0316227766f, 1.0f, 1e-6f, 1e-6f);
113 	SCOPED_TRACE("reverse->forward");
114 	test_accuracy(zimg::colorspace::log316_inverse_oetf, zimg::colorspace::log316_oetf, 0.0f, 1.0f, 1e-6f, 1e-6f);
115 
116 	SCOPED_TRACE("wtw");
117 	test_monotonic(zimg::colorspace::log316_inverse_oetf, 1.0f, 2.0f, 1UL << 16);
118 	test_monotonic(zimg::colorspace::log316_oetf, 1.0f, 2.0f, 1UL << 16);
119 }
120 
TEST(GammaTest,test_rec_470m_470bg)121 TEST(GammaTest, test_rec_470m_470bg)
122 {
123 	SCOPED_TRACE("470m");
124 	test_accuracy(zimg::colorspace::rec_470m_oetf, zimg::colorspace::rec_470m_inverse_oetf, 0.0f, 1.0f, 1e-6f, 1e-6f);
125 	test_accuracy(zimg::colorspace::rec_470m_inverse_oetf, zimg::colorspace::rec_470m_oetf, 0.0f, 1.0f, 1e-6f, 1e-6f);
126 	SCOPED_TRACE("470bg");
127 	test_accuracy(zimg::colorspace::rec_470bg_oetf, zimg::colorspace::rec_470bg_inverse_oetf, 0.0f, 1.0f, 1e-6f, 1e-6f);
128 	test_accuracy(zimg::colorspace::rec_470bg_inverse_oetf, zimg::colorspace::rec_470bg_oetf, 0.0f, 1.0f, 1e-6f, 1e-6f);
129 }
130 
TEST(GammaTest,test_smpte_240m)131 TEST(GammaTest, test_smpte_240m)
132 {
133 	EXPECT_EQ(0.0f, zimg::colorspace::smpte_240m_oetf(0.0f));
134 	EXPECT_EQ(1.0f, zimg::colorspace::smpte_240m_oetf(1.0f));
135 	EXPECT_EQ(0.0f, zimg::colorspace::smpte_240m_inverse_oetf(0.0f));
136 	EXPECT_EQ(1.0f, zimg::colorspace::smpte_240m_inverse_oetf(1.0f));
137 
138 	SCOPED_TRACE("forward");
139 	test_monotonic(zimg::colorspace::smpte_240m_oetf, 0.0f, 1.0f, 1UL << 16);
140 	SCOPED_TRACE("reverse");
141 	test_monotonic(zimg::colorspace::smpte_240m_inverse_oetf, 0.0f, 1.0f, 1UL << 16);
142 	SCOPED_TRACE("forward->reverse");
143 	test_accuracy(zimg::colorspace::smpte_240m_oetf, zimg::colorspace::smpte_240m_inverse_oetf, 0.0f, 1.0f, 1e-6f, 1e-6f);
144 	SCOPED_TRACE("reverse->forward");
145 	test_accuracy(zimg::colorspace::smpte_240m_inverse_oetf, zimg::colorspace::smpte_240m_oetf, 0.0f, 1.0f, 1e-6f, 1e-6f);
146 
147 	SCOPED_TRACE("wtw");
148 	test_monotonic(zimg::colorspace::smpte_240m_oetf, 1.0f, 2.0f, 1UL << 16);
149 	test_monotonic(zimg::colorspace::smpte_240m_inverse_oetf, 1.0f, 2.0f, 1UL << 16);
150 
151 	SCOPED_TRACE("btb");
152 	test_monotonic(zimg::colorspace::smpte_240m_oetf, -1.0f, 0.0f, 1UL << 16);
153 	test_monotonic(zimg::colorspace::smpte_240m_inverse_oetf, -1.0f, 0.0f, 1UL << 16);
154 }
155 
TEST(GammaTest,test_rec1886)156 TEST(GammaTest, test_rec1886)
157 {
158 	EXPECT_EQ(0.0f, zimg::colorspace::rec_1886_inverse_eotf(0.0f));
159 	EXPECT_EQ(1.0f, zimg::colorspace::rec_1886_inverse_eotf(1.0f));
160 	EXPECT_EQ(0.0f, zimg::colorspace::rec_1886_eotf(0.0f));
161 	EXPECT_EQ(1.0f, zimg::colorspace::rec_1886_eotf(1.0f));
162 
163 	SCOPED_TRACE("forward");
164 	test_monotonic(zimg::colorspace::rec_1886_inverse_eotf, 0.0f, 1.0f, 1UL << 16);
165 	SCOPED_TRACE("reverse");
166 	test_monotonic(zimg::colorspace::rec_1886_eotf, 0.0f, 1.0f, 1UL << 16);
167 	SCOPED_TRACE("forward->reverse");
168 	test_accuracy(zimg::colorspace::rec_1886_inverse_eotf, zimg::colorspace::rec_1886_eotf, 0.0f, 1.0f, 1e-6f, 1e-6f);
169 	SCOPED_TRACE("reverse->forward");
170 	test_accuracy(zimg::colorspace::rec_1886_eotf, zimg::colorspace::rec_1886_inverse_eotf, 0.0f, 1.0f, 1e-6f, 1e-6f);
171 
172 	SCOPED_TRACE("wtw");
173 	test_monotonic(zimg::colorspace::rec_1886_inverse_eotf, 1.0f, 2.0f, 1UL << 16);
174 	test_monotonic(zimg::colorspace::rec_1886_eotf, 1.0f, 2.0f, 1UL << 16);
175 
176 	SCOPED_TRACE("btb");
177 	test_monotonic(zimg::colorspace::rec_1886_inverse_eotf, -1.0f, 0.0f, 1UL << 16);
178 	test_monotonic(zimg::colorspace::rec_1886_eotf, -1.0f, 0.0f, 1UL << 16);
179 }
180 
TEST(GammaTest,test_srgb)181 TEST(GammaTest, test_srgb)
182 {
183 	EXPECT_EQ(0.0f, zimg::colorspace::srgb_inverse_eotf(0.0f));
184 	EXPECT_EQ(1.0f, zimg::colorspace::srgb_inverse_eotf(1.0f));
185 	EXPECT_EQ(0.0f, zimg::colorspace::srgb_eotf(0.0f));
186 	EXPECT_EQ(1.0f, zimg::colorspace::srgb_eotf(1.0f));
187 
188 	SCOPED_TRACE("forward");
189 	test_monotonic(zimg::colorspace::srgb_inverse_eotf, 0.0f, 1.0f, 1UL << 16);
190 	SCOPED_TRACE("reverse");
191 	test_monotonic(zimg::colorspace::srgb_eotf, 0.0f, 1.0f, 1UL << 16);
192 	SCOPED_TRACE("forward->reverse");
193 	test_accuracy(zimg::colorspace::srgb_inverse_eotf, zimg::colorspace::srgb_eotf, 0.0f, 1.0f, 1e-6f, 1e-6f);
194 	SCOPED_TRACE("reverse->forward");
195 	test_accuracy(zimg::colorspace::srgb_eotf, zimg::colorspace::srgb_inverse_eotf, 0.0f, 1.0f, 1e-6f, 1e-6f);
196 
197 	SCOPED_TRACE("wtw");
198 	test_monotonic(zimg::colorspace::srgb_inverse_eotf, 1.0f, 2.0f, 1UL << 16);
199 	test_monotonic(zimg::colorspace::srgb_eotf, 1.0f, 2.0f, 1UL << 16);
200 
201 	SCOPED_TRACE("btb");
202 	test_monotonic(zimg::colorspace::srgb_inverse_eotf, -1.0f, 0.0f, 1UL << 16);
203 	test_monotonic(zimg::colorspace::srgb_eotf, -1.0f, 0.0f, 1UL << 16);
204 }
205 
TEST(GammaTest,test_xvycc)206 TEST(GammaTest, test_xvycc)
207 {
208 	SCOPED_TRACE("oetf forward");
209 	test_accuracy(zimg::colorspace::xvycc_oetf, zimg::colorspace::rec_709_inverse_oetf, 0.0f, 1.0f, 1e-6f, 1e-6f);
210 	SCOPED_TRACE("oetf reverse");
211 	test_accuracy(zimg::colorspace::xvycc_inverse_oetf, zimg::colorspace::rec_709_oetf, 0.0f, 1.0f, 1e-6f, 1e-6f);
212 	SCOPED_TRACE("eotf forward");
213 	test_accuracy(zimg::colorspace::xvycc_oetf, zimg::colorspace::rec_709_inverse_oetf, 0.0f, 1.0f, 1e-6f, 1e-6f);
214 	SCOPED_TRACE("eotf reverse");
215 	test_accuracy(zimg::colorspace::xvycc_inverse_oetf, zimg::colorspace::rec_709_oetf, 0.0f, 1.0f, 1e-6f, 1e-6f);
216 
217 	SCOPED_TRACE("oetf wtw");
218 	test_monotonic(zimg::colorspace::xvycc_oetf, 1.0f, 2.0f, 1UL << 16);
219 	test_monotonic(zimg::colorspace::xvycc_inverse_oetf, 1.0f, 2.0f, 1UL << 16);
220 	SCOPED_TRACE("eotf wtw");
221 	test_monotonic(zimg::colorspace::xvycc_inverse_eotf, 1.0f, 2.0f, 1UL << 16);
222 	test_monotonic(zimg::colorspace::xvycc_eotf, 1.0f, 2.0f, 1UL << 16);
223 	SCOPED_TRACE("oetf btb");
224 	test_monotonic(zimg::colorspace::xvycc_oetf, -1.0f, 0.0f, 1UL << 16);
225 	test_monotonic(zimg::colorspace::xvycc_inverse_oetf, -1.0f, 0.0f, 1UL << 16);
226 	SCOPED_TRACE("eotf btb");
227 	test_monotonic(zimg::colorspace::xvycc_inverse_eotf, -1.0f, 0.0f, 1UL << 16);
228 	test_monotonic(zimg::colorspace::xvycc_eotf, -1.0f, 1.0f, 1UL << 16);
229 }
230 
TEST(GammaTest,test_st_2084)231 TEST(GammaTest, test_st_2084)
232 {
233 	EXPECT_EQ(0.0f, zimg::colorspace::st_2084_inverse_eotf(0.0f));
234 	EXPECT_EQ(1.0f, zimg::colorspace::st_2084_inverse_eotf(1.0f));
235 	EXPECT_EQ(0.0f, zimg::colorspace::st_2084_eotf(0.0f));
236 	EXPECT_EQ(1.0f, zimg::colorspace::st_2084_eotf(1.0f));
237 
238 	SCOPED_TRACE("forward");
239 	test_monotonic(zimg::colorspace::st_2084_inverse_eotf, 0.0f, 1.0f, 1UL << 16);
240 	SCOPED_TRACE("reverse");
241 	test_monotonic(zimg::colorspace::st_2084_eotf, 0.0f, 1.0f, 1UL << 16);
242 	SCOPED_TRACE("forward->reverse");
243 	test_accuracy(zimg::colorspace::st_2084_inverse_eotf, zimg::colorspace::st_2084_eotf, 0.0f, 1.0f, 1e-4f, 1e-6f);
244 	SCOPED_TRACE("reverse->forward");
245 	test_accuracy(zimg::colorspace::st_2084_eotf, zimg::colorspace::st_2084_inverse_eotf, 0.0f, 1.0f, 1e-4f, 1e-6f);
246 
247 	SCOPED_TRACE("wtw");
248 	test_monotonic(zimg::colorspace::st_2084_inverse_eotf, 1.0f, 2.0f, 1UL << 16);
249 	test_monotonic(zimg::colorspace::st_2084_eotf, 1.0f, 2.0f, 1UL << 16);
250 }
251 
TEST(GammaTest,test_st_2084_oetf)252 TEST(GammaTest, test_st_2084_oetf)
253 {
254 	EXPECT_EQ(0.0f, zimg::colorspace::st_2084_oetf(0.0f));
255 	EXPECT_NEAR(1.0f, zimg::colorspace::st_2084_oetf(1.0f), 1e-6f);
256 	EXPECT_EQ(0.0f, zimg::colorspace::st_2084_inverse_oetf(0.0f));
257 	EXPECT_NEAR(1.0f, zimg::colorspace::st_2084_inverse_oetf(1.0f), 1e-6f);
258 
259 	SCOPED_TRACE("forward");
260 	test_monotonic(zimg::colorspace::st_2084_oetf, 0.0f, 1.0f, 1UL << 16);
261 	SCOPED_TRACE("reverse");
262 	test_monotonic(zimg::colorspace::st_2084_inverse_oetf, 0.0f, 1.0f, 1UL << 16);
263 	SCOPED_TRACE("forward->reverse");
264 	test_accuracy(zimg::colorspace::st_2084_oetf, zimg::colorspace::st_2084_inverse_oetf, 0.0f, 1.0f, 1e-4f, 1e-6f);
265 	SCOPED_TRACE("reverse->forward");
266 	test_accuracy(zimg::colorspace::st_2084_inverse_oetf, zimg::colorspace::st_2084_oetf, 0.0f, 1.0f, 1e-4f, 1e-6f);
267 
268 	SCOPED_TRACE("wtw");
269 	test_monotonic(zimg::colorspace::st_2084_oetf, 1.0f, 2.0f, 1UL << 16);
270 	test_monotonic(zimg::colorspace::st_2084_inverse_oetf, 1.0f, 2.0f, 1UL << 16);
271 }
272 
TEST(GammaTest,test_arib_b67)273 TEST(GammaTest, test_arib_b67)
274 {
275 	EXPECT_EQ(0.0f, zimg::colorspace::arib_b67_oetf(0.0f));
276 	EXPECT_EQ(0.5f, zimg::colorspace::arib_b67_oetf(1.0f / 12.0f));
277 	EXPECT_EQ(1.0f, zimg::colorspace::arib_b67_oetf(1.0f));
278 	EXPECT_EQ(0.0f, zimg::colorspace::arib_b67_inverse_oetf(0.0f));
279 	EXPECT_EQ(1.0f / 12.0f, zimg::colorspace::arib_b67_inverse_oetf(0.5f));
280 	EXPECT_NEAR(1.0f, zimg::colorspace::arib_b67_inverse_oetf(1.0f), 1e-6f);
281 
282 	SCOPED_TRACE("forward");
283 	test_monotonic(zimg::colorspace::arib_b67_oetf, 0.0f, 1.0f, 1UL << 16);
284 	SCOPED_TRACE("reverse");
285 	test_monotonic(zimg::colorspace::arib_b67_inverse_oetf, 0.0f, 1.0f, 1UL << 16);
286 	SCOPED_TRACE("forward->reverse");
287 	test_accuracy(zimg::colorspace::arib_b67_oetf, zimg::colorspace::arib_b67_inverse_oetf, 0.0f, 1.0f, 1e-6f, 1e-6f);
288 	SCOPED_TRACE("reverse->forward");
289 	test_accuracy(zimg::colorspace::arib_b67_inverse_oetf, zimg::colorspace::arib_b67_oetf, 0.0f, 1.0f, 1e-6f, 1e-6f);
290 
291 	SCOPED_TRACE("wtw");
292 	test_monotonic(zimg::colorspace::arib_b67_oetf, 1.0f, 2.0f, 1UL << 16);
293 	test_monotonic(zimg::colorspace::arib_b67_inverse_oetf, 1.0f, 2.0f, 1UL << 16);
294 }
295 
TEST(GammaTest,test_arib_b67_eotf)296 TEST(GammaTest, test_arib_b67_eotf)
297 {
298 	EXPECT_EQ(0.0f, zimg::colorspace::arib_b67_inverse_eotf(0.0f));
299 	EXPECT_EQ(1.0f, zimg::colorspace::arib_b67_inverse_eotf(1.0f));
300 	EXPECT_EQ(0.0f, zimg::colorspace::arib_b67_eotf(0.0f));
301 	EXPECT_NEAR(1.0f, zimg::colorspace::arib_b67_eotf(1.0f), 1e-6f);
302 
303 	SCOPED_TRACE("forward");
304 	test_monotonic(zimg::colorspace::arib_b67_inverse_eotf, 0.0f, 1.0f, 1UL << 16);
305 	SCOPED_TRACE("reverse");
306 	test_monotonic(zimg::colorspace::arib_b67_eotf, 0.0f, 1.0f, 1UL << 16);
307 	SCOPED_TRACE("forward->reverse");
308 	test_accuracy(zimg::colorspace::arib_b67_inverse_eotf, zimg::colorspace::arib_b67_eotf, 0.0f, 1.0f, 1e-6f, 1e-6f);
309 	SCOPED_TRACE("reverse->forward");
310 	test_accuracy(zimg::colorspace::arib_b67_eotf, zimg::colorspace::arib_b67_inverse_eotf, 0.0f, 1.0f, 1e-6f, 1e-6f);
311 
312 	SCOPED_TRACE("wtw");
313 	test_monotonic(zimg::colorspace::arib_b67_inverse_eotf, 1.0f, 2.0f, 1UL << 16);
314 	test_monotonic(zimg::colorspace::arib_b67_eotf, 1.0f, 2.0f, 1UL << 16);
315 }
316