/*
 * This file contains the YUV output functions for YUV420 modes.
 */

#include <string.h>
#include "inttypes.h"
#include "ka_drawers.h"
#include "ka_colconv.h"
#include <assert.h>

/**
 * Converts an YUV420 source into a NV12 destination, 100% zoom.
 * NV12: Plane 0 is 8bpp Y, Plane 1 is 16bpp &Cr.Cb, 2x2 sub-sampled
 *
 * @param  paint  draw parameters
 */
void ka_drawyuv420_z1_NV12_neon(const ka_paint_t* paint)
{
  const ka_neon_yuv_to_rgb_t* params = paint->params;
  int height;
  uint8_t *dst_y;
  uint16_t *dst_uv;
  uint8_t *y, *u, *v;
  int uv_delta, dst_uv_delta;

  dst_y = paint->dst;
  dst_uv = (uint16_t*) paint->dst_u;
  dst_uv_delta = (paint->dst_u_bpr >> 1); // cf. int16_t

  y = paint->base.y;                // luminance address
  u = paint->base.cb;               // chroma b source address
  v = paint->base.cr;               // chroma r source address
  uv_delta = (paint->yc_width >> 1) + paint->uv_skip; // 1 u/v line

  // Loop: copy 2 lines of luma, 1 half-line of uv chroma
  for (height = paint->yc_height; height > 0; height -= 2)
  {
    // Copy even luma row
    ka_yuv_correction_neon(dst_y, y, paint->yc_width, params->yfac, params->yadd);
    y += paint->yc_bpr;
    dst_y += paint->dst_bpr;
    // Copy odd luma row (beware of uneven number of rows)
    if (height != 1)
    {
      ka_yuv_correction_neon(dst_y, y, paint->yc_width, params->yfac, params->yadd);
      y += paint->yc_bpr;
      dst_y += paint->dst_bpr;
    }

    // Copy chroma, half-line of uv chroma
    ka_uv_correction_neon(dst_uv, u, v, paint->yc_width >> 1, params->colfac, params->coladd);
    dst_uv += dst_uv_delta;
    u += uv_delta;
    v += uv_delta;
  }

  assert(dst_y == paint->dst + paint->yc_height * paint->dst_bpr);
  assert(dst_uv == (uint16_t*) (paint->dst_u + (paint->yc_height >> 1) * paint->dst_u_bpr));
  assert(y == paint->base.y + paint->yc_height * paint->yc_bpr);
  assert(u == paint->base.cb + (paint->yc_height >> 1) * (paint->yc_bpr >> 1));
  assert(v == paint->base.cr + (paint->yc_height >> 1) * (paint->yc_bpr >> 1));
}

/**
 * Converts an YUV420 source into a NV21 destination, 100% zoom.
 * NV21: Plane 0 is 8bpp Y, Plane 1 is 16bpp &Cb.Cr, 2x2 sub-sampled
 *
 * @param  paint  draw parameters
 */
void ka_drawyuv420_z1_NV21_neon(const ka_paint_t* paint)
{
  const ka_neon_yuv_to_rgb_t* params = paint->params;
  int height;
  uint8_t *dst_y;
  uint16_t *dst_uv;
  uint8_t *y, *u, *v;
  int uv_delta, dst_uv_delta;

  dst_y = paint->dst;
  dst_uv = (uint16_t*) paint->dst_u;
  dst_uv_delta = (paint->dst_u_bpr >> 1); // cf. int16_t

  y = paint->base.y;                // luminance address
  u = paint->base.cb;               // chroma b source address
  v = paint->base.cr;               // chroma r source address
  uv_delta = (paint->yc_width >> 1) + paint->uv_skip; // 1 u/v line

  // Loop: copy 2 lines of luma, 1 half-line of uv chroma
  for (height = paint->yc_height; height > 0; height -= 2)
  {
    // Copy even luma row
    ka_yuv_correction_neon(dst_y, y, paint->yc_width, params->yfac, params->yadd);
    y += paint->yc_bpr;
    dst_y += paint->dst_bpr;
    // Copy odd luma row (beware of uneven number of rows)
    if (height != 1)
    {
      ka_yuv_correction_neon(dst_y, y, paint->yc_width, params->yfac, params->yadd);
      y += paint->yc_bpr;
      dst_y += paint->dst_bpr;
    }

    // Copy chroma, half-line of vu chroma
    ka_uv_correction_neon(dst_uv, v, u, paint->yc_width >> 1, params->colfac, params->coladd);
    dst_uv += dst_uv_delta;
    u += uv_delta;
    v += uv_delta;
  }

  assert(dst_y == paint->dst + paint->yc_height * paint->dst_bpr);
  assert(dst_uv == (uint16_t*) (paint->dst_u + (paint->yc_height >> 1) * paint->dst_u_bpr));
  assert(y == paint->base.y + paint->yc_height * paint->yc_bpr);
  assert(u == paint->base.cb + (paint->yc_height >> 1) * (paint->yc_bpr >> 1));
  assert(v == paint->base.cr + (paint->yc_height >> 1) * (paint->yc_bpr >> 1));
}

/**
 * Converts an YUV420 source into a YV12 destination, 100% zoom.
 * YV12: Plane 0 is 8bpp Y, Plane 1 Cb, Plane 2 Cr, 2x2 sub-sampled
 *
 * @param  paint  draw parameters
 */
void ka_drawyuv420_z1_YV12_neon(const ka_paint_t* paint)
{
  const ka_neon_yuv_to_rgb_t* params = paint->params;
  int height;
  uint8_t *dst_y, *dst_u, *dst_v;
  uint8_t *y, *u, *v;
  int uv_delta;

  dst_y = paint->dst;
  dst_u = paint->dst_u;
  dst_v = paint->dst_v;

  y = paint->base.y;                // luminance address
  u = paint->base.cb;               // chroma b source address
  v = paint->base.cr;               // chroma r source address
  uv_delta = (paint->yc_width >> 1) + paint->uv_skip; // 1 u/v line

  // Loop: copy 2 lines of luma, 1 half-line of u/v chroma
  for (height = paint->yc_height; height > 0; height -= 2)
  {
    // Copy even luma row
    ka_yuv_correction_neon(dst_y, y, paint->yc_width, params->yfac, params->yadd);
    y += paint->yc_bpr;
    dst_y += paint->dst_bpr;
    // Copy odd luma row (beware of uneven number of rows)
    if (height != 1)
    {
      ka_yuv_correction_neon(dst_y, y, paint->yc_width, params->yfac, params->yadd);
      y += paint->yc_bpr;
      dst_y += paint->dst_bpr;
    }

    // Copy chroma, half-line of u chroma, half-line of v chroma
    // source sub-sampling = destination sub-sampling
    ka_yuv_correction_neon(dst_u, u, paint->yc_width >> 1, params->colfac, params->coladd);
    ka_yuv_correction_neon(dst_v, v, paint->yc_width >> 1, params->colfac, params->coladd);
    dst_u += paint->dst_u_bpr;
    dst_v += paint->dst_v_bpr;
    u += uv_delta;
    v += uv_delta;
  }

  assert(dst_y == paint->dst + paint->yc_height * paint->dst_bpr);
  assert(dst_u == paint->dst_u + (paint->yc_height >> 1) * paint->dst_u_bpr);
  assert(dst_v == paint->dst_v + (paint->yc_height >> 1) * paint->dst_v_bpr);
  assert(y == paint->base.y + paint->yc_height * paint->yc_bpr);
  assert(u == paint->base.cb + (paint->yc_height >> 1) * (paint->yc_bpr >> 1));
  assert(v == paint->base.cr + (paint->yc_height >> 1) * (paint->yc_bpr >> 1));
}

/**
 * Converts an YUV420 source into a YV16 destination, 100% zoom.
 * YV16: Plane 0 is 8bpp Y, Plane 1 Cb, Plane 2 Cr, 2x1 sub-sampled
 *
 * @param  paint  draw parameters
 */
void ka_drawyuv420_z1_YV16_neon(const ka_paint_t* paint)
{
  const ka_neon_yuv_to_rgb_t* params = paint->params;
  int height;
  uint8_t *dst_y, *dst_u, *dst_v;
  uint8_t *y, *u, *v;
  int uv_delta;

  dst_y = paint->dst;
  dst_u = paint->dst_u;
  dst_v = paint->dst_v;

  y = paint->base.y;                // luminance address
  u = paint->base.cb;               // chroma b source address
  v = paint->base.cr;               // chroma r source address
  uv_delta = (paint->yc_width >> 1) + paint->uv_skip; // 1 u/v line

  // Loop: copy 1 lines of luma, 1 half-line of uv chroma
  for (height = 0; height < paint->yc_height; height++)
  {
    // Copy luma row
    ka_yuv_correction_neon(dst_y, y, paint->yc_width, params->yfac, params->yadd);
    y += paint->yc_bpr;
    dst_y += paint->dst_bpr;

    // Copy chroma, half-line of u chroma, half-line of v chroma
    ka_yuv_correction_neon(dst_u, u, paint->yc_width >> 1, params->colfac, params->coladd);
    ka_yuv_correction_neon(dst_v, v, paint->yc_width >> 1, params->colfac, params->coladd);
    dst_u += paint->dst_u_bpr;
    dst_v += paint->dst_v_bpr;
    // Beware, source is sub-sampled vertically, not destination
    if (height & 1)
    {
      u += uv_delta;
      v += uv_delta;
    }
  }

  assert(dst_y == paint->dst + paint->yc_height * paint->dst_bpr);
  assert(dst_u == paint->dst_u + paint->yc_height * paint->dst_u_bpr);
  assert(dst_v == paint->dst_v + paint->yc_height * paint->dst_v_bpr);
  assert(y == paint->base.y + paint->yc_height * paint->yc_bpr);
  assert(u == paint->base.cb + (paint->yc_height >> 1) * (paint->yc_bpr >> 1));
  assert(v == paint->base.cr + (paint->yc_height >> 1) * (paint->yc_bpr >> 1));
}
