/*------------------------------------------------------------------------------
 src/lib/djImage/pixelCopy.cxx
 
 Copyright (c) 2004-2006 Darby Johnston.
 All rights reserved.
 Use is subject to license terms.
------------------------------------------------------------------------------*/

//#define DJ_DBG
#include "djPixel.h"

namespace dj {

// Type conversion.
//
#define _DJ_U8_TO_U8_0(in) (in)
#define _DJ_U8_TO_U16_0(in) pixelU8ToU16(in)
#define _DJ_U8_TO_F32_0(in) pixelU8ToF32(in)
#define _DJ_U16_TO_U8_0(in) pixelU16ToU8(in)
#define _DJ_U16_TO_U16_0(in) (in)
#define _DJ_U16_TO_F32_0(in) pixelU16ToF32(in)
#define _DJ_F32_TO_U8_0(in) pixelF32ToU8(in)
#define _DJ_F32_TO_U16_0(in) pixelF32ToU16(in)
#define _DJ_F32_TO_F32_0(in) (in)

#define _DJ_U8_TO_U8_1(in) (in)
#define _DJ_U8_TO_U16_1(in) pixelU8ToU16(in)
#define _DJ_U8_TO_F32_1(in) pixelU8ToF32(in)
#define _DJ_U16_TO_U8_1(in) pixelU16ToU8((in << 8) | (in >> 8))
#define _DJ_U16_TO_U16_1(in) ((in << 8) | (in >> 8))
#define _DJ_U16_TO_F32_1(in) pixelU16ToF32((in << 8) | (in >> 8))
#define _DJ_F32_TO_U8_1(in) pixelF32ToU8(in)
#define _DJ_F32_TO_U16_1(in) pixelF32ToU16(in)
#define _DJ_F32_TO_F32_1(in) (in)

#define _DJ_U8_ZERO pixelU8Min
#define _DJ_U16_ZERO pixelU16Min
#define _DJ_F32_ZERO 0.0
#define _DJ_U8_ONE pixelU8Max
#define _DJ_U16_ONE pixelU16Max
#define _DJ_F32_ONE 1.0
#define _DJ_U8_CTYPE PixelU8
#define _DJ_U16_CTYPE PixelU16
#define _DJ_F32_CTYPE PixelF32

// L format conversion.
//
#define _DJ_L_TO_L(IN_TYPE, OUT_TYPE, ENDIAN) \
  outP[0] = _DJ_##IN_TYPE##_TO_##OUT_TYPE##_##ENDIAN(inP[0]);
#define _DJ_L_TO_LA(IN_TYPE, OUT_TYPE, ENDIAN) \
  outP[0] = _DJ_##IN_TYPE##_TO_##OUT_TYPE##_##ENDIAN(inP[0]); \
  outP[1] = _DJ_##OUT_TYPE##_ONE;
#define _DJ_L_TO_RGB(IN_TYPE, OUT_TYPE, ENDIAN) \
  outP[0] = outP[1] = outP[2] = \
    _DJ_##IN_TYPE##_TO_##OUT_TYPE##_##ENDIAN(inP[0]);
#define _DJ_L_TO_RGBA(IN_TYPE, OUT_TYPE, ENDIAN) \
  outP[0] = outP[1] = outP[2] = \
    _DJ_##IN_TYPE##_TO_##OUT_TYPE##_##ENDIAN(inP[0]); \
  outP[3] = _DJ_##OUT_TYPE##_ONE;

// LA format conversion.
//
#define _DJ_LA_TO_L(IN_TYPE, OUT_TYPE, ENDIAN) \
  _DJ_L_TO_L(IN_TYPE, OUT_TYPE, ENDIAN)
#define _DJ_LA_TO_LA(IN_TYPE, OUT_TYPE, ENDIAN) \
  outP[0] = _DJ_##IN_TYPE##_TO_##OUT_TYPE##_##ENDIAN(inP[0]); \
  outP[1] = _DJ_##IN_TYPE##_TO_##OUT_TYPE##_##ENDIAN(inP[1]);
#define _DJ_LA_TO_RGB(IN_TYPE, OUT_TYPE, ENDIAN) \
  _DJ_L_TO_RGB(IN_TYPE, OUT_TYPE, ENDIAN)
#define _DJ_LA_TO_RGBA(IN_TYPE, OUT_TYPE, ENDIAN) \
  _DJ_L_TO_RGB(IN_TYPE, OUT_TYPE, ENDIAN) \
  outP[3] = _DJ_##IN_TYPE##_TO_##OUT_TYPE##_##ENDIAN(inP[1]);

// RGB format conversion.
//
#define _DJ_RGB_TO_L(IN_TYPE, OUT_TYPE, ENDIAN) \
  outP[0] =_DJ_##OUT_TYPE##_CTYPE( \
    ( \
      _DJ_##IN_TYPE##_TO_##OUT_TYPE##_##ENDIAN(inP[0]) + \
      _DJ_##IN_TYPE##_TO_##OUT_TYPE##_##ENDIAN(inP[1]) + \
      _DJ_##IN_TYPE##_TO_##OUT_TYPE##_##ENDIAN(inP[2]) \
    ) / 3.0 \
  );
#define _DJ_RGB_TO_LA(IN_TYPE, OUT_TYPE, ENDIAN) \
  _DJ_RGB_TO_L(IN_TYPE, OUT_TYPE, ENDIAN) \
  outP[1] = _DJ_##OUT_TYPE##_ONE;
#define _DJ_RGB_TO_RGB(IN_TYPE, OUT_TYPE, ENDIAN) \
  outP[0] = _DJ_##IN_TYPE##_TO_##OUT_TYPE##_##ENDIAN(inP[0]); \
  outP[1] = _DJ_##IN_TYPE##_TO_##OUT_TYPE##_##ENDIAN(inP[1]); \
  outP[2] = _DJ_##IN_TYPE##_TO_##OUT_TYPE##_##ENDIAN(inP[2]);
#define _DJ_RGB_TO_RGBA(IN_TYPE, OUT_TYPE, ENDIAN) \
  _DJ_RGB_TO_RGB(IN_TYPE, OUT_TYPE, ENDIAN) \
  outP[3] = _DJ_##OUT_TYPE##_ONE;

// RGBA format conversion.
//
#define _DJ_RGBA_TO_L(IN_TYPE, OUT_TYPE, ENDIAN) \
  _DJ_RGB_TO_L(IN_TYPE, OUT_TYPE, ENDIAN)
#define _DJ_RGBA_TO_LA(IN_TYPE, OUT_TYPE, ENDIAN) \
  _DJ_RGBA_TO_L(IN_TYPE, OUT_TYPE, ENDIAN) \
  outP[1] = _DJ_##IN_TYPE##_TO_##OUT_TYPE##_##ENDIAN(inP[3]);
#define _DJ_RGBA_TO_RGB(IN_TYPE, OUT_TYPE, ENDIAN) \
  _DJ_RGB_TO_RGB(IN_TYPE, OUT_TYPE, ENDIAN)
#define _DJ_RGBA_TO_RGBA(IN_TYPE, OUT_TYPE, ENDIAN) \
  _DJ_RGBA_TO_RGB(IN_TYPE, OUT_TYPE, ENDIAN) \
  outP[3] = _DJ_##IN_TYPE##_TO_##OUT_TYPE##_##ENDIAN(inP[3]);

// Copy function.
//
#define _DJ_COPY_FNC_ARGS \
  const void * in, int inInc, \
  void * out, int outInc, \
  int size
typedef void (_CopyFnc)(_DJ_COPY_FNC_ARGS);
#define _DJ_COPY_FNC(IN_FORMAT, IN_TYPE, OUT_FORMAT, OUT_TYPE, ENDIAN) \
static void _##IN_FORMAT##_##IN_TYPE##_To_##OUT_FORMAT##_##OUT_TYPE##_##ENDIAN( \
  _DJ_COPY_FNC_ARGS \
) { \
  DJ_DBG_FNC("dj::_copy"); \
  DJ_DBG_PRN("in = " << PIXEL_##IN_FORMAT << " " << PIXEL_##IN_TYPE); \
  DJ_DBG_PRN("in inc = " << inInc); \
  DJ_DBG_PRN("out = " << PIXEL_##OUT_FORMAT << " " << PIXEL_##OUT_TYPE); \
  DJ_DBG_PRN("out inc = " << outInc); \
  DJ_DBG_PRN("size = " << size); \
  DJ_DBG_PRN("endian = " << endian); \
  const _DJ_##IN_TYPE##_CTYPE * inP = \
    reinterpret_cast<const _DJ_##IN_TYPE##_CTYPE *>(in); \
  const _DJ_##IN_TYPE##_CTYPE * const end = inP + size * inInc; \
  _DJ_##OUT_TYPE##_CTYPE * outP = \
    reinterpret_cast<_DJ_##OUT_TYPE##_CTYPE *>(out); \
  for (; inP != end; inP += inInc, outP += outInc) { \
    _DJ_##IN_FORMAT##_TO_##OUT_FORMAT(IN_TYPE, OUT_TYPE, ENDIAN) \
  } \
}

// Define functions.
//
#define _DJ_FNC_DEFINE0(FNC, IN_FORMAT, IN_TYPE, OUT_FORMAT, OUT_TYPE) \
  FNC(IN_FORMAT, IN_TYPE, OUT_FORMAT, OUT_TYPE, 0) \
  FNC(IN_FORMAT, IN_TYPE, OUT_FORMAT, OUT_TYPE, 1)
#define _DJ_FNC_DEFINE1(FNC, IN_FORMAT, IN_TYPE, OUT_FORMAT) \
  _DJ_FNC_DEFINE0(FNC, IN_FORMAT, IN_TYPE, OUT_FORMAT, U8) \
  _DJ_FNC_DEFINE0(FNC, IN_FORMAT, IN_TYPE, OUT_FORMAT, U16) \
  _DJ_FNC_DEFINE0(FNC, IN_FORMAT, IN_TYPE, OUT_FORMAT, F32)
#define _DJ_FNC_DEFINE2(FNC, IN_FORMAT, IN_TYPE) \
  _DJ_FNC_DEFINE1(FNC, IN_FORMAT, IN_TYPE, L) \
  _DJ_FNC_DEFINE1(FNC, IN_FORMAT, IN_TYPE, LA) \
  _DJ_FNC_DEFINE1(FNC, IN_FORMAT, IN_TYPE, RGB) \
  _DJ_FNC_DEFINE1(FNC, IN_FORMAT, IN_TYPE, RGBA)
#define _DJ_FNC_DEFINE3(FNC, IN_FORMAT) \
  _DJ_FNC_DEFINE2(FNC, IN_FORMAT, U8) \
  _DJ_FNC_DEFINE2(FNC, IN_FORMAT, U16) \
  _DJ_FNC_DEFINE2(FNC, IN_FORMAT, F32)
_DJ_FNC_DEFINE3(_DJ_COPY_FNC, L)
_DJ_FNC_DEFINE3(_DJ_COPY_FNC, LA)
_DJ_FNC_DEFINE3(_DJ_COPY_FNC, RGB)
_DJ_FNC_DEFINE3(_DJ_COPY_FNC, RGBA)

// Function tables.
//
#define _DJ_FNC_TABLE0(IN_FORMAT, IN_TYPE, OUT_FORMAT, OUT_TYPE) \
  { \
    _##IN_FORMAT##_##IN_TYPE##_To_##OUT_FORMAT##_##OUT_TYPE##_0, \
    _##IN_FORMAT##_##IN_TYPE##_To_##OUT_FORMAT##_##OUT_TYPE##_1 \
  }
#define _DJ_FNC_TABLE1(IN_FORMAT, IN_TYPE, OUT_FORMAT) \
  { \
    _DJ_FNC_TABLE0(IN_FORMAT, IN_TYPE, OUT_FORMAT, U8), \
    _DJ_FNC_TABLE0(IN_FORMAT, IN_TYPE, OUT_FORMAT, U16), \
    _DJ_FNC_TABLE0(IN_FORMAT, IN_TYPE, OUT_FORMAT, F32) \
  }
#define _DJ_FNC_TABLE2(IN_FORMAT, IN_TYPE) \
  { \
    _DJ_FNC_TABLE1(IN_FORMAT, IN_TYPE, L), \
    _DJ_FNC_TABLE1(IN_FORMAT, IN_TYPE, LA), \
    _DJ_FNC_TABLE1(IN_FORMAT, IN_TYPE, RGB), \
    _DJ_FNC_TABLE1(IN_FORMAT, IN_TYPE, RGBA) \
  }
#define _DJ_FNC_TABLE3(IN_FORMAT) \
  { \
    _DJ_FNC_TABLE2(IN_FORMAT, U8), \
    _DJ_FNC_TABLE2(IN_FORMAT, U16), \
    _DJ_FNC_TABLE2(IN_FORMAT, F32) \
  }
static _CopyFnc * _copyFncTbl
  [pixelFormatSize][pixelTypeSize]
  [pixelFormatSize][pixelTypeSize]
  [2] =
{
  _DJ_FNC_TABLE3(L),
  _DJ_FNC_TABLE3(LA),
  _DJ_FNC_TABLE3(RGB),
  _DJ_FNC_TABLE3(RGBA)
};

void pixelCopy(
  const void * in, void * out,
  int size, const Pixel & inPixel, const Pixel & outPixel,
  int proxyScale,
  bool mirror,
  int interleave,
  bool endian
) {
  if (size <= 0 || proxyScale <= 0) return;

  DJ_DBG_FNC("dj::pixelCopy");
  DJ_DBG_PRN("size = " << size);
  DJ_DBG_PRN("pixel = " << inPixel << " " << outPixel);
  DJ_DBG_PRN("proxy scale = " << proxyScale);
  DJ_DBG_PRN("mirror = " << mirror);
  DJ_DBG_PRN("interleave = " << interleave);
  DJ_DBG_PRN("endian = " << endian);

  const bool extras =
    (proxyScale != 1) || mirror || (interleave != 0) || endian;

  if (inPixel == outPixel && ! extras) {

    DJ_DBG_PRN("fast");

    memCopy(in, out, size * pixelSize(outPixel));
  
  } else {
    
    DJ_DBG_PRN("slow");
    
    PIXEL_FORMAT inFormat = inPixel.format(), outFormat = outPixel.format();
    int inInc = pixelSize(inFormat) * proxyScale;
    int outInc = pixelSize(outFormat);
    switch (interleave) {
      case 0: break;
      case 1: outFormat = PIXEL_L; break;
      case -1: inFormat = PIXEL_L; break;
    }
    uint8_t * p = reinterpret_cast<uint8_t *>(out);
    if (mirror) {
      p += (size - 1) * outInc * pixelSize(outPixel.type());
      outInc = -outInc;
    }
    _copyFncTbl[inFormat][inPixel.type()][outFormat][outPixel.type()][endian](
      in, inInc, p, outInc, size
    );
  }
}

void pixelPlanar(
  const void * in, void * out,
  const Vec2i & size,
  PIXEL_FORMAT format, PIXEL_TYPE inType, PIXEL_TYPE outType
) {
  for (int c = 0; c < pixelSize(format); ++c)
    pixelCopy(
      reinterpret_cast<const uint8_t *>(in) + c * pixelSize(inType),
      reinterpret_cast<uint8_t *>(out) + c * area(size) * pixelSize(outType),
      area(size), Pixel(format, inType), Pixel(PIXEL_L, outType),
      1, false, -1
    );
}

}
