
#include <stdlib.h>
#include "aa01.h"


//-----------------------------------------------------------------------------
static
unsigned char *get_luminances(unsigned char *pixels, int bpl, int height, int depth) {

  int bpp = depth / 8;
  int width = bpl / bpp;
  unsigned char *luminances = (unsigned char *)malloc( width * height );
    if (! luminances) return 0;

  int y = height;
  while ( y-- ) {
    unsigned char *p_line = pixels + y * bpl;
    unsigned char *l_line = luminances + y * width;
    for (int x=0; x<width; ++x) {
      unsigned char *pp = p_line + x*bpp;
      int temp = (114*pp[0] + 587*pp[1] + 299*pp[2]) / 1000;
      if (256 <= temp) temp = 255;
      l_line[x] = temp;
    }
  }
  return luminances;
}


//-----------------------------------------------------------------------------
static
void release_luminances(unsigned char *luminances) {
  free(luminances);
}

//-----------------------------------------------------------------------------
static
int compare(unsigned char *luminances, int width,
         int x1, int y1, int x2, int y2,
         int threshold )
{
  unsigned char *pA = luminances + y1*width + x1;
  unsigned char *pB = luminances + y2*width + x2;
  if (*pA < *pB) {
    return (*pB - *pA > threshold)
               ? threshold
               : 0;
  }

  return (*pA - *pB > threshold)
             ? threshold
             : 0;
}

//-----------------------------------------------------------------------------
// s1 .. dst ど同じ位置のピクセル
// s2 .. 参照色
static
void blend( unsigned char *dst, unsigned char *s1, unsigned char *s2, int level ) {
  dst[0] = s1[0] + ((int)s2[0] - s1[0]) * level / 255;
  dst[1] = s1[1] + ((int)s2[1] - s1[1]) * level / 255;
  dst[2] = s1[2] + ((int)s2[2] - s1[2]) * level / 255;
}


//-----------------------------------------------------------------------------
static
void blend_horizontal_line(
        unsigned char *dst,
        unsigned char *src,
        int bpl,
        int bpp,
        int height,
        int y,
        int S, int M1, int M2, int E,
        int sign )  // 1..1--45deg / -1..135--179deg
{
  unsigned char *dst_line = dst + y*bpl;
  unsigned char *src_line = src + y*bpl;
  int span = ((E-M2)<(M1-S)) ? (E-M2)/2+1 : (M1-S)/2+1;
  int level_unit = 255/((span<<1)+1);

  // S〜M1 を処理
  int P = M1;
  if ( (sign==1 && y == height-1)||
       (sign==-1&& y == 0) )
  {
    // 現在が一番(下|上)のラインならばさらに(下|上)のラインから色を調達することが
    // できないので，(下|上)には M2 点の色があると仮定する
    unsigned char *src_ref = src_line + M2*bpp;
    while (M1-P < span) {
      unsigned char *dst_p = dst_line + P*bpp;
      unsigned char *src_p = src_line + P*bpp;
      int level = level_unit * (span - M1 + P);
      blend(dst_p, src_p, src_ref, level);
      --P;
    }

  }else{
    while (M1-P < span) {
      unsigned char *dst_p = dst_line + P*bpp;
      unsigned char *src_p = src_line + P*bpp;;
      unsigned char *src_ref = src_line + P*bpp + sign*bpl;
      int level = level_unit * (span - M1 + P);
      blend(dst_p, src_p, src_ref, level);
      --P;
    }
  }

 // M2〜E を処理
  P = M2;
  if ( (sign==1 && y == 0) ||
       (sign==-1&& y == height-1) )
  {
    // 一番(上|下)のラインならば，さらに(上|下)から色を持ってくることはできない
    unsigned char *src_ref = src_line + M1*bpp;
    while (P-M2 < span) {
      unsigned char *dst_p = dst_line + P*bpp;
      unsigned char *src_p = src_line + P*bpp;
      int level = level_unit * (span - P + M2);
      blend(dst_p, src_p, src_ref, level);
      ++P;
    }

  }else{
    while (P-M2 < span) {
      unsigned char *dst_p = dst_line + P*bpp;
      unsigned char *src_p = src_line + P*bpp;
      unsigned char *src_ref = src_line + P*bpp + (-sign)*bpl;
      int level = level_unit * (span - P + M2);
      blend(dst_p, src_p, src_ref, level);
      ++P;
    }
  }
}

//-----------------------------------------------------------------------------
static
void blend_vertical_line (
        unsigned char *dst,
        unsigned char *src,
        int bpl,
        int bpp,
        int width,
        int x,
        int S, int M1, int M2, int E,
        int sign )  // 1..91--134deg / -1..46--89deg
{
  // blend_horizontal_line の縦と横を替えたもの
  blend_horizontal_line(dst, src, bpp, bpl, width, x, S, M1, M2, E, sign );
}



//-----------------------------------------------------------------------------
// sign 1 .. 1--45度
//     -1 .. 135--179度
static
void horizontal_anti_alias(
        unsigned char *dst,
        unsigned char *src,
        unsigned char *luminances,
        int bpl,
        int height,
        int depth,
        int threshold,
        int sign )
{
  int bpp = depth / 8;
  int width = bpl / bpp;
  int y = height;
  while ( --y > 0 ) {

    int x = 0;
    while (x < width-1) {
      int S, M1, M2, E;
      S = M1 = M2 = E = -1;

      // 現在の点と(下|上)の点に有意の差があれば
      if ( compare(luminances, width, x, y, x, y+sign, threshold) ) {
        S = x;  // S 点とする
      }else{
        ++x;
        continue;
      }

      //// M1 点を探す
      // x が端まで到達するか，
      // 現在の点と(下|上)の点に差がなくなるか
      // M1 点が見つかればループを抜ける
      while (x<(width-1) && compare(luminances, width, x, y, x, y+sign, threshold) ) {

        // 現在の点と右(上|下)の点に差がなく，
        // 現在の点と右の点に差があれば，M1 点の発見とする
        if ( !compare(luminances, width, x, y, x+1, y-sign, threshold) &&
              compare(luminances, width, x, y, x+1, y, threshold) )
        {
          M1 = x;
          ++x;
          break;
        }

        ++x;
      }

      // M1 点を見つけられなければ S 点の検索に戻る
      if (M1 < 0) {
        ++x;
        continue;
      }

      // M1 点が発見されていれば，E 点を探す
      //  ..  ここに来れば必ずアンチエイリアスは施される
      M2 = x;               // M2 == M1+1 の関係が常に成り立つ

      //// E 点を探す
      // x が端まで到達するか，
      // 現在の点と(上|下)の点に差がなくなれば E 点の発見とする
      while (x<width && compare(luminances, width, x, y, x, y-sign, threshold) ) {
        ++x;
      }
      E = x;

      blend_horizontal_line(
              dst, src,
              bpl, bpp,
              height, y,
              S, M1, M2, E,
              sign );

      x = M2;

    } // while x < width-1
  } // while h

}



//-----------------------------------------------------------------------------
// sign 1 .. 91--134deg
//     -1 .. 46--89deg
static
void vertical_anti_alias(
        unsigned char *dst,
        unsigned char *src,
        unsigned char *luminances,
        int bpl,
        int height,
        int depth,
        int threshold,
        int sign )
{
  int bpp = depth/8;
  int width = bpl / bpp;
  int x = width;
  while ( --x > 0 ) {

    int y = 0;
    while (y < height-1) {
      int S, M1, M2, E;
      S = M1 = M2 = E = -1;

      // 現在の点と(右|左)の点に有意の差があれば
      if ( compare(luminances, width, x, y, x+sign, y, threshold) ) {
        S = y;  // S 点とする
        //++y;  // (45|135)度の処理とかぶらないようにする
      }else{
        ++y;
        continue;
      }

      //// M1 点を探す
      // y が端まで到達するか，
      // 現在の点と(右|左)の点に差がなくなるか
      // M1 点が見つかればループを抜ける
      while (y<(height-1) && compare(luminances, width, x, y, x+sign, y, threshold) ) {

        // 現在の点と(左|右)下の点に差がなく，
        // 現在の点と下の点に差があれば，M1 点の発見とする
        if ( !compare(luminances, width, x, y, x-sign, y+1, threshold) &&
              compare(luminances, width, x, y, x, y+1, threshold) )
        {
          M1 = y;
          ++y;
          break;
        }

        ++y;
      }

      // M1 点を見つけられなければ S 点の検索に戻る
      if (M1 < 0) {
        ++y;
        continue;
      }

      // M1 点が発見されていれば，E 点を探す
      //  ..  ここに来れば必ずアンチエイリアスは施される
      M2 = y;               // M2 == M1+1 の関係が常に成り立つ

      //// E 点を探す
      // x が端まで到達するか，
      // 現在の点と(左|右)の点に差がなくなれば E 点の発見とする
      while (y<height && compare(luminances, width, x, y, x-sign, y, threshold) ) {
        ++y;
      }
      E = y;

      blend_vertical_line(
              dst, src,
              bpl, bpp,
              width, x,
              S, M1, M2, E,
              sign );

      y = M2;

    } // while y < height-1
  } // while x
}



//=============================================================================
//  アンチエイリアスを施す
//
//  thre     int              / 輝度の閾値
//  dst      unsigned char *  / 出力する場所
//  src      unsigned char *  / 入力
//  bpl      int              / Bytes per line，画像の横幅というわけではない
//  height   int              / 画像の高さ
//  depth    int              / 画素深度，bits per pel

// 前提：
//  ・bpl は 4 の倍数になっていること
//  ・dst と src は少なくとも同じ大きさのバッファか，もしくは dst の方が大きい
//    また，src は少なくとも bpl * height バイトである
//  ・必ず24or32bitであること

int anti_alias(
    int threshold,
    unsigned char *dst,
    unsigned char *src,
    int bpl,
    int height,
    int depth)
{
    // 輝度バッファを作成
    unsigned char *luminances = get_luminances(src, bpl, height, depth);

    for ( int y=0; y<height; ++y ) {
      unsigned int *src_p = (unsigned int *)(src + y * bpl);
      unsigned int *dst_p = (unsigned int *)(dst + y * bpl);
      for ( int x=0; x<bpl; x+=sizeof(unsigned int) ) {
        *dst_p = *src_p;
        ++dst_p;
        ++src_p;
      }
    }

    // 横方向のアンチエイリアス
    horizontal_anti_alias(dst, src, luminances, bpl, height, depth, threshold, 1);
    horizontal_anti_alias(dst, src, luminances, bpl, height, depth, threshold, -1);

    // 縦方向のアンチエイリアス
    vertical_anti_alias(dst, src, luminances, bpl, height, depth, threshold, 1);
    vertical_anti_alias(dst, src, luminances, bpl, height, depth, threshold, -1);

    // 輝度バッファを開放
    release_luminances(luminances);

    return 0;
}


