chromium/ui/display/win/color_profile_reader.cc

// Copyright 2017 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "ui/display/win/color_profile_reader.h"

#include <windows.h>

#include <stddef.h>

#include "base/files/file_util.h"
#include "base/functional/bind.h"
#include "base/task/thread_pool.h"
#include "base/threading/scoped_thread_priority.h"
#include "ui/display/win/display_info.h"
#include "ui/gfx/icc_profile.h"

namespace display {
namespace win {
namespace {

BOOL CALLBACK EnumMonitorForProfilePathCallback(HMONITOR monitor,
                                                HDC input_hdc,
                                                LPRECT rect,
                                                LPARAM data) {
  std::wstring device_name;
  MONITORINFOEX monitor_info;
  ::ZeroMemory(&monitor_info, sizeof(monitor_info));
  monitor_info.cbSize = sizeof(monitor_info);
  ::GetMonitorInfo(monitor, &monitor_info);
  device_name = std::wstring(monitor_info.szDevice);

  std::wstring profile_path;
  HDC hdc = ::CreateDC(monitor_info.szDevice, NULL, NULL, NULL);
  if (hdc) {
    DWORD path_length = MAX_PATH;
    WCHAR path[MAX_PATH + 1];
    BOOL result = ::GetICMProfile(hdc, &path_length, path);
    ::DeleteDC(hdc);
    if (result)
      profile_path = std::wstring(path);
  }
  int64_t display_id =
      internal::DisplayInfo::DisplayIdFromMonitorInfo(monitor_info);
  std::map<int64_t, std::wstring>* display_id_to_path_map =
      reinterpret_cast<std::map<int64_t, std::wstring>*>(data);
  (*display_id_to_path_map)[display_id] = profile_path;
  return TRUE;
}

}  // namespace

ColorProfileReader::ColorProfileReader(Client* client) : client_(client) {}

ColorProfileReader::~ColorProfileReader() {}

void ColorProfileReader::UpdateIfNeeded() {
  // There is a potential race condition wherein the result from
  // EnumDisplayMonitors is already stale by the time that we get
  // back to BuildDisplayIdToPathMapCompleted.  To fix this we would
  // need to record the fact that we early-out-ed because of
  // update_in_flight_ was true, and then re-issue
  // BuildDisplayIdToPathMapOnBackgroundThread when the update
  // returned.
  if (update_in_flight_)
    return;

  update_in_flight_ = true;

  // Enumerate device profile paths on a background thread.  When this
  // completes it will run another task on a background thread to read
  // the profiles. This can impact the color of the browser so we want
  // to set this to a higher priority to complete the task earlier
  // during startup.
  base::ThreadPool::PostTaskAndReplyWithResult(
      FROM_HERE, {base::MayBlock(), base::TaskPriority::USER_VISIBLE},
      base::BindOnce(
          &ColorProfileReader::BuildDisplayIdToPathMapOnBackgroundThread),
      base::BindOnce(&ColorProfileReader::BuildDisplayIdToPathMapCompleted,
                     weak_factory_.GetWeakPtr()));
}

// static
ColorProfileReader::DisplayIdToPathMap
ColorProfileReader::BuildDisplayIdToPathMapOnBackgroundThread() {
  SCOPED_MAY_LOAD_LIBRARY_AT_BACKGROUND_PRIORITY();
  DisplayIdToPathMap display_id_to_path_map;
  EnumDisplayMonitors(nullptr, nullptr, EnumMonitorForProfilePathCallback,
                      reinterpret_cast<LPARAM>(&display_id_to_path_map));
  return display_id_to_path_map;
}

void ColorProfileReader::BuildDisplayIdToPathMapCompleted(
    DisplayIdToPathMap new_display_id_to_path_map) {
  DCHECK(update_in_flight_);

  // Are there any changes from previous results
  if (display_id_to_path_map_ == new_display_id_to_path_map) {
    update_in_flight_ = false;
    return;
  }

  display_id_to_path_map_ = new_display_id_to_path_map;

  base::ThreadPool::PostTaskAndReplyWithResult(
      FROM_HERE, {base::MayBlock(), base::TaskPriority::USER_VISIBLE},
      base::BindOnce(&ColorProfileReader::ReadProfilesOnBackgroundThread,
                     new_display_id_to_path_map),
      base::BindOnce(&ColorProfileReader::ReadProfilesCompleted,
                     weak_factory_.GetWeakPtr()));
}

// static
ColorProfileReader::DisplayIdToDataMap
ColorProfileReader::ReadProfilesOnBackgroundThread(
    DisplayIdToPathMap new_display_id_to_path_map) {
  DisplayIdToDataMap new_display_id_to_data_map;
  for (const auto& [display_id, profile_path] : new_display_id_to_path_map) {
    std::string profile_data;
    base::ReadFileToString(base::FilePath(profile_path), &profile_data);
    new_display_id_to_data_map[display_id] = profile_data;
  }
  return new_display_id_to_data_map;
}

void ColorProfileReader::ReadProfilesCompleted(
    DisplayIdToDataMap display_id_to_data_map) {
  DCHECK(update_in_flight_);
  update_in_flight_ = false;

  display_id_to_profile_map_.clear();
  for (const auto& [display_id, profile_data] : display_id_to_data_map) {
    if (!profile_data.empty()) {
      display_id_to_profile_map_[display_id] =
          gfx::ICCProfile::FromData(profile_data.data(), profile_data.size());
    }
  }

  client_->OnColorProfilesChanged();
}

gfx::ColorSpace ColorProfileReader::GetDisplayColorSpace(
    int64_t display_id) const {
  gfx::ICCProfile icc_profile;
  auto found = display_id_to_profile_map_.find(display_id);
  if (found != display_id_to_profile_map_.end())
    icc_profile = found->second;
  return icc_profile.IsValid() ? icc_profile.GetPrimariesOnlyColorSpace()
                               : gfx::ColorSpace::CreateSRGB();
}

}  // namespace win
}  // namespace display