blob: 0b53fd187edd0112f29c16a0f201f361be17997b [file] [log] [blame]
Avi Drissman3e1a26c2022-09-15 20:26:031// Copyright 2014 The Chromium Authors
[email protected]aa0759e02014-04-04 19:13:402// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
[email protected]90533fc2014-04-16 18:23:595#include "ui/display/util/display_util.h"
[email protected]aa0759e02014-04-04 19:13:406
avice85b4292015-12-24 21:08:257#include <stddef.h>
8
Femi Adegunloyef500de32022-05-05 23:09:229#include "base/command_line.h"
Jan Wilken Dörrie721926d2020-12-09 23:57:4710#include "base/containers/contains.h"
[email protected]aa0759e02014-04-04 19:13:4011#include "base/logging.h"
Tom Anderson9babb5a2019-11-11 23:35:5412#include "base/metrics/histogram_functions.h"
Mitsuru Oshimadd6758c2022-04-19 06:06:0913#include "base/no_destructor.h"
14#include "base/notreached.h"
Femi Adegunloyef500de32022-05-05 23:09:2215#include "ui/display/types/display_snapshot.h"
Tom Anderson9babb5a2019-11-11 23:35:5416#include "ui/display/util/edid_parser.h"
Femi Adegunloyef500de32022-05-05 23:09:2217#include "ui/gfx/icc_profile.h"
[email protected]aa0759e02014-04-04 19:13:4018
kylechar79bed53c2016-09-01 13:02:3119namespace display {
[email protected]aa0759e02014-04-04 19:13:4020
21namespace {
22
Mitsuru Oshimadd6758c2022-04-19 06:06:0923base::flat_set<int64_t>* internal_display_ids() {
24 static base::NoDestructor<base::flat_set<int64_t>> display_ids;
25 return display_ids.get();
Tom Anderson740573b2022-08-24 23:05:3926}
Mitsuru Oshimadd6758c2022-04-19 06:06:0927
[email protected]aa0759e02014-04-04 19:13:4028// A list of bogus sizes in mm that should be ignored.
29// See crbug.com/136533. The first element maintains the minimum
30// size required to be valid size.
31const int kInvalidDisplaySizeList[][2] = {
Tom Anderson9babb5a2019-11-11 23:35:5432 {40, 30},
33 {50, 40},
34 {160, 90},
35 {160, 100},
[email protected]aa0759e02014-04-04 19:13:4036};
37
Tom Anderson9babb5a2019-11-11 23:35:5438// Used in the GetColorSpaceFromEdid function to collect data on whether the
39// color space extracted from an EDID blob passed the sanity checks.
40void EmitEdidColorSpaceChecksOutcomeUma(EdidColorSpaceChecksOutcome outcome) {
41 base::UmaHistogramEnumeration("DrmUtil.GetColorSpaceFromEdid.ChecksOutcome",
42 outcome);
43}
44
Miguel Casas9e3f10c2020-04-30 21:53:0045// Returns true if each and all matrix values are within |epsilon| distance.
46bool NearlyEqual(const skcms_Matrix3x3& lhs,
47 const skcms_Matrix3x3& rhs,
48 float epsilon) {
49 for (int r = 0; r < 3; r++) {
50 for (int c = 0; c < 3; c++) {
51 if (std::abs(lhs.vals[r][c] - rhs.vals[r][c]) > epsilon)
52 return false;
53 }
54 }
55 return true;
56}
57
[email protected]aa0759e02014-04-04 19:13:4058} // namespace
59
Evan Stadee8599182020-07-08 17:48:3560bool IsDisplaySizeValid(const gfx::Size& physical_size) {
[email protected]aa0759e02014-04-04 19:13:4061 // Ignore if the reported display is smaller than minimum size.
62 if (physical_size.width() <= kInvalidDisplaySizeList[0][0] ||
63 physical_size.height() <= kInvalidDisplaySizeList[0][1]) {
[email protected]8c1a2252014-04-15 22:37:2264 VLOG(1) << "Smaller than minimum display size";
Evan Stadee8599182020-07-08 17:48:3565 return false;
[email protected]aa0759e02014-04-04 19:13:4066 }
Daniel Chengf9920542022-02-27 01:15:0867 for (size_t i = 1; i < std::size(kInvalidDisplaySizeList); ++i) {
[email protected]aa0759e02014-04-04 19:13:4068 const gfx::Size size(kInvalidDisplaySizeList[i][0],
69 kInvalidDisplaySizeList[i][1]);
70 if (physical_size == size) {
Nate Fischer816c9842022-02-02 03:52:4271 VLOG(1) << "Invalid display size detected:" << size.ToString();
Evan Stadee8599182020-07-08 17:48:3572 return false;
[email protected]aa0759e02014-04-04 19:13:4073 }
74 }
Evan Stadee8599182020-07-08 17:48:3575 return true;
[email protected]aa0759e02014-04-04 19:13:4076}
77
robert.bradford3d88a672015-12-10 11:51:3878int64_t GenerateDisplayID(uint16_t manufacturer_id,
79 uint32_t product_code_hash,
80 uint8_t output_index) {
81 return ((static_cast<int64_t>(manufacturer_id) << 40) |
82 (static_cast<int64_t>(product_code_hash) << 8) | output_index);
83}
84
Tom Anderson9babb5a2019-11-11 23:35:5485gfx::ColorSpace GetColorSpaceFromEdid(const display::EdidParser& edid_parser) {
86 const SkColorSpacePrimaries primaries = edid_parser.primaries();
87
88 // Sanity check: primaries should verify By <= Ry <= Gy, Bx <= Rx and Gx <=
89 // Rx, to guarantee that the R, G and B colors are each in the correct region.
90 if (!(primaries.fBX <= primaries.fRX && primaries.fGX <= primaries.fRX &&
91 primaries.fBY <= primaries.fRY && primaries.fRY <= primaries.fGY)) {
92 EmitEdidColorSpaceChecksOutcomeUma(
93 EdidColorSpaceChecksOutcome::kErrorBadCoordinates);
94 return gfx::ColorSpace();
95 }
96
97 // Sanity check: the area spawned by the primaries' triangle is too small,
98 // i.e. less than half the surface of the triangle spawned by sRGB/BT.709.
99 constexpr double kBT709PrimariesArea = 0.0954;
100 const float primaries_area_twice =
101 (primaries.fRX * primaries.fGY) + (primaries.fBX * primaries.fRY) +
102 (primaries.fGX * primaries.fBY) - (primaries.fBX * primaries.fGY) -
103 (primaries.fGX * primaries.fRY) - (primaries.fRX * primaries.fBY);
104 if (primaries_area_twice < kBT709PrimariesArea) {
105 EmitEdidColorSpaceChecksOutcomeUma(
106 EdidColorSpaceChecksOutcome::kErrorPrimariesAreaTooSmall);
107 return gfx::ColorSpace();
108 }
109
110 // Sanity check: https://crbug.com/809909, the blue primary coordinates should
111 // not be too far left/upwards of the expected location (namely [0.15, 0.06]
112 // for sRGB/ BT.709/ Adobe RGB/ DCI-P3, and [0.131, 0.046] for BT.2020).
113 constexpr float kExpectedBluePrimaryX = 0.15f;
114 constexpr float kBluePrimaryXDelta = 0.02f;
115 constexpr float kExpectedBluePrimaryY = 0.06f;
116 constexpr float kBluePrimaryYDelta = 0.031f;
117 const bool is_blue_primary_broken =
118 (std::abs(primaries.fBX - kExpectedBluePrimaryX) > kBluePrimaryXDelta) ||
119 (std::abs(primaries.fBY - kExpectedBluePrimaryY) > kBluePrimaryYDelta);
120 if (is_blue_primary_broken) {
121 EmitEdidColorSpaceChecksOutcomeUma(
122 EdidColorSpaceChecksOutcome::kErrorBluePrimaryIsBroken);
123 return gfx::ColorSpace();
124 }
125
Miguel Casas9e3f10c2020-04-30 21:53:00126 skcms_Matrix3x3 primaries_matrix;
127 if (!primaries.toXYZD50(&primaries_matrix)) {
Tom Anderson9babb5a2019-11-11 23:35:54128 EmitEdidColorSpaceChecksOutcomeUma(
129 EdidColorSpaceChecksOutcome::kErrorCannotExtractToXYZD50);
130 return gfx::ColorSpace();
131 }
132
Miguel Casas9e3f10c2020-04-30 21:53:00133 // Snap the primaries to those of BT.709/sRGB for performance purposes, see
134 // crbug.com/1073467. kPrimariesTolerance is an educated guess from various
135 // ChromeOS panels observations.
136 auto color_space_primaries = gfx::ColorSpace::PrimaryID::INVALID;
137 constexpr float kPrimariesTolerance = 0.025;
138 if (NearlyEqual(primaries_matrix, SkNamedGamut::kSRGB, kPrimariesTolerance))
139 color_space_primaries = gfx::ColorSpace::PrimaryID::BT709;
140
Peter Kasting03a695582021-06-14 20:28:59141 const float gamma = edid_parser.gamma();
142 if (gamma < 1.0f) {
Tom Anderson9babb5a2019-11-11 23:35:54143 EmitEdidColorSpaceChecksOutcomeUma(
144 EdidColorSpaceChecksOutcome::kErrorBadGamma);
145 return gfx::ColorSpace();
146 }
147 EmitEdidColorSpaceChecksOutcomeUma(EdidColorSpaceChecksOutcome::kSuccess);
148
Miguel Casas9e3f10c2020-04-30 21:53:00149 auto transfer_id = gfx::ColorSpace::TransferID::INVALID;
Tom Anderson9babb5a2019-11-11 23:35:54150 if (base::Contains(edid_parser.supported_color_primary_ids(),
151 gfx::ColorSpace::PrimaryID::BT2020)) {
152 if (base::Contains(edid_parser.supported_color_transfer_ids(),
Christopher Cameron6c9cfff02022-01-25 01:29:03153 gfx::ColorSpace::TransferID::PQ)) {
154 transfer_id = gfx::ColorSpace::TransferID::PQ;
Tom Anderson9babb5a2019-11-11 23:35:54155 } else if (base::Contains(edid_parser.supported_color_transfer_ids(),
Christopher Cameron6c9cfff02022-01-25 01:29:03156 gfx::ColorSpace::TransferID::HLG)) {
157 transfer_id = gfx::ColorSpace::TransferID::HLG;
Tom Anderson9babb5a2019-11-11 23:35:54158 }
159 } else if (gamma == 2.2f) {
160 transfer_id = gfx::ColorSpace::TransferID::GAMMA22;
161 } else if (gamma == 2.4f) {
162 transfer_id = gfx::ColorSpace::TransferID::GAMMA24;
163 }
164
Miguel Casas9e3f10c2020-04-30 21:53:00165 // Prefer to return a name-based ColorSpace to ease subsequent calculations.
166 if (transfer_id != gfx::ColorSpace::TransferID::INVALID) {
167 if (color_space_primaries != gfx::ColorSpace::PrimaryID::INVALID)
168 return gfx::ColorSpace(color_space_primaries, transfer_id);
169 return gfx::ColorSpace::CreateCustom(primaries_matrix, transfer_id);
170 }
Tom Anderson9babb5a2019-11-11 23:35:54171
172 skcms_TransferFunction transfer = {gamma, 1.f, 0.f, 0.f, 0.f, 0.f, 0.f};
Miguel Casas9e3f10c2020-04-30 21:53:00173 if (color_space_primaries == gfx::ColorSpace::PrimaryID::INVALID)
174 return gfx::ColorSpace::CreateCustom(primaries_matrix, transfer);
175 return gfx::ColorSpace(
176 color_space_primaries, gfx::ColorSpace::TransferID::CUSTOM,
177 gfx::ColorSpace::MatrixID::RGB, gfx::ColorSpace::RangeID::FULL,
178 /*custom_primary_matrix=*/nullptr, &transfer);
Tom Anderson9babb5a2019-11-11 23:35:54179}
180
Mitsuru Oshimadd6758c2022-04-19 06:06:09181bool CompareDisplayIds(int64_t id1, int64_t id2) {
182 if (id1 == id2)
183 return false;
184 // Output index is stored in the first 8 bits. See GetDisplayIdFromEDID
185 // in edid_parser.cc.
186 int index_1 = id1 & 0xFF;
187 int index_2 = id2 & 0xFF;
188 DCHECK_NE(index_1, index_2) << id1 << " and " << id2;
189 bool first_is_internal = IsInternalDisplayId(id1);
190 bool second_is_internal = IsInternalDisplayId(id2);
191 if (first_is_internal && !second_is_internal)
192 return true;
193 if (!first_is_internal && second_is_internal)
194 return false;
195 return index_1 < index_2;
196}
197
198bool IsInternalDisplayId(int64_t display_id) {
199 return base::Contains(*internal_display_ids(), display_id);
200}
201
202const base::flat_set<int64_t>& GetInternalDisplayIds() {
203 return *internal_display_ids();
204}
205
206// static
207bool HasInternalDisplay() {
208 return !GetInternalDisplayIds().empty();
209}
210
211void SetInternalDisplayIds(base::flat_set<int64_t> display_ids) {
212 *internal_display_ids() = std::move(display_ids);
213}
214
Femi Adegunloyef500de32022-05-05 23:09:22215gfx::ColorSpace ForcedColorProfileStringToColorSpace(const std::string& value) {
216 if (value == "srgb")
217 return gfx::ColorSpace::CreateSRGB();
218 if (value == "display-p3-d65")
219 return gfx::ColorSpace::CreateDisplayP3D65();
Christopher Cameronb15ffe62022-11-15 10:25:31220 if (value == "rec2020") {
221 return gfx::ColorSpace(gfx::ColorSpace::PrimaryID::BT2020,
222 gfx::ColorSpace::TransferID::BT2020_10);
223 }
Femi Adegunloyef500de32022-05-05 23:09:22224 if (value == "scrgb-linear")
Christopher Camerona1fc9b102022-05-12 18:23:08225 return gfx::ColorSpace::CreateSRGBLinear();
Femi Adegunloyef500de32022-05-05 23:09:22226 if (value == "hdr10")
227 return gfx::ColorSpace::CreateHDR10();
228 if (value == "extended-srgb")
229 return gfx::ColorSpace::CreateExtendedSRGB();
230 if (value == "generic-rgb") {
231 return gfx::ColorSpace(gfx::ColorSpace::PrimaryID::APPLE_GENERIC_RGB,
232 gfx::ColorSpace::TransferID::GAMMA18);
233 }
234 if (value == "color-spin-gamma24") {
235 // Run this color profile through an ICC profile. The resulting color space
236 // is slightly different from the input color space, and removing the ICC
237 // profile would require rebaselineing many layout tests.
238 gfx::ColorSpace color_space(
239 gfx::ColorSpace::PrimaryID::WIDE_GAMUT_COLOR_SPIN,
240 gfx::ColorSpace::TransferID::GAMMA24);
241 return gfx::ICCProfile::FromColorSpace(color_space).GetColorSpace();
242 }
243 LOG(ERROR) << "Invalid forced color profile: \"" << value << "\"";
244 return gfx::ColorSpace::CreateSRGB();
245}
246
247gfx::ColorSpace GetForcedDisplayColorProfile() {
248 DCHECK(HasForceDisplayColorProfile());
249 std::string value =
250 base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
251 /*switches::kForceDisplayColorProfile=*/"force-color-profile");
252 return ForcedColorProfileStringToColorSpace(value);
253}
254
255bool HasForceDisplayColorProfile() {
256 return base::CommandLine::ForCurrentProcess()->HasSwitch(
257 /*switches::kForceDisplayColorProfile=*/"force-color-profile");
258}
259
260#if BUILDFLAG(IS_CHROMEOS)
261// Constructs the raster DisplayColorSpaces out of |snapshot_color_space|,
262// including the HDR ones if present and |allow_high_bit_depth| is set.
263gfx::DisplayColorSpaces CreateDisplayColorSpaces(
264 const gfx::ColorSpace& snapshot_color_space,
265 bool allow_high_bit_depth,
266 const absl::optional<gfx::HDRStaticMetadata>& hdr_static_metadata) {
267 if (HasForceDisplayColorProfile()) {
268 return gfx::DisplayColorSpaces(GetForcedDisplayColorProfile(),
269 DisplaySnapshot::PrimaryFormat());
270 }
271
272 // ChromeOS VMs (e.g. amd64-generic or betty) have INVALID Primaries; just
273 // pass the color space along.
274 if (!snapshot_color_space.IsValid()) {
275 return gfx::DisplayColorSpaces(snapshot_color_space,
276 DisplaySnapshot::PrimaryFormat());
277 }
278
279 const auto primary_id = snapshot_color_space.GetPrimaryID();
280
281 skcms_Matrix3x3 primary_matrix{};
282 if (primary_id == gfx::ColorSpace::PrimaryID::CUSTOM)
283 snapshot_color_space.GetPrimaryMatrix(&primary_matrix);
284
285 // Reconstruct the native colorspace with an IEC61966 2.1 transfer function
286 // for SDR content (matching that of sRGB).
287 gfx::ColorSpace sdr_color_space;
288 if (primary_id == gfx::ColorSpace::PrimaryID::CUSTOM) {
289 sdr_color_space = gfx::ColorSpace::CreateCustom(
290 primary_matrix, gfx::ColorSpace::TransferID::SRGB);
291 } else {
292 sdr_color_space =
293 gfx::ColorSpace(primary_id, gfx::ColorSpace::TransferID::SRGB);
294 }
295 gfx::DisplayColorSpaces display_color_spaces = gfx::DisplayColorSpaces(
296 sdr_color_space, DisplaySnapshot::PrimaryFormat());
297
298 if (allow_high_bit_depth && snapshot_color_space.IsHDR()) {
299 gfx::ColorSpace hdr_color_space;
300 if (primary_id == gfx::ColorSpace::PrimaryID::CUSTOM) {
301 hdr_color_space = gfx::ColorSpace::CreatePiecewiseHDR(
Femi Adegunloyeb6246cb52022-05-06 13:49:21302 primary_id, display::kSDRJoint, display::kHDRLevel, &primary_matrix);
Femi Adegunloyef500de32022-05-05 23:09:22303 } else {
Femi Adegunloyeb6246cb52022-05-06 13:49:21304 hdr_color_space = gfx::ColorSpace::CreatePiecewiseHDR(
305 primary_id, display::kSDRJoint, display::kHDRLevel);
Femi Adegunloyef500de32022-05-05 23:09:22306 }
307
308 display_color_spaces.SetOutputColorSpaceAndBufferFormat(
309 gfx::ContentColorUsage::kHDR, false /* needs_alpha */, hdr_color_space,
310 gfx::BufferFormat::RGBA_1010102);
311 display_color_spaces.SetOutputColorSpaceAndBufferFormat(
312 gfx::ContentColorUsage::kHDR, true /* needs_alpha */, hdr_color_space,
313 gfx::BufferFormat::RGBA_1010102);
314
315 // TODO(https://crbug.com/1286074): Populate maximum luminance based on
316 // `hdr_static_metadata`. For now, assume that the HDR maximum luminance
317 // is 1,000% of the SDR maximum luminance.
318 constexpr float kHDRMaxLuminanceRelative = 10.f;
319 display_color_spaces.SetHDRMaxLuminanceRelative(kHDRMaxLuminanceRelative);
320 }
321 return display_color_spaces;
322}
323#endif // BUILDFLAG(IS_CHROMEOS)
324
kylechar79bed53c2016-09-01 13:02:31325} // namespace display