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

#include "media/base/video_util.h"

#include <array>
#include <cmath>

#include "base/bits.h"
#include "base/check_op.h"
#include "base/compiler_specific.h"
#include "base/containers/span.h"
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/logging.h"
#include "base/notreached.h"
#include "base/numerics/safe_conversions.h"
#include "base/numerics/safe_math.h"
#include "base/time/time.h"
#include "base/trace_event/trace_event.h"
#include "components/viz/common/resources/shared_image_format.h"
#include "gpu/GLES2/gl2extchromium.h"
#include "gpu/command_buffer/client/raster_interface.h"
#include "media/base/limits.h"
#include "media/base/video_frame.h"
#include "media/base/video_frame_pool.h"
#include "media/base/video_types.h"
#include "media/base/wait_and_replace_sync_token_client.h"
#include "third_party/libyuv/include/libyuv.h"
#include "third_party/skia/include/core/SkAlphaType.h"
#include "third_party/skia/include/core/SkColorType.h"
#include "third_party/skia/include/core/SkImage.h"
#include "third_party/skia/include/core/SkImageInfo.h"
#include "third_party/skia/include/core/SkPixmap.h"
#include "third_party/skia/include/core/SkRefCnt.h"
#include "third_party/skia/include/core/SkYUVAPixmaps.h"
#include "third_party/skia/include/gpu/GpuTypes.h"
#include "ui/gfx/color_space.h"

namespace media {

namespace {

// Helper to apply padding to the region outside visible rect up to the coded
// size with the repeated last column / row of the visible rect.
void FillRegionOutsideVisibleRect(uint8_t* data,
                                  size_t stride,
                                  const gfx::Size& coded_size,
                                  const gfx::Size& visible_size) {
  if (visible_size.IsEmpty()) {
    if (!coded_size.IsEmpty())
      UNSAFE_TODO(memset(data, 0, coded_size.height() * stride));
    return;
  }

  const int coded_width = coded_size.width();
  if (visible_size.width() < coded_width) {
    const int pad_length = coded_width - visible_size.width();
    uint8_t* dst = UNSAFE_TODO(data + visible_size.width());
    for (int i = 0; i < visible_size.height();
         ++i, UNSAFE_TODO(dst += stride)) {
      UNSAFE_TODO(memset(dst, *(dst - 1), pad_length));
    }
  }

  if (visible_size.height() < coded_size.height()) {
    uint8_t* dst = UNSAFE_TODO(data + visible_size.height() * stride);
    uint8_t* src = UNSAFE_TODO(dst - stride);
    for (int i = visible_size.height(); i < coded_size.height();
         ++i, UNSAFE_TODO(dst += stride)) {
      UNSAFE_TODO(memcpy(dst, src, coded_width));
    }
  }
}

VideoPixelFormat ReadbackFormat(const VideoFrame& frame) {
  // The |frame|.BitDepth() restriction is to avoid treating a P010LE frame as a
  // low-bit depth frame.
  bool si_prefers_external_sampler =
      frame.HasSharedImage() &&
      frame.shared_image()->format().PrefersExternalSampler();
  if (si_prefers_external_sampler && frame.BitDepth() == 8u) {
    return PIXEL_FORMAT_XRGB;
  }

  switch (frame.format()) {
    case PIXEL_FORMAT_I420:
    case PIXEL_FORMAT_I420A:
    case PIXEL_FORMAT_I422:
    case PIXEL_FORMAT_I444:
    case PIXEL_FORMAT_ARGB:
    case PIXEL_FORMAT_XRGB:
    case PIXEL_FORMAT_ABGR:
    case PIXEL_FORMAT_XBGR:
    case PIXEL_FORMAT_NV12:
    case PIXEL_FORMAT_NV16:
    case PIXEL_FORMAT_NV24:
    case PIXEL_FORMAT_NV12A:
    case PIXEL_FORMAT_P010LE:
    case PIXEL_FORMAT_P210LE:
    case PIXEL_FORMAT_P410LE:
      return frame.format();
    default:
      // Currently unsupported.
      return PIXEL_FORMAT_UNKNOWN;
  }
}

void LetterboxPlane(const gfx::Rect& view_area_in_bytes,
                    base::span<uint8_t> plane_data,
                    int rows,
                    int row_bytes,
                    int stride,
                    int bytes_per_element,
                    uint8_t fill_byte) {
  if (view_area_in_bytes.IsEmpty()) {
    libyuv::SetPlane(plane_data.data(), stride, row_bytes, rows, fill_byte);
    return;
  }

  if (view_area_in_bytes.y() > 0) {
    libyuv::SetPlane(plane_data.data(), stride, row_bytes,
                     view_area_in_bytes.y(), fill_byte);
    plane_data = plane_data.subspan(
        static_cast<size_t>(stride * view_area_in_bytes.y()));
  }

  if (view_area_in_bytes.width() < row_bytes) {
    if (view_area_in_bytes.x() > 0) {
      libyuv::SetPlane(plane_data.data(), stride, view_area_in_bytes.x(),
                       view_area_in_bytes.height(), fill_byte);
    }
    if (view_area_in_bytes.right() < row_bytes) {
      libyuv::SetPlane(
          plane_data.subspan(static_cast<size_t>(view_area_in_bytes.right()))
              .data(),
          stride, row_bytes - view_area_in_bytes.right(),
          view_area_in_bytes.height(), fill_byte);
    }
  }

  plane_data = plane_data.subspan(
      static_cast<size_t>(stride * view_area_in_bytes.height()));

  if (view_area_in_bytes.bottom() < rows) {
    libyuv::SetPlane(plane_data.data(), stride, row_bytes,
                     rows - view_area_in_bytes.bottom(), fill_byte);
  }
}

void LetterboxPlane(VideoFrame* frame,
                    int plane,
                    base::span<uint8_t> plane_data,
                    const gfx::Rect& view_area_in_pixels,
                    uint8_t fill_byte) {
  const int rows = frame->rows(plane);
  const int row_bytes = frame->row_bytes(plane);
  const int stride = frame->stride(plane);
  const int bytes_per_element =
      VideoFrame::BytesPerElement(frame->format(), plane);

  gfx::Rect view_area_in_bytes(view_area_in_pixels.x() * bytes_per_element,
                               view_area_in_pixels.y(),
                               view_area_in_pixels.width() * bytes_per_element,
                               view_area_in_pixels.height());

  CHECK_GE(stride, row_bytes);
  CHECK_GE(view_area_in_bytes.x(), 0);
  CHECK_GE(view_area_in_bytes.y(), 0);
  CHECK_LE(view_area_in_bytes.right(), row_bytes);
  CHECK_LE(view_area_in_bytes.bottom(), rows);

  LetterboxPlane(view_area_in_bytes, plane_data, rows, row_bytes, stride,
                 bytes_per_element, fill_byte);
}

// Helper for `LetterboxVideoFrame()`, assumes that if |frame| is
// MappableSI-backed, the frame's shared image is already mapped (via a call to
// `Map()`).
void LetterboxPlane(VideoFrame* frame,
                    gpu::ClientSharedImage::ScopedMapping* scoped_mapping,
                    int plane,
                    const gfx::Rect& view_area_in_pixels,
                    uint8_t fill_byte) {
  base::span<uint8_t> plane_data;
  if (frame->HasDirectCpuAccess()) {
    plane_data = frame->writable_span(plane);
  } else if (scoped_mapping) {
    plane_data = scoped_mapping->GetMemoryForPlane(plane);
  }
  CHECK(!plane_data.empty());

  LetterboxPlane(frame, plane, plane_data, view_area_in_pixels, fill_byte);
}

void ProcessAsyncMappingResult(
    scoped_refptr<VideoFrame> video_frame,
    base::OnceCallback<void(scoped_refptr<VideoFrame>)> result_cb,
    std::unique_ptr<gpu::ClientSharedImage::ScopedMapping> scoped_mapping) {
  CHECK(video_frame);
  if (!scoped_mapping) {
    std::move(result_cb).Run(nullptr);
    return;
  }

  const size_t num_planes = VideoFrame::NumPlanes(video_frame->format());
  std::array<base::span<uint8_t>, VideoFrame::kMaxPlanes> planes = {};
  for (size_t i = 0; i < num_planes; i++) {
    planes[i] = scoped_mapping->GetMemoryForPlane(i);
  }

  auto mapped_frame = VideoFrame::WrapExternalYuvDataWithLayout(
      video_frame->layout(), video_frame->visible_rect(),
      video_frame->natural_size(), planes[0], planes[1], planes[2],
      video_frame->timestamp());

  if (!mapped_frame) {
    std::move(result_cb).Run(nullptr);
    return;
  }

  mapped_frame->set_color_space(video_frame->ColorSpace());
  mapped_frame->set_hdr_metadata(video_frame->hdr_metadata());
  mapped_frame->metadata().MergeMetadataFrom(video_frame->metadata());

  // Pass |video_frame| so that it outlives |mapped_frame| and the mapped buffer
  // is unmapped on destruction.
  mapped_frame->AddDestructionObserver(base::BindOnce(
      [](scoped_refptr<VideoFrame> frame,
         std::unique_ptr<gpu::ClientSharedImage::ScopedMapping>
             scoped_mapping) {
        CHECK(scoped_mapping);
        // The VideoFrame::ScopedMapping must be destroyed before the
        // VideoFrame that produced it in order to avoid dangling pointers.
        scoped_mapping.reset();
      },
      std::move(video_frame), std::move(scoped_mapping)));
  std::move(result_cb).Run(std::move(mapped_frame));
}

}  // namespace

void FillYUV(VideoFrame* frame, uint8_t y, uint8_t u, uint8_t v) {
  libyuv::I420Rect(frame->writable_data(VideoFrame::Plane::kY),
                   frame->stride(VideoFrame::Plane::kY),
                   frame->writable_data(VideoFrame::Plane::kU),
                   frame->stride(VideoFrame::Plane::kU),
                   frame->writable_data(VideoFrame::Plane::kV),
                   frame->stride(VideoFrame::Plane::kV), 0, 0,
                   frame->coded_size().width(), frame->coded_size().height(), y,
                   u, v);
}

void FillYUVA(VideoFrame* frame, uint8_t y, uint8_t u, uint8_t v, uint8_t a) {
  // Fill Y, U and V planes.
  FillYUV(frame, y, u, v);

  // Fill the A plane.
  libyuv::SetPlane(frame->writable_data(VideoFrame::Plane::kA),
                   frame->stride(VideoFrame::Plane::kA),
                   frame->row_bytes(VideoFrame::Plane::kA),
                   frame->rows(VideoFrame::Plane::kA), a);
}

void LetterboxVideoFrame(VideoFrame* frame, const gfx::Rect& view_area) {
  std::unique_ptr<gpu::ClientSharedImage::ScopedMapping> scoped_mapping;
  if (frame->HasMappableSharedImage()) {
    scoped_mapping = frame->shared_image()->Map();
    CHECK(scoped_mapping);
  }

  switch (frame->format()) {
    case PIXEL_FORMAT_ARGB:
      LetterboxPlane(frame, scoped_mapping.get(), VideoFrame::Plane::kARGB,
                     view_area, 0x00);
      break;
    case PIXEL_FORMAT_YV12:
    case PIXEL_FORMAT_I420: {
      DCHECK(!(view_area.x() & 1));
      DCHECK(!(view_area.y() & 1));
      DCHECK(!(view_area.width() & 1));
      DCHECK(!(view_area.height() & 1));

      LetterboxPlane(frame, scoped_mapping.get(), VideoFrame::Plane::kY,
                     view_area, 0x00);
      gfx::Rect half_view_area(view_area.x() / 2, view_area.y() / 2,
                               view_area.width() / 2, view_area.height() / 2);
      LetterboxPlane(frame, scoped_mapping.get(), VideoFrame::Plane::kU,
                     half_view_area, 0x80);
      LetterboxPlane(frame, scoped_mapping.get(), VideoFrame::Plane::kV,
                     half_view_area, 0x80);
      break;
    }
    case PIXEL_FORMAT_NV12: {
      DCHECK(!(view_area.x() & 1));
      DCHECK(!(view_area.y() & 1));
      DCHECK(!(view_area.width() & 1));
      DCHECK(!(view_area.height() & 1));

      LetterboxPlane(frame, scoped_mapping.get(), VideoFrame::Plane::kY,
                     view_area, 0x00);
      gfx::Rect half_view_area(view_area.x() / 2, view_area.y() / 2,
                               view_area.width() / 2, view_area.height() / 2);

      LetterboxPlane(frame, scoped_mapping.get(), VideoFrame::Plane::kUV,
                     half_view_area, 0x80);
      break;
    }
    case PIXEL_FORMAT_NV12A: {
      DCHECK(!(view_area.x() & 1));
      DCHECK(!(view_area.y() & 1));
      DCHECK(!(view_area.width() & 1));
      DCHECK(!(view_area.height() & 1));

      LetterboxPlane(frame, scoped_mapping.get(), VideoFrame::Plane::kY,
                     view_area, 0x00);
      gfx::Rect half_view_area(view_area.x() / 2, view_area.y() / 2,
                               view_area.width() / 2, view_area.height() / 2);
      LetterboxPlane(frame, scoped_mapping.get(), VideoFrame::Plane::kUV,
                     half_view_area, 0x80);
      LetterboxPlane(frame, scoped_mapping.get(),
                     VideoFrame::Plane::kATriPlanar, view_area, 0x00);
      break;
    }
    default:
      NOTREACHED();
  }
}

void RotatePlaneByPixels(const uint8_t* src,
                         uint8_t* dest,
                         int width,
                         int height,
                         int rotation,  // Clockwise.
                         bool flip_vert,
                         bool flip_horiz) {
  DCHECK((width > 0) && (height > 0) &&
         ((width & 1) == 0) && ((height & 1) == 0) &&
         (rotation >= 0) && (rotation < 360) && (rotation % 90 == 0));

  // Consolidate cases. Only 0 and 90 are left.
  if (rotation == 180 || rotation == 270) {
    rotation -= 180;
    flip_vert = !flip_vert;
    flip_horiz = !flip_horiz;
  }

  int num_rows = height;
  int num_cols = width;
  int src_stride = width;
  // During pixel copying, the corresponding incremental of dest pointer
  // when src pointer moves to next row.
  int dest_row_step = width;
  // During pixel copying, the corresponding incremental of dest pointer
  // when src pointer moves to next column.
  int dest_col_step = 1;

  if (rotation == 0) {
    if (flip_horiz) {
      // Use pixel copying.
      dest_col_step = -1;
      if (flip_vert) {
        // Rotation 180.
        dest_row_step = -width;
        UNSAFE_TODO(dest += height * width - 1);
      } else {
        UNSAFE_TODO(dest += width - 1);
      }
    } else {
      if (flip_vert) {
        // Fast copy by rows.
        UNSAFE_TODO(dest += width * (height - 1));
        for (int row = 0; row < height; ++row) {
          UNSAFE_TODO(memcpy(dest, src, width));
          UNSAFE_TODO(src += width);
          UNSAFE_TODO(dest -= width);
        }
      } else {
        UNSAFE_TODO(memcpy(dest, src, width * height));
      }
      return;
    }
  } else if (rotation == 90) {
    int offset;
    if (width > height) {
      offset = (width - height) / 2;
      UNSAFE_TODO(src += offset);
      num_rows = num_cols = height;
    } else {
      offset = (height - width) / 2;
      UNSAFE_TODO(src += width * offset);
      num_rows = num_cols = width;
    }

    dest_col_step = (flip_vert ? -width : width);
    dest_row_step = (flip_horiz ? 1 : -1);
    if (flip_horiz) {
      if (flip_vert) {
        UNSAFE_TODO(dest += (width > height ? width * (height - 1) + offset
                                            : width * (height - offset - 1)));
      } else {
        UNSAFE_TODO(dest += (width > height ? offset : width * offset));
      }
    } else {
      if (flip_vert) {
        UNSAFE_TODO(dest += (width > height ? width * height - offset - 1
                                            : width * (height - offset) - 1));
      } else {
        UNSAFE_TODO(dest += (width > height ? width - offset - 1
                                            : width * (offset + 1) - 1));
      }
    }
  } else {
    NOTREACHED();
  }

  // Copy pixels.
  for (int row = 0; row < num_rows; ++row) {
    const uint8_t* src_ptr = src;
    uint8_t* dest_ptr = dest;
    for (int col = 0; col < num_cols; ++col) {
      *dest_ptr = *UNSAFE_TODO(src_ptr++);
      UNSAFE_TODO(dest_ptr += dest_col_step);
    }
    UNSAFE_TODO(src += src_stride);
    UNSAFE_TODO(dest += dest_row_step);
  }
}

// Helper function to return |a| divided by |b|, rounded to the nearest integer.
static int RoundedDivision(int64_t a, int b) {
  DCHECK_GE(a, 0);
  DCHECK_GT(b, 0);
  base::CheckedNumeric<uint64_t> result(a);
  result += b / 2;
  result /= b;
  return base::ValueOrDieForType<int>(result);
}

// Common logic for the letterboxing and scale-within/scale-encompassing
// functions.  Scales |size| to either fit within or encompass |target|,
// depending on whether |fit_within_target| is true.
static gfx::Size ScaleSizeToTarget(const gfx::Size& size,
                                   const gfx::Size& target,
                                   bool fit_within_target) {
  if (size.IsEmpty())
    return gfx::Size();  // Corner case: Aspect ratio is undefined.

  const int64_t x = static_cast<int64_t>(size.width()) * target.height();
  const int64_t y = static_cast<int64_t>(size.height()) * target.width();
  const bool use_target_width = fit_within_target ? (y < x) : (x < y);
  return use_target_width ?
      gfx::Size(target.width(), RoundedDivision(y, size.width())) :
      gfx::Size(RoundedDivision(x, size.height()), target.height());
}

gfx::Rect ComputeLetterboxRegion(const gfx::Rect& bounds,
                                 const gfx::Size& content) {
  // If |content| has an undefined aspect ratio, let's not try to divide by
  // zero.
  if (content.IsEmpty())
    return gfx::Rect();

  gfx::Rect result = bounds;
  result.ClampToCenteredSize(ScaleSizeToTarget(content, bounds.size(), true));
  return result;
}

gfx::Rect ComputeLetterboxRegionForI420(const gfx::Rect& bounds,
                                        const gfx::Size& content) {
  DCHECK_EQ(bounds.x() % 2, 0);
  DCHECK_EQ(bounds.y() % 2, 0);
  DCHECK_EQ(bounds.width() % 2, 0);
  DCHECK_EQ(bounds.height() % 2, 0);

  gfx::Rect result = ComputeLetterboxRegion(bounds, content);

  if (result.x() & 1) {
    // This is always legal since bounds.x() was even and result.x() must always
    // be greater or equal to bounds.x().
    result.set_x(result.x() - 1);

    // The result.x() was nudged to the left, so if the width is odd, it should
    // be perfectly legal to nudge it up by one to make it even.
    if (result.width() & 1)
      result.set_width(result.width() + 1);
  } else /* if (result.x() is even) */ {
    if (result.width() & 1)
      result.set_width(result.width() - 1);
  }

  if (result.y() & 1) {
    // These operations are legal for the same reasons mentioned above for
    // result.x().
    result.set_y(result.y() - 1);
    if (result.height() & 1)
      result.set_height(result.height() + 1);
  } else /* if (result.y() is even) */ {
    if (result.height() & 1)
      result.set_height(result.height() - 1);
  }

  return result;
}

gfx::Rect MinimallyShrinkRectForI420(const gfx::Rect& rect) {
  constexpr int kMinDimension = -1 * limits::kMaxDimension;
  DCHECK(gfx::Rect(kMinDimension, kMinDimension, limits::kMaxDimension * 2,
                   limits::kMaxDimension * 2)
             .Contains(rect));

  const auto positive_mod = [](int a, int b) { return (a % b + b) % b; };

  const int left = rect.x() + positive_mod(rect.x(), 2);
  const int top = rect.y() + positive_mod(rect.y(), 2);
  const int right = rect.right() - (rect.right() % 2);
  const int bottom = rect.bottom() - (rect.bottom() % 2);

  return gfx::Rect(left, top, std::max(0, right - left),
                   std::max(0, bottom - top));
}

gfx::Size ScaleSizeToFitWithinTarget(const gfx::Size& size,
                                     const gfx::Size& target) {
  return ScaleSizeToTarget(size, target, true);
}

gfx::Size ScaleSizeToEncompassTarget(const gfx::Size& size,
                                     const gfx::Size& target) {
  return ScaleSizeToTarget(size, target, false);
}

gfx::Rect CropSizeForScalingToTarget(const gfx::Size& size,
                                     const gfx::Size& target,
                                     size_t alignment) {
  DCHECK_GT(alignment, 0u);
  if (size.IsEmpty() || target.IsEmpty())
    return gfx::Rect();

  gfx::Rect crop(ScaleSizeToFitWithinTarget(target, size));
  crop.set_width(base::checked_cast<int>(base::bits::AlignDown(
      base::checked_cast<size_t>(crop.width()), alignment)));
  crop.set_height(base::checked_cast<int>(base::bits::AlignDown(
      base::checked_cast<size_t>(crop.height()), alignment)));
  crop.set_x(base::checked_cast<int>(base::bits::AlignDown(
      base::checked_cast<size_t>((size.width() - crop.width()) / 2),
      alignment)));
  crop.set_y(base::checked_cast<int>(base::bits::AlignDown(
      base::checked_cast<size_t>((size.height() - crop.height()) / 2),
      alignment)));
  DCHECK(gfx::Rect(size).Contains(crop));
  return crop;
}

gfx::Size GetRectSizeFromOrigin(const gfx::Rect& rect) {
  return gfx::Size(rect.right(), rect.bottom());
}

gfx::Size PadToMatchAspectRatio(const gfx::Size& size,
                                const gfx::Size& target) {
  if (target.IsEmpty())
    return gfx::Size();  // Aspect ratio is undefined.

  const int64_t x = static_cast<int64_t>(size.width()) * target.height();
  const int64_t y = static_cast<int64_t>(size.height()) * target.width();
  if (x < y)
    return gfx::Size(RoundedDivision(y, target.height()), size.height());
  return gfx::Size(size.width(), RoundedDivision(x, target.width()));
}

scoped_refptr<VideoFrame> ConvertToMemoryMappedFrame(
    scoped_refptr<VideoFrame> video_frame) {
  CHECK(video_frame);
  CHECK(video_frame->HasMappableSharedImage());

  auto scoped_mapping = video_frame->shared_image()->Map();
  if (!scoped_mapping) {
    return nullptr;
  }

  const size_t num_planes = VideoFrame::NumPlanes(video_frame->format());
  std::array<base::span<uint8_t>, VideoFrame::kMaxPlanes> planes = {};
  for (size_t i = 0; i < num_planes; i++)
    planes[i] = scoped_mapping->GetMemoryForPlane(i);

  auto mapped_frame = VideoFrame::WrapExternalYuvDataWithLayout(
      video_frame->layout(), video_frame->visible_rect(),
      video_frame->natural_size(), planes[0], planes[1], planes[2],
      video_frame->timestamp());

  if (!mapped_frame) {
    return nullptr;
  }

  mapped_frame->set_color_space(video_frame->ColorSpace());
  mapped_frame->set_hdr_metadata(video_frame->hdr_metadata());
  mapped_frame->metadata().MergeMetadataFrom(video_frame->metadata());

  // Pass |video_frame| so that it outlives |mapped_frame| and the mapped buffer
  // is unmapped on destruction.
  mapped_frame->AddDestructionObserver(base::BindOnce(
      [](scoped_refptr<VideoFrame> frame,
         std::unique_ptr<gpu::ClientSharedImage::ScopedMapping>
             scoped_mapping) {
        CHECK(scoped_mapping);
        // The ScopedMapping must be destroyed before the VideoFrame that
        // produced it in order to avoid dangling pointers.
        scoped_mapping.reset();
      },
      std::move(video_frame), std::move(scoped_mapping)));
  return mapped_frame;
}

void ConvertToMemoryMappedFrameAsync(
    scoped_refptr<VideoFrame> video_frame,
    base::OnceCallback<void(scoped_refptr<VideoFrame>)> result_cb) {
  CHECK(video_frame);
  CHECK(video_frame->HasMappableSharedImage());

  video_frame->shared_image()->MapAsync(base::BindOnce(
      &ProcessAsyncMappingResult, video_frame, std::move(result_cb)));
}

scoped_refptr<VideoFrame> WrapAsI420VideoFrame(
    scoped_refptr<VideoFrame> frame) {
  DCHECK_EQ(VideoFrame::STORAGE_OWNED_MEMORY, frame->storage_type());
  DCHECK_EQ(PIXEL_FORMAT_I420A, frame->format());

  scoped_refptr<VideoFrame> wrapped_frame = VideoFrame::WrapVideoFrame(
      frame, PIXEL_FORMAT_I420, frame->visible_rect(), frame->natural_size());
  return wrapped_frame;
}

bool I420CopyWithPadding(const VideoFrame& src_frame, VideoFrame* dst_frame) {
  if (!dst_frame || !dst_frame->HasDirectCpuAccess()) {
    return false;
  }

  DCHECK_GE(dst_frame->coded_size().width(), src_frame.visible_rect().width());
  DCHECK_GE(dst_frame->coded_size().height(),
            src_frame.visible_rect().height());
  DCHECK(dst_frame->visible_rect().origin().IsOrigin());

  if (libyuv::I420Copy(src_frame.visible_data(VideoFrame::Plane::kY),
                       src_frame.stride(VideoFrame::Plane::kY),
                       src_frame.visible_data(VideoFrame::Plane::kU),
                       src_frame.stride(VideoFrame::Plane::kU),
                       src_frame.visible_data(VideoFrame::Plane::kV),
                       src_frame.stride(VideoFrame::Plane::kV),
                       dst_frame->writable_data(VideoFrame::Plane::kY),
                       dst_frame->stride(VideoFrame::Plane::kY),
                       dst_frame->writable_data(VideoFrame::Plane::kU),
                       dst_frame->stride(VideoFrame::Plane::kU),
                       dst_frame->writable_data(VideoFrame::Plane::kV),
                       dst_frame->stride(VideoFrame::Plane::kV),
                       src_frame.visible_rect().width(),
                       src_frame.visible_rect().height())) {
    return false;
  }

  // Padding the region outside the visible rect with the repeated last
  // column / row of the visible rect. This can improve the coding efficiency.
  FillRegionOutsideVisibleRect(dst_frame->writable_data(VideoFrame::Plane::kY),
                               dst_frame->stride(VideoFrame::Plane::kY),
                               dst_frame->coded_size(),
                               src_frame.visible_rect().size());
  FillRegionOutsideVisibleRect(
      dst_frame->writable_data(VideoFrame::Plane::kU),
      dst_frame->stride(VideoFrame::Plane::kU),
      VideoFrame::PlaneSize(PIXEL_FORMAT_I420, VideoFrame::Plane::kU,
                            dst_frame->coded_size()),
      VideoFrame::PlaneSize(PIXEL_FORMAT_I420, VideoFrame::Plane::kU,
                            src_frame.visible_rect().size()));
  FillRegionOutsideVisibleRect(
      dst_frame->writable_data(VideoFrame::Plane::kV),
      dst_frame->stride(VideoFrame::Plane::kV),
      VideoFrame::PlaneSize(PIXEL_FORMAT_I420, VideoFrame::Plane::kV,
                            dst_frame->coded_size()),
      VideoFrame::PlaneSize(PIXEL_FORMAT_I420, VideoFrame::Plane::kV,
                            src_frame.visible_rect().size()));

  return true;
}

scoped_refptr<VideoFrame> ReadbackTextureBackedFrameToMemorySync(
    VideoFrame& txt_frame,
    gpu::raster::RasterInterface* ri,
    VideoFramePool* pool) {
  DCHECK(ri);

  TRACE_EVENT1("media", "ReadbackTextureBackedFrameToMemorySync", "timestamp",
               txt_frame.timestamp());
  VideoPixelFormat format = ReadbackFormat(txt_frame);
  if (format == PIXEL_FORMAT_UNKNOWN) {
    DLOG(ERROR) << "Readback is not possible for this frame: "
                << txt_frame.AsHumanReadableString();
    return nullptr;
  }

  scoped_refptr<VideoFrame> result =
      pool ? pool->CreateFrame(format, txt_frame.coded_size(),
                               txt_frame.visible_rect(),
                               txt_frame.natural_size(), txt_frame.timestamp())
           : VideoFrame::CreateFrame(
                 format, txt_frame.coded_size(), txt_frame.visible_rect(),
                 txt_frame.natural_size(), txt_frame.timestamp());
  if (!result) {
    DLOG(ERROR) << "Failed to create VideoFrame, coded_size="
                << txt_frame.coded_size().ToString();
    return nullptr;
  }
  result->set_color_space(txt_frame.ColorSpace());
  result->set_hdr_metadata(txt_frame.hdr_metadata());
  result->metadata().MergeMetadataFrom(txt_frame.metadata());
  result->metadata().ClearTextureFrameMetadata();

  // NOTE: Iterating over the number of planes of the readback format (rather
  // than `txt_frame`) ensures that frames with external
  // sampling are correctly sampled as a single opaque texture, as
  // ReadbackFormat() returns RGB for such frames.
  size_t planes = VideoFrame::NumPlanes(format);
  for (size_t plane = 0; plane < planes; plane++) {
    gfx::Rect src_rect(0, 0, txt_frame.columns(plane), txt_frame.rows(plane));
    if (!ReadbackTexturePlaneToMemorySync(txt_frame, plane, src_rect,
                                          result->writable_data(plane),
                                          result->stride(plane), ri)) {
      return nullptr;
    }
  }
  return result;
}

bool ReadbackTexturePlaneToMemorySync(VideoFrame& src_frame,
                                      size_t src_plane,
                                      gfx::Rect& src_rect,
                                      uint8_t* dest_pixels,
                                      size_t dest_stride,
                                      gpu::raster::RasterInterface* ri) {
  DCHECK(ri);
  VideoPixelFormat format = ReadbackFormat(src_frame);
  if (format == PIXEL_FORMAT_UNKNOWN) {
    DLOG(ERROR) << "Readback is not possible for this frame: "
                << src_frame.AsHumanReadableString();
    return false;
  }

  bool has_alpha = !IsOpaque(format);
  auto sk_color_type = SkColorTypeForPlane(format, src_plane);
  auto sk_alpha_type = has_alpha ? kUnpremul_SkAlphaType : kOpaque_SkAlphaType;

  auto info = SkImageInfo::Make(src_rect.width(), src_rect.height(),
                                sk_color_type, sk_alpha_type);

  // Perform readback passing the appropriate `src_plane` for the mailbox.
  auto mailbox = src_frame.shared_image()->mailbox();
  auto sync_token = src_frame.acquire_sync_token();
  std::unique_ptr<gpu::RasterScopedAccess> ri_access =
      src_frame.shared_image()->BeginRasterAccess(ri, sync_token,
                                                  /*readonly=*/true);
  bool readback_result =
      ri->ReadbackImagePixels(mailbox, info, dest_stride, src_rect.x(),
                              src_rect.y(), src_plane, dest_pixels);

  bool result = readback_result &&
                ri->GetGraphicsResetStatusKHR() == GL_NO_ERROR &&
                ri->GetError() == GL_NO_ERROR;
  if (result) {
    WaitAndReplaceSyncTokenClient client(ri, std::move(ri_access));
    src_frame.UpdateReleaseSyncToken(&client);
  } else {
    gpu::RasterScopedAccess::EndAccess(std::move(ri_access));
  }

  return result;
}

// Media pixel format enums have names opposite to their byte order.
// That's why PIXEL_FORMAT_ABGR corresponds to kRGBA_8888_SkColorType
// and so on.
SkColorType SkColorTypeForPlaneNoCheck(VideoPixelFormat format, size_t plane) {
  switch (format) {
    case PIXEL_FORMAT_I420:
    case PIXEL_FORMAT_I420A:
    case PIXEL_FORMAT_I422:
    case PIXEL_FORMAT_I444:
      return kR8_unorm_SkColorType;
    case PIXEL_FORMAT_NV12:
    case PIXEL_FORMAT_NV16:
    case PIXEL_FORMAT_NV24:
      return plane == VideoFrame::Plane::kY ? kR8_unorm_SkColorType
                                            : kR8G8_unorm_SkColorType;
    case PIXEL_FORMAT_NV12A:
      return plane == VideoFrame::Plane::kY ||
                     plane == VideoFrame::Plane::kATriPlanar
                 ? kR8_unorm_SkColorType
                 : kR8G8_unorm_SkColorType;
    case PIXEL_FORMAT_P010LE:
    case PIXEL_FORMAT_P210LE:
    case PIXEL_FORMAT_P410LE:
      return plane == VideoFrame::Plane::kY ? kR16_unorm_SkColorType
                                            : kR16G16_unorm_SkColorType;
    case PIXEL_FORMAT_XBGR:
    case PIXEL_FORMAT_ABGR:
      return kRGBA_8888_SkColorType;
    case PIXEL_FORMAT_XRGB:
    case PIXEL_FORMAT_ARGB:
      return kBGRA_8888_SkColorType;
    case PIXEL_FORMAT_RGBAF16:
      return kRGBA_F16_SkColorType;
    default:
      return kUnknown_SkColorType;
  }
}

SkColorType SkColorTypeForPlane(VideoPixelFormat format, size_t plane) {
  const auto result = SkColorTypeForPlaneNoCheck(format, plane);
  CHECK_NE(result, kUnknown_SkColorType);
  return result;
}

VideoPixelFormat VideoPixelFormatFromSkColorType(SkColorType sk_color_type,
                                                 bool is_opaque) {
  switch (sk_color_type) {
    case kRGBA_8888_SkColorType:
      return is_opaque ? PIXEL_FORMAT_XBGR : PIXEL_FORMAT_ABGR;
    case kBGRA_8888_SkColorType:
      return is_opaque ? PIXEL_FORMAT_XRGB : PIXEL_FORMAT_ARGB;
    case kRGBA_F16_SkColorType:
      return PIXEL_FORMAT_RGBAF16;
    default:
      return PIXEL_FORMAT_UNKNOWN;
  }
}

SkYUVAInfo::PlaneConfig SkYUVAPlaneConfigForFormat(VideoPixelFormat format) {
  switch (format) {
    case PIXEL_FORMAT_UYVY:
    case PIXEL_FORMAT_YUY2:
    case PIXEL_FORMAT_ARGB:
    case PIXEL_FORMAT_BGRA:
    case PIXEL_FORMAT_XRGB:
    case PIXEL_FORMAT_RGB24:
    case PIXEL_FORMAT_MJPEG:
    case PIXEL_FORMAT_Y16:
    case PIXEL_FORMAT_ABGR:
    case PIXEL_FORMAT_XBGR:
    case PIXEL_FORMAT_XR30:
    case PIXEL_FORMAT_XB30:
    case PIXEL_FORMAT_RGBAF16:
    case PIXEL_FORMAT_UNKNOWN:
      return SkYUVAInfo::PlaneConfig::kUnknown;
    case PIXEL_FORMAT_I420:
    case PIXEL_FORMAT_I422:
    case PIXEL_FORMAT_I444:
    case PIXEL_FORMAT_YUV420P10:
    case PIXEL_FORMAT_YUV422P10:
    case PIXEL_FORMAT_YUV444P10:
    case PIXEL_FORMAT_YUV420P12:
    case PIXEL_FORMAT_YUV422P12:
    case PIXEL_FORMAT_YUV444P12:
      return SkYUVAInfo::PlaneConfig::kY_U_V;
    case PIXEL_FORMAT_YV12:
      return SkYUVAInfo::PlaneConfig::kY_V_U;
    case PIXEL_FORMAT_I420A:
    case PIXEL_FORMAT_I422A:
    case PIXEL_FORMAT_I444A:
    case PIXEL_FORMAT_YUV420AP10:
    case PIXEL_FORMAT_YUV422AP10:
    case PIXEL_FORMAT_YUV444AP10:
      return SkYUVAInfo::PlaneConfig::kY_U_V_A;
    case PIXEL_FORMAT_NV12:
    case PIXEL_FORMAT_NV16:
    case PIXEL_FORMAT_NV21:
    case PIXEL_FORMAT_NV24:
    case PIXEL_FORMAT_P010LE:
    case PIXEL_FORMAT_P210LE:
    case PIXEL_FORMAT_P410LE:
      return SkYUVAInfo::PlaneConfig::kY_UV;
    case PIXEL_FORMAT_NV12A:
      return SkYUVAInfo::PlaneConfig::kY_UV_A;
  }
}

SkYUVAInfo::Subsampling SkYUVASubsamplingForFormat(VideoPixelFormat format) {
  switch (VideoPixelFormatToChromaSampling(format)) {
    case VideoChromaSampling::k420:
      return SkYUVAInfo::Subsampling::k420;
    case VideoChromaSampling::k422:
      return SkYUVAInfo::Subsampling::k422;
    case VideoChromaSampling::k444:
      return SkYUVAInfo::Subsampling::k444;
    default:
      return SkYUVAInfo::Subsampling::kUnknown;
  }
}

scoped_refptr<VideoFrame> CreateFromSkImage(sk_sp<SkImage> sk_image,
                                            const gfx::Rect& visible_rect,
                                            const gfx::Size& natural_size,
                                            base::TimeDelta timestamp,
                                            bool force_opaque) {
  DCHECK(!sk_image->isTextureBacked());

  // A given SkImage may not exist until it's rasterized.
  if (sk_image->isLazyGenerated())
    sk_image = sk_image->makeRasterImage();

  const auto format = VideoPixelFormatFromSkColorType(
      sk_image->colorType(), sk_image->isOpaque() || force_opaque);
  if (VideoFrameLayout::NumPlanes(format) != 1) {
    DLOG(ERROR) << "Invalid SkColorType for CreateFromSkImage";
    return nullptr;
  }

  SkPixmap pm;
  const bool peek_result = sk_image->peekPixels(&pm);
  DCHECK(peek_result);

  auto coded_size = gfx::Size(sk_image->width(), sk_image->height());
  auto layout = VideoFrameLayout::CreateWithStrides(
      format, coded_size, std::vector<size_t>(1, pm.rowBytes()));
  if (!layout)
    return nullptr;

  auto frame = VideoFrame::WrapExternalDataWithLayout(
      *layout, visible_rect, natural_size,
      // TODO(crbug.com/40162403): We should be able to wrap readonly memory in
      // a VideoFrame instead of using writable_addr() here.
      // SAFETY: We take the pointer and size from SkPixmap, we rely on Skia
      // to give us a valid memory region.
      UNSAFE_BUFFERS(base::span(reinterpret_cast<uint8_t*>(pm.writable_addr()),
                                pm.computeByteSize())),
      timestamp);
  if (!frame)
    return nullptr;

  if (sk_image->colorSpace()) {
    frame->set_color_space(gfx::ColorSpace(*sk_image->colorSpace()));
  }

  frame->AddDestructionObserver(
      base::DoNothingWithBoundArgs(std::move(sk_image)));
  return frame;
}

SkYUVAInfo::PlaneConfig ToSkYUVAPlaneConfig(viz::SharedImageFormat format) {
  using PlaneConfig = viz::SharedImageFormat::PlaneConfig;
  switch (format.plane_config()) {
    case PlaneConfig::kY_U_V:
      return SkYUVAInfo::PlaneConfig::kY_U_V;
    case PlaneConfig::kY_V_U:
      return SkYUVAInfo::PlaneConfig::kY_V_U;
    case PlaneConfig::kY_UV:
      return SkYUVAInfo::PlaneConfig::kY_UV;
    case PlaneConfig::kY_UV_A:
      return SkYUVAInfo::PlaneConfig::kY_UV_A;
    case PlaneConfig::kY_U_V_A:
      return SkYUVAInfo::PlaneConfig::kY_U_V_A;
  }
}

SkYUVAInfo::Subsampling ToSkYUVASubsampling(viz::SharedImageFormat format) {
  using Subsampling = viz::SharedImageFormat::Subsampling;
  switch (format.subsampling()) {
    case Subsampling::k420:
      return SkYUVAInfo::Subsampling::k420;
    case Subsampling::k422:
      return SkYUVAInfo::Subsampling::k422;
    case Subsampling::k444:
      return SkYUVAInfo::Subsampling::k444;
  }
}

const libyuv::YuvConstants* GetYuvContantsForColorSpace(
    SkYUVColorSpace cs,
    bool output_argb_matrix) {
#define YUV_MATRIX(matrix) (output_argb_matrix ? matrix : matrix##VU)
  switch (cs) {
    case kJPEG_Full_SkYUVColorSpace:
      return &YUV_MATRIX(libyuv::kYuvJPEGConstants);
    case kRec601_Limited_SkYUVColorSpace:
      return &YUV_MATRIX(libyuv::kYuvI601Constants);
    case kRec709_Full_SkYUVColorSpace:
      return &YUV_MATRIX(libyuv::kYuvF709Constants);
    case kRec709_Limited_SkYUVColorSpace:
      return &YUV_MATRIX(libyuv::kYuvH709Constants);
    case kBT2020_8bit_Full_SkYUVColorSpace:
    case kBT2020_10bit_Full_SkYUVColorSpace:
    case kBT2020_12bit_Full_SkYUVColorSpace:
    case kBT2020_16bit_Full_SkYUVColorSpace:
      return &YUV_MATRIX(libyuv::kYuvV2020Constants);
    case kBT2020_8bit_Limited_SkYUVColorSpace:
    case kBT2020_10bit_Limited_SkYUVColorSpace:
    case kBT2020_12bit_Limited_SkYUVColorSpace:
    case kBT2020_16bit_Limited_SkYUVColorSpace:
      return &YUV_MATRIX(libyuv::kYuv2020Constants);
    case kFCC_Full_SkYUVColorSpace:
    case kFCC_Limited_SkYUVColorSpace:
    case kSMPTE240_Full_SkYUVColorSpace:
    case kSMPTE240_Limited_SkYUVColorSpace:
    case kYDZDX_Full_SkYUVColorSpace:
    case kYDZDX_Limited_SkYUVColorSpace:
    case kGBR_Full_SkYUVColorSpace:
    case kGBR_Limited_SkYUVColorSpace:
    case kYCgCo_8bit_Full_SkYUVColorSpace:
    case kYCgCo_8bit_Limited_SkYUVColorSpace:
    case kYCgCo_10bit_Full_SkYUVColorSpace:
    case kYCgCo_10bit_Limited_SkYUVColorSpace:
    case kYCgCo_12bit_Full_SkYUVColorSpace:
    case kYCgCo_12bit_Limited_SkYUVColorSpace:
    case kYCgCo_16bit_Full_SkYUVColorSpace:
    case kYCgCo_16bit_Limited_SkYUVColorSpace:
      // TODO(crbug.com/41486014): Return color space for default
      // kRec601_SkYUVColorSpace as libyuv does not have FCC, SMPTE240M, YDZDX,
      // GBR, YCgCo equivalent support.
      return &YUV_MATRIX(libyuv::kYuvI601Constants);
    case kIdentity_SkYUVColorSpace:
      NOTREACHED();
  };
#undef YUV_MATRIX
}

}  // namespace media
