////////////////////////////////////////////////////////
//
// GEM - Graphics Environment for Multimedia
//
// Implementation file
//
// SPDX-FileCopyrightText: © 2003, Chris Clepper and the GEM contributors
// SPDX-License-Identifier: GPL-2.0-or-later
//
////////////////////////////////////////////////////////

#include "pix_compare.h"
#include "Utils/Functions.h"

CPPEXTERN_NEW_WITH_ONE_ARG(pix_compare, t_floatarg, A_DEFFLOAT);

/////////////////////////////////////////////////////////
//
// pix_compare
//
/////////////////////////////////////////////////////////
// Constructor
//
/////////////////////////////////////////////////////////
pix_compare :: pix_compare(t_floatarg f) :
  m_direction(!(!(int)f))
{}

/////////////////////////////////////////////////////////
// Destructor
//
/////////////////////////////////////////////////////////
pix_compare :: ~pix_compare()
{}

/////////////////////////////////////////////////////////
// processImage
//
/////////////////////////////////////////////////////////
void pix_compare :: processRGBA_RGBA(imageStruct &image,
                                     imageStruct &right)
{
  long src,h,w;
  src =0;
  if (m_direction) {
    for (h=0; h<image.ysize; h++) {
      for(w=0; w<image.xsize; w++) {
        if ((90*image.data[src+chRed]+115*image.data[src+chGreen]+51*image.data[src
             +chBlue]) <
            (90*right.data[src+chRed]+115*right.data[src+chGreen]+51*right.data[src
                +chBlue])) {
          image.data[src+chRed] = right.data[src+chRed];
          image.data[src+chBlue] = right.data[src+chBlue];
          image.data[src+chGreen] = right.data[src+chGreen];
        }
        src+=4;
      }
    }
  } else {
    for (h=0; h<image.ysize; h++) {
      for(w=0; w<image.xsize; w++) {
        if ((90*image.data[src+chRed]+115*image.data[src+chGreen]+51*image.data[src
             +chBlue]) >
            (90*right.data[src+chRed]+115*right.data[src+chGreen]+51*right.data[src
                +chBlue])) {
          image.data[src+chRed] = right.data[src+chRed];
          image.data[src+chBlue] = right.data[src+chBlue];
          image.data[src+chGreen] = right.data[src+chGreen];
        }
        src+=4;
      }
    }
  };
}

/////////////////////////////////////////////////////////
// processImage
//
/////////////////////////////////////////////////////////
void pix_compare :: processGray_Gray(imageStruct &image,
                                     imageStruct &right)
{
  long src,h,w;
  src =0;
  if (m_direction) {
    for (h=0; h<image.ysize; h++) {
      for(w=0; w<image.xsize; w++) {
        if (image.data[src+chGray] < right.data[src+chGray]) {
          image.data[src+chGray] = right.data[src+chGray];
        }
        src++;
      }
    }
  } else {
    for (h=0; h<image.ysize; h++) {
      for(w=0; w<image.xsize; w++) {
        if (image.data[src+chGray]>right.data[src+chGray]) {
          image.data[src+chGray] = right.data[src+chGray];
        }
        src++;
      }
    }
  }
}

/////////////////////////////////////////////////////////
// do the YUV processing here
//
/////////////////////////////////////////////////////////
void pix_compare :: processYUV_YUV(imageStruct &image, imageStruct &right)
{
  long src,h,w;
  src =0;

  //format is U Y V Y
  if (m_direction) {
    for (h=0; h<image.ysize; h++) {
      for(w=0; w<image.xsize/2; w++) {
        if ((image.data[src+1] < right.data[src+1])
            &&(image.data[src+3] < right.data[src+3])) {
          image.data[src] = right.data[src];
          image.data[src+1] = right.data[src+1];
          image.data[src+2] = right.data[src+2];
          image.data[src+3] = right.data[src+3];
        }
        src+=4;
      }
    }
  } else {
    for (h=0; h<image.ysize; h++) {
      for(w=0; w<image.xsize/2; w++) {
        if ((image.data[src+1] > right.data[src+1])
            &&(image.data[src+3] > right.data[src+3])) {
          image.data[src] = right.data[src];
          image.data[src+1] = right.data[src+1];
          image.data[src+2] = right.data[src+2];
          image.data[src+3] = right.data[src+3];
        }
        src+=4;
      }
    }
  }
}

#ifdef __MMX__
void pix_compare :: processGray_MMX(imageStruct &image, imageStruct &right)
{
  long datasize =   image.xsize * image.ysize * image.csize;
  datasize=datasize/sizeof(__m64)+(datasize%sizeof(__m64)!=0);
  __m64*leftPix =  (__m64*)image.data;
  __m64*rightPix = (__m64*)right.data;

  __m64 l, r, b;
  __m64 zeros = _mm_set1_pi8((unsigned char)0x00);
  //format is U Y V Y
  if (m_direction) {
    while(datasize--) {
      l=leftPix[datasize];
      r=rightPix[datasize];

      b=_mm_subs_pu8   (l, r);
      b=_mm_cmpeq_pi8  (b, zeros);
      r=_mm_and_si64   (r, b);
      l=_mm_andnot_si64(b, l);

      leftPix[datasize]=_mm_or_si64(l, r);
    }
  } else {
    while(datasize--) {
      l=leftPix[datasize];
      r=rightPix[datasize];

      b=_mm_subs_pu8   (l, r);
      b=_mm_cmpeq_pi8  (b, zeros);
      l=_mm_and_si64   (l, b);
      r=_mm_andnot_si64(b, r);

      leftPix[datasize]=_mm_or_si64(l, r);
    }
  }
  _mm_empty();
}

void pix_compare :: processYUV_MMX(imageStruct &image, imageStruct &right)
{
  long datasize =   image.xsize * image.ysize * image.csize;
  datasize=datasize/sizeof(__m64)+(datasize%sizeof(__m64)!=0);
  __m64*leftPix =  (__m64*)image.data;
  __m64*rightPix = (__m64*)right.data;

  __m64 l, r, b;
  __m64 mask = _mm_setr_pi8((unsigned char)0x00,
                            (unsigned char)0xFF,
                            (unsigned char)0x00,
                            (unsigned char)0xFF,
                            (unsigned char)0x00,
                            (unsigned char)0xFF,
                            (unsigned char)0x00,
                            (unsigned char)0xFF);
  __m64 zeros = _mm_set1_pi8((unsigned char)0x00);
  //format is U Y V Y
  if (m_direction) {
    while(datasize--) {
      l=leftPix[datasize];
      r=rightPix[datasize];
      b=_mm_subs_pu8(l, r);
      b=_mm_and_si64(b, mask);
      b=_mm_cmpeq_pi32(b, zeros);
      r=_mm_and_si64(r, b);
      l=_mm_andnot_si64(b, l);

      leftPix[datasize]=_mm_or_si64(l, r);
    }
  } else {
    while(datasize--) {
      l=leftPix[datasize];
      r=rightPix[datasize];
      b=_mm_subs_pu8(r, l);
      b=_mm_and_si64(b, mask);
      b=_mm_cmpeq_pi32(b, zeros);
      r=_mm_and_si64(r, b);
      l=_mm_andnot_si64(b, l);

      leftPix[datasize]=_mm_or_si64(l, r);
    }
  }
  _mm_empty();
}
#endif

/////////////////////////////////////////////////////////
// do the Altivec YUV processing here
//
/////////////////////////////////////////////////////////
#ifdef __VEC__
void pix_compare :: processYUV_Altivec(imageStruct &image,
                                       imageStruct &right)
{
  int h,w,i,j;

  h = image.ysize;
  w = image.xsize/8;

  //check to see if the buffer isn't 16byte aligned (highly unlikely)
  if (image.ysize*image.xsize % 16 != 0) {
    error("image not properly aligned for Altivec");
    return;
  }

  vector unsigned short UVres1, Yres1, UVres2,Yres2;//interleave;
  vector unsigned short hiImage, loImage;
  vector bool short     Ymask1;
  vector unsigned char  one = vec_splat_u8(1);

  vector unsigned char *inData = (vector unsigned char*) image.data;
  vector unsigned char *rightData = (vector unsigned char*) right.data;

#ifndef PPC970
  //setup the cache prefetch -- A MUST!!!
  UInt32                      prefetchSize = GetPrefetchConstant( 16, 1,
      256 );
  vec_dst( inData, prefetchSize, 0 );
  vec_dst( rightData, prefetchSize, 1 );
#endif
  if (m_direction) {

    for ( i=0; i<h; i++) {
      for (j=0; j<w; j++) {
#ifndef PPC970
        //this function is probably memory bound on most G4's -- what else is new?
        vec_dst( inData, prefetchSize, 0 );
        vec_dst( rightData, prefetchSize, 1 );
#endif

        //separate the U and V from Y
        UVres1 = (vector unsigned short)vec_mule(one,inData[0]);
        UVres2 = (vector unsigned short)vec_mule(one,rightData[0]);

        //vec_mulo Y * 1 to short vector Y Y Y Y shorts
        Yres1 = (vector unsigned short)vec_mulo(one,inData[0]);
        Yres2 = (vector unsigned short)vec_mulo(one,rightData[0]);

        //compare the Y values
        Ymask1 = vec_cmpgt(Yres1,Yres2);

        //bitwise comparison and move using the result of the comparison as a mask
        Yres1 = vec_sel(Yres2,Yres1,Ymask1);

        UVres1 = vec_sel(UVres2,UVres1,Ymask1);

        //merge the Y and UV back together
        hiImage = vec_mergeh(UVres1,Yres1);
        loImage = vec_mergel(UVres1,Yres1);

        //pack it back down to unsigned char to store
        inData[0] = vec_packsu(hiImage,loImage);

        inData++;
        rightData++;

      }
#ifndef PPC970
      vec_dss(1);
      vec_dss(0);
#endif

    }
  } else {

    for ( i=0; i<h; i++) {
      for (j=0; j<w; j++) {
#ifndef PPC970
        vec_dst( inData, prefetchSize, 0 );
        vec_dst( rightData, prefetchSize, 1 );
#endif

        UVres1 = (vector unsigned short)vec_mule(one,inData[0]);
        UVres2 = (vector unsigned short)vec_mule(one,rightData[0]);

        //vec_mulo Y * 1 to short vector Y Y Y Y shorts
        Yres1 = (vector unsigned short)vec_mulo(one,inData[0]);
        Yres2 = (vector unsigned short)vec_mulo(one,rightData[0]);

        Ymask1 = vec_cmplt(Yres1,Yres2);

        Yres1 = vec_sel(Yres2,Yres1,Ymask1);

        UVres1 = vec_sel(UVres2,UVres1,Ymask1);

        hiImage = vec_mergeh(UVres1,Yres1);
        loImage = vec_mergel(UVres1,Yres1);

        inData[0] = vec_packsu(hiImage,loImage);

        inData++;
        rightData++;

      }
#ifndef PPC970
      vec_dss(1);
      vec_dss(0);
#endif
    }
  }
}
#endif

/////////////////////////////////////////////////////////
// static member function
//
/////////////////////////////////////////////////////////
void pix_compare :: obj_setupCallback(t_class *classPtr)
{
  class_addmethod(classPtr,
                  reinterpret_cast<t_method>(&pix_compare::directionCallback),
                  gensym("direction"), A_DEFFLOAT, A_NULL);
}

void pix_compare :: directionCallback(void *data, t_float state)
{
  GetMyClass(data)->m_direction=!(!(int)state);
  GetMyClass(data)->setPixModified();
}
