chromium/components/exo/wayland/zcr_color_manager.cc

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

#include "components/exo/wayland/zcr_color_manager.h"

#include <chrome-color-management-server-protocol.h>
#include <wayland-server-core.h>

#include <algorithm>
#include <cstdint>
#include <memory>

#include "ash/shell.h"
#include "base/logging.h"
#include "base/memory/raw_ptr.h"
#include "base/notreached.h"
#include "base/strings/stringprintf.h"
#include "components/exo/surface.h"
#include "components/exo/surface_observer.h"
#include "components/exo/wayland/server.h"
#include "components/exo/wayland/server_util.h"
#include "components/exo/wayland/wayland_display_observer.h"
#include "components/exo/wayland/wayland_display_output.h"
#include "components/exo/wm_helper.h"
#include "third_party/skia/include/core/SkColorSpace.h"
#include "third_party/skia/modules/skcms/skcms.h"
#include "ui/base/wayland/color_manager_util.h"
#include "ui/display/display.h"
#include "ui/display/display_features.h"
#include "ui/display/display_observer.h"
#include "ui/display/manager/display_manager.h"
#include "ui/display/screen.h"
#include "ui/display/types/display_constants.h"
#include "ui/gfx/color_space.h"
#include "ui/gfx/display_color_spaces.h"
#include "ui/gfx/geometry/triangle_f.h"

namespace exo {
namespace wayland {

namespace {

#define PARAM_TO_FLOAT(x) (x / 10000.f)
#define FLOAT_TO_PARAM(x) (x * 10000)

constexpr auto kDefaultColorSpace = gfx::ColorSpace::CreateSRGB();

// Wrapper around a |gfx::ColorSpace| that tracks additional data useful to
// the protocol. These live as wayland resource data.
class ColorManagerColorSpace {
 public:
  explicit ColorManagerColorSpace(gfx::ColorSpace color_space, uint32_t version)
      : color_space(color_space),
        eotf(ui::wayland::ToColorManagerEOTF(color_space, version)),
        matrix(ui::wayland::ToColorManagerMatrix(color_space.GetMatrixID(),
                                                 version)),
        range(ui::wayland::ToColorManagerRange(color_space.GetRangeID(),
                                               version)),
        primaries(color_space.GetPrimaries()) {}

  ColorManagerColorSpace(gfx::ColorSpace color_space,
                         zcr_color_manager_v1_eotf_names eotf,
                         zcr_color_manager_v1_matrix_names matrix,
                         zcr_color_manager_v1_range_names range,
                         SkColorSpacePrimaries primaries)
      : color_space(color_space),
        eotf(eotf),
        matrix(matrix),
        range(range),
        primaries(primaries) {}

  virtual ~ColorManagerColorSpace() = default;

  const gfx::ColorSpace color_space;
  const zcr_color_manager_v1_eotf_names eotf;
  const zcr_color_manager_v1_matrix_names matrix;
  const zcr_color_manager_v1_range_names range;
  const SkColorSpacePrimaries primaries;

  void SendColorSpaceInfo(wl_resource* color_space_resource) {
    SendCustomColorSpaceInfo(color_space_resource);
    if (wl_resource_get_version(color_space_resource) <
        ZCR_COLOR_SPACE_V1_COMPLETE_PARAMS_SINCE_VERSION) {
      zcr_color_space_v1_send_params(
          color_space_resource, eotf,
          static_cast<int>(FLOAT_TO_PARAM(primaries.fRX)),
          static_cast<int>(FLOAT_TO_PARAM(primaries.fRY)),
          static_cast<int>(FLOAT_TO_PARAM(primaries.fGX)),
          static_cast<int>(FLOAT_TO_PARAM(primaries.fGY)),
          static_cast<int>(FLOAT_TO_PARAM(primaries.fBX)),
          static_cast<int>(FLOAT_TO_PARAM(primaries.fBY)),
          static_cast<int>(FLOAT_TO_PARAM(primaries.fWX)),
          static_cast<int>(FLOAT_TO_PARAM(primaries.fWY)));
    } else {
      zcr_color_space_v1_send_complete_params(
          color_space_resource, eotf, matrix, range,
          static_cast<int>(FLOAT_TO_PARAM(primaries.fRX)),
          static_cast<int>(FLOAT_TO_PARAM(primaries.fRY)),
          static_cast<int>(FLOAT_TO_PARAM(primaries.fGX)),
          static_cast<int>(FLOAT_TO_PARAM(primaries.fGY)),
          static_cast<int>(FLOAT_TO_PARAM(primaries.fBX)),
          static_cast<int>(FLOAT_TO_PARAM(primaries.fBY)),
          static_cast<int>(FLOAT_TO_PARAM(primaries.fWX)),
          static_cast<int>(FLOAT_TO_PARAM(primaries.fWY)));
    }
    zcr_color_space_v1_send_done(color_space_resource);
  }

  virtual void SendCustomColorSpaceInfo(wl_resource* color_space_resource) {}
};

class NameBasedColorSpace final : public ColorManagerColorSpace {
 public:
  explicit NameBasedColorSpace(
      gfx::ColorSpace color_space,
      zcr_color_manager_v1_chromaticity_names chromaticity,
      zcr_color_manager_v1_eotf_names eotf,
      zcr_color_manager_v1_matrix_names matrix,
      zcr_color_manager_v1_range_names range,
      zcr_color_manager_v1_whitepoint_names whitepoint)
      : ColorManagerColorSpace(color_space,
                               eotf,
                               matrix,
                               range,
                               color_space.GetPrimaries()),
        chromaticity(chromaticity),
        whitepoint(whitepoint) {}

  const zcr_color_manager_v1_chromaticity_names chromaticity;
  const zcr_color_manager_v1_whitepoint_names whitepoint;

  void SendCustomColorSpaceInfo(wl_resource* color_space_resource) override {
    if (wl_resource_get_version(color_space_resource) <
        ZCR_COLOR_SPACE_V1_COMPLETE_NAMES_SINCE_VERSION) {
      zcr_color_space_v1_send_names(color_space_resource, eotf, chromaticity,
                                    whitepoint);
    } else {
      zcr_color_space_v1_send_complete_names(
          color_space_resource, eotf, chromaticity, whitepoint, matrix, range);
    }
  }
};

// Wrap a surface pointer and handle relevant events.
// TODO(b/207031122): This class should also watch for display color space
// changes and update clients.
class ColorManagerSurface final : public SurfaceObserver {
 public:
  explicit ColorManagerSurface(Server* server,
                               wl_resource* color_manager_surface_resource,
                               Surface* surface)
      : server_(server),
        color_manager_surface_resource_(color_manager_surface_resource),
        scoped_surface_(std::make_unique<ScopedSurface>(surface, this)) {}
  ColorManagerSurface(ColorManagerSurface&) = delete;
  ColorManagerSurface(ColorManagerSurface&&) = delete;
  ~ColorManagerSurface() override = default;

  // Safely set the color space (doing nothing if the surface was destroyed).
  void SetColorSpace(gfx::ColorSpace color_space) {
    Surface* surface = scoped_surface_->get();
    if (!surface)
      return;

    surface->SetColorSpace(color_space);
  }

 private:
  // SurfaceObserver:
  void OnDisplayChanged(Surface* surface,
                        int64_t old_display,
                        int64_t new_display) override {
    wl_client* client = wl_resource_get_client(color_manager_surface_resource_);
    wl_resource* display_resource =
        server_->GetOutputResource(client, new_display);

    if (!display_resource)
      return;

    const auto* wm_helper = WMHelper::GetInstance();

    if (old_display != display::kInvalidDisplayId) {
      const auto& old_display_info = wm_helper->GetDisplayInfo(old_display);
      const auto& new_display_info = wm_helper->GetDisplayInfo(new_display);

      if (old_display_info.display_color_spaces() ==
          new_display_info.display_color_spaces())
        return;
    }

    zcr_color_management_surface_v1_send_preferred_color_space(
        color_manager_surface_resource_, display_resource);
  }

  void OnSurfaceDestroying(Surface* surface) override {
    scoped_surface_.reset();
  }

  raw_ptr<Server> server_;
  raw_ptr<wl_resource> color_manager_surface_resource_;
  std::unique_ptr<ScopedSurface> scoped_surface_;
};

class ColorManagerObserver : public WaylandDisplayObserver {
 public:
  ColorManagerObserver(WaylandDisplayHandler* wayland_display_handler,
                       wl_resource* color_management_output_resource,
                       wl_resource* output_resource)
      : wayland_display_handler_(wayland_display_handler),
        color_management_output_resource_(color_management_output_resource),
        output_resource_(output_resource) {
    wayland_display_handler->AddObserver(this);
  }

  ColorManagerObserver(const ColorManagerObserver&) = delete;
  ColorManagerObserver& operator=(const ColorManagerObserver&) = delete;

  ~ColorManagerObserver() override {
    if (wayland_display_handler_)
      wayland_display_handler_->RemoveObserver(this);
  }

  gfx::ColorSpace GetColorSpace() const {
    if (!wayland_display_handler_) {
      LOG(WARNING) << "Wayland output was destroyed and not replaced.";
      return gfx::ColorSpace::CreateSRGB();
    }

    // Lacros only checks if the colorspace is HDR or not. So send display
    // colorspace if HDR is possible, otherwise just send SRGB.
    if (base::FeatureList::IsEnabled(
            display::features::kUseHDRTransferFunction)) {
      // Snapshot ColorSpace is only valid for ScreenAsh.
      return ash::Shell::Get()
          ->display_manager()
          ->GetDisplayInfo(wayland_display_handler_->id())
          .GetSnapshotColorSpace();
    }
    return gfx::ColorSpace::CreateSRGB();
  }

  WaylandDisplayHandler* wayland_display_handler() {
    return wayland_display_handler_;
  }

  wl_resource* GetOutputResource() { return output_resource_; }

  // Overriden from WaylandDisplayObserver.
  void OnOutputDestroyed() override {
    wayland_display_handler_->RemoveObserver(this);
    wayland_display_handler_ = nullptr;
  }

  // Overridden from WaylandDisplayObserver.
  bool SendDisplayMetrics(const display::Display& display,
                          uint32_t changed_metrics) override {
    if (!(changed_metrics &
          display::DisplayObserver::DISPLAY_METRIC_COLOR_SPACE)) {
      return false;
    }

    zcr_color_management_output_v1_send_color_space_changed(
        color_management_output_resource_);
    return true;
  }
  void SendActiveDisplay() override {}

 private:
  raw_ptr<WaylandDisplayHandler> wayland_display_handler_;
  const raw_ptr<wl_resource> color_management_output_resource_;
  raw_ptr<wl_resource, DanglingUntriaged> output_resource_;
};

////////////////////////////////////////////////////////////////////////////////
// zcr_color_management_color_space_v1_interface:

void color_space_get_information(struct wl_client* client,
                                 struct wl_resource* color_space_resource) {
  GetUserDataAs<ColorManagerColorSpace>(color_space_resource)
      ->SendColorSpaceInfo(color_space_resource);
}

void color_space_destroy(struct wl_client* client,
                         struct wl_resource* color_space_resource) {
  wl_resource_destroy(color_space_resource);
}

const struct zcr_color_space_v1_interface color_space_v1_implementation = {
    color_space_get_information, color_space_destroy};

////////////////////////////////////////////////////////////////////////////////
// zcr_color_management_output_v1_interface:

void color_management_output_get_color_space(
    struct wl_client* client,
    struct wl_resource* color_management_output_resource,
    uint32_t id) {
  auto* color_management_output_observer =
      GetUserDataAs<ColorManagerObserver>(color_management_output_resource);

  // create new zcr color space for the current color space of the output
  auto color_space = std::make_unique<ColorManagerColorSpace>(
      color_management_output_observer->GetColorSpace(),
      wl_resource_get_version(color_management_output_resource));

  wl_resource* color_space_resource = wl_resource_create(
      client, &zcr_color_space_v1_interface,
      wl_resource_get_version(color_management_output_resource), id);

  SetImplementation(color_space_resource, &color_space_v1_implementation,
                    std::move(color_space));
}

void color_management_output_destroy(
    struct wl_client* client,
    struct wl_resource* color_management_output_resource) {
  wl_resource_destroy(color_management_output_resource);
}

const struct zcr_color_management_output_v1_interface
    color_management_output_v1_implementation = {
        color_management_output_get_color_space,
        color_management_output_destroy};

////////////////////////////////////////////////////////////////////////////////
// zcr_color_management_surface_v1_interface:

void color_management_surface_set_alpha_mode(
    struct wl_client* client,
    struct wl_resource* color_management_surface_resource,
    uint32_t alpha_mode) {
  NOTIMPLEMENTED();
}

void color_management_surface_set_extended_dynamic_range(
    struct wl_client* client,
    struct wl_resource* color_management_surface_resource,
    uint32_t value) {
  NOTIMPLEMENTED();
}

void color_management_surface_set_color_space(
    struct wl_client* client,
    struct wl_resource* color_management_surface_resource,
    struct wl_resource* color_space_resource,
    uint32_t render_intent) {
  auto* color_manager_color_space =
      GetUserDataAs<ColorManagerColorSpace>(color_space_resource);
  GetUserDataAs<ColorManagerSurface>(color_management_surface_resource)
      ->SetColorSpace(color_manager_color_space->color_space);
}

void color_management_surface_set_default_color_space(
    struct wl_client* client,
    struct wl_resource* color_management_surface_resource) {
  GetUserDataAs<ColorManagerSurface>(color_management_surface_resource)
      ->SetColorSpace(kDefaultColorSpace);
}

void color_management_surface_destroy(
    struct wl_client* client,
    struct wl_resource* color_management_surface_resource) {
  GetUserDataAs<ColorManagerSurface>(color_management_surface_resource)
      ->SetColorSpace(kDefaultColorSpace);

  wl_resource_destroy(color_management_surface_resource);
}

const struct zcr_color_management_surface_v1_interface
    color_management_surface_v1_implementation = {
        color_management_surface_set_alpha_mode,
        color_management_surface_set_extended_dynamic_range,
        color_management_surface_set_color_space,
        color_management_surface_set_default_color_space,
        color_management_surface_destroy};

////////////////////////////////////////////////////////////////////////////////
// zcr_color_manager_v1_interface:

void CreateColorSpace(struct wl_client* client,
                      int32_t resource_version,
                      int32_t color_space_creator_id,
                      std::unique_ptr<ColorManagerColorSpace> color_space) {
  wl_resource* color_space_resource = wl_resource_create(
      client, &zcr_color_space_v1_interface, resource_version, /*id=*/0);
  wl_resource* color_space_creator_resource =
      wl_resource_create(client, &zcr_color_space_creator_v1_interface,
                         resource_version, color_space_creator_id);
  SetImplementation(color_space_resource, &color_space_v1_implementation,
                    std::move(color_space));
  zcr_color_space_creator_v1_send_created(color_space_creator_resource,
                                          color_space_resource);
  // The resource should be immediately destroyed once it's sent its event.
  wl_resource_destroy(color_space_creator_resource);
}

void SendColorCreationError(struct wl_client* client,
                            int32_t color_space_creator_id,
                            int32_t error_flags) {
  wl_resource* color_space_creator_resource = wl_resource_create(
      client, &zcr_color_space_creator_v1_interface, 1, color_space_creator_id);

  zcr_color_space_creator_v1_send_error(color_space_creator_resource,
                                        error_flags);

  // The resource should be immediately destroyed once it's sent its event.
  wl_resource_destroy(color_space_creator_resource);
}

void color_manager_create_color_space_from_icc(
    struct wl_client* client,
    struct wl_resource* color_manager_resource,
    uint32_t id,
    int32_t icc) {
  NOTIMPLEMENTED();
}

// Not all MatrixID and RangeID combinations are supported in DRM/KMS
// so alter their values to something more acceptable in those cases.
void adjust_matrix_and_range(gfx::ColorSpace::MatrixID* matrix_id,
                             gfx::ColorSpace::RangeID* range_id) {
  // TODO(b/233667677): Passing MatrixID=BT2020_NCL and RangeID=LIMITED results
  // in improper rendering, until that is supported default to {RGB, FULL} which
  // is identical to composited video values.
  if (*matrix_id == gfx::ColorSpace::MatrixID::BT2020_NCL) {
    *matrix_id = gfx::ColorSpace::MatrixID::RGB;
    *range_id = gfx::ColorSpace::RangeID::FULL;
  }
}

// TODO(b/206971557): This doesn't handle the user-set white point yet.
void color_manager_create_color_space_from_complete_names(
    struct wl_client* client,
    struct wl_resource* color_manager_resource,
    uint32_t id,
    uint32_t eotf,
    uint32_t chromaticity,
    uint32_t whitepoint,
    uint32_t matrix,
    uint32_t range) {
  uint32_t error_flags = 0;

  auto chromaticity_id = gfx::ColorSpace::PrimaryID::INVALID;
  const auto maybe_primary = ui::wayland::kChromaticityMap.find(chromaticity);
  if (maybe_primary != std::end(ui::wayland::kChromaticityMap)) {
    chromaticity_id = maybe_primary->second.primary;
  } else {
    DLOG(ERROR) << "Unable to find named chromaticity for id=" << chromaticity;
    error_flags |= ZCR_COLOR_SPACE_CREATOR_V1_CREATION_ERROR_BAD_PRIMARIES;
  }

  auto matrix_id = gfx::ColorSpace::MatrixID::INVALID;
  const auto maybe_matrix = ui::wayland::kMatrixMap.find(matrix);
  if (maybe_matrix != std::end(ui::wayland::kMatrixMap)) {
    matrix_id = maybe_matrix->second.matrix;
  } else {
    DLOG(ERROR) << "Unable to find named matrix for id=" << matrix;
    wl_resource_post_error(color_manager_resource,
                           ZCR_COLOR_MANAGER_V1_ERROR_BAD_ENUM,
                           "Unable to find a matrix matching %d", matrix);
  }

  auto range_id = gfx::ColorSpace::RangeID::INVALID;
  const auto maybe_range = ui::wayland::kRangeMap.find(range);
  if (maybe_range != std::end(ui::wayland::kRangeMap)) {
    range_id = maybe_range->second.range;
  } else {
    DLOG(ERROR) << "Unable to find named range for id=" << range;
    wl_resource_post_error(color_manager_resource,
                           ZCR_COLOR_MANAGER_V1_ERROR_BAD_ENUM,
                           "Unable to find a range matching %d", range);
  }
  adjust_matrix_and_range(&matrix_id, &range_id);

  auto eotf_id = gfx::ColorSpace::TransferID::INVALID;
  const auto maybe_eotf = ui::wayland::kEotfMap.find(eotf);
  if (maybe_eotf != std::end(ui::wayland::kEotfMap)) {
    eotf_id = maybe_eotf->second.transfer;
  } else if (ui::wayland::kHDRTransferMap.contains(eotf)) {
    auto transfer_fn = ui::wayland::kHDRTransferMap.at(eotf).transfer_fn;
    CreateColorSpace(
        client, wl_resource_get_version(color_manager_resource), id,
        std::make_unique<NameBasedColorSpace>(
            gfx::ColorSpace(chromaticity_id,
                            gfx::ColorSpace::TransferID::CUSTOM_HDR, matrix_id,
                            range_id, nullptr, &transfer_fn),
            static_cast<zcr_color_manager_v1_chromaticity_names>(chromaticity),
            static_cast<zcr_color_manager_v1_eotf_names>(eotf),
            static_cast<zcr_color_manager_v1_matrix_names>(matrix),
            static_cast<zcr_color_manager_v1_range_names>(range),
            static_cast<zcr_color_manager_v1_whitepoint_names>(whitepoint)));
    return;
  } else {
    DLOG(ERROR) << "Unable to find named eotf for id=" << eotf;
    wl_resource_post_error(color_manager_resource,
                           ZCR_COLOR_MANAGER_V1_ERROR_BAD_ENUM,
                           "Unable to find an EOTF matching %d", eotf);
  }

  if (error_flags)
    SendColorCreationError(client, id, error_flags);

  CreateColorSpace(
      client, wl_resource_get_version(color_manager_resource), id,
      std::make_unique<NameBasedColorSpace>(
          gfx::ColorSpace(chromaticity_id, eotf_id, matrix_id, range_id),
          static_cast<zcr_color_manager_v1_chromaticity_names>(chromaticity),
          static_cast<zcr_color_manager_v1_eotf_names>(eotf),
          static_cast<zcr_color_manager_v1_matrix_names>(matrix),
          static_cast<zcr_color_manager_v1_range_names>(range),
          static_cast<zcr_color_manager_v1_whitepoint_names>(whitepoint)));
}

void color_manager_create_color_space_from_names_DEPRECATED(
    struct wl_client* client,
    struct wl_resource* color_manager_resource,
    uint32_t id,
    uint32_t eotf,
    uint32_t chromaticity,
    uint32_t whitepoint) {
  LOG(WARNING) << "Deprecated request:  create_color_space_from_names";
  color_manager_create_color_space_from_complete_names(
      client, color_manager_resource, id, eotf,
      ZCR_COLOR_MANAGER_V1_MATRIX_NAMES_RGB,
      ZCR_COLOR_MANAGER_V1_RANGE_NAMES_FULL, chromaticity, whitepoint);
}

void color_manager_create_color_space_from_complete_params(
    struct wl_client* client,
    struct wl_resource* color_manager_resource,
    uint32_t id,
    uint32_t eotf,
    uint32_t matrix,
    uint32_t range,
    uint32_t primary_r_x,
    uint32_t primary_r_y,
    uint32_t primary_g_x,
    uint32_t primary_g_y,
    uint32_t primary_b_x,
    uint32_t primary_b_y,
    uint32_t white_point_x,
    uint32_t white_point_y) {
  SkColorSpacePrimaries primaries = {
      PARAM_TO_FLOAT(primary_r_x),   PARAM_TO_FLOAT(primary_r_y),
      PARAM_TO_FLOAT(primary_g_x),   PARAM_TO_FLOAT(primary_g_y),
      PARAM_TO_FLOAT(primary_b_x),   PARAM_TO_FLOAT(primary_b_y),
      PARAM_TO_FLOAT(white_point_x), PARAM_TO_FLOAT(white_point_y)};

  gfx::PointF r(primaries.fRX, primaries.fRY);
  gfx::PointF g(primaries.fGX, primaries.fGY);
  gfx::PointF b(primaries.fBX, primaries.fBY);
  gfx::PointF w(primaries.fWX, primaries.fWY);
  if (!gfx::PointIsInTriangle(w, r, g, b)) {
    auto error_message = base::StringPrintf(
        "White point %s must be inside of the triangle r=%s g=%s b=%s",
        w.ToString().c_str(), r.ToString().c_str(), g.ToString().c_str(),
        b.ToString().c_str());
    DLOG(ERROR) << error_message;
    wl_resource_post_error(color_manager_resource,
                           ZCR_COLOR_MANAGER_V1_ERROR_BAD_PARAM, "%s",
                           error_message.c_str());
    return;
  }

  auto matrix_id = gfx::ColorSpace::MatrixID::INVALID;
  const auto maybe_matrix = ui::wayland::kMatrixMap.find(matrix);
  if (maybe_matrix != std::end(ui::wayland::kMatrixMap)) {
    matrix_id = maybe_matrix->second.matrix;
  } else {
    DLOG(ERROR) << "Unable to find named matrix for id=" << matrix;
    wl_resource_post_error(color_manager_resource,
                           ZCR_COLOR_MANAGER_V1_ERROR_BAD_ENUM,
                           "Unable to find a matrix matching %d", matrix);
  }

  auto range_id = gfx::ColorSpace::RangeID::INVALID;
  const auto maybe_range = ui::wayland::kRangeMap.find(range);
  if (maybe_range != std::end(ui::wayland::kRangeMap)) {
    range_id = maybe_range->second.range;
  } else {
    DLOG(ERROR) << "Unable to find named range for id=" << range;
    wl_resource_post_error(color_manager_resource,
                           ZCR_COLOR_MANAGER_V1_ERROR_BAD_ENUM,
                           "Unable to find a range matching %d", range);
  }
  adjust_matrix_and_range(&matrix_id, &range_id);

  auto eotf_id = gfx::ColorSpace::TransferID::INVALID;
  const auto maybe_eotf = ui::wayland::kEotfMap.find(eotf);
  if (maybe_eotf != std::end(ui::wayland::kEotfMap)) {
    eotf_id = maybe_eotf->second.transfer;
  } else {
    DLOG(ERROR) << "Unable to find named transfer function for id=" << eotf;
    wl_resource_post_error(color_manager_resource,
                           ZCR_COLOR_MANAGER_V1_ERROR_BAD_ENUM,
                           "Unable to find an EOTF matching %d", eotf);
    return;
  }

  skcms_Matrix3x3 xyzd50 = {};
  if (!primaries.toXYZD50(&xyzd50)) {
    DLOG(ERROR) << base::StringPrintf(
        "Unable to translate color space primaries to XYZD50: "
        "{%f, %f, %f, %f, %f, %f, %f, %f}",
        primaries.fRX, primaries.fRY, primaries.fGX, primaries.fGY,
        primaries.fBX, primaries.fBY, primaries.fWX, primaries.fWY);

    SendColorCreationError(
        client, id, ZCR_COLOR_SPACE_CREATOR_V1_CREATION_ERROR_BAD_PRIMARIES);
    return;
  }

  auto primary_id = gfx::ColorSpace::PrimaryID::CUSTOM;
  CreateColorSpace(client, wl_resource_get_version(color_manager_resource), id,
                   std::make_unique<ColorManagerColorSpace>(
                       gfx::ColorSpace(primary_id, eotf_id, matrix_id, range_id,
                                       &xyzd50, nullptr),
                       kZcrColorManagerVersion));
}

void color_manager_create_color_space_from_params_DEPRECATED(
    struct wl_client* client,
    struct wl_resource* color_manager_resource,
    uint32_t id,
    uint32_t eotf,
    uint32_t primary_r_x,
    uint32_t primary_r_y,
    uint32_t primary_g_x,
    uint32_t primary_g_y,
    uint32_t primary_b_x,
    uint32_t primary_b_y,
    uint32_t white_point_x,
    uint32_t white_point_y) {
  LOG(WARNING) << "Deprecated request:  create_color_space_from_params";
  color_manager_create_color_space_from_complete_params(
      client, color_manager_resource, id, eotf,
      ZCR_COLOR_MANAGER_V1_MATRIX_NAMES_RGB,
      ZCR_COLOR_MANAGER_V1_RANGE_NAMES_FULL, primary_r_x, primary_r_y,
      primary_g_x, primary_g_y, primary_b_x, primary_b_y, white_point_x,
      white_point_y);
}

void color_manager_get_color_management_output(
    struct wl_client* client,
    struct wl_resource* color_manager_resource,
    uint32_t id,
    struct wl_resource* output) {
  wl_resource* color_management_output_resource =
      wl_resource_create(client, &zcr_color_management_output_v1_interface,
                         wl_resource_get_version(color_manager_resource), id);
  auto* display_handler = GetUserDataAs<WaylandDisplayHandler>(output);
  auto color_management_output_observer =
      std::make_unique<ColorManagerObserver>(
          display_handler, color_management_output_resource, output);
  SetImplementation(color_management_output_resource,
                    &color_management_output_v1_implementation,
                    std::move(color_management_output_observer));
}

void color_manager_get_color_management_surface(
    struct wl_client* client,
    struct wl_resource* color_manager_resource,
    uint32_t id,
    struct wl_resource* surface_resource) {
  wl_resource* color_management_surface_resource =
      wl_resource_create(client, &zcr_color_management_surface_v1_interface,
                         wl_resource_get_version(color_manager_resource), id);

  SetImplementation(color_management_surface_resource,
                    &color_management_surface_v1_implementation,
                    std::make_unique<ColorManagerSurface>(
                        GetUserDataAs<Server>(color_manager_resource),
                        color_management_surface_resource,
                        GetUserDataAs<Surface>(surface_resource)));
}

void color_manager_destroy(struct wl_client* client,
                           struct wl_resource* color_manager_resource) {
  wl_resource_destroy(color_manager_resource);
}

const struct zcr_color_manager_v1_interface color_manager_v1_implementation = {
    color_manager_create_color_space_from_icc,
    color_manager_create_color_space_from_names_DEPRECATED,
    color_manager_create_color_space_from_params_DEPRECATED,
    color_manager_get_color_management_output,
    color_manager_get_color_management_surface,
    color_manager_destroy,
    color_manager_create_color_space_from_complete_names,
    color_manager_create_color_space_from_complete_params};

}  // namespace

void bind_zcr_color_manager(wl_client* client,
                            void* data,
                            uint32_t version,
                            uint32_t id) {
  wl_resource* color_manager_resource =
      wl_resource_create(client, &zcr_color_manager_v1_interface,
                         std::min(version, kZcrColorManagerVersion), id);

  wl_resource_set_implementation(color_manager_resource,
                                 &color_manager_v1_implementation, data,
                                 /*destroy=*/nullptr);
}

}  // namespace wayland
}  // namespace exo