/******************************************************************************
 **      Filename:    intfx.c
 **      Purpose:     Integer character normalization & feature extraction
 **      Author:      Robert Moss
 **      History:     Tue May 21 15:51:57 MDT 1991, RWM, Created.
 **
 **      (c) Copyright Hewlett-Packard Company, 1988.
 ** Licensed under the Apache License, Version 2.0 (the "License");
 ** you may not use this file except in compliance with the License.
 ** You may obtain a copy of the License at
 ** http://www.apache.org/licenses/LICENSE-2.0
 ** Unless required by applicable law or agreed to in writing, software
 ** distributed under the License is distributed on an "AS IS" BASIS,
 ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 ** See the License for the specific language governing permissions and
 ** limitations under the License.
 ******************************************************************************/
/**----------------------------------------------------------------------------
          Include Files and Type Defines
----------------------------------------------------------------------------**/
#include "intfx.h"
#include "intmatcher.h"
#include "const.h"
#ifdef __UNIX__
#include <assert.h>
#endif

/**----------------------------------------------------------------------------
          Private Function Prototypes
----------------------------------------------------------------------------**/
int SaveFeature(); 
UINT8 TableLookup(); 
UINT8 MySqrt2(); 
void ClipRadius(); 

make_int_var (RadiusGyrMinMan, 255, MakeRadiusGyrMinMan,
16, 10, SetRadiusGyrMinMan,
"Minimum Radius of Gyration Mantissa 0-255:        ");

make_int_var (RadiusGyrMinExp, 0, MakeRadiusGyrMinExp,
16, 11, SetRadiusGyrMinExp,
"Minimum Radius of Gyration Exponent 0-255:        ");

make_int_var (RadiusGyrMaxMan, 158, MakeRadiusGyrMaxMan,
16, 12, SetRadiusGyrMaxMan,
"Maximum Radius of Gyration Mantissa 0-255:        ");

make_int_var (RadiusGyrMaxExp, 8, MakeRadiusGyrMaxExp,
16, 13, SetRadiusGyrMaxExp,
"Maximum Radius of Gyration Exponent 0-255:        ");

/**----------------------------------------------------------------------------
        Global Data Definitions and Declarations
----------------------------------------------------------------------------**/
#define  ATAN_TABLE_SIZE    64

static UINT8 AtanTable[ATAN_TABLE_SIZE];

/**----------------------------------------------------------------------------
            Public Code
----------------------------------------------------------------------------**/
/*---------------------------------------------------------------------------*/
void InitIntegerFX() { 
  int i;

  for (i = 0; i < ATAN_TABLE_SIZE; i++)
    AtanTable[i] =
      (UINT8) (atan ((i / (float) ATAN_TABLE_SIZE)) * 128.0 / PI + 0.5);

}


/*--------------------------------------------------------------------------*/
int ExtractIntFeat(TBLOB *Blob,
                   INT_FEATURE_ARRAY BLFeat,
                   INT_FEATURE_ARRAY CNFeat,
                   INT_FX_RESULT Results) {

  TESSLINE *OutLine;
  EDGEPT *Loop, *LoopStart, *Segment;
  INT16 LastX, LastY, Xmean, Ymean;
  INT32 NormX, NormY, DeltaX, DeltaY;
  INT32 Xsum, Ysum;
  UINT32 Ix, Iy, LengthSum;
  UINT16 n;
  UINT8 Theta;
  UINT16 NumBLFeatures, NumCNFeatures;
  UINT8 RxInv, RyInv;            /* x.xxxxxxx  *  2^Exp  */
  UINT8 RxExp, RyExp;
                                 /* sxxxxxxxxxxxxxxxxxxxxxxx.xxxxxxxx */
  register INT32 pfX, pfY, dX, dY;
  UINT16 Length;
  register int i;

  Results->Length = 0;
  Results->Xmean = 0;
  Results->Ymean = 0;
  Results->Rx = 0;
  Results->Ry = 0;
  Results->NumBL = 0;
  Results->NumCN = 0;

  /* find Xmean, Ymean */
  NumBLFeatures = 0;
  NumCNFeatures = 0;
  OutLine = Blob->outlines;
  Xsum = 0;
  Ysum = 0;
  LengthSum = 0;
  while (OutLine != NULL) {
    LoopStart = OutLine->loop;
    Loop = LoopStart;
    LastX = Loop->pos.x;
    LastY = Loop->pos.y;
    /* Check for bad loops */
    if ((Loop == NULL) || (Loop->next == NULL) || (Loop->next == LoopStart))
      return FALSE;
    do {
      Segment = Loop;
      Loop = Loop->next;
      NormX = Loop->pos.x;
      NormY = Loop->pos.y;

      n = 1;
      if (!is_hidden_edge (Segment)) {
        DeltaX = NormX - LastX;
        DeltaY = NormY - LastY;
        Length = MySqrt (DeltaX, DeltaY);
        n = ((Length << 2) + Length + 32) >> 6;
        if (n != 0) {
          Xsum += ((LastX << 1) + DeltaX) * (int) Length;
          Ysum += ((LastY << 1) + DeltaY) * (int) Length;
          LengthSum += Length;
        }
      }
      if (n != 0) {              /* Throw away a point that is too close */
        LastX = NormX;
        LastY = NormY;
      }
    }
    while (Loop != LoopStart);
    OutLine = OutLine->next;
  }
  if (LengthSum == 0)
    return FALSE;
  Xmean = (Xsum / (INT32) LengthSum) >> 1;
  Ymean = (Ysum / (INT32) LengthSum) >> 1;

  Results->Length = LengthSum;
  Results->Xmean = Xmean;
  Results->Ymean = Ymean;

  /* extract Baseline normalized features,     */
  /* and find 2nd moments & radius of gyration */
  Ix = 0;
  Iy = 0;
  NumBLFeatures = 0;
  OutLine = Blob->outlines;
  while (OutLine != NULL) {
    LoopStart = OutLine->loop;
    Loop = LoopStart;
    LastX = Loop->pos.x - Xmean;
    LastY = Loop->pos.y;
    /* Check for bad loops */
    if ((Loop == NULL) || (Loop->next == NULL) || (Loop->next == LoopStart))
      return FALSE;
    do {
      Segment = Loop;
      Loop = Loop->next;
      NormX = Loop->pos.x - Xmean;
      NormY = Loop->pos.y;

      n = 1;
      if (!is_hidden_edge (Segment)) {
        DeltaX = NormX - LastX;
        DeltaY = NormY - LastY;
        Length = MySqrt (DeltaX, DeltaY);
        n = ((Length << 2) + Length + 32) >> 6;
        if (n != 0) {
          Theta = TableLookup (DeltaY, DeltaX);
          dX = (DeltaX << 8) / n;
          dY = (DeltaY << 8) / n;
          pfX = (LastX << 8) + (dX >> 1);
          pfY = (LastY << 8) + (dY >> 1);
          Ix += ((pfY >> 8) - Ymean) * ((pfY >> 8) - Ymean);
          Iy += (pfX >> 8) * (pfX >> 8);
          if (SaveFeature (BLFeat, NumBLFeatures, (INT16) (pfX >> 8),
            (INT16) ((pfY >> 8) - 128),
            Theta) == FALSE)
            return FALSE;
          NumBLFeatures++;
          for (i = 1; i < n; i++) {
            pfX += dX;
            pfY += dY;
            Ix += ((pfY >> 8) - Ymean) * ((pfY >> 8) - Ymean);
            Iy += (pfX >> 8) * (pfX >> 8);
            if (SaveFeature
              (BLFeat, NumBLFeatures, (INT16) (pfX >> 8),
              (INT16) ((pfY >> 8) - 128), Theta) == FALSE)
              return FALSE;
            NumBLFeatures++;
          }
        }
      }
      if (n != 0) {              /* Throw away a point that is too close */
        LastX = NormX;
        LastY = NormY;
      }
    }
    while (Loop != LoopStart);
    OutLine = OutLine->next;
  }
  if (Ix == 0)
    Ix = 1;
  if (Iy == 0)
    Iy = 1;
  RxInv = MySqrt2 (NumBLFeatures, Ix, &RxExp);
  RyInv = MySqrt2 (NumBLFeatures, Iy, &RyExp);
  ClipRadius(&RxInv, &RxExp, &RyInv, &RyExp); 

  Results->Rx = (INT16) (51.2 / (double) RxInv * pow (2.0, (double) RxExp));
  Results->Ry = (INT16) (51.2 / (double) RyInv * pow (2.0, (double) RyExp));
  Results->NumBL = NumBLFeatures;

  /* extract character normalized features */
  NumCNFeatures = 0;
  OutLine = Blob->outlines;
  while (OutLine != NULL) {
    LoopStart = OutLine->loop;
    Loop = LoopStart;
    LastX = (Loop->pos.x - Xmean) * RyInv;
    LastY = (Loop->pos.y - Ymean) * RxInv;
    LastX >>= (INT8) RyExp;
    LastY >>= (INT8) RxExp;
    /* Check for bad loops */
    if ((Loop == NULL) || (Loop->next == NULL) || (Loop->next == LoopStart))
      return FALSE;
    do {
      Segment = Loop;
      Loop = Loop->next;
      NormX = (Loop->pos.x - Xmean) * RyInv;
      NormY = (Loop->pos.y - Ymean) * RxInv;
      NormX >>= (INT8) RyExp;
      NormY >>= (INT8) RxExp;

      n = 1;
      if (!is_hidden_edge (Segment)) {
        DeltaX = NormX - LastX;
        DeltaY = NormY - LastY;
        Length = MySqrt (DeltaX, DeltaY);
        n = ((Length << 2) + Length + 32) >> 6;
        if (n != 0) {
          Theta = TableLookup (DeltaY, DeltaX);
          dX = (DeltaX << 8) / n;
          dY = (DeltaY << 8) / n;
          pfX = (LastX << 8) + (dX >> 1);
          pfY = (LastY << 8) + (dY >> 1);
          if (SaveFeature (CNFeat, NumCNFeatures, (INT16) (pfX >> 8),
            (INT16) ((pfY >> 8)), Theta) == FALSE)
            return FALSE;
          NumCNFeatures++;
          for (i = 1; i < n; i++) {
            pfX += dX;
            pfY += dY;
            if (SaveFeature
              (CNFeat, NumCNFeatures, (INT16) (pfX >> 8),
              (INT16) ((pfY >> 8)), Theta) == FALSE)
              return FALSE;
            NumCNFeatures++;
          }
        }
      }
      if (n != 0) {              /* Throw away a point that is too close */
        LastX = NormX;
        LastY = NormY;
      }
    }
    while (Loop != LoopStart);
    OutLine = OutLine->next;
  }

  Results->NumCN = NumCNFeatures;
  return TRUE;
}


/*--------------------------------------------------------------------------*/
UINT8 TableLookup(INT32 Y, INT32 X) { 
  INT16 Angle;
  UINT16 Ratio;
  UINT32 AbsX, AbsY;

  assert ((X != 0) || (Y != 0));
  if (X < 0)
    AbsX = -X;
  else
    AbsX = X;
  if (Y < 0)
    AbsY = -Y;
  else
    AbsY = Y;
  if (AbsX > AbsY)
    Ratio = AbsY * ATAN_TABLE_SIZE / AbsX;
  else
    Ratio = AbsX * ATAN_TABLE_SIZE / AbsY;
  if (Ratio >= ATAN_TABLE_SIZE)
    Ratio = ATAN_TABLE_SIZE - 1;
  Angle = AtanTable[Ratio];
  if (X >= 0)
    if (Y >= 0)
      if (AbsX > AbsY)
        Angle = Angle;
  else
    Angle = 64 - Angle;
  else if (AbsX > AbsY)
    Angle = 256 - Angle;
  else
    Angle = 192 + Angle;
  else if (Y >= 0)
  if (AbsX > AbsY)
    Angle = 128 - Angle;
  else
    Angle = 64 + Angle;
  else if (AbsX > AbsY)
    Angle = 128 + Angle;
  else
    Angle = 192 - Angle;

  /* reverse angles to match old feature extractor:   Angle += PI */
  Angle += 128;
  Angle &= 255;
  return (UINT8) Angle;
}


/*--------------------------------------------------------------------------*/
int SaveFeature(INT_FEATURE_ARRAY FeatureArray,
                UINT16 FeatureNum,
                INT16 X,
                INT16 Y,
                UINT8 Theta) {
  INT_FEATURE Feature;

  if (FeatureNum >= MAX_NUM_INT_FEATURES)
    return FALSE;

  Feature = &(FeatureArray[FeatureNum]);

  X = X + 128;
  Y = Y + 128;

  if (X > 255)
    Feature->X = 255;
  else if (X < 0)
    Feature->X = 0;
  else
    Feature->X = X;

  if (Y > 255)
    Feature->Y = 255;
  else if (Y < 0)
    Feature->Y = 0;
  else
    Feature->Y = Y;

  Feature->Theta = Theta;

  return TRUE;
}


/*---------------------------------------------------------------------------*/
UINT16 MySqrt(INT32 X, INT32 Y) { 
  register UINT16 SqRoot;
  register UINT32 Square;
  register UINT16 BitLocation;
  register UINT32 Sum;

  if (X < 0)
    X = -X;
  if (Y < 0)
    Y = -Y;

  if (X > EvidenceMultMask)
    X = EvidenceMultMask;
  if (Y > EvidenceMultMask)
    Y = EvidenceMultMask;

  Sum = X * X + Y * Y;

  BitLocation = 1024;
  SqRoot = 0;
  do {
    Square = (SqRoot | BitLocation) * (SqRoot | BitLocation);
    if (Square <= Sum)
      SqRoot |= BitLocation;
    BitLocation >>= 1;
  }
  while (BitLocation);

  return SqRoot;
}


/*--------------------------------------------------------------------------*/
UINT8 MySqrt2(UINT16 N, UINT32 I, UINT8 *Exp) { 
  register INT8 k;
  register UINT32 N2;
  register UINT8 SqRoot;
  register UINT16 Square;
  register UINT8 BitLocation;
  register UINT16 Ratio;

  N2 = N * 41943;

  k = 9;
  while ((N2 & 0xc0000000) == 0) {
    N2 <<= 2;
    k += 1;
  }

  while ((I & 0xc0000000) == 0) {
    I <<= 2;
    k -= 1;
  }

  if (((N2 & 0x80000000) == 0) && ((I & 0x80000000) == 0)) {
    N2 <<= 1;
    I <<= 1;
  }

  N2 &= 0xffff0000;
  I >>= 14;
  Ratio = N2 / I;

  BitLocation = 128;
  SqRoot = 0;
  do {
    Square = (SqRoot | BitLocation) * (SqRoot | BitLocation);
    if (Square <= Ratio)
      SqRoot |= BitLocation;
    BitLocation >>= 1;
  }
  while (BitLocation);

  if (k < 0) {
    *Exp = 0;
    return 255;
  }
  else {
    *Exp = k;
    return SqRoot;
  }
}


/*-------------------------------------------------------------------------*/
void ClipRadius(UINT8 *RxInv, UINT8 *RxExp, UINT8 *RyInv, UINT8 *RyExp) { 
  register UINT8 AM, BM, AE, BE;
  register UINT8 BitN, LastCarry;
  int RxInvLarge, RyInvSmall;

  AM = RadiusGyrMinMan;
  AE = RadiusGyrMinExp;
  BM = *RxInv;
  BE = *RxExp;
  LastCarry = 1;
  while ((AM != 0) || (BM != 0)) {
    if (AE > BE) {
      BitN = LastCarry + (AM & 1) + 1;
      AM >>= 1;
      AE--;
    }
    else if (AE < BE) {
      BitN = LastCarry + (!(BM & 1));
      BM >>= 1;
      BE--;
    }
    else {                       /* AE == BE */
      BitN = LastCarry + (AM & 1) + (!(BM & 1));
      AM >>= 1;
      BM >>= 1;
      AE--;
      BE--;
    }
    LastCarry = (BitN & 2) > 1;
    BitN = BitN & 1;
  }
  BitN = LastCarry + 1;
  LastCarry = (BitN & 2) > 1;
  BitN = BitN & 1;

  if (BitN == 1) {
    *RxInv = RadiusGyrMinMan;
    *RxExp = RadiusGyrMinExp;
  }

  AM = RadiusGyrMinMan;
  AE = RadiusGyrMinExp;
  BM = *RyInv;
  BE = *RyExp;
  LastCarry = 1;
  while ((AM != 0) || (BM != 0)) {
    if (AE > BE) {
      BitN = LastCarry + (AM & 1) + 1;
      AM >>= 1;
      AE--;
    }
    else if (AE < BE) {
      BitN = LastCarry + (!(BM & 1));
      BM >>= 1;
      BE--;
    }
    else {                       /* AE == BE */
      BitN = LastCarry + (AM & 1) + (!(BM & 1));
      AM >>= 1;
      BM >>= 1;
      AE--;
      BE--;
    }
    LastCarry = (BitN & 2) > 1;
    BitN = BitN & 1;
  }
  BitN = LastCarry + 1;
  LastCarry = (BitN & 2) > 1;
  BitN = BitN & 1;

  if (BitN == 1) {
    *RyInv = RadiusGyrMinMan;
    *RyExp = RadiusGyrMinExp;
  }

  AM = RadiusGyrMaxMan;
  AE = RadiusGyrMaxExp;
  BM = *RxInv;
  BE = *RxExp;
  LastCarry = 1;
  while ((AM != 0) || (BM != 0)) {
    if (AE > BE) {
      BitN = LastCarry + (AM & 1) + 1;
      AM >>= 1;
      AE--;
    }
    else if (AE < BE) {
      BitN = LastCarry + (!(BM & 1));
      BM >>= 1;
      BE--;
    }
    else {                       /* AE == BE */
      BitN = LastCarry + (AM & 1) + (!(BM & 1));
      AM >>= 1;
      BM >>= 1;
      AE--;
      BE--;
    }
    LastCarry = (BitN & 2) > 1;
    BitN = BitN & 1;
  }
  BitN = LastCarry + 1;
  LastCarry = (BitN & 2) > 1;
  BitN = BitN & 1;

  if (BitN == 1)
    RxInvLarge = 1;
  else
    RxInvLarge = 0;

  AM = *RyInv;
  AE = *RyExp;
  BM = RadiusGyrMaxMan;
  BE = RadiusGyrMaxExp;
  LastCarry = 1;
  while ((AM != 0) || (BM != 0)) {
    if (AE > BE) {
      BitN = LastCarry + (AM & 1) + 1;
      AM >>= 1;
      AE--;
    }
    else if (AE < BE) {
      BitN = LastCarry + (!(BM & 1));
      BM >>= 1;
      BE--;
    }
    else {                       /* AE == BE */
      BitN = LastCarry + (AM & 1) + (!(BM & 1));
      AM >>= 1;
      BM >>= 1;
      AE--;
      BE--;
    }
    LastCarry = (BitN & 2) > 1;
    BitN = BitN & 1;
  }
  BitN = LastCarry + 1;
  LastCarry = (BitN & 2) > 1;
  BitN = BitN & 1;

  if (BitN == 1)
    RyInvSmall = 1;
  else
    RyInvSmall = 0;

  if (RxInvLarge && RyInvSmall) {
    *RyInv = RadiusGyrMaxMan;
    *RyExp = RadiusGyrMaxExp;
  }

}
