/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#ifndef MOZILLA_GFX_TYPES_H_
#define MOZILLA_GFX_TYPES_H_

#include "mozilla/DefineEnum.h"  // for MOZ_DEFINE_ENUM_CLASS_WITH_BASE
#include "mozilla/EndianUtils.h"
#include "mozilla/EnumeratedRange.h"
#include "mozilla/MacroArgs.h"  // for MOZ_CONCAT
#include "mozilla/TypedEnumBits.h"

#include <iosfwd>  // for ostream
#include <stddef.h>
#include <stdint.h>
#include <optional>

namespace mozilla {
namespace gfx {

typedef float Float;
typedef double Double;

enum class SurfaceType : int8_t {
  DATA,                   /* Data surface - bitmap in memory */
  CAIRO,                  /* Surface wrapping a cairo surface */
  CAIRO_IMAGE,            /* Data surface wrapping a cairo image surface */
  COREGRAPHICS_IMAGE,     /* Surface wrapping a CoreGraphics Image */
  COREGRAPHICS_CGCONTEXT, /* Surface wrapping a CG context */
  SKIA,                   /* Surface wrapping a Skia bitmap */
  RECORDING,              /* Surface used for recording */
  DATA_SHARED,            /* Data surface using shared memory */
  DATA_RECYCLING_SHARED,  /* Data surface using shared memory */
  OFFSET,                 /* Offset */
  DATA_ALIGNED,           /* Data surface using aligned heap memory */
  DATA_SHARED_WRAPPER,    /* Shared memory mapped in from another process */
  BLOB_IMAGE,             /* Recorded blob image */
  DATA_MAPPED,            /* Data surface wrapping a ScopedMap */
  WEBGL,                  /* Surface wrapping a DrawTargetWebgl texture */
  D3D11_TEXTURE,          /* Surface wrapping a D3D11Texture */
};

enum class SurfaceFormat : int8_t {
  // The following values are named to reflect layout of colors in memory, from
  // lowest byte to highest byte. The 32-bit value layout depends on machine
  // endianness.
  //               in-memory            32-bit LE value   32-bit BE value
  B8G8R8A8,  // [BB, GG, RR, AA]     0xAARRGGBB        0xBBGGRRAA
  B8G8R8X8,  // [BB, GG, RR, 00]     0x00RRGGBB        0xBBGGRR00
  R8G8B8A8,  // [RR, GG, BB, AA]     0xAABBGGRR        0xRRGGBBAA
  R8G8B8X8,  // [RR, GG, BB, 00]     0x00BBGGRR        0xRRGGBB00
  A8R8G8B8,  // [AA, RR, GG, BB]     0xBBGGRRAA        0xAARRGGBB
  X8R8G8B8,  // [00, RR, GG, BB]     0xBBGGRR00        0x00RRGGBB

  R8G8B8,
  B8G8R8,

  // The _UINT16 suffix here indicates that the name reflects the layout when
  // viewed as a uint16_t value. In memory these values are stored using native
  // endianness.
  R5G6B5_UINT16,  // 0bRRRRRGGGGGGBBBBB

  // This one is a single-byte, so endianness isn't an issue.
  A8,
  A16,

  R8G8,
  R16G16,

  // These ones are their own special cases.
  YUV420,     // Sometimes called YU12. 3 planes of 8 bit Y, then Cb, then Cr.
              // 4:2:0 chroma subsampling.
  YUV420P10,  // YUV420 but with 16 bit plane values where the 6 most
              // significant bits are 0 (so it's 10-bit format).
  YUV422P10,  // 3 planes like YUV420, but with 4:2:2 chroma subampling and
              // 16 bit plane values where the 6 least significant bits are 0.
  NV12,       // 2 planes. YUV 4:2:0 image with a plane of 8 bit Y samples
              // followed by an interleaved U/V plane containing 8 bit 2x2
              // subsampled colour difference samples.
  P016,       // Similar to NV12, but with 16 bits plane values
  P010,       // Identical to P016 but the 6 least significant bits are 0.
              // With DXGI in theory entirely compatible, however practice has
              // shown that it's not the case.
  NV16,       // Similar to NV12, but with 4:2:2 chroma subsampling. Technically
              // 8 bit, but we only use it for 10 bit, and it's really only here
              // to support the macOS bi-planar 422 formats.
  YUY2,       // Sometimes called YUYV. Single plane / packed YUV 4:2:2 8 bit
              // samples interleaved as Y`0 Cb Y`1 Cr. Since 4 pixels require
              // 64 bits, this can also be considered a 16bpp format, but each
              // component is only 8 bits. We sometimes pack RGBA data into
              // this format.
  HSV,
  Lab,
  Depth,

  // This represents the unknown format.
  UNKNOWN,  // TODO: Replace uses with Maybe<SurfaceFormat>.

// The following values are endian-independent synonyms. The _UINT32 suffix
// indicates that the name reflects the layout when viewed as a uint32_t
// value.
#if MOZ_LITTLE_ENDIAN()
  A8R8G8B8_UINT32 = B8G8R8A8,  // 0xAARRGGBB
  X8R8G8B8_UINT32 = B8G8R8X8,  // 0x00RRGGBB
#elif MOZ_BIG_ENDIAN()
  A8R8G8B8_UINT32 = A8R8G8B8,  // 0xAARRGGBB
  X8R8G8B8_UINT32 = X8R8G8B8,  // 0x00RRGGBB
#else
#  error "bad endianness"
#endif

  // The following values are OS and endian-independent synonyms.
  //
  // TODO(aosmond): When everything blocking bug 1581828 has been resolved, we
  // can make this use R8B8G8A8 and R8B8G8X8 for non-Windows platforms.
  OS_RGBA = A8R8G8B8_UINT32,
  OS_RGBX = X8R8G8B8_UINT32
};

enum class SubpixelOrder : uint8_t {
  UNKNOWN,
  RGB,
  BGR,
  VRGB,
  VBGR,
};

struct SurfaceFormatInfo {
  bool hasColor;
  bool hasAlpha;
  bool isYuv;
  std::optional<uint8_t> bytesPerPixel;
};
inline std::optional<SurfaceFormatInfo> Info(const SurfaceFormat aFormat) {
  auto info = SurfaceFormatInfo{};

  switch (aFormat) {
    case SurfaceFormat::B8G8R8A8:
    case SurfaceFormat::R8G8B8A8:
    case SurfaceFormat::A8R8G8B8:
      info.hasColor = true;
      info.hasAlpha = true;
      break;

    case SurfaceFormat::B8G8R8X8:
    case SurfaceFormat::R8G8B8X8:
    case SurfaceFormat::X8R8G8B8:
    case SurfaceFormat::R8G8B8:
    case SurfaceFormat::B8G8R8:
    case SurfaceFormat::R5G6B5_UINT16:
    case SurfaceFormat::R8G8:
    case SurfaceFormat::R16G16:
    case SurfaceFormat::HSV:
    case SurfaceFormat::Lab:
      info.hasColor = true;
      info.hasAlpha = false;
      break;

    case SurfaceFormat::A8:
    case SurfaceFormat::A16:
      info.hasColor = false;
      info.hasAlpha = true;
      break;

    case SurfaceFormat::YUV420:
    case SurfaceFormat::YUV420P10:
    case SurfaceFormat::YUV422P10:
    case SurfaceFormat::NV12:
    case SurfaceFormat::P016:
    case SurfaceFormat::P010:
    case SurfaceFormat::NV16:
    case SurfaceFormat::YUY2:
      info.hasColor = true;
      info.hasAlpha = false;
      info.isYuv = true;
      break;

    case SurfaceFormat::Depth:
      info.hasColor = false;
      info.hasAlpha = false;
      info.isYuv = false;
      break;

    case SurfaceFormat::UNKNOWN:
      break;
  }

  // -
  // bytesPerPixel

  switch (aFormat) {
    case SurfaceFormat::B8G8R8A8:
    case SurfaceFormat::R8G8B8A8:
    case SurfaceFormat::A8R8G8B8:
    case SurfaceFormat::B8G8R8X8:
    case SurfaceFormat::R8G8B8X8:
    case SurfaceFormat::X8R8G8B8:
    case SurfaceFormat::R16G16:
      info.bytesPerPixel = 4;
      break;

    case SurfaceFormat::R8G8B8:
    case SurfaceFormat::B8G8R8:
      info.bytesPerPixel = 3;
      break;

    case SurfaceFormat::R5G6B5_UINT16:
    case SurfaceFormat::R8G8:
    case SurfaceFormat::A16:
    case SurfaceFormat::Depth:  // uint16_t
      info.bytesPerPixel = 2;
      break;

    case SurfaceFormat::A8:
      info.bytesPerPixel = 1;
      break;

    case SurfaceFormat::HSV:
    case SurfaceFormat::Lab:
      info.bytesPerPixel = 3 * sizeof(float);
      break;

    case SurfaceFormat::YUV420:
    case SurfaceFormat::YUV420P10:
    case SurfaceFormat::YUV422P10:
    case SurfaceFormat::NV12:
    case SurfaceFormat::P016:
    case SurfaceFormat::P010:
    case SurfaceFormat::NV16:
    case SurfaceFormat::YUY2:
    case SurfaceFormat::UNKNOWN:
      break;  // No bytesPerPixel per se.
  }

  // -

  if (aFormat == SurfaceFormat::UNKNOWN) {
    return {};
  }
  return info;
}

std::ostream& operator<<(std::ostream& aOut, const SurfaceFormat& aFormat);

// Represents the bit-shifts required to access color channels when the layout
// is viewed as a uint32_t value.
enum class SurfaceFormatBit : uint32_t {
#if MOZ_LITTLE_ENDIAN()
  R8G8B8A8_R = 0,
  R8G8B8A8_G = 8,
  R8G8B8A8_B = 16,
  R8G8B8A8_A = 24,
#elif MOZ_BIG_ENDIAN()
  R8G8B8A8_A = 0,
  R8G8B8A8_B = 8,
  R8G8B8A8_G = 16,
  R8G8B8A8_R = 24,
#else
#  error "bad endianness"
#endif

  // The following values are endian-independent for A8R8G8B8_UINT32.
  A8R8G8B8_UINT32_B = 0,
  A8R8G8B8_UINT32_G = 8,
  A8R8G8B8_UINT32_R = 16,
  A8R8G8B8_UINT32_A = 24,

  // The following values are OS and endian-independent.
  //
  // TODO(aosmond): When everything blocking bug 1581828 has been resolved, we
  // can make this use R8G8B8A8_X for non-Windows platforms.
  OS_R = A8R8G8B8_UINT32_R,
  OS_G = A8R8G8B8_UINT32_G,
  OS_B = A8R8G8B8_UINT32_B,
  OS_A = A8R8G8B8_UINT32_A,
};

inline uint32_t operator<<(uint8_t a, SurfaceFormatBit b) {
  return a << static_cast<uint32_t>(b);
}

inline uint32_t operator>>(uint32_t a, SurfaceFormatBit b) {
  return a >> static_cast<uint32_t>(b);
}

static inline int BytesPerPixel(SurfaceFormat aFormat) {
  // TODO: return Info(aFormat).value().bytesPerPixel.value();
  switch (aFormat) {
    case SurfaceFormat::A8:
      return 1;
    case SurfaceFormat::R5G6B5_UINT16:
    case SurfaceFormat::A16:
      return 2;
    case SurfaceFormat::R8G8B8:
    case SurfaceFormat::B8G8R8:
      return 3;
    case SurfaceFormat::HSV:
    case SurfaceFormat::Lab:
      return 3 * sizeof(float);
    case SurfaceFormat::Depth:
      return sizeof(uint16_t);
    default:
      return 4;
  }
}

inline bool IsOpaque(SurfaceFormat aFormat) {
  // TODO: return Info(aFormat).value().hasAlpha;
  switch (aFormat) {
    case SurfaceFormat::B8G8R8X8:
    case SurfaceFormat::R8G8B8X8:
    case SurfaceFormat::X8R8G8B8:
    case SurfaceFormat::R5G6B5_UINT16:
    case SurfaceFormat::R8G8B8:
    case SurfaceFormat::B8G8R8:
    case SurfaceFormat::R8G8:
    case SurfaceFormat::HSV:
    case SurfaceFormat::Lab:
    case SurfaceFormat::Depth:
    case SurfaceFormat::YUV420:
    case SurfaceFormat::NV12:
    case SurfaceFormat::P010:
    case SurfaceFormat::P016:
    case SurfaceFormat::YUY2:
      return true;
    default:
      return false;
  }
}

// These are standardized Coding-independent Code Points
// See [Rec. ITU-T H.273
// (12/2016)](https://www.itu.int/rec/T-REC-H.273-201612-I/en)
//
// We deliberately use an unscoped enum with fixed uint8_t representation since
// all possible values [0, 255] are legal, but it's unwieldy to declare 200+
// "RESERVED" enumeration values. Having a fixed underlying type avoids any
// potential UB and avoids the need for a cast when passing these values across
// FFI to functions like qcms_profile_create_cicp.
namespace CICP {
enum ColourPrimaries : uint8_t {
  CP_RESERVED_MIN = 0,  // 0, 3, [13, 21], [23, 255] are all reserved
  CP_BT709 = 1,
  CP_UNSPECIFIED = 2,
  CP_BT470M = 4,
  CP_BT470BG = 5,
  CP_BT601 = 6,
  CP_SMPTE240 = 7,
  CP_GENERIC_FILM = 8,
  CP_BT2020 = 9,
  CP_XYZ = 10,
  CP_SMPTE431 = 11,
  CP_SMPTE432 = 12,
  CP_EBU3213 = 22,
};

inline bool IsReserved(ColourPrimaries aIn) {
  switch (aIn) {
    case CP_BT709:
    case CP_UNSPECIFIED:
    case CP_BT470M:
    case CP_BT470BG:
    case CP_BT601:
    case CP_SMPTE240:
    case CP_GENERIC_FILM:
    case CP_BT2020:
    case CP_XYZ:
    case CP_SMPTE431:
    case CP_SMPTE432:
    case CP_EBU3213:
      return false;
    default:
      return true;
  }
}

enum TransferCharacteristics : uint8_t {
  TC_RESERVED_MIN = 0,  // 0, 3, [19, 255] are all reserved
  TC_BT709 = 1,
  TC_UNSPECIFIED = 2,
  TC_BT470M = 4,
  TC_BT470BG = 5,
  TC_BT601 = 6,
  TC_SMPTE240 = 7,
  TC_LINEAR = 8,
  TC_LOG_100 = 9,
  TC_LOG_100_SQRT10 = 10,
  TC_IEC61966 = 11,
  TC_BT_1361 = 12,
  TC_SRGB = 13,
  TC_BT2020_10BIT = 14,
  TC_BT2020_12BIT = 15,
  TC_SMPTE2084 = 16,
  TC_SMPTE428 = 17,
  TC_HLG = 18,
};

inline bool IsReserved(TransferCharacteristics aIn) {
  switch (aIn) {
    case TC_BT709:
    case TC_UNSPECIFIED:
    case TC_BT470M:
    case TC_BT470BG:
    case TC_BT601:
    case TC_SMPTE240:
    case TC_LINEAR:
    case TC_LOG_100:
    case TC_LOG_100_SQRT10:
    case TC_IEC61966:
    case TC_BT_1361:
    case TC_SRGB:
    case TC_BT2020_10BIT:
    case TC_BT2020_12BIT:
    case TC_SMPTE2084:
    case TC_SMPTE428:
    case TC_HLG:
      return false;
    default:
      return true;
  }
}

enum MatrixCoefficients : uint8_t {
  MC_IDENTITY = 0,
  MC_BT709 = 1,
  MC_UNSPECIFIED = 2,
  MC_RESERVED_MIN = 3,  // 3, [15, 255] are all reserved
  MC_FCC = 4,
  MC_BT470BG = 5,
  MC_BT601 = 6,
  MC_SMPTE240 = 7,
  MC_YCGCO = 8,
  MC_BT2020_NCL = 9,
  MC_BT2020_CL = 10,
  MC_SMPTE2085 = 11,
  MC_CHROMAT_NCL = 12,
  MC_CHROMAT_CL = 13,
  MC_ICTCP = 14,
};

inline bool IsReserved(MatrixCoefficients aIn) {
  switch (aIn) {
    case MC_IDENTITY:
    case MC_BT709:
    case MC_UNSPECIFIED:
    case MC_RESERVED_MIN:
    case MC_FCC:
    case MC_BT470BG:
    case MC_BT601:
    case MC_SMPTE240:
    case MC_YCGCO:
    case MC_BT2020_NCL:
    case MC_BT2020_CL:
    case MC_SMPTE2085:
    case MC_CHROMAT_NCL:
    case MC_CHROMAT_CL:
    case MC_ICTCP:
      return false;
    default:
      return true;
  }
}
}  // namespace CICP

// The matrix coeffiecients used for YUV to RGB conversion.
enum class YUVColorSpace : uint8_t {
  BT601,
  BT709,
  BT2020,
  Identity,  // Todo: s/YUVColorSpace/ColorSpace/, s/Identity/SRGB/
  Default = BT709,
  _First = BT601,
  _Last = Identity,
};

enum class ColorDepth : uint8_t {
  COLOR_8,
  COLOR_10,
  COLOR_12,
  COLOR_16,
  _First = COLOR_8,
  _Last = COLOR_16,
};

std::ostream& operator<<(std::ostream& aOut, const ColorDepth& aColorDepth);

enum class TransferFunction : uint8_t {
  BT709,
  SRGB,
  PQ,
  HLG,
  _First = BT709,
  _Last = HLG,
  Default = BT709,
};

enum class ColorRange : uint8_t {
  LIMITED,
  FULL,
  _First = LIMITED,
  _Last = FULL,
};

// Really "YcbcrColorColorSpace"
enum class YUVRangedColorSpace : uint8_t {
  BT601_Narrow = 0,
  BT601_Full,
  BT709_Narrow,
  BT709_Full,
  BT2020_Narrow,
  BT2020_Full,
  GbrIdentity,

  _First = BT601_Narrow,
  _Last = GbrIdentity,
  Default = BT709_Narrow,
};

// I can either come up with a longer "very clever" name that doesn't conflict
// with FilterSupport.h, embrace and expand FilterSupport, or rename the old
// one.
// Some times Worse Is Better.
enum class ColorSpace2 : uint8_t {
  Display,
  UNKNOWN = Display,  // We feel sufficiently bad about this TODO.
  SRGB,
  DISPLAY_P3,
  BT601_525,  // aka smpte170m NTSC
  BT709,      // Same gamut as SRGB, but different gamma.
  BT601_625 =
      BT709,  // aka bt470bg PAL. Basically BT709, just Xg is 0.290 not 0.300.
  BT2020,
  _First = Display,
  _Last = BT2020,
};

inline ColorSpace2 ToColorSpace2(const YUVColorSpace in) {
  switch (in) {
    case YUVColorSpace::BT601:
      return ColorSpace2::BT601_525;
    case YUVColorSpace::BT709:
      return ColorSpace2::BT709;
    case YUVColorSpace::BT2020:
      return ColorSpace2::BT2020;
    case YUVColorSpace::Identity:
      return ColorSpace2::SRGB;
  }
  MOZ_ASSERT_UNREACHABLE();
}

inline YUVColorSpace ToYUVColorSpace(const ColorSpace2 in) {
  switch (in) {
    case ColorSpace2::BT601_525:
      return YUVColorSpace::BT601;
    case ColorSpace2::BT709:
      return YUVColorSpace::BT709;
    case ColorSpace2::BT2020:
      return YUVColorSpace::BT2020;
    case ColorSpace2::SRGB:
      return YUVColorSpace::Identity;

    case ColorSpace2::UNKNOWN:
    case ColorSpace2::DISPLAY_P3:
      MOZ_CRASH("Bad ColorSpace2 for ToYUVColorSpace");
  }
  MOZ_ASSERT_UNREACHABLE();
}

struct FromYUVRangedColorSpaceT final {
  const YUVColorSpace space;
  const ColorRange range;
};

inline FromYUVRangedColorSpaceT FromYUVRangedColorSpace(
    const YUVRangedColorSpace s) {
  switch (s) {
    case YUVRangedColorSpace::BT601_Narrow:
      return {YUVColorSpace::BT601, ColorRange::LIMITED};
    case YUVRangedColorSpace::BT601_Full:
      return {YUVColorSpace::BT601, ColorRange::FULL};

    case YUVRangedColorSpace::BT709_Narrow:
      return {YUVColorSpace::BT709, ColorRange::LIMITED};
    case YUVRangedColorSpace::BT709_Full:
      return {YUVColorSpace::BT709, ColorRange::FULL};

    case YUVRangedColorSpace::BT2020_Narrow:
      return {YUVColorSpace::BT2020, ColorRange::LIMITED};
    case YUVRangedColorSpace::BT2020_Full:
      return {YUVColorSpace::BT2020, ColorRange::FULL};

    case YUVRangedColorSpace::GbrIdentity:
      return {YUVColorSpace::Identity, ColorRange::FULL};
  }
  MOZ_CRASH("bad YUVRangedColorSpace");
}

// Todo: This should go in the CPP.
inline YUVRangedColorSpace ToYUVRangedColorSpace(const YUVColorSpace space,
                                                 const ColorRange range) {
  bool narrow;
  switch (range) {
    case ColorRange::FULL:
      narrow = false;
      break;
    case ColorRange::LIMITED:
      narrow = true;
      break;
  }

  switch (space) {
    case YUVColorSpace::Identity:
      MOZ_ASSERT(range == ColorRange::FULL);
      return YUVRangedColorSpace::GbrIdentity;

    case YUVColorSpace::BT601:
      return narrow ? YUVRangedColorSpace::BT601_Narrow
                    : YUVRangedColorSpace::BT601_Full;

    case YUVColorSpace::BT709:
      return narrow ? YUVRangedColorSpace::BT709_Narrow
                    : YUVRangedColorSpace::BT709_Full;

    case YUVColorSpace::BT2020:
      return narrow ? YUVRangedColorSpace::BT2020_Narrow
                    : YUVRangedColorSpace::BT2020_Full;
  }
  MOZ_CRASH("bad YUVColorSpace");
}

template <typename DescriptorT>
inline YUVRangedColorSpace GetYUVRangedColorSpace(const DescriptorT& d) {
  return ToYUVRangedColorSpace(d.yUVColorSpace(), d.colorRange());
}

static inline SurfaceFormat SurfaceFormatForColorDepth(ColorDepth aColorDepth) {
  SurfaceFormat format = SurfaceFormat::A8;
  switch (aColorDepth) {
    case ColorDepth::COLOR_8:
      break;
    case ColorDepth::COLOR_10:
    case ColorDepth::COLOR_12:
    case ColorDepth::COLOR_16:
      format = SurfaceFormat::A16;
      break;
  }
  return format;
}

static inline uint8_t BitDepthForColorDepth(ColorDepth aColorDepth) {
  uint8_t depth = 8;
  switch (aColorDepth) {
    case ColorDepth::COLOR_8:
      break;
    case ColorDepth::COLOR_10:
      depth = 10;
      break;
    case ColorDepth::COLOR_12:
      depth = 12;
      break;
    case ColorDepth::COLOR_16:
      depth = 16;
      break;
  }
  return depth;
}

static inline ColorDepth ColorDepthForBitDepth(uint8_t aBitDepth) {
  ColorDepth depth = ColorDepth::COLOR_8;
  switch (aBitDepth) {
    case 8:
      break;
    case 10:
      depth = ColorDepth::COLOR_10;
      break;
    case 12:
      depth = ColorDepth::COLOR_12;
      break;
    case 16:
      depth = ColorDepth::COLOR_16;
      break;
  }
  return depth;
}

// 10 and 12 bits color depth image are using 16 bits integers for storage
// As such we need to rescale the value from 10 or 12 bits to 16.
static inline uint32_t RescalingFactorForColorDepth(ColorDepth aColorDepth) {
  uint32_t factor = 1;
  switch (aColorDepth) {
    case ColorDepth::COLOR_8:
      break;
    case ColorDepth::COLOR_10:
      factor = 64;
      break;
    case ColorDepth::COLOR_12:
      factor = 16;
      break;
    case ColorDepth::COLOR_16:
      break;
  }
  return factor;
}

enum class ChromaSubsampling : uint8_t {
  FULL,
  HALF_WIDTH,
  HALF_WIDTH_AND_HEIGHT,
  _First = FULL,
  _Last = HALF_WIDTH_AND_HEIGHT,
};

template <typename T>
static inline T ChromaSize(const T& aYSize, ChromaSubsampling aSubsampling) {
  switch (aSubsampling) {
    case ChromaSubsampling::FULL:
      return aYSize;
    case ChromaSubsampling::HALF_WIDTH:
      return T((aYSize.width + 1) / 2, aYSize.height);
    case ChromaSubsampling::HALF_WIDTH_AND_HEIGHT:
      return T((aYSize.width + 1) / 2, (aYSize.height + 1) / 2);
  }
  MOZ_CRASH("bad ChromaSubsampling");
}

enum class FilterType : int8_t {
  BLEND = 0,
  TRANSFORM,
  MORPHOLOGY,
  COLOR_MATRIX,
  FLOOD,
  TILE,
  TABLE_TRANSFER,
  DISCRETE_TRANSFER,
  LINEAR_TRANSFER,
  GAMMA_TRANSFER,
  CONVOLVE_MATRIX,
  DISPLACEMENT_MAP,
  TURBULENCE,
  ARITHMETIC_COMBINE,
  COMPOSITE,
  DIRECTIONAL_BLUR,
  GAUSSIAN_BLUR,
  POINT_DIFFUSE,
  POINT_SPECULAR,
  SPOT_DIFFUSE,
  SPOT_SPECULAR,
  DISTANT_DIFFUSE,
  DISTANT_SPECULAR,
  CROP,
  PREMULTIPLY,
  UNPREMULTIPLY,
  OPACITY
};

enum class DrawTargetType : int8_t {
  SOFTWARE_RASTER = 0,
  HARDWARE_RASTER,
  VECTOR
};

enum class BackendType : int8_t {
  NONE = 0,
  CAIRO,
  SKIA,
  RECORDING,
  WEBRENDER_TEXT,
  WEBGL,

  // Add new entries above this line.
  BACKEND_LAST
};

enum class RecorderType : int8_t {
  UNKNOWN,
  PRIVATE,
  MEMORY,
  CANVAS,
  PRFILEDESC,
  WEBRENDER
};

enum class FontType : int8_t {
  DWRITE,
  GDI,
  MAC,
  FONTCONFIG,
  FREETYPE,
  UNKNOWN
};

enum class NativeSurfaceType : int8_t {
  D3D10_TEXTURE,
  CAIRO_CONTEXT,
  CGCONTEXT,
  CGCONTEXT_ACCELERATED,
  OPENGL_TEXTURE,
  WEBGL_CONTEXT
};

enum class FontStyle : int8_t { NORMAL, ITALIC, BOLD, BOLD_ITALIC };

enum class FontHinting : int8_t { NONE, LIGHT, NORMAL, FULL };

enum class CompositionOp : int8_t {
  OP_CLEAR,
  OP_OVER,
  OP_ADD,
  OP_ATOP,
  OP_OUT,
  OP_IN,
  OP_SOURCE,
  OP_DEST_IN,
  OP_DEST_OUT,
  OP_DEST_OVER,
  OP_DEST_ATOP,
  OP_XOR,
  OP_MULTIPLY,
  OP_SCREEN,
  OP_OVERLAY,
  OP_DARKEN,
  OP_LIGHTEN,
  OP_COLOR_DODGE,
  OP_COLOR_BURN,
  OP_HARD_LIGHT,
  OP_SOFT_LIGHT,
  OP_DIFFERENCE,
  OP_EXCLUSION,
  OP_HUE,
  OP_SATURATION,
  OP_COLOR,
  OP_LUMINOSITY,
  OP_COUNT
};

enum class Axis : int8_t { X_AXIS, Y_AXIS, BOTH };

enum class ExtendMode : int8_t {
  CLAMP,     // Do not repeat
  REPEAT,    // Repeat in both axis
  REPEAT_X,  // Only X axis
  REPEAT_Y,  // Only Y axis
  REFLECT    // Mirror the image
};

enum class FillRule : int8_t { FILL_WINDING, FILL_EVEN_ODD };

enum class AntialiasMode : int8_t { NONE, GRAY, SUBPIXEL, DEFAULT };

// See https://en.wikipedia.org/wiki/Texture_filtering
enum class SamplingFilter : int8_t {
  GOOD,
  LINEAR,
  POINT,
  SENTINEL  // one past the last valid value
};

std::ostream& operator<<(std::ostream& aOut, const SamplingFilter& aFilter);

// clang-format off
MOZ_DEFINE_ENUM_CLASS_WITH_BASE(PatternType, int8_t, (
  COLOR,
  SURFACE,
  LINEAR_GRADIENT,
  RADIAL_GRADIENT,
  CONIC_GRADIENT
));
// clang-format on

enum class JoinStyle : int8_t {
  BEVEL,
  ROUND,
  MITER,  //!< Mitered if within the miter limit, else, if the backend supports
          //!< it, the miter is clamped. If the backend does not support miter
          //!< clamping the behavior is as for MITER_OR_BEVEL.
  MITER_OR_BEVEL  //!< Mitered if within the miter limit, else beveled.
};

enum class CapStyle : int8_t { BUTT, ROUND, SQUARE };

enum class SamplingBounds : int8_t { UNBOUNDED, BOUNDED };

// Moz2d version for SVG mask types
enum class LuminanceType : int8_t {
  LUMINANCE,
  LINEARRGB,
};

/* Color is stored in non-premultiplied form in sRGB color space */
struct sRGBColor {
 public:
  constexpr sRGBColor() : r(0.0f), g(0.0f), b(0.0f), a(0.0f) {}
  constexpr sRGBColor(Float aR, Float aG, Float aB, Float aA)
      : r(aR), g(aG), b(aB), a(aA) {}
  constexpr sRGBColor(Float aR, Float aG, Float aB)
      : r(aR), g(aG), b(aB), a(1.0f) {}

  static constexpr sRGBColor White(float aA) {
    return sRGBColor(1.f, 1.f, 1.f, aA);
  }

  static constexpr sRGBColor Black(float aA) {
    return sRGBColor(0.f, 0.f, 0.f, aA);
  }

  static constexpr sRGBColor OpaqueWhite() { return White(1.f); }

  static constexpr sRGBColor OpaqueBlack() { return Black(1.f); }

  static constexpr sRGBColor FromU8(uint8_t aR, uint8_t aG, uint8_t aB,
                                    uint8_t aA) {
    return sRGBColor(float(aR) / 255.f, float(aG) / 255.f, float(aB) / 255.f,
                     float(aA) / 255.f);
  }

  static constexpr sRGBColor FromABGR(uint32_t aColor) {
    return sRGBColor(((aColor >> 0) & 0xff) * (1.0f / 255.0f),
                     ((aColor >> 8) & 0xff) * (1.0f / 255.0f),
                     ((aColor >> 16) & 0xff) * (1.0f / 255.0f),
                     ((aColor >> 24) & 0xff) * (1.0f / 255.0f));
  }

  // The "Unusual" prefix is to avoid unintentionally using this function when
  // FromABGR(), which is much more common, is needed.
  static constexpr sRGBColor UnusualFromARGB(uint32_t aColor) {
    return sRGBColor(((aColor >> 16) & 0xff) * (1.0f / 255.0f),
                     ((aColor >> 8) & 0xff) * (1.0f / 255.0f),
                     ((aColor >> 0) & 0xff) * (1.0f / 255.0f),
                     ((aColor >> 24) & 0xff) * (1.0f / 255.0f));
  }

  constexpr uint32_t ToABGR() const {
    return uint32_t(r * 255.0f) | uint32_t(g * 255.0f) << 8 |
           uint32_t(b * 255.0f) << 16 | uint32_t(a * 255.0f) << 24;
  }

  constexpr sRGBColor Premultiplied() const {
    return sRGBColor(r * a, g * a, b * a, a);
  }

  constexpr sRGBColor Unpremultiplied() const {
    return a > 0.f ? sRGBColor(r / a, g / a, b / a, a) : *this;
  }

  // The "Unusual" prefix is to avoid unintentionally using this function when
  // ToABGR(), which is much more common, is needed.
  uint32_t UnusualToARGB() const {
    return uint32_t(b * 255.0f) | uint32_t(g * 255.0f) << 8 |
           uint32_t(r * 255.0f) << 16 | uint32_t(a * 255.0f) << 24;
  }

  bool operator==(const sRGBColor& aColor) const {
    return r == aColor.r && g == aColor.g && b == aColor.b && a == aColor.a;
  }

  bool operator!=(const sRGBColor& aColor) const { return !(*this == aColor); }

  Float r, g, b, a;
};

/* Color is stored in non-premultiplied form in device color space */
struct DeviceColor {
 public:
  constexpr DeviceColor() : r(0.0f), g(0.0f), b(0.0f), a(0.0f) {}
  constexpr DeviceColor(Float aR, Float aG, Float aB, Float aA)
      : r(aR), g(aG), b(aB), a(aA) {}
  constexpr DeviceColor(Float aR, Float aG, Float aB)
      : r(aR), g(aG), b(aB), a(1.0f) {}

  /* The following Mask* variants are helpers used to make it clear when a
   * particular color is being used for masking purposes. These masks should
   * never be colored managed. */
  static DeviceColor Mask(float aC, float aA) {
    return DeviceColor(aC, aC, aC, aA);
  }

  static DeviceColor MaskWhite(float aA) { return Mask(1.f, aA); }

  static DeviceColor MaskBlack(float aA) { return Mask(0.f, aA); }

  static DeviceColor MaskOpaqueWhite() { return MaskWhite(1.f); }

  static DeviceColor MaskOpaqueBlack() { return MaskBlack(1.f); }

  static DeviceColor FromU8(uint8_t aR, uint8_t aG, uint8_t aB, uint8_t aA) {
    return DeviceColor(float(aR) / 255.f, float(aG) / 255.f, float(aB) / 255.f,
                       float(aA) / 255.f);
  }

  static DeviceColor FromABGR(uint32_t aColor) {
    DeviceColor newColor(((aColor >> 0) & 0xff) * (1.0f / 255.0f),
                         ((aColor >> 8) & 0xff) * (1.0f / 255.0f),
                         ((aColor >> 16) & 0xff) * (1.0f / 255.0f),
                         ((aColor >> 24) & 0xff) * (1.0f / 255.0f));

    return newColor;
  }

  // The "Unusual" prefix is to avoid unintentionally using this function when
  // FromABGR(), which is much more common, is needed.
  static DeviceColor UnusualFromARGB(uint32_t aColor) {
    DeviceColor newColor(((aColor >> 16) & 0xff) * (1.0f / 255.0f),
                         ((aColor >> 8) & 0xff) * (1.0f / 255.0f),
                         ((aColor >> 0) & 0xff) * (1.0f / 255.0f),
                         ((aColor >> 24) & 0xff) * (1.0f / 255.0f));

    return newColor;
  }

  uint32_t ToABGR() const {
    return uint32_t(r * 255.0f) | uint32_t(g * 255.0f) << 8 |
           uint32_t(b * 255.0f) << 16 | uint32_t(a * 255.0f) << 24;
  }

  // The "Unusual" prefix is to avoid unintentionally using this function when
  // ToABGR(), which is much more common, is needed.
  uint32_t UnusualToARGB() const {
    return uint32_t(b * 255.0f) | uint32_t(g * 255.0f) << 8 |
           uint32_t(r * 255.0f) << 16 | uint32_t(a * 255.0f) << 24;
  }

  bool operator==(const DeviceColor& aColor) const {
    return r == aColor.r && g == aColor.g && b == aColor.b && a == aColor.a;
  }

  bool operator!=(const DeviceColor& aColor) const {
    return !(*this == aColor);
  }

  friend std::ostream& operator<<(std::ostream& aOut,
                                  const DeviceColor& aColor);

  Float r, g, b, a;
};

struct GradientStop {
  bool operator<(const GradientStop& aOther) const {
    return offset < aOther.offset;
  }

  Float offset;
  DeviceColor color;
};

enum class JobStatus { Complete, Wait, Yield, Error };

enum class DeviceResetReason {
  OK = 0,        // No reset.
  HUNG,          // Windows specific, guilty device reset.
  REMOVED,       // Windows specific, device removed or driver upgraded.
  RESET,         // Guilty device reset.
  DRIVER_ERROR,  // Innocent device reset.
  INVALID_CALL,  // Windows specific, guilty device reset.
  OUT_OF_MEMORY,
  FORCED_RESET,  // Simulated device reset.
  OTHER,         // Unrecognized reason for device reset.
  NVIDIA_VIDEO,  // Linux specific, NVIDIA video memory was reset.
  UNKNOWN,       // GL specific, unknown if guilty or innocent.
  _First = OK,
  _Last = UNKNOWN,
};

enum class DeviceResetDetectPlace {
  WR_BEGIN_FRAME = 0,
  WR_WAIT_FOR_GPU,
  WR_POST_UPDATE,
  WR_SYNC_OBJRCT,
  WR_SIMULATE,
  WIDGET,
  CANVAS_TRANSLATOR,
  _First = WR_BEGIN_FRAME,
  _Last = CANVAS_TRANSLATOR,
};

enum class ForcedDeviceResetReason {
  OPENSHAREDHANDLE = 0,
  COMPOSITOR_UPDATED,
};

}  // namespace gfx
}  // namespace mozilla

// XXX: temporary
typedef mozilla::gfx::SurfaceFormat gfxImageFormat;

#if defined(XP_WIN) && defined(MOZ_GFX)
#  ifdef GFX2D_INTERNAL
#    define GFX2D_API __declspec(dllexport)
#  else
#    define GFX2D_API __declspec(dllimport)
#  endif
#else
#  define GFX2D_API
#endif

namespace mozilla {

// Side constants for use in various places.
enum Side : uint8_t { eSideTop, eSideRight, eSideBottom, eSideLeft };

std::ostream& operator<<(std::ostream&, const mozilla::Side&);

constexpr auto AllPhysicalSides() {
  return mozilla::MakeInclusiveEnumeratedRange(eSideTop, eSideLeft);
}

enum class SideBits {
  eNone = 0,
  eTop = 1 << eSideTop,
  eRight = 1 << eSideRight,
  eBottom = 1 << eSideBottom,
  eLeft = 1 << eSideLeft,
  eTopBottom = SideBits::eTop | SideBits::eBottom,
  eLeftRight = SideBits::eLeft | SideBits::eRight,
  eAll = SideBits::eTopBottom | SideBits::eLeftRight
};

MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(SideBits)

inline constexpr SideBits SideToSideBit(mozilla::Side aSide) {
  return SideBits(1 << aSide);
}

enum Corner : uint8_t {
  // This order is important!
  eCornerTopLeft = 0,
  eCornerTopRight = 1,
  eCornerBottomRight = 2,
  eCornerBottomLeft = 3
};

// RectCornerRadii::radii depends on this value. It is not being added to
// Corner because we want to lift the responsibility to handle it in the
// switch-case.
constexpr int eCornerCount = 4;

constexpr auto AllPhysicalCorners() {
  return mozilla::MakeInclusiveEnumeratedRange(eCornerTopLeft,
                                               eCornerBottomLeft);
}

// Indices into "half corner" arrays (nsStyleCorners e.g.)
enum HalfCorner : uint8_t {
  // This order is important!
  eCornerTopLeftX = 0,
  eCornerTopLeftY = 1,
  eCornerTopRightX = 2,
  eCornerTopRightY = 3,
  eCornerBottomRightX = 4,
  eCornerBottomRightY = 5,
  eCornerBottomLeftX = 6,
  eCornerBottomLeftY = 7
};

constexpr auto AllPhysicalHalfCorners() {
  return mozilla::MakeInclusiveEnumeratedRange(eCornerTopLeftX,
                                               eCornerBottomLeftY);
}

// The result of these conversion functions are exhaustively checked in
// nsFrame.cpp, which also serves as usage examples.

constexpr bool HalfCornerIsX(HalfCorner aHalfCorner) {
  return !(aHalfCorner % 2);
}

constexpr Corner HalfToFullCorner(HalfCorner aHalfCorner) {
  return Corner(aHalfCorner / 2);
}

constexpr HalfCorner FullToHalfCorner(Corner aCorner, bool aIsVertical) {
  return HalfCorner(aCorner * 2 + aIsVertical);
}

constexpr bool SideIsVertical(mozilla::Side aSide) { return aSide % 2; }

// @param aIsSecond when true, return the clockwise second of the two
// corners associated with aSide. For example, with aSide = eSideBottom the
// result is eCornerBottomRight when aIsSecond is false, and
// eCornerBottomLeft when aIsSecond is true.
constexpr Corner SideToFullCorner(mozilla::Side aSide, bool aIsSecond) {
  return Corner((aSide + aIsSecond) % 4);
}

// @param aIsSecond see SideToFullCorner.
// @param aIsParallel return the half-corner that is parallel with aSide
// when aIsParallel is true. For example with aSide=eSideTop, aIsSecond=true
// the result is eCornerTopRightX when aIsParallel is true, and
// eCornerTopRightY when aIsParallel is false (because "X" is parallel with
// eSideTop/eSideBottom, similarly "Y" is parallel with
// eSideLeft/eSideRight)
constexpr HalfCorner SideToHalfCorner(mozilla::Side aSide, bool aIsSecond,
                                      bool aIsParallel) {
  return HalfCorner(((aSide + aIsSecond) * 2 + (aSide + !aIsParallel) % 2) % 8);
}

}  // namespace mozilla

#endif /* MOZILLA_GFX_TYPES_H_ */
