/*
 * Copyright 2024 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
 * https://www.apache.org/licenses/LICENSE-2.0> or the MIT license
 * <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your
 * option. This file may not be copied, modified, or distributed
 * except according to those terms.
 */

#include <cstring>
#include <cstdint>
#include <cmath>

#include "ultrahdr/editorhelper.h"
#include "ultrahdr/gainmapmath.h"

namespace ultrahdr {

template <typename T>
void rotate_buffer_clockwise(T* src_buffer, T* dst_buffer, int src_w, int src_h, int src_stride,
                             int dst_stride, int degree) {
  if (degree == 90) {
    int dst_w = src_h;
    int dst_h = src_w;
    for (int i = 0; i < dst_h; i++) {
      for (int j = 0; j < dst_w; j++) {
        dst_buffer[i * dst_stride + j] = src_buffer[(src_h - j - 1) * src_stride + i];
      }
    }
  } else if (degree == 180) {
    int dst_w = src_w;
    int dst_h = src_h;
    for (int i = 0; i < dst_h; i++) {
      for (int j = 0; j < dst_w; j++) {
        dst_buffer[i * dst_stride + j] = src_buffer[(src_h - i - 1) * src_stride + (src_w - j - 1)];
      }
    }
  } else if (degree == 270) {
    int dst_w = src_h;
    int dst_h = src_w;
    for (int i = 0; i < dst_h; i++) {
      for (int j = 0; j < dst_w; j++) {
        dst_buffer[i * dst_stride + j] = src_buffer[j * src_stride + (src_w - i - 1)];
      }
    }
  }
}

template <typename T>
void mirror_buffer(T* src_buffer, T* dst_buffer, int src_w, int src_h, int src_stride,
                   int dst_stride, uhdr_mirror_direction_t direction) {
  if (direction == UHDR_MIRROR_VERTICAL) {
    for (int i = 0; i < src_h; i++) {
      memcpy(&dst_buffer[(src_h - i - 1) * dst_stride], &src_buffer[i * src_stride],
             src_w * sizeof(T));
    }
  } else if (direction == UHDR_MIRROR_HORIZONTAL) {
    for (int i = 0; i < src_h; i++) {
      for (int j = 0; j < src_w; j++) {
        dst_buffer[i * dst_stride + j] = src_buffer[i * src_stride + (src_w - j - 1)];
      }
    }
  }
}

template <typename T>
void crop_buffer(T* src_buffer, T* dst_buffer, int src_stride, int dst_stride, int left, int top,
                 int wd, int ht) {
  for (int row = 0; row < ht; row++) {
    memcpy(&dst_buffer[row * dst_stride], &src_buffer[(top + row) * src_stride + left],
           wd * sizeof(T));
  }
}

// TODO (dichenzhang): legacy method, need to be removed
template <typename T>
void resize_buffer(T* src_buffer, T* dst_buffer, int src_w, int src_h, int dst_w, int dst_h,
                   int src_stride, int dst_stride) {
  for (int i = 0; i < dst_h; i++) {
    for (int j = 0; j < dst_w; j++) {
      dst_buffer[i * dst_stride + j] =
          src_buffer[i * (src_h / dst_h) * src_stride + j * (src_w / dst_w)];
    }
  }
}

// This function performs bicubic interpolation on a 1D signal.
double bicubic_interpolate(double p0, double p1, double p2, double p3, double x) {
  // Calculate the weights for the four neighboring points.
  double w0 = (1 - x) * (1 - x) * (1 - x);
  double w1 = 3 * x * (1 - x) * (1 - x);
  double w2 = 3 * x * x * (1 - x);
  double w3 = x * x * x;

  // Calculate the interpolated value.
  return w0 * p0 + w1 * p1 + w2 * p2 + w3 * p3;
}

std::unique_ptr<uhdr_raw_image_ext_t> resize_image(uhdr_raw_image_t* src, int dst_w, int dst_h) {
  GetPixelFn get_pixel_fn = getPixelFn(src->fmt);
  if (get_pixel_fn == nullptr) {
    return nullptr;
  }

  PutPixelFn put_pixel_fn = putPixelFn(src->fmt);
  if (put_pixel_fn == nullptr) {
    return nullptr;
  }

  std::unique_ptr<uhdr_raw_image_ext_t> dst = std::make_unique<uhdr_raw_image_ext_t>(
      src->fmt, src->cg, src->ct, src->range, dst_w, dst_h, 64);

  int src_w = src->w;
  int src_h = src->h;
  double scale_x = (double)src_w / dst_w;
  double scale_y = (double)src_h / dst_h;
  for (int y = 0; y < dst_h; y++) {
    for (int x = 0; x < dst_w; x++) {
      double ori_x = x * scale_x;
      double ori_y = y * scale_y;
      int p0_x = CLIP3((int)floor(ori_x), 0, src_w - 1);
      int p0_y = CLIP3((int)floor(ori_y), 0, src_h - 1);
      int p1_x = CLIP3((p0_x + 1), 0, src_w - 1);
      int p1_y = p0_y;
      int p2_x = p0_x;
      int p2_y = CLIP3((p0_y + 1), 0, src_h - 1);
      int p3_x = CLIP3((p0_x + 1), 0, src_w - 1);
      int p3_y = CLIP3((p0_y + 1), 0, src_h - 1);

      Color p0 = get_pixel_fn(src, p0_x, p0_y);
      Color p1 = get_pixel_fn(src, p1_x, p1_y);
      Color p2 = get_pixel_fn(src, p2_x, p2_y);
      Color p3 = get_pixel_fn(src, p3_x, p3_y);

      Color interp;
      interp.r = (float)bicubic_interpolate(p0.r, p1.r, p2.r, p3.r, ori_x - p0_x);
      if (src->fmt != UHDR_IMG_FMT_8bppYCbCr400) {
        interp.g = (float)bicubic_interpolate(p0.g, p1.g, p2.g, p3.g, ori_x - p0_x);
        interp.b = (float)bicubic_interpolate(p0.b, p1.b, p2.b, p3.b, ori_x - p0_x);
      }
      put_pixel_fn(dst.get(), x, y, interp);
    }
  }
  return dst;
}

template void mirror_buffer<uint8_t>(uint8_t*, uint8_t*, int, int, int, int,
                                     uhdr_mirror_direction_t);
template void mirror_buffer<uint16_t>(uint16_t*, uint16_t*, int, int, int, int,
                                      uhdr_mirror_direction_t);
template void mirror_buffer<uint32_t>(uint32_t*, uint32_t*, int, int, int, int,
                                      uhdr_mirror_direction_t);
template void mirror_buffer<uint64_t>(uint64_t*, uint64_t*, int, int, int, int,
                                      uhdr_mirror_direction_t);

template void rotate_buffer_clockwise<uint8_t>(uint8_t*, uint8_t*, int, int, int, int, int);
template void rotate_buffer_clockwise<uint16_t>(uint16_t*, uint16_t*, int, int, int, int, int);
template void rotate_buffer_clockwise<uint32_t>(uint32_t*, uint32_t*, int, int, int, int, int);
template void rotate_buffer_clockwise<uint64_t>(uint64_t*, uint64_t*, int, int, int, int, int);

template void resize_buffer<uint8_t>(uint8_t*, uint8_t*, int, int, int, int, int, int);
template void resize_buffer<uint16_t>(uint16_t*, uint16_t*, int, int, int, int, int, int);
template void resize_buffer<uint32_t>(uint32_t*, uint32_t*, int, int, int, int, int, int);
template void resize_buffer<uint64_t>(uint64_t*, uint64_t*, int, int, int, int, int, int);

uhdr_mirror_effect::uhdr_mirror_effect(uhdr_mirror_direction_t direction) : m_direction{direction} {
#if (defined(UHDR_ENABLE_INTRINSICS) && (defined(__ARM_NEON__) || defined(__ARM_NEON)))
  m_mirror_uint8_t = mirror_buffer_neon<uint8_t>;
  m_mirror_uint16_t = mirror_buffer_neon<uint16_t>;
  m_mirror_uint32_t = mirror_buffer_neon<uint32_t>;
  m_mirror_uint64_t = mirror_buffer_neon<uint64_t>;
#else
  m_mirror_uint8_t = mirror_buffer<uint8_t>;
  m_mirror_uint16_t = mirror_buffer<uint16_t>;
  m_mirror_uint32_t = mirror_buffer<uint32_t>;
  m_mirror_uint64_t = mirror_buffer<uint64_t>;
#endif
}

uhdr_rotate_effect::uhdr_rotate_effect(int degree) : m_degree{degree} {
#if (defined(UHDR_ENABLE_INTRINSICS) && (defined(__ARM_NEON__) || defined(__ARM_NEON)))
  m_rotate_uint8_t = rotate_buffer_clockwise_neon<uint8_t>;
  m_rotate_uint16_t = rotate_buffer_clockwise_neon<uint16_t>;
  m_rotate_uint32_t = rotate_buffer_clockwise_neon<uint32_t>;
  m_rotate_uint64_t = rotate_buffer_clockwise_neon<uint64_t>;
#else
  m_rotate_uint8_t = rotate_buffer_clockwise<uint8_t>;
  m_rotate_uint16_t = rotate_buffer_clockwise<uint16_t>;
  m_rotate_uint32_t = rotate_buffer_clockwise<uint32_t>;
  m_rotate_uint64_t = rotate_buffer_clockwise<uint64_t>;
#endif
}

uhdr_crop_effect::uhdr_crop_effect(int left, int right, int top, int bottom)
    : m_left(left), m_right(right), m_top(top), m_bottom(bottom) {
  m_crop_uint8_t = crop_buffer<uint8_t>;
  m_crop_uint16_t = crop_buffer<uint16_t>;
  m_crop_uint32_t = crop_buffer<uint32_t>;
  m_crop_uint64_t = crop_buffer<uint64_t>;
}

uhdr_resize_effect::uhdr_resize_effect(int width, int height) : m_width{width}, m_height{height} {
  m_resize_uint8_t = resize_buffer<uint8_t>;
  m_resize_uint16_t = resize_buffer<uint16_t>;
  m_resize_uint32_t = resize_buffer<uint32_t>;
  m_resize_uint64_t = resize_buffer<uint64_t>;
}

std::unique_ptr<uhdr_raw_image_ext_t> apply_rotate(ultrahdr::uhdr_rotate_effect_t* desc,
                                                   uhdr_raw_image_t* src,
                                                   [[maybe_unused]] void* gl_ctxt,
                                                   [[maybe_unused]] void* texture) {
#ifdef UHDR_ENABLE_GLES
  if ((src->fmt == UHDR_IMG_FMT_32bppRGBA1010102 || src->fmt == UHDR_IMG_FMT_32bppRGBA8888 ||
       src->fmt == UHDR_IMG_FMT_64bppRGBAHalfFloat || src->fmt == UHDR_IMG_FMT_8bppYCbCr400) &&
      gl_ctxt != nullptr && *static_cast<GLuint*>(texture) != 0) {
    return apply_rotate_gles(desc, src, static_cast<ultrahdr::uhdr_opengl_ctxt*>(gl_ctxt),
                             static_cast<GLuint*>(texture));
  }
#endif
  std::unique_ptr<uhdr_raw_image_ext_t> dst;

  if (desc->m_degree == 90 || desc->m_degree == 270) {
    dst = std::make_unique<uhdr_raw_image_ext_t>(src->fmt, src->cg, src->ct, src->range, src->h,
                                                 src->w, 64);
  } else if (desc->m_degree == 180) {
    dst = std::make_unique<uhdr_raw_image_ext_t>(src->fmt, src->cg, src->ct, src->range, src->w,
                                                 src->h, 64);
  } else {
    return nullptr;
  }

  if (src->fmt == UHDR_IMG_FMT_24bppYCbCrP010) {
    uint16_t* src_buffer = static_cast<uint16_t*>(src->planes[UHDR_PLANE_Y]);
    uint16_t* dst_buffer = static_cast<uint16_t*>(dst->planes[UHDR_PLANE_Y]);
    desc->m_rotate_uint16_t(src_buffer, dst_buffer, src->w, src->h, src->stride[UHDR_PLANE_Y],
                            dst->stride[UHDR_PLANE_Y], desc->m_degree);
    uint32_t* src_uv_buffer = static_cast<uint32_t*>(src->planes[UHDR_PLANE_UV]);
    uint32_t* dst_uv_buffer = static_cast<uint32_t*>(dst->planes[UHDR_PLANE_UV]);
    desc->m_rotate_uint32_t(src_uv_buffer, dst_uv_buffer, src->w / 2, src->h / 2,
                            src->stride[UHDR_PLANE_UV] / 2, dst->stride[UHDR_PLANE_UV] / 2,
                            desc->m_degree);
  } else if (src->fmt == UHDR_IMG_FMT_12bppYCbCr420 || src->fmt == UHDR_IMG_FMT_8bppYCbCr400) {
    uint8_t* src_buffer = static_cast<uint8_t*>(src->planes[UHDR_PLANE_Y]);
    uint8_t* dst_buffer = static_cast<uint8_t*>(dst->planes[UHDR_PLANE_Y]);
    desc->m_rotate_uint8_t(src_buffer, dst_buffer, src->w, src->h, src->stride[UHDR_PLANE_Y],
                           dst->stride[UHDR_PLANE_Y], desc->m_degree);
    if (src->fmt == UHDR_IMG_FMT_12bppYCbCr420) {
      for (int i = 1; i < 3; i++) {
        src_buffer = static_cast<uint8_t*>(src->planes[i]);
        dst_buffer = static_cast<uint8_t*>(dst->planes[i]);
        desc->m_rotate_uint8_t(src_buffer, dst_buffer, src->w / 2, src->h / 2, src->stride[i],
                               dst->stride[i], desc->m_degree);
      }
    }
  } else if (src->fmt == UHDR_IMG_FMT_32bppRGBA1010102 || src->fmt == UHDR_IMG_FMT_32bppRGBA8888) {
    uint32_t* src_buffer = static_cast<uint32_t*>(src->planes[UHDR_PLANE_PACKED]);
    uint32_t* dst_buffer = static_cast<uint32_t*>(dst->planes[UHDR_PLANE_PACKED]);
    desc->m_rotate_uint32_t(src_buffer, dst_buffer, src->w, src->h, src->stride[UHDR_PLANE_PACKED],
                            dst->stride[UHDR_PLANE_PACKED], desc->m_degree);
  } else if (src->fmt == UHDR_IMG_FMT_64bppRGBAHalfFloat) {
    uint64_t* src_buffer = static_cast<uint64_t*>(src->planes[UHDR_PLANE_PACKED]);
    uint64_t* dst_buffer = static_cast<uint64_t*>(dst->planes[UHDR_PLANE_PACKED]);
    desc->m_rotate_uint64_t(src_buffer, dst_buffer, src->w, src->h, src->stride[UHDR_PLANE_PACKED],
                            dst->stride[UHDR_PLANE_PACKED], desc->m_degree);
  } else if (src->fmt == UHDR_IMG_FMT_24bppYCbCr444) {
    for (int i = 0; i < 3; i++) {
      uint8_t* src_buffer = static_cast<uint8_t*>(src->planes[i]);
      uint8_t* dst_buffer = static_cast<uint8_t*>(dst->planes[i]);
      desc->m_rotate_uint8_t(src_buffer, dst_buffer, src->w, src->h, src->stride[i], dst->stride[i],
                             desc->m_degree);
    }
  } else if (src->fmt == UHDR_IMG_FMT_30bppYCbCr444) {
    for (int i = 0; i < 3; i++) {
      uint16_t* src_buffer = static_cast<uint16_t*>(src->planes[i]);
      uint16_t* dst_buffer = static_cast<uint16_t*>(dst->planes[i]);
      desc->m_rotate_uint16_t(src_buffer, dst_buffer, src->w, src->h, src->stride[i],
                              dst->stride[i], desc->m_degree);
    }
  }
  return dst;
}

std::unique_ptr<uhdr_raw_image_ext_t> apply_mirror(ultrahdr::uhdr_mirror_effect_t* desc,
                                                   uhdr_raw_image_t* src,
                                                   [[maybe_unused]] void* gl_ctxt,
                                                   [[maybe_unused]] void* texture) {
#ifdef UHDR_ENABLE_GLES
  if ((src->fmt == UHDR_IMG_FMT_32bppRGBA1010102 || src->fmt == UHDR_IMG_FMT_32bppRGBA8888 ||
       src->fmt == UHDR_IMG_FMT_64bppRGBAHalfFloat || src->fmt == UHDR_IMG_FMT_8bppYCbCr400) &&
      gl_ctxt != nullptr && *static_cast<GLuint*>(texture) != 0) {
    return apply_mirror_gles(desc, src, static_cast<ultrahdr::uhdr_opengl_ctxt*>(gl_ctxt),
                             static_cast<GLuint*>(texture));
  }
#endif
  std::unique_ptr<uhdr_raw_image_ext_t> dst = std::make_unique<uhdr_raw_image_ext_t>(
      src->fmt, src->cg, src->ct, src->range, src->w, src->h, 64);

  if (src->fmt == UHDR_IMG_FMT_24bppYCbCrP010) {
    uint16_t* src_buffer = static_cast<uint16_t*>(src->planes[UHDR_PLANE_Y]);
    uint16_t* dst_buffer = static_cast<uint16_t*>(dst->planes[UHDR_PLANE_Y]);
    desc->m_mirror_uint16_t(src_buffer, dst_buffer, src->w, src->h, src->stride[UHDR_PLANE_Y],
                            dst->stride[UHDR_PLANE_Y], desc->m_direction);
    uint32_t* src_uv_buffer = static_cast<uint32_t*>(src->planes[UHDR_PLANE_UV]);
    uint32_t* dst_uv_buffer = static_cast<uint32_t*>(dst->planes[UHDR_PLANE_UV]);
    desc->m_mirror_uint32_t(src_uv_buffer, dst_uv_buffer, src->w / 2, src->h / 2,
                            src->stride[UHDR_PLANE_UV] / 2, dst->stride[UHDR_PLANE_UV] / 2,
                            desc->m_direction);
  } else if (src->fmt == UHDR_IMG_FMT_12bppYCbCr420 || src->fmt == UHDR_IMG_FMT_8bppYCbCr400) {
    uint8_t* src_buffer = static_cast<uint8_t*>(src->planes[UHDR_PLANE_Y]);
    uint8_t* dst_buffer = static_cast<uint8_t*>(dst->planes[UHDR_PLANE_Y]);
    desc->m_mirror_uint8_t(src_buffer, dst_buffer, src->w, src->h, src->stride[UHDR_PLANE_Y],
                           dst->stride[UHDR_PLANE_Y], desc->m_direction);
    if (src->fmt == UHDR_IMG_FMT_12bppYCbCr420) {
      for (int i = 1; i < 3; i++) {
        src_buffer = static_cast<uint8_t*>(src->planes[i]);
        dst_buffer = static_cast<uint8_t*>(dst->planes[i]);
        desc->m_mirror_uint8_t(src_buffer, dst_buffer, src->w / 2, src->h / 2, src->stride[i],
                               dst->stride[i], desc->m_direction);
      }
    }
  } else if (src->fmt == UHDR_IMG_FMT_32bppRGBA1010102 || src->fmt == UHDR_IMG_FMT_32bppRGBA8888) {
    uint32_t* src_buffer = static_cast<uint32_t*>(src->planes[UHDR_PLANE_PACKED]);
    uint32_t* dst_buffer = static_cast<uint32_t*>(dst->planes[UHDR_PLANE_PACKED]);
    desc->m_mirror_uint32_t(src_buffer, dst_buffer, src->w, src->h, src->stride[UHDR_PLANE_PACKED],
                            dst->stride[UHDR_PLANE_PACKED], desc->m_direction);
  } else if (src->fmt == UHDR_IMG_FMT_64bppRGBAHalfFloat) {
    uint64_t* src_buffer = static_cast<uint64_t*>(src->planes[UHDR_PLANE_PACKED]);
    uint64_t* dst_buffer = static_cast<uint64_t*>(dst->planes[UHDR_PLANE_PACKED]);
    desc->m_mirror_uint64_t(src_buffer, dst_buffer, src->w, src->h, src->stride[UHDR_PLANE_PACKED],
                            dst->stride[UHDR_PLANE_PACKED], desc->m_direction);
  } else if (src->fmt == UHDR_IMG_FMT_24bppYCbCr444) {
    for (int i = 0; i < 3; i++) {
      uint8_t* src_buffer = static_cast<uint8_t*>(src->planes[i]);
      uint8_t* dst_buffer = static_cast<uint8_t*>(dst->planes[i]);
      desc->m_mirror_uint8_t(src_buffer, dst_buffer, src->w, src->h, src->stride[i], dst->stride[i],
                             desc->m_direction);
    }
  } else if (src->fmt == UHDR_IMG_FMT_30bppYCbCr444) {
    for (int i = 0; i < 3; i++) {
      uint16_t* src_buffer = static_cast<uint16_t*>(src->planes[i]);
      uint16_t* dst_buffer = static_cast<uint16_t*>(dst->planes[i]);
      desc->m_mirror_uint16_t(src_buffer, dst_buffer, src->w, src->h, src->stride[i],
                              dst->stride[i], desc->m_direction);
    }
  }
  return dst;
}

std::unique_ptr<uhdr_raw_image_ext_t> apply_crop(ultrahdr::uhdr_crop_effect_t* desc,
                                                 uhdr_raw_image_t* src, int left, int top, int wd,
                                                 int ht, [[maybe_unused]] void* gl_ctxt,
                                                 [[maybe_unused]] void* texture) {
#ifdef UHDR_ENABLE_GLES
  if ((src->fmt == UHDR_IMG_FMT_32bppRGBA1010102 || src->fmt == UHDR_IMG_FMT_32bppRGBA8888 ||
       src->fmt == UHDR_IMG_FMT_64bppRGBAHalfFloat || src->fmt == UHDR_IMG_FMT_8bppYCbCr400) &&
      gl_ctxt != nullptr && *static_cast<GLuint*>(texture) != 0) {
    return apply_crop_gles(src, left, top, wd, ht,
                           static_cast<ultrahdr::uhdr_opengl_ctxt*>(gl_ctxt),
                           static_cast<GLuint*>(texture));
  }
#endif
  std::unique_ptr<uhdr_raw_image_ext_t> dst =
      std::make_unique<uhdr_raw_image_ext_t>(src->fmt, src->cg, src->ct, src->range, wd, ht, 64);

  if (src->fmt == UHDR_IMG_FMT_24bppYCbCrP010) {
    uint16_t* src_buffer = static_cast<uint16_t*>(src->planes[UHDR_PLANE_Y]);
    uint16_t* dst_buffer = static_cast<uint16_t*>(dst->planes[UHDR_PLANE_Y]);
    desc->m_crop_uint16_t(src_buffer, dst_buffer, src->stride[UHDR_PLANE_Y],
                          dst->stride[UHDR_PLANE_Y], left, top, wd, ht);
    uint32_t* src_uv_buffer = static_cast<uint32_t*>(src->planes[UHDR_PLANE_UV]);
    uint32_t* dst_uv_buffer = static_cast<uint32_t*>(dst->planes[UHDR_PLANE_UV]);
    desc->m_crop_uint32_t(src_uv_buffer, dst_uv_buffer, src->stride[UHDR_PLANE_UV] / 2,
                          dst->stride[UHDR_PLANE_UV] / 2, left / 2, top / 2, wd / 2, ht / 2);
  } else if (src->fmt == UHDR_IMG_FMT_12bppYCbCr420 || src->fmt == UHDR_IMG_FMT_8bppYCbCr400) {
    uint8_t* src_buffer = static_cast<uint8_t*>(src->planes[UHDR_PLANE_Y]);
    uint8_t* dst_buffer = static_cast<uint8_t*>(dst->planes[UHDR_PLANE_Y]);
    desc->m_crop_uint8_t(src_buffer, dst_buffer, src->stride[UHDR_PLANE_Y],
                         dst->stride[UHDR_PLANE_Y], left, top, wd, ht);
    if (src->fmt == UHDR_IMG_FMT_12bppYCbCr420) {
      for (int i = 1; i < 3; i++) {
        src_buffer = static_cast<uint8_t*>(src->planes[i]);
        dst_buffer = static_cast<uint8_t*>(dst->planes[i]);
        desc->m_crop_uint8_t(src_buffer, dst_buffer, src->stride[i], dst->stride[i], left / 2,
                             top / 2, wd / 2, ht / 2);
      }
    }
  } else if (src->fmt == UHDR_IMG_FMT_32bppRGBA1010102 || src->fmt == UHDR_IMG_FMT_32bppRGBA8888) {
    uint32_t* src_buffer = static_cast<uint32_t*>(src->planes[UHDR_PLANE_PACKED]);
    uint32_t* dst_buffer = static_cast<uint32_t*>(dst->planes[UHDR_PLANE_PACKED]);
    desc->m_crop_uint32_t(src_buffer, dst_buffer, src->stride[UHDR_PLANE_PACKED],
                          dst->stride[UHDR_PLANE_PACKED], left, top, wd, ht);
  } else if (src->fmt == UHDR_IMG_FMT_64bppRGBAHalfFloat) {
    uint64_t* src_buffer = static_cast<uint64_t*>(src->planes[UHDR_PLANE_PACKED]);
    uint64_t* dst_buffer = static_cast<uint64_t*>(dst->planes[UHDR_PLANE_PACKED]);
    desc->m_crop_uint64_t(src_buffer, dst_buffer, src->stride[UHDR_PLANE_PACKED],
                          dst->stride[UHDR_PLANE_PACKED], left, top, wd, ht);
  } else if (src->fmt == UHDR_IMG_FMT_24bppYCbCr444) {
    for (int i = 0; i < 3; i++) {
      uint8_t* src_buffer = static_cast<uint8_t*>(src->planes[i]);
      uint8_t* dst_buffer = static_cast<uint8_t*>(dst->planes[i]);
      desc->m_crop_uint8_t(src_buffer, dst_buffer, src->stride[i], dst->stride[i], left, top, wd,
                           ht);
    }
  } else if (src->fmt == UHDR_IMG_FMT_30bppYCbCr444) {
    for (int i = 0; i < 3; i++) {
      uint16_t* src_buffer = static_cast<uint16_t*>(src->planes[i]);
      uint16_t* dst_buffer = static_cast<uint16_t*>(dst->planes[i]);
      desc->m_crop_uint16_t(src_buffer, dst_buffer, src->stride[UHDR_PLANE_PACKED],
                            dst->stride[UHDR_PLANE_PACKED], left, top, wd, ht);
    }
  }
  return dst;
}

std::unique_ptr<uhdr_raw_image_ext_t> apply_resize(ultrahdr::uhdr_resize_effect_t* desc,
                                                   uhdr_raw_image_t* src, int dst_w, int dst_h,
                                                   [[maybe_unused]] void* gl_ctxt,
                                                   [[maybe_unused]] void* texture) {
#ifdef UHDR_ENABLE_GLES
  if ((src->fmt == UHDR_IMG_FMT_32bppRGBA1010102 || src->fmt == UHDR_IMG_FMT_32bppRGBA8888 ||
       src->fmt == UHDR_IMG_FMT_64bppRGBAHalfFloat || src->fmt == UHDR_IMG_FMT_8bppYCbCr400) &&
      gl_ctxt != nullptr && *static_cast<GLuint*>(texture) != 0) {
    return apply_resize_gles(src, dst_w, dst_h, static_cast<ultrahdr::uhdr_opengl_ctxt*>(gl_ctxt),
                             static_cast<GLuint*>(texture));
  }
#endif
  std::unique_ptr<uhdr_raw_image_ext_t> dst = std::make_unique<uhdr_raw_image_ext_t>(
      src->fmt, src->cg, src->ct, src->range, dst_w, dst_h, 64);

  if (src->fmt == UHDR_IMG_FMT_24bppYCbCrP010) {
    uint16_t* src_buffer = static_cast<uint16_t*>(src->planes[UHDR_PLANE_Y]);
    uint16_t* dst_buffer = static_cast<uint16_t*>(dst->planes[UHDR_PLANE_Y]);
    desc->m_resize_uint16_t(src_buffer, dst_buffer, src->w, src->h, dst->w, dst->h,
                            src->stride[UHDR_PLANE_Y], dst->stride[UHDR_PLANE_Y]);
    uint32_t* src_uv_buffer = static_cast<uint32_t*>(src->planes[UHDR_PLANE_UV]);
    uint32_t* dst_uv_buffer = static_cast<uint32_t*>(dst->planes[UHDR_PLANE_UV]);
    desc->m_resize_uint32_t(src_uv_buffer, dst_uv_buffer, src->w / 2, src->h / 2, dst->w / 2,
                            dst->h / 2, src->stride[UHDR_PLANE_UV] / 2,
                            dst->stride[UHDR_PLANE_UV] / 2);
  } else if (src->fmt == UHDR_IMG_FMT_12bppYCbCr420 || src->fmt == UHDR_IMG_FMT_8bppYCbCr400) {
    uint8_t* src_buffer = static_cast<uint8_t*>(src->planes[UHDR_PLANE_Y]);
    uint8_t* dst_buffer = static_cast<uint8_t*>(dst->planes[UHDR_PLANE_Y]);
    desc->m_resize_uint8_t(src_buffer, dst_buffer, src->w, src->h, dst->w, dst->h,
                           src->stride[UHDR_PLANE_Y], dst->stride[UHDR_PLANE_Y]);
    if (src->fmt == UHDR_IMG_FMT_12bppYCbCr420) {
      for (int i = 1; i < 3; i++) {
        src_buffer = static_cast<uint8_t*>(src->planes[i]);
        dst_buffer = static_cast<uint8_t*>(dst->planes[i]);
        desc->m_resize_uint8_t(src_buffer, dst_buffer, src->w / 2, src->h / 2, dst->w / 2,
                               dst->h / 2, src->stride[i], dst->stride[i]);
      }
    }
  } else if (src->fmt == UHDR_IMG_FMT_32bppRGBA1010102 || src->fmt == UHDR_IMG_FMT_32bppRGBA8888) {
    uint32_t* src_buffer = static_cast<uint32_t*>(src->planes[UHDR_PLANE_PACKED]);
    uint32_t* dst_buffer = static_cast<uint32_t*>(dst->planes[UHDR_PLANE_PACKED]);
    desc->m_resize_uint32_t(src_buffer, dst_buffer, src->w, src->h, dst->w, dst->h,
                            src->stride[UHDR_PLANE_PACKED], dst->stride[UHDR_PLANE_PACKED]);
  } else if (src->fmt == UHDR_IMG_FMT_64bppRGBAHalfFloat) {
    uint64_t* src_buffer = static_cast<uint64_t*>(src->planes[UHDR_PLANE_PACKED]);
    uint64_t* dst_buffer = static_cast<uint64_t*>(dst->planes[UHDR_PLANE_PACKED]);
    desc->m_resize_uint64_t(src_buffer, dst_buffer, src->w, src->h, dst->w, dst->h,
                            src->stride[UHDR_PLANE_PACKED], dst->stride[UHDR_PLANE_PACKED]);
  } else if (src->fmt == UHDR_IMG_FMT_24bppYCbCr444) {
    for (int i = 0; i < 3; i++) {
      uint8_t* src_buffer = static_cast<uint8_t*>(src->planes[i]);
      uint8_t* dst_buffer = static_cast<uint8_t*>(dst->planes[i]);
      desc->m_resize_uint8_t(src_buffer, dst_buffer, src->w, src->h, dst->w, dst->h, src->stride[i],
                             dst->stride[i]);
    }
  } else if (src->fmt == UHDR_IMG_FMT_30bppYCbCr444) {
    for (int i = 0; i < 3; i++) {
      uint16_t* src_buffer = static_cast<uint16_t*>(src->planes[i]);
      uint16_t* dst_buffer = static_cast<uint16_t*>(dst->planes[i]);
      desc->m_resize_uint16_t(src_buffer, dst_buffer, src->w, src->h, dst->w, dst->h,
                              src->stride[i], dst->stride[i]);
    }
  }
  return dst;
}

}  // namespace ultrahdr
