#include "ui/display/util/display_util.h"
#include <stddef.h>
#include <array>
#include "base/command_line.h"
#include "base/compiler_specific.h"
#include "base/containers/contains.h"
#include "base/logging.h"
#include "base/metrics/histogram_functions.h"
#include "base/no_destructor.h"
#include "base/notreached.h"
#include "components/device_event_log/device_event_log.h"
#include "components/viz/common/resources/shared_image_format.h"
#include "ui/display/types/display_snapshot.h"
#include "ui/display/util/edid_parser.h"
#include "ui/gfx/icc_profile.h"
#if BUILDFLAG(IS_CHROMEOS)
#include "ui/display/display_features.h"
#endif
namespace display {
namespace {
base::flat_set<int64_t>* internal_display_ids() {
static base::NoDestructor<base::flat_set<int64_t>> display_ids;
return display_ids.get();
}
constexpr auto kInvalidDisplaySizeList =
std::to_array<std::array<int, 2>>({
{40, 30},
{50, 40},
{160, 90},
{160, 100},
});
void EmitEdidColorSpaceChecksOutcomeUma(EdidColorSpaceChecksOutcome outcome) {
base::UmaHistogramEnumeration("DrmUtil.GetColorSpaceFromEdid.ChecksOutcome",
outcome);
}
bool NearlyEqual(const skcms_Matrix3x3& lhs,
const skcms_Matrix3x3& rhs,
float epsilon) {
for (int r = 0; r < 3; r++) {
for (int c = 0; c < 3; c++) {
if (std::abs(UNSAFE_TODO(lhs.vals[r][c]) - UNSAFE_TODO(rhs.vals[r][c])) >
epsilon) {
return false;
}
}
}
return true;
}
}
bool IsDisplaySizeValid(const gfx::Size& physical_size) {
if (physical_size.width() <= kInvalidDisplaySizeList[0][0] ||
physical_size.height() <= kInvalidDisplaySizeList[0][1]) {
VLOG(1) << "Smaller than minimum display size";
return false;
}
for (size_t i = 1; i < std::size(kInvalidDisplaySizeList); ++i) {
const gfx::Size size(kInvalidDisplaySizeList[i][0],
kInvalidDisplaySizeList[i][1]);
if (physical_size == size) {
VLOG(1) << "Invalid display size detected:" << size.ToString();
return false;
}
}
return true;
}
int64_t GenerateDisplayID(uint16_t manufacturer_id,
uint32_t product_code_hash,
uint8_t output_index) {
return ((static_cast<int64_t>(manufacturer_id) << 40) |
(static_cast<int64_t>(product_code_hash) << 8) | output_index);
}
gfx::ColorSpace GetColorSpaceFromEdid(const display::EdidParser& edid_parser) {
const SkColorSpacePrimaries primaries = edid_parser.primaries();
if (!(primaries.fBX <= primaries.fRX && primaries.fGX <= primaries.fRX &&
primaries.fBY <= primaries.fRY && primaries.fRY <= primaries.fGY)) {
EmitEdidColorSpaceChecksOutcomeUma(
EdidColorSpaceChecksOutcome::kErrorBadCoordinates);
return gfx::ColorSpace();
}
constexpr double kBT709PrimariesArea = 0.0954;
const float primaries_area_twice =
(primaries.fRX * primaries.fGY) + (primaries.fBX * primaries.fRY) +
(primaries.fGX * primaries.fBY) - (primaries.fBX * primaries.fGY) -
(primaries.fGX * primaries.fRY) - (primaries.fRX * primaries.fBY);
if (primaries_area_twice < kBT709PrimariesArea) {
EmitEdidColorSpaceChecksOutcomeUma(
EdidColorSpaceChecksOutcome::kErrorPrimariesAreaTooSmall);
return gfx::ColorSpace();
}
constexpr float kExpectedBluePrimaryX = 0.15f;
constexpr float kBluePrimaryXDelta = 0.02f;
constexpr float kExpectedBluePrimaryY = 0.06f;
constexpr float kBluePrimaryYDelta = 0.031f;
const bool is_blue_primary_broken =
(std::abs(primaries.fBX - kExpectedBluePrimaryX) > kBluePrimaryXDelta) ||
(std::abs(primaries.fBY - kExpectedBluePrimaryY) > kBluePrimaryYDelta);
if (is_blue_primary_broken) {
EmitEdidColorSpaceChecksOutcomeUma(
EdidColorSpaceChecksOutcome::kErrorBluePrimaryIsBroken);
return gfx::ColorSpace();
}
skcms_Matrix3x3 primaries_matrix;
if (!primaries.toXYZD50(&primaries_matrix)) {
EmitEdidColorSpaceChecksOutcomeUma(
EdidColorSpaceChecksOutcome::kErrorCannotExtractToXYZD50);
return gfx::ColorSpace();
}
auto color_space_primaries = gfx::ColorSpace::PrimaryID::INVALID;
constexpr float kPrimariesTolerance = 0.025;
if (NearlyEqual(primaries_matrix, SkNamedGamut::kSRGB, kPrimariesTolerance)) {
color_space_primaries = gfx::ColorSpace::PrimaryID::BT709;
} else if (NearlyEqual(primaries_matrix, SkNamedGamut::kDisplayP3,
kPrimariesTolerance)) {
color_space_primaries = gfx::ColorSpace::PrimaryID::P3;
}
const float gamma = edid_parser.gamma();
if (gamma < 1.0f) {
EmitEdidColorSpaceChecksOutcomeUma(
EdidColorSpaceChecksOutcome::kErrorBadGamma);
return gfx::ColorSpace();
}
EmitEdidColorSpaceChecksOutcomeUma(EdidColorSpaceChecksOutcome::kSuccess);
auto transfer_id = gfx::ColorSpace::TransferID::INVALID;
if (base::Contains(
edid_parser.supported_color_primary_matrix_ids(),
EdidParser::PrimaryMatrixPair(gfx::ColorSpace::PrimaryID::BT2020,
gfx::ColorSpace::MatrixID::RGB)) ||
base::Contains(edid_parser.supported_color_primary_matrix_ids(),
EdidParser::PrimaryMatrixPair(
gfx::ColorSpace::PrimaryID::BT2020,
gfx::ColorSpace::MatrixID::BT2020_NCL))) {
if (base::Contains(edid_parser.supported_color_transfer_ids(),
gfx::ColorSpace::TransferID::PQ)) {
transfer_id = gfx::ColorSpace::TransferID::PQ;
#if BUILDFLAG(IS_CHROMEOS)
if (base::FeatureList::IsEnabled(
display::features::kEnableExternalDisplayHDR10Mode) &&
edid_parser.is_external_display() &&
base::Contains(
edid_parser.supported_color_primary_matrix_ids(),
EdidParser::PrimaryMatrixPair(gfx::ColorSpace::PrimaryID::BT2020,
gfx::ColorSpace::MatrixID::RGB))) {
return gfx::ColorSpace::CreateHDR10();
}
#endif
} else if (base::Contains(edid_parser.supported_color_transfer_ids(),
gfx::ColorSpace::TransferID::HLG)) {
transfer_id = gfx::ColorSpace::TransferID::HLG;
}
} else if (base::Contains(edid_parser.supported_color_primary_matrix_ids(),
EdidParser::PrimaryMatrixPair(
gfx::ColorSpace::PrimaryID::P3,
gfx::ColorSpace::MatrixID::RGB))) {
return gfx::ColorSpace::CreateDisplayP3D65();
} else if (gamma == 2.2f) {
transfer_id = gfx::ColorSpace::TransferID::GAMMA22;
} else if (gamma == 2.4f) {
transfer_id = gfx::ColorSpace::TransferID::GAMMA24;
}
if (transfer_id != gfx::ColorSpace::TransferID::INVALID) {
if (color_space_primaries != gfx::ColorSpace::PrimaryID::INVALID)
return gfx::ColorSpace(color_space_primaries, transfer_id);
return gfx::ColorSpace::CreateCustom(primaries_matrix, transfer_id);
}
skcms_TransferFunction transfer = {gamma, 1.f, 0.f, 0.f, 0.f, 0.f, 0.f};
if (color_space_primaries == gfx::ColorSpace::PrimaryID::INVALID)
return gfx::ColorSpace::CreateCustom(primaries_matrix, transfer);
return gfx::ColorSpace(
color_space_primaries, gfx::ColorSpace::TransferID::CUSTOM,
gfx::ColorSpace::MatrixID::RGB, gfx::ColorSpace::RangeID::FULL,
nullptr, &transfer);
}
bool CompareDisplayIds(int64_t id1, int64_t id2) {
if (id1 == id2)
return false;
int index_1 = id1 & 0xFF;
int index_2 = id2 & 0xFF;
DCHECK_NE(index_1, index_2) << id1 << " and " << id2;
bool first_is_internal = IsInternalDisplayId(id1);
bool second_is_internal = IsInternalDisplayId(id2);
if (first_is_internal && !second_is_internal)
return true;
if (!first_is_internal && second_is_internal)
return false;
return index_1 < index_2;
}
bool IsInternalDisplayId(int64_t display_id) {
return base::Contains(*internal_display_ids(), display_id);
}
const base::flat_set<int64_t>& GetInternalDisplayIds() {
return *internal_display_ids();
}
bool HasInternalDisplay() {
return !GetInternalDisplayIds().empty();
}
void SetInternalDisplayIds(base::flat_set<int64_t> display_ids) {
*internal_display_ids() = std::move(display_ids);
}
void AddInternalDisplayId(int64_t display_id) {
internal_display_ids()->insert(display_id);
}
void RemoveInternalDisplayId(int64_t display_id) {
internal_display_ids()->erase(display_id);
}
gfx::ColorSpace ForcedColorProfileStringToColorSpace(const std::string& value) {
if (value == "srgb")
return gfx::ColorSpace::CreateSRGB();
if (value == "display-p3-d65")
return gfx::ColorSpace::CreateDisplayP3D65();
if (value == "rec2020") {
return gfx::ColorSpace(gfx::ColorSpace::PrimaryID::BT2020,
gfx::ColorSpace::TransferID::BT2020_10);
}
if (value == "scrgb-linear")
return gfx::ColorSpace::CreateSRGBLinear();
if (value == "hdr10")
return gfx::ColorSpace::CreateHDR10();
if (value == "extended-srgb")
return gfx::ColorSpace::CreateExtendedSRGB();
if (value == "generic-rgb") {
return gfx::ColorSpace(gfx::ColorSpace::PrimaryID::APPLE_GENERIC_RGB,
gfx::ColorSpace::TransferID::GAMMA18);
}
if (value == "color-spin-gamma24") {
gfx::ColorSpace color_space(
gfx::ColorSpace::PrimaryID::WIDE_GAMUT_COLOR_SPIN,
gfx::ColorSpace::TransferID::GAMMA24);
return gfx::ICCProfile::FromColorSpace(color_space).GetColorSpace();
}
LOG(ERROR) << "Invalid forced color profile: \"" << value << "\"";
return gfx::ColorSpace::CreateSRGB();
}
gfx::ColorSpace GetForcedDisplayColorProfile() {
DCHECK(HasForceDisplayColorProfile());
std::string value =
base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
"force-color-profile");
return ForcedColorProfileStringToColorSpace(value);
}
bool HasForceDisplayColorProfile() {
return base::CommandLine::ForCurrentProcess()->HasSwitch(
"force-color-profile");
}
#if BUILDFLAG(IS_CHROMEOS)
gfx::DisplayColorSpaces CreateDisplayColorSpaces(
const gfx::ColorSpace& snapshot_color_space,
bool allow_high_bit_depth,
const std::optional<gfx::HDRStaticMetadata>& hdr_static_metadata) {
if (HasForceDisplayColorProfile()) {
return gfx::DisplayColorSpaces(GetForcedDisplayColorProfile(),
DisplaySnapshot::PrimaryFormat());
}
if (!snapshot_color_space.IsValid()) {
return gfx::DisplayColorSpaces(snapshot_color_space,
DisplaySnapshot::PrimaryFormat());
}
skcms_Matrix3x3 primary_matrix = SkNamedGamut::kSRGB;
gfx::ColorSpace sdr_color_space = gfx::ColorSpace::CreateCustom(
primary_matrix, gfx::ColorSpace::TransferID::SRGB);
gfx::DisplayColorSpaces display_color_spaces = gfx::DisplayColorSpaces(
sdr_color_space, DisplaySnapshot::PrimaryFormat());
if (allow_high_bit_depth && snapshot_color_space.IsHDR()) {
gfx::ColorSpace hdr_color_space = gfx::ColorSpace::CreateCustom(
primary_matrix, gfx::ColorSpace::TransferID::SRGB_HDR);
display_color_spaces.SetOutputColorSpaceAndFormat(
gfx::ContentColorUsage::kHDR, false , hdr_color_space,
viz::SinglePlaneFormat::kRGBA_1010102);
display_color_spaces.SetOutputColorSpaceAndFormat(
gfx::ContentColorUsage::kHDR, true , hdr_color_space,
viz::SinglePlaneFormat::kRGBA_1010102);
display_color_spaces.SetHDRMaxLuminanceRelative(1.1f);
}
#if BUILDFLAG(IS_CHROMEOS)
if (allow_high_bit_depth &&
snapshot_color_space == gfx::ColorSpace::CreateHDR10() &&
base::FeatureList::IsEnabled(
display::features::kEnableExternalDisplayHDR10Mode)) {
display_color_spaces = gfx::DisplayColorSpaces(
gfx::ColorSpace::CreateHDR10(), viz::SinglePlaneFormat::kRGBA_1010102);
display_color_spaces.SetSDRMaxLuminanceNits(
hdr_static_metadata->max / kDefaultHdrMaxLuminanceRelative);
display_color_spaces.SetHDRMaxLuminanceRelative(
kDefaultHdrMaxLuminanceRelative);
}
#endif
return display_color_spaces;
}
#endif
int ConnectorIndex8(int device_index, int display_index) {
DCHECK_LT(device_index, 16);
DCHECK_LT(display_index, 16);
return ((device_index << 4) + display_index) & 0xFF;
}
uint16_t ConnectorIndex16(uint8_t device_index, uint8_t display_index) {
return ((device_index << 8) + display_index) & 0xFFFF;
}
}