// Copyright 2015 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/gfx/buffer_format_util.h"

#include "base/check_op.h"
#include "base/notreached.h"
#include "base/numerics/safe_math.h"
#include "ui/gfx/switches.h"

namespace gfx {
namespace {

const BufferFormat kBufferFormats[] = {
    BufferFormat::R_8,          BufferFormat::R_16,
    BufferFormat::RG_88,        BufferFormat::RG_1616,
    BufferFormat::BGR_565,      BufferFormat::RGBA_4444,
    BufferFormat::RGBX_8888,    BufferFormat::RGBA_8888,
    BufferFormat::BGRX_8888,    BufferFormat::BGRA_1010102,
    BufferFormat::RGBA_1010102, BufferFormat::BGRA_8888,
    BufferFormat::RGBA_F16,     BufferFormat::YUV_420_BIPLANAR,
    BufferFormat::YVU_420,      BufferFormat::YUVA_420_TRIPLANAR,
    BufferFormat::P010};

static_assert(std::size(kBufferFormats) ==
                  (static_cast<int>(BufferFormat::LAST) + 1),
              "BufferFormat::LAST must be last value of kBufferFormats");

}  // namespace

std::vector<BufferFormat> GetBufferFormatsForTesting() {
  return std::vector<BufferFormat>(kBufferFormats,
                                   kBufferFormats + std::size(kBufferFormats));
}

size_t AlphaBitsForBufferFormat(BufferFormat format) {
  switch (format) {
    case BufferFormat::RGBA_4444:
      return 4;
    case BufferFormat::RGBA_8888:
      return 8;
    case BufferFormat::BGRA_1010102:
    case BufferFormat::RGBA_1010102:
      return 2;
    case BufferFormat::BGRA_8888:
    case BufferFormat::YUVA_420_TRIPLANAR:
      return 8;
    case BufferFormat::RGBA_F16:
      return 16;
    case BufferFormat::R_8:
    case BufferFormat::R_16:
    case BufferFormat::RG_88:
    case BufferFormat::RG_1616:
    case BufferFormat::BGR_565:
    case BufferFormat::RGBX_8888:
    case BufferFormat::BGRX_8888:
    case BufferFormat::YVU_420:
    case BufferFormat::YUV_420_BIPLANAR:
    case BufferFormat::P010:
      return 0;
  }
  NOTREACHED();
  return 0;
}

size_t NumberOfPlanesForLinearBufferFormat(BufferFormat format) {
  switch (format) {
    case BufferFormat::R_8:
    case BufferFormat::R_16:
    case BufferFormat::RG_88:
    case BufferFormat::RG_1616:
    case BufferFormat::BGR_565:
    case BufferFormat::RGBA_4444:
    case BufferFormat::RGBX_8888:
    case BufferFormat::RGBA_8888:
    case BufferFormat::BGRX_8888:
    case BufferFormat::BGRA_1010102:
    case BufferFormat::RGBA_1010102:
    case BufferFormat::BGRA_8888:
    case BufferFormat::RGBA_F16:
      return 1;
    case BufferFormat::YUV_420_BIPLANAR:
    case BufferFormat::P010:
      return 2;
    case BufferFormat::YVU_420:
    case BufferFormat::YUVA_420_TRIPLANAR:
      return 3;
  }
  NOTREACHED();
  return 0;
}

size_t SubsamplingFactorForBufferFormat(BufferFormat format, size_t plane) {
  switch (format) {
    case BufferFormat::R_8:
    case BufferFormat::R_16:
    case BufferFormat::RG_88:
    case BufferFormat::RG_1616:
    case BufferFormat::BGR_565:
    case BufferFormat::RGBA_4444:
    case BufferFormat::RGBX_8888:
    case BufferFormat::RGBA_8888:
    case BufferFormat::BGRX_8888:
    case BufferFormat::BGRA_1010102:
    case BufferFormat::RGBA_1010102:
    case BufferFormat::BGRA_8888:
    case BufferFormat::RGBA_F16:
      return 1;
    case BufferFormat::YVU_420: {
      constexpr size_t factor[] = {1, 2, 2};
      DCHECK_LT(plane, std::size(factor));
      return factor[plane];
    }
    case BufferFormat::YUV_420_BIPLANAR:
    case BufferFormat::P010: {
      constexpr size_t factor[] = {1, 2};
      DCHECK_LT(plane, std::size(factor));
      return factor[plane];
    }
    case BufferFormat::YUVA_420_TRIPLANAR: {
      constexpr size_t factor[] = {1, 2, 1};
      DCHECK_LT(plane, std::size(factor));
      return factor[plane];
    }
  }
  NOTREACHED();
  return 0;
}

base::CheckedNumeric<size_t> PlaneWidthForBufferFormatChecked(
    size_t width,
    BufferFormat format,
    size_t plane) {
  const size_t subsample = SubsamplingFactorForBufferFormat(format, plane);
  return base::CheckDiv(base::CheckAdd(width, base::CheckSub(subsample, 1)),
                        subsample);
}

base::CheckedNumeric<size_t> PlaneHeightForBufferFormatChecked(
    size_t height,
    BufferFormat format,
    size_t plane) {
  const size_t subsample = SubsamplingFactorForBufferFormat(format, plane);
  return base::CheckDiv(base::CheckAdd(height, base::CheckSub(subsample, 1)),
                        subsample);
}

size_t BytesPerPixelForBufferFormat(BufferFormat format, size_t plane) {
  switch (format) {
    case BufferFormat::R_8:
      return 1;
    case BufferFormat::R_16:
    case BufferFormat::RG_88:
    case BufferFormat::BGR_565:
    case BufferFormat::RGBA_4444:
      return 2;
    case BufferFormat::RG_1616:
    case BufferFormat::BGRX_8888:
    case BufferFormat::BGRA_1010102:
    case BufferFormat::RGBA_1010102:
    case BufferFormat::RGBX_8888:
    case BufferFormat::RGBA_8888:
    case BufferFormat::BGRA_8888:
      return 4;
    case BufferFormat::RGBA_F16:
      return 8;
    case BufferFormat::YVU_420:
      return 1;
    case BufferFormat::YUV_420_BIPLANAR:
    case BufferFormat::YUVA_420_TRIPLANAR:
      return SubsamplingFactorForBufferFormat(format, plane);
    case BufferFormat::P010:
      return 2 * SubsamplingFactorForBufferFormat(format, plane);
  }
  NOTREACHED();
  return 0;
}

size_t RowByteAlignmentForBufferFormat(BufferFormat format, size_t plane) {
  switch (format) {
    case BufferFormat::R_8:
    case BufferFormat::R_16:
    case BufferFormat::RG_88:
    case BufferFormat::BGR_565:
    case BufferFormat::RGBA_4444:
    case BufferFormat::RG_1616:
    case BufferFormat::BGRX_8888:
    case BufferFormat::BGRA_1010102:
    case BufferFormat::RGBA_1010102:
    case BufferFormat::RGBX_8888:
    case BufferFormat::RGBA_8888:
    case BufferFormat::BGRA_8888:
      return 4;
    case BufferFormat::RGBA_F16:
      return 8;
    case BufferFormat::YVU_420:
      return 1;
    case BufferFormat::YUV_420_BIPLANAR:
    case BufferFormat::YUVA_420_TRIPLANAR:
    case BufferFormat::P010:
      return BytesPerPixelForBufferFormat(format, plane);
  }
  NOTREACHED();
  return 0;
}

size_t RowSizeForBufferFormat(size_t width, BufferFormat format, size_t plane) {
  size_t row_size = 0;
  bool valid = RowSizeForBufferFormatChecked(width, format, plane, &row_size);
  DCHECK(valid);
  return row_size;
}

bool RowSizeForBufferFormatChecked(size_t width,
                                   BufferFormat format,
                                   size_t plane,
                                   size_t* size_in_bytes) {
  base::CheckedNumeric<size_t> checked_size =
      PlaneWidthForBufferFormatChecked(width, format, plane);
  checked_size *= BytesPerPixelForBufferFormat(format, plane);
  const size_t alignment = RowByteAlignmentForBufferFormat(format, plane);
  checked_size = (checked_size + alignment - 1) & ~(alignment - 1);
  if (!checked_size.IsValid())
    return false;

  *size_in_bytes = checked_size.ValueOrDie();
  return true;
}

size_t PlaneSizeForBufferFormat(const Size& size,
                                BufferFormat format,
                                size_t plane) {
  size_t plane_size = 0;
  bool valid =
      PlaneSizeForBufferFormatChecked(size, format, plane, &plane_size);
  DCHECK(valid);
  return plane_size;
}

bool PlaneSizeForBufferFormatChecked(const Size& size,
                                     BufferFormat format,
                                     size_t plane,
                                     size_t* size_in_bytes) {
  size_t row_size = 0;
  if (!RowSizeForBufferFormatChecked(base::checked_cast<size_t>(size.width()),
                                     format, plane, &row_size)) {
    return false;
  }
  base::CheckedNumeric<size_t> checked_plane_size = row_size;
  checked_plane_size *= PlaneHeightForBufferFormatChecked(
      base::checked_cast<size_t>(size.height()), format, plane);
  if (!checked_plane_size.IsValid())
    return false;

  *size_in_bytes = checked_plane_size.ValueOrDie();
  return true;
}

size_t BufferSizeForBufferFormat(const Size& size, BufferFormat format) {
  size_t buffer_size = 0;
  bool valid = BufferSizeForBufferFormatChecked(size, format, &buffer_size);
  DCHECK(valid);
  return buffer_size;
}

bool BufferSizeForBufferFormatChecked(const Size& size,
                                      BufferFormat format,
                                      size_t* size_in_bytes) {
  base::CheckedNumeric<size_t> checked_size = 0;
  size_t num_planes = NumberOfPlanesForLinearBufferFormat(format);
  for (size_t i = 0; i < num_planes; ++i) {
    size_t plane_size = 0;
    if (!PlaneSizeForBufferFormatChecked(size, format, i, &plane_size))
      return false;
    checked_size += plane_size;
    if (!checked_size.IsValid())
      return false;
  }

  *size_in_bytes = checked_size.ValueOrDie();
  return true;
}

size_t BufferOffsetForBufferFormat(const Size& size,
                                   BufferFormat format,
                                   size_t plane) {
  DCHECK_LT(plane, gfx::NumberOfPlanesForLinearBufferFormat(format));

  switch (format) {
    case BufferFormat::R_8:
    case BufferFormat::R_16:
    case BufferFormat::RG_88:
    case BufferFormat::RG_1616:
    case BufferFormat::BGR_565:
    case BufferFormat::RGBA_4444:
    case BufferFormat::RGBX_8888:
    case BufferFormat::RGBA_8888:
    case BufferFormat::BGRX_8888:
    case BufferFormat::BGRA_1010102:
    case BufferFormat::RGBA_1010102:
    case BufferFormat::BGRA_8888:
    case BufferFormat::RGBA_F16:
      return 0;
    case BufferFormat::YVU_420:
    case BufferFormat::YUV_420_BIPLANAR:
    case BufferFormat::YUVA_420_TRIPLANAR:
    case BufferFormat::P010: {
      size_t offset = 0;
      for (size_t i = 0; i < plane; i++) {
        offset += PlaneSizeForBufferFormat(size, format, i);
      }
      return offset;
    }
  }
  NOTREACHED();
  return 0;
}

const char* BufferFormatToString(BufferFormat format) {
  switch (format) {
    case BufferFormat::R_8:
      return "R_8";
    case BufferFormat::R_16:
      return "R_16";
    case BufferFormat::RG_88:
      return "RG_88";
    case BufferFormat::RG_1616:
      return "RG_1616";
    case BufferFormat::BGR_565:
      return "BGR_565";
    case BufferFormat::RGBA_4444:
      return "RGBA_4444";
    case BufferFormat::RGBX_8888:
      return "RGBX_8888";
    case BufferFormat::RGBA_8888:
      return "RGBA_8888";
    case BufferFormat::BGRX_8888:
      return "BGRX_8888";
    case BufferFormat::BGRA_1010102:
      return "BGRA_1010102";
    case BufferFormat::RGBA_1010102:
      return "RGBA_1010102";
    case BufferFormat::BGRA_8888:
      return "BGRA_8888";
    case BufferFormat::RGBA_F16:
      return "RGBA_F16";
    case BufferFormat::YVU_420:
      return "YVU_420";
    case BufferFormat::YUV_420_BIPLANAR:
      return "YUV_420_BIPLANAR";
    case BufferFormat::YUVA_420_TRIPLANAR:
      return "YUVA_420_TRIPLANAR";
    case BufferFormat::P010:
      return "P010";
  }
  NOTREACHED()
      << "Invalid BufferFormat: "
      << static_cast<typename std::underlying_type<BufferFormat>::type>(format);
  return "Invalid Format";
}

const char* BufferPlaneToString(BufferPlane format) {
  switch (format) {
    case BufferPlane::DEFAULT:
      return "DEFAULT";
    case BufferPlane::Y:
      return "Y";
    case BufferPlane::UV:
      return "UV";
    case BufferPlane::U:
      return "U";
    case BufferPlane::V:
      return "V";
    case BufferPlane::A:
      return "A";
  }
  NOTREACHED() << "Invalid BufferPlane: "
               << static_cast<typename std::underlying_type<BufferPlane>::type>(
                      format);
  return "Invalid Plane";
}

bool IsOddHeightMultiPlanarBuffersAllowed() {
  return base::FeatureList::IsEnabled(features::kOddHeightMultiPlanarBuffers);
}

bool IsOddWidthMultiPlanarBuffersAllowed() {
  return base::FeatureList::IsEnabled(features::kOddWidthMultiPlanarBuffers);
}

}  // namespace gfx