display_util.cc 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321
  1. // Copyright 2014 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 "ui/display/util/display_util.h"
  5. #include <stddef.h>
  6. #include "base/command_line.h"
  7. #include "base/containers/contains.h"
  8. #include "base/logging.h"
  9. #include "base/metrics/histogram_functions.h"
  10. #include "base/no_destructor.h"
  11. #include "base/notreached.h"
  12. #include "ui/display/types/display_snapshot.h"
  13. #include "ui/display/util/edid_parser.h"
  14. #include "ui/gfx/icc_profile.h"
  15. namespace display {
  16. namespace {
  17. base::flat_set<int64_t>* internal_display_ids() {
  18. static base::NoDestructor<base::flat_set<int64_t>> display_ids;
  19. return display_ids.get();
  20. };
  21. // A list of bogus sizes in mm that should be ignored.
  22. // See crbug.com/136533. The first element maintains the minimum
  23. // size required to be valid size.
  24. const int kInvalidDisplaySizeList[][2] = {
  25. {40, 30},
  26. {50, 40},
  27. {160, 90},
  28. {160, 100},
  29. };
  30. // Used in the GetColorSpaceFromEdid function to collect data on whether the
  31. // color space extracted from an EDID blob passed the sanity checks.
  32. void EmitEdidColorSpaceChecksOutcomeUma(EdidColorSpaceChecksOutcome outcome) {
  33. base::UmaHistogramEnumeration("DrmUtil.GetColorSpaceFromEdid.ChecksOutcome",
  34. outcome);
  35. }
  36. // Returns true if each and all matrix values are within |epsilon| distance.
  37. bool NearlyEqual(const skcms_Matrix3x3& lhs,
  38. const skcms_Matrix3x3& rhs,
  39. float epsilon) {
  40. for (int r = 0; r < 3; r++) {
  41. for (int c = 0; c < 3; c++) {
  42. if (std::abs(lhs.vals[r][c] - rhs.vals[r][c]) > epsilon)
  43. return false;
  44. }
  45. }
  46. return true;
  47. }
  48. } // namespace
  49. bool IsDisplaySizeValid(const gfx::Size& physical_size) {
  50. // Ignore if the reported display is smaller than minimum size.
  51. if (physical_size.width() <= kInvalidDisplaySizeList[0][0] ||
  52. physical_size.height() <= kInvalidDisplaySizeList[0][1]) {
  53. VLOG(1) << "Smaller than minimum display size";
  54. return false;
  55. }
  56. for (size_t i = 1; i < std::size(kInvalidDisplaySizeList); ++i) {
  57. const gfx::Size size(kInvalidDisplaySizeList[i][0],
  58. kInvalidDisplaySizeList[i][1]);
  59. if (physical_size == size) {
  60. VLOG(1) << "Invalid display size detected:" << size.ToString();
  61. return false;
  62. }
  63. }
  64. return true;
  65. }
  66. int64_t GenerateDisplayID(uint16_t manufacturer_id,
  67. uint32_t product_code_hash,
  68. uint8_t output_index) {
  69. return ((static_cast<int64_t>(manufacturer_id) << 40) |
  70. (static_cast<int64_t>(product_code_hash) << 8) | output_index);
  71. }
  72. gfx::ColorSpace GetColorSpaceFromEdid(const display::EdidParser& edid_parser) {
  73. const SkColorSpacePrimaries primaries = edid_parser.primaries();
  74. // Sanity check: primaries should verify By <= Ry <= Gy, Bx <= Rx and Gx <=
  75. // Rx, to guarantee that the R, G and B colors are each in the correct region.
  76. if (!(primaries.fBX <= primaries.fRX && primaries.fGX <= primaries.fRX &&
  77. primaries.fBY <= primaries.fRY && primaries.fRY <= primaries.fGY)) {
  78. EmitEdidColorSpaceChecksOutcomeUma(
  79. EdidColorSpaceChecksOutcome::kErrorBadCoordinates);
  80. return gfx::ColorSpace();
  81. }
  82. // Sanity check: the area spawned by the primaries' triangle is too small,
  83. // i.e. less than half the surface of the triangle spawned by sRGB/BT.709.
  84. constexpr double kBT709PrimariesArea = 0.0954;
  85. const float primaries_area_twice =
  86. (primaries.fRX * primaries.fGY) + (primaries.fBX * primaries.fRY) +
  87. (primaries.fGX * primaries.fBY) - (primaries.fBX * primaries.fGY) -
  88. (primaries.fGX * primaries.fRY) - (primaries.fRX * primaries.fBY);
  89. if (primaries_area_twice < kBT709PrimariesArea) {
  90. EmitEdidColorSpaceChecksOutcomeUma(
  91. EdidColorSpaceChecksOutcome::kErrorPrimariesAreaTooSmall);
  92. return gfx::ColorSpace();
  93. }
  94. // Sanity check: https://crbug.com/809909, the blue primary coordinates should
  95. // not be too far left/upwards of the expected location (namely [0.15, 0.06]
  96. // for sRGB/ BT.709/ Adobe RGB/ DCI-P3, and [0.131, 0.046] for BT.2020).
  97. constexpr float kExpectedBluePrimaryX = 0.15f;
  98. constexpr float kBluePrimaryXDelta = 0.02f;
  99. constexpr float kExpectedBluePrimaryY = 0.06f;
  100. constexpr float kBluePrimaryYDelta = 0.031f;
  101. const bool is_blue_primary_broken =
  102. (std::abs(primaries.fBX - kExpectedBluePrimaryX) > kBluePrimaryXDelta) ||
  103. (std::abs(primaries.fBY - kExpectedBluePrimaryY) > kBluePrimaryYDelta);
  104. if (is_blue_primary_broken) {
  105. EmitEdidColorSpaceChecksOutcomeUma(
  106. EdidColorSpaceChecksOutcome::kErrorBluePrimaryIsBroken);
  107. return gfx::ColorSpace();
  108. }
  109. skcms_Matrix3x3 primaries_matrix;
  110. if (!primaries.toXYZD50(&primaries_matrix)) {
  111. EmitEdidColorSpaceChecksOutcomeUma(
  112. EdidColorSpaceChecksOutcome::kErrorCannotExtractToXYZD50);
  113. return gfx::ColorSpace();
  114. }
  115. // Snap the primaries to those of BT.709/sRGB for performance purposes, see
  116. // crbug.com/1073467. kPrimariesTolerance is an educated guess from various
  117. // ChromeOS panels observations.
  118. auto color_space_primaries = gfx::ColorSpace::PrimaryID::INVALID;
  119. constexpr float kPrimariesTolerance = 0.025;
  120. if (NearlyEqual(primaries_matrix, SkNamedGamut::kSRGB, kPrimariesTolerance))
  121. color_space_primaries = gfx::ColorSpace::PrimaryID::BT709;
  122. const float gamma = edid_parser.gamma();
  123. if (gamma < 1.0f) {
  124. EmitEdidColorSpaceChecksOutcomeUma(
  125. EdidColorSpaceChecksOutcome::kErrorBadGamma);
  126. return gfx::ColorSpace();
  127. }
  128. EmitEdidColorSpaceChecksOutcomeUma(EdidColorSpaceChecksOutcome::kSuccess);
  129. auto transfer_id = gfx::ColorSpace::TransferID::INVALID;
  130. if (base::Contains(edid_parser.supported_color_primary_ids(),
  131. gfx::ColorSpace::PrimaryID::BT2020)) {
  132. if (base::Contains(edid_parser.supported_color_transfer_ids(),
  133. gfx::ColorSpace::TransferID::PQ)) {
  134. transfer_id = gfx::ColorSpace::TransferID::PQ;
  135. } else if (base::Contains(edid_parser.supported_color_transfer_ids(),
  136. gfx::ColorSpace::TransferID::HLG)) {
  137. transfer_id = gfx::ColorSpace::TransferID::HLG;
  138. }
  139. } else if (gamma == 2.2f) {
  140. transfer_id = gfx::ColorSpace::TransferID::GAMMA22;
  141. } else if (gamma == 2.4f) {
  142. transfer_id = gfx::ColorSpace::TransferID::GAMMA24;
  143. }
  144. // Prefer to return a name-based ColorSpace to ease subsequent calculations.
  145. if (transfer_id != gfx::ColorSpace::TransferID::INVALID) {
  146. if (color_space_primaries != gfx::ColorSpace::PrimaryID::INVALID)
  147. return gfx::ColorSpace(color_space_primaries, transfer_id);
  148. return gfx::ColorSpace::CreateCustom(primaries_matrix, transfer_id);
  149. }
  150. skcms_TransferFunction transfer = {gamma, 1.f, 0.f, 0.f, 0.f, 0.f, 0.f};
  151. if (color_space_primaries == gfx::ColorSpace::PrimaryID::INVALID)
  152. return gfx::ColorSpace::CreateCustom(primaries_matrix, transfer);
  153. return gfx::ColorSpace(
  154. color_space_primaries, gfx::ColorSpace::TransferID::CUSTOM,
  155. gfx::ColorSpace::MatrixID::RGB, gfx::ColorSpace::RangeID::FULL,
  156. /*custom_primary_matrix=*/nullptr, &transfer);
  157. }
  158. bool CompareDisplayIds(int64_t id1, int64_t id2) {
  159. if (id1 == id2)
  160. return false;
  161. // Output index is stored in the first 8 bits. See GetDisplayIdFromEDID
  162. // in edid_parser.cc.
  163. int index_1 = id1 & 0xFF;
  164. int index_2 = id2 & 0xFF;
  165. DCHECK_NE(index_1, index_2) << id1 << " and " << id2;
  166. bool first_is_internal = IsInternalDisplayId(id1);
  167. bool second_is_internal = IsInternalDisplayId(id2);
  168. if (first_is_internal && !second_is_internal)
  169. return true;
  170. if (!first_is_internal && second_is_internal)
  171. return false;
  172. return index_1 < index_2;
  173. }
  174. bool IsInternalDisplayId(int64_t display_id) {
  175. return base::Contains(*internal_display_ids(), display_id);
  176. }
  177. const base::flat_set<int64_t>& GetInternalDisplayIds() {
  178. return *internal_display_ids();
  179. }
  180. // static
  181. bool HasInternalDisplay() {
  182. return !GetInternalDisplayIds().empty();
  183. }
  184. void SetInternalDisplayIds(base::flat_set<int64_t> display_ids) {
  185. *internal_display_ids() = std::move(display_ids);
  186. }
  187. gfx::ColorSpace ForcedColorProfileStringToColorSpace(const std::string& value) {
  188. if (value == "srgb")
  189. return gfx::ColorSpace::CreateSRGB();
  190. if (value == "display-p3-d65")
  191. return gfx::ColorSpace::CreateDisplayP3D65();
  192. if (value == "scrgb-linear")
  193. return gfx::ColorSpace::CreateSRGBLinear();
  194. if (value == "hdr10")
  195. return gfx::ColorSpace::CreateHDR10();
  196. if (value == "extended-srgb")
  197. return gfx::ColorSpace::CreateExtendedSRGB();
  198. if (value == "generic-rgb") {
  199. return gfx::ColorSpace(gfx::ColorSpace::PrimaryID::APPLE_GENERIC_RGB,
  200. gfx::ColorSpace::TransferID::GAMMA18);
  201. }
  202. if (value == "color-spin-gamma24") {
  203. // Run this color profile through an ICC profile. The resulting color space
  204. // is slightly different from the input color space, and removing the ICC
  205. // profile would require rebaselineing many layout tests.
  206. gfx::ColorSpace color_space(
  207. gfx::ColorSpace::PrimaryID::WIDE_GAMUT_COLOR_SPIN,
  208. gfx::ColorSpace::TransferID::GAMMA24);
  209. return gfx::ICCProfile::FromColorSpace(color_space).GetColorSpace();
  210. }
  211. LOG(ERROR) << "Invalid forced color profile: \"" << value << "\"";
  212. return gfx::ColorSpace::CreateSRGB();
  213. }
  214. gfx::ColorSpace GetForcedDisplayColorProfile() {
  215. DCHECK(HasForceDisplayColorProfile());
  216. std::string value =
  217. base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
  218. /*switches::kForceDisplayColorProfile=*/"force-color-profile");
  219. return ForcedColorProfileStringToColorSpace(value);
  220. }
  221. bool HasForceDisplayColorProfile() {
  222. return base::CommandLine::ForCurrentProcess()->HasSwitch(
  223. /*switches::kForceDisplayColorProfile=*/"force-color-profile");
  224. }
  225. #if BUILDFLAG(IS_CHROMEOS)
  226. // Constructs the raster DisplayColorSpaces out of |snapshot_color_space|,
  227. // including the HDR ones if present and |allow_high_bit_depth| is set.
  228. gfx::DisplayColorSpaces CreateDisplayColorSpaces(
  229. const gfx::ColorSpace& snapshot_color_space,
  230. bool allow_high_bit_depth,
  231. const absl::optional<gfx::HDRStaticMetadata>& hdr_static_metadata) {
  232. if (HasForceDisplayColorProfile()) {
  233. return gfx::DisplayColorSpaces(GetForcedDisplayColorProfile(),
  234. DisplaySnapshot::PrimaryFormat());
  235. }
  236. // ChromeOS VMs (e.g. amd64-generic or betty) have INVALID Primaries; just
  237. // pass the color space along.
  238. if (!snapshot_color_space.IsValid()) {
  239. return gfx::DisplayColorSpaces(snapshot_color_space,
  240. DisplaySnapshot::PrimaryFormat());
  241. }
  242. const auto primary_id = snapshot_color_space.GetPrimaryID();
  243. skcms_Matrix3x3 primary_matrix{};
  244. if (primary_id == gfx::ColorSpace::PrimaryID::CUSTOM)
  245. snapshot_color_space.GetPrimaryMatrix(&primary_matrix);
  246. // Reconstruct the native colorspace with an IEC61966 2.1 transfer function
  247. // for SDR content (matching that of sRGB).
  248. gfx::ColorSpace sdr_color_space;
  249. if (primary_id == gfx::ColorSpace::PrimaryID::CUSTOM) {
  250. sdr_color_space = gfx::ColorSpace::CreateCustom(
  251. primary_matrix, gfx::ColorSpace::TransferID::SRGB);
  252. } else {
  253. sdr_color_space =
  254. gfx::ColorSpace(primary_id, gfx::ColorSpace::TransferID::SRGB);
  255. }
  256. gfx::DisplayColorSpaces display_color_spaces = gfx::DisplayColorSpaces(
  257. sdr_color_space, DisplaySnapshot::PrimaryFormat());
  258. if (allow_high_bit_depth && snapshot_color_space.IsHDR()) {
  259. gfx::ColorSpace hdr_color_space;
  260. if (primary_id == gfx::ColorSpace::PrimaryID::CUSTOM) {
  261. hdr_color_space = gfx::ColorSpace::CreatePiecewiseHDR(
  262. primary_id, display::kSDRJoint, display::kHDRLevel, &primary_matrix);
  263. } else {
  264. hdr_color_space = gfx::ColorSpace::CreatePiecewiseHDR(
  265. primary_id, display::kSDRJoint, display::kHDRLevel);
  266. }
  267. display_color_spaces.SetOutputColorSpaceAndBufferFormat(
  268. gfx::ContentColorUsage::kHDR, false /* needs_alpha */, hdr_color_space,
  269. gfx::BufferFormat::RGBA_1010102);
  270. display_color_spaces.SetOutputColorSpaceAndBufferFormat(
  271. gfx::ContentColorUsage::kHDR, true /* needs_alpha */, hdr_color_space,
  272. gfx::BufferFormat::RGBA_1010102);
  273. // TODO(https://crbug.com/1286074): Populate maximum luminance based on
  274. // `hdr_static_metadata`. For now, assume that the HDR maximum luminance
  275. // is 1,000% of the SDR maximum luminance.
  276. constexpr float kHDRMaxLuminanceRelative = 10.f;
  277. display_color_spaces.SetHDRMaxLuminanceRelative(kHDRMaxLuminanceRelative);
  278. }
  279. return display_color_spaces;
  280. }
  281. #endif // BUILDFLAG(IS_CHROMEOS)
  282. } // namespace display