color_space_unittest.cc 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355
  1. // Copyright 2017 The Chromium Authors. All rights reserved.
  2. // Use of this source code is governed by a BSD-style license that can be
  3. // found in the LICENSE file.
  4. #include <algorithm>
  5. #include <cmath>
  6. #include <tuple>
  7. #include "testing/gtest/include/gtest/gtest.h"
  8. #include "ui/gfx/color_space.h"
  9. #include "ui/gfx/skia_color_space_util.h"
  10. namespace gfx {
  11. namespace {
  12. // Returns the L-infty difference of u and v.
  13. float Diff(const SkV4& u, const SkV4& v) {
  14. return std::max({std::abs(u.x - v.x), std::abs(u.y - v.y),
  15. std::abs(u.z - v.z), std::abs(u.w - v.w)});
  16. }
  17. TEST(ColorSpace, RGBToYUV) {
  18. const float kEpsilon = 1.0e-3f;
  19. const size_t kNumTestRGBs = 3;
  20. SkV4 test_rgbs[kNumTestRGBs] = {
  21. {1.f, 0.f, 0.f, 1.f},
  22. {0.f, 1.f, 0.f, 1.f},
  23. {0.f, 0.f, 1.f, 1.f},
  24. };
  25. const size_t kNumColorSpaces = 4;
  26. gfx::ColorSpace color_spaces[kNumColorSpaces] = {
  27. gfx::ColorSpace::CreateREC601(),
  28. gfx::ColorSpace::CreateREC709(),
  29. gfx::ColorSpace::CreateJpeg(),
  30. gfx::ColorSpace::CreateXYZD50(),
  31. };
  32. SkV4 expected_yuvs[kNumColorSpaces][kNumTestRGBs] = {
  33. // REC601
  34. {
  35. {0.3195f, 0.3518f, 0.9392f, 1.0000f},
  36. {0.5669f, 0.2090f, 0.1322f, 1.0000f},
  37. {0.1607f, 0.9392f, 0.4286f, 1.0000f},
  38. },
  39. // REC709
  40. {
  41. {0.2453f, 0.3994f, 0.9392f, 1.0000f},
  42. {0.6770f, 0.1614f, 0.1011f, 1.0000f},
  43. {0.1248f, 0.9392f, 0.4597f, 1.0000f},
  44. },
  45. // Jpeg
  46. {
  47. {0.2990f, 0.3313f, 1.0000f, 1.0000f},
  48. {0.5870f, 0.1687f, 0.0813f, 1.0000f},
  49. {0.1140f, 1.0000f, 0.4187f, 1.0000f},
  50. },
  51. // XYZD50
  52. {
  53. {1.0000f, 0.0000f, 0.0000f, 1.0000f},
  54. {0.0000f, 1.0000f, 0.0000f, 1.0000f},
  55. {0.0000f, 0.0000f, 1.0000f, 1.0000f},
  56. },
  57. };
  58. for (size_t i = 0; i < kNumColorSpaces; ++i) {
  59. SkM44 transfer = color_spaces[i].GetTransferMatrix(/*bit_depth=*/8);
  60. SkM44 range_adjust = color_spaces[i].GetRangeAdjustMatrix(/*bit_depth=*/8);
  61. SkM44 range_adjust_inv;
  62. EXPECT_TRUE(range_adjust.invert(&range_adjust_inv));
  63. for (size_t j = 0; j < kNumTestRGBs; ++j) {
  64. SkV4 yuv = range_adjust_inv * transfer * test_rgbs[j];
  65. EXPECT_LT(Diff(yuv, expected_yuvs[i][j]), kEpsilon);
  66. }
  67. }
  68. }
  69. TEST(ColorSpace, RangeAdjust) {
  70. const float kEpsilon = 1.0e-3f;
  71. const size_t kNumTestYUVs = 2;
  72. SkV4 test_yuvs[kNumTestYUVs] = {
  73. {1.f, 1.f, 1.f, 1.f},
  74. {0.f, 0.f, 0.f, 1.f},
  75. };
  76. const size_t kNumBitDepths = 3;
  77. int bit_depths[kNumBitDepths] = {8, 10, 12};
  78. const size_t kNumColorSpaces = 3;
  79. ColorSpace color_spaces[kNumColorSpaces] = {
  80. ColorSpace::CreateREC601(),
  81. ColorSpace::CreateJpeg(),
  82. ColorSpace(ColorSpace::PrimaryID::INVALID,
  83. ColorSpace::TransferID::INVALID, ColorSpace::MatrixID::YCOCG,
  84. ColorSpace::RangeID::LIMITED),
  85. };
  86. SkV4 expected_yuvs[kNumColorSpaces][kNumBitDepths][kNumTestYUVs] = {
  87. // REC601
  88. {
  89. // 8bpc
  90. {
  91. {235.f / 255.f, 239.5f / 255.f, 239.5f / 255.f, 1.0000f},
  92. {16.f / 255.f, 15.5f / 255.f, 15.5f / 255.f, 1.0000f},
  93. },
  94. // 10bpc
  95. {
  96. {940.f / 1023.f, 959.5f / 1023.f, 959.5f / 1023.f, 1.0000f},
  97. {64.f / 1023.f, 63.5f / 1023.f, 63.5f / 1023.f, 1.0000f},
  98. },
  99. // 12bpc
  100. {
  101. {3760.f / 4095.f, 3839.5f / 4095.f, 3839.5f / 4095.f, 1.0000f},
  102. {256.f / 4095.f, 255.5f / 4095.f, 255.5f / 4095.f, 1.0000f},
  103. },
  104. },
  105. // Jpeg
  106. {
  107. // 8bpc
  108. {
  109. {1.0000f, 1.0000f, 1.0000f, 1.0000f},
  110. {0.0000f, 0.0000f, 0.0000f, 1.0000f},
  111. },
  112. // 10bpc
  113. {
  114. {1.0000f, 1.0000f, 1.0000f, 1.0000f},
  115. {0.0000f, 0.0000f, 0.0000f, 1.0000f},
  116. },
  117. // 12bpc
  118. {
  119. {1.0000f, 1.0000f, 1.0000f, 1.0000f},
  120. {0.0000f, 0.0000f, 0.0000f, 1.0000f},
  121. },
  122. },
  123. // YCoCg
  124. {
  125. // 8bpc
  126. {
  127. {235.f / 255.f, 235.f / 255.f, 235.f / 255.f, 1.0000f},
  128. {16.f / 255.f, 16.f / 255.f, 16.f / 255.f, 1.0000f},
  129. },
  130. // 10bpc
  131. {
  132. {940.f / 1023.f, 940.f / 1023.f, 940.f / 1023.f, 1.0000f},
  133. {64.f / 1023.f, 64.f / 1023.f, 64.f / 1023.f, 1.0000f},
  134. },
  135. // 12bpc
  136. {
  137. {3760.f / 4095.f, 3760.f / 4095.f, 3760.f / 4095.f, 1.0000f},
  138. {256.f / 4095.f, 256.f / 4095.f, 256.f / 4095.f, 1.0000f},
  139. },
  140. },
  141. };
  142. for (size_t i = 0; i < kNumColorSpaces; ++i) {
  143. for (size_t j = 0; j < kNumBitDepths; ++j) {
  144. SkM44 range_adjust = color_spaces[i].GetRangeAdjustMatrix(bit_depths[j]);
  145. SkM44 range_adjust_inv;
  146. EXPECT_TRUE(range_adjust.invert(&range_adjust_inv));
  147. for (size_t k = 0; k < kNumTestYUVs; ++k) {
  148. SkV4 yuv = range_adjust_inv * test_yuvs[k];
  149. EXPECT_LT(Diff(yuv, expected_yuvs[i][j][k]), kEpsilon);
  150. }
  151. }
  152. }
  153. }
  154. TEST(ColorSpace, Blending) {
  155. ColorSpace display_color_space;
  156. // A linear transfer function being used for HDR should be blended using an
  157. // sRGB-like transfer function.
  158. display_color_space = ColorSpace::CreateSRGBLinear();
  159. EXPECT_FALSE(display_color_space.IsSuitableForBlending());
  160. // If not used for HDR, a linear transfer function should be left unchanged.
  161. display_color_space = ColorSpace::CreateXYZD50();
  162. EXPECT_TRUE(display_color_space.IsSuitableForBlending());
  163. }
  164. TEST(ColorSpace, ConversionToAndFromSkColorSpace) {
  165. skcms_Matrix3x3 primary_matrix = {{
  166. {0.205276f, 0.625671f, 0.060867f},
  167. {0.149185f, 0.063217f, 0.744553f},
  168. {0.609741f, 0.311111f, 0.019470f},
  169. }};
  170. skcms_TransferFunction transfer_fn = {2.1f, 1.f, 0.f, 0.f, 0.f, 0.f, 0.f};
  171. ColorSpace color_spaces[] = {
  172. ColorSpace(ColorSpace::PrimaryID::BT709, ColorSpace::TransferID::SRGB),
  173. ColorSpace(ColorSpace::PrimaryID::ADOBE_RGB,
  174. ColorSpace::TransferID::SRGB),
  175. ColorSpace(ColorSpace::PrimaryID::P3, ColorSpace::TransferID::LINEAR),
  176. ColorSpace(ColorSpace::PrimaryID::BT2020, ColorSpace::TransferID::SRGB),
  177. ColorSpace::CreateCustom(primary_matrix, transfer_fn),
  178. // HDR
  179. ColorSpace::CreateSRGBLinear(),
  180. };
  181. sk_sp<SkColorSpace> sk_color_spaces[] = {
  182. SkColorSpace::MakeSRGB(),
  183. SkColorSpace::MakeRGB(SkNamedTransferFn::kSRGB, SkNamedGamut::kAdobeRGB),
  184. SkColorSpace::MakeRGB(SkNamedTransferFn::kLinear,
  185. SkNamedGamut::kDisplayP3),
  186. SkColorSpace::MakeRGB(SkNamedTransferFn::kSRGB, SkNamedGamut::kRec2020),
  187. SkColorSpace::MakeRGB(transfer_fn, primary_matrix),
  188. // HDR
  189. SkColorSpace::MakeSRGBLinear(),
  190. };
  191. static_assert(std::size(color_spaces) == std::size(sk_color_spaces), "");
  192. // Test that converting from ColorSpace to SkColorSpace is producing an
  193. // equivalent representation.
  194. for (size_t i = 0; i < std::size(color_spaces); ++i) {
  195. EXPECT_TRUE(SkColorSpace::Equals(color_spaces[i].ToSkColorSpace().get(),
  196. sk_color_spaces[i].get()))
  197. << " on iteration i = " << i;
  198. }
  199. // Invariant test: Test that converting a SkColorSpace to a ColorSpace is
  200. // producing an equivalent representation; and then converting the converted
  201. // ColorSpace back to SkColorSpace is also producing an equivalent
  202. // representation.
  203. for (size_t i = 0; i < std::size(color_spaces); ++i) {
  204. const ColorSpace from_sk_color_space(*sk_color_spaces[i],
  205. color_spaces[i].IsHDR());
  206. EXPECT_EQ(color_spaces[i], from_sk_color_space);
  207. EXPECT_TRUE(SkColorSpace::Equals(
  208. sk_color_spaces[i].get(), from_sk_color_space.ToSkColorSpace().get()));
  209. }
  210. }
  211. TEST(ColorSpace, PQAndHLGToSkColorSpace) {
  212. const float kEpsilon = 1.0e-2f;
  213. const auto hlg = ColorSpace::CreateHLG();
  214. const auto pq = ColorSpace::CreateHDR10();
  215. // For each test case, `pq_signal` maps to `pq_nits`.
  216. constexpr size_t kNumCases = 3;
  217. float pq_signal[kNumCases] = {
  218. 0.508078421517399f,
  219. 0.5806888810416109f,
  220. 0.6765848107833876,
  221. };
  222. float pq_nits[kNumCases] = {
  223. 100,
  224. 203,
  225. 500,
  226. };
  227. const float kPQSignalFor203Nits = pq_signal[1];
  228. const float kHLGSignalFor203Nits = 0.75f;
  229. for (size_t i = 0; i < kNumCases; ++i) {
  230. const float sdr_white_level = pq_nits[i];
  231. sk_sp<SkColorSpace> sk_hlg = hlg.ToSkColorSpace(sdr_white_level);
  232. sk_sp<SkColorSpace> sk_pq = pq.ToSkColorSpace(sdr_white_level);
  233. // The PQ signal that maps to `sdr_white_level` nits should map to 1.
  234. skcms_TransferFunction pq_fn = {0};
  235. sk_pq->transferFn(&pq_fn);
  236. EXPECT_NEAR(1.f, skcms_TransferFunction_eval(&pq_fn, pq_signal[i]),
  237. kEpsilon);
  238. // The HLG signal value of 0.75 should always map to the same value that
  239. // the PQ signal for 203 nits maps to.
  240. skcms_TransferFunction hlg_fn = {0};
  241. sk_hlg->transferFn(&hlg_fn);
  242. EXPECT_NEAR(skcms_TransferFunction_eval(&pq_fn, kPQSignalFor203Nits),
  243. skcms_TransferFunction_eval(&hlg_fn, kHLGSignalFor203Nits),
  244. kEpsilon);
  245. }
  246. }
  247. TEST(ColorSpace, MixedInvalid) {
  248. ColorSpace color_space;
  249. color_space = color_space.GetWithMatrixAndRange(ColorSpace::MatrixID::INVALID,
  250. ColorSpace::RangeID::INVALID);
  251. EXPECT_TRUE(!color_space.IsValid());
  252. color_space = color_space.GetWithMatrixAndRange(
  253. ColorSpace::MatrixID::SMPTE170M, ColorSpace::RangeID::LIMITED);
  254. EXPECT_TRUE(!color_space.IsValid());
  255. }
  256. TEST(ColorSpace, MixedSRGBWithRec601) {
  257. const ColorSpace expected_color_space =
  258. ColorSpace(ColorSpace::PrimaryID::BT709, ColorSpace::TransferID::SRGB,
  259. ColorSpace::MatrixID::SMPTE170M, ColorSpace::RangeID::LIMITED);
  260. ColorSpace color_space = ColorSpace::CreateSRGB();
  261. color_space = color_space.GetWithMatrixAndRange(
  262. ColorSpace::MatrixID::SMPTE170M, ColorSpace::RangeID::LIMITED);
  263. EXPECT_TRUE(expected_color_space.IsValid());
  264. EXPECT_EQ(color_space, expected_color_space);
  265. }
  266. TEST(ColorSpace, MixedHDR10WithRec709) {
  267. const ColorSpace expected_color_space =
  268. ColorSpace(ColorSpace::PrimaryID::BT2020, ColorSpace::TransferID::PQ,
  269. ColorSpace::MatrixID::BT709, ColorSpace::RangeID::LIMITED);
  270. ColorSpace color_space = ColorSpace::CreateHDR10();
  271. color_space = color_space.GetWithMatrixAndRange(ColorSpace::MatrixID::BT709,
  272. ColorSpace::RangeID::LIMITED);
  273. EXPECT_TRUE(expected_color_space.IsValid());
  274. EXPECT_EQ(color_space, expected_color_space);
  275. }
  276. TEST(ColorSpace, GetsPrimariesTransferMatrixAndRange) {
  277. ColorSpace color_space(
  278. ColorSpace::PrimaryID::BT709, ColorSpace::TransferID::BT709,
  279. ColorSpace::MatrixID::BT709, ColorSpace::RangeID::LIMITED);
  280. EXPECT_EQ(color_space.GetPrimaryID(), ColorSpace::PrimaryID::BT709);
  281. EXPECT_EQ(color_space.GetTransferID(), ColorSpace::TransferID::BT709);
  282. EXPECT_EQ(color_space.GetMatrixID(), ColorSpace::MatrixID::BT709);
  283. EXPECT_EQ(color_space.GetRangeID(), ColorSpace::RangeID::LIMITED);
  284. }
  285. TEST(ColorSpace, ExpectationsMatchSRGB) {
  286. ColorSpace::PrimaryID primary_ids[] = {
  287. ColorSpace::PrimaryID::BT709,
  288. ColorSpace::PrimaryID::BT470M,
  289. ColorSpace::PrimaryID::BT470BG,
  290. ColorSpace::PrimaryID::SMPTE170M,
  291. ColorSpace::PrimaryID::SMPTE240M,
  292. ColorSpace::PrimaryID::FILM,
  293. ColorSpace::PrimaryID::BT2020,
  294. ColorSpace::PrimaryID::SMPTEST428_1,
  295. ColorSpace::PrimaryID::SMPTEST431_2,
  296. ColorSpace::PrimaryID::P3,
  297. ColorSpace::PrimaryID::XYZ_D50,
  298. ColorSpace::PrimaryID::ADOBE_RGB,
  299. ColorSpace::PrimaryID::APPLE_GENERIC_RGB,
  300. ColorSpace::PrimaryID::WIDE_GAMUT_COLOR_SPIN,
  301. };
  302. // Create a custom color space with the sRGB primary matrix.
  303. ColorSpace srgb = ColorSpace::CreateSRGB();
  304. skcms_Matrix3x3 to_XYZD50;
  305. srgb.GetPrimaryMatrix(&to_XYZD50);
  306. ColorSpace custom_srgb =
  307. ColorSpace::CreateCustom(to_XYZD50, ColorSpace::TransferID::SRGB);
  308. for (auto id : primary_ids) {
  309. ColorSpace color_space(id, ColorSpace::TransferID::SRGB);
  310. // The precomputed results for Contains(sRGB) should match the calculation
  311. // performed on a custom color space with sRGB primaries.
  312. EXPECT_EQ(color_space.Contains(srgb), color_space.Contains(custom_srgb));
  313. }
  314. }
  315. } // namespace
  316. } // namespace gfx