/** \file
 * \brief IM Lua 5 Binding
 *
 * See Copyright Notice in im_lib.h
 * $Id: imlua_process.c,v 1.15 2011/08/29 16:21:31 scuri Exp $
 */

#include <memory.h>
#include <math.h>
#include <stdlib.h>

#include "im.h"
#include "im_image.h"
#include "im_process.h"
#include "im_util.h"

#include <lua.h>
#include <lauxlib.h>

#include "imlua.h"
#include "imlua_aux.h"
#include "imlua_image.h"



/*****************************************************************************\
 Image Statistics Calculations
\*****************************************************************************/

/*****************************************************************************\
 im.CalcRMSError(image1, image2)
\*****************************************************************************/
static int imluaCalcRMSError (lua_State *L)
{
  imImage* image1 = imlua_checkimage(L, 1);
  imImage* image2 = imlua_checkimage(L, 2);

  imlua_match(L, image1, image2);

  lua_pushnumber(L, imCalcRMSError(image1, image2));
  return 1;
}

/*****************************************************************************\
 im.CalcSNR(src_image, noise_image)
\*****************************************************************************/
static int imluaCalcSNR (lua_State *L)
{
  imImage* src_image = imlua_checkimage(L, 1);
  imImage* noise_image = imlua_checkimage(L, 2);

  imlua_match(L, src_image, noise_image);

  lua_pushnumber(L, imCalcSNR(src_image, noise_image));
  return 1;
}

/*****************************************************************************\
 im.CalcCountColors(src_image)
\*****************************************************************************/
static int imluaCalcCountColors (lua_State *L)
{
  imImage* src_image = imlua_checkimage(L, 1);

  imlua_checkdatatype(L, 1, src_image, IM_BYTE);
  if (src_image->color_space >= IM_CMYK)
    luaL_argerror(L, 1, "color space can be RGB, Gray, Binary or Map only");

  lua_pushnumber(L, imCalcCountColors(src_image));
  return 1;
}

/*****************************************************************************\
 im.CalcHistogram(src_image, plane, cumulative)
\*****************************************************************************/
static int imluaCalcHistogram (lua_State *L)
{
  imImage* src_image = imlua_checkimage(L, 1);
  int plane = luaL_checkint(L, 2);
  int cumulative = lua_toboolean(L, 3);

  switch (src_image->data_type)
  {
  case IM_BYTE:
    {
      unsigned long hist[256];
      imCalcHistogram((imbyte*) src_image->data[plane], src_image->count, hist, cumulative);
      imlua_newarrayulong(L, hist, 256, 0);
    }
    break;

  case IM_USHORT:
    {
      unsigned long hist[65535];
      imCalcUShortHistogram(src_image->data[plane], src_image->count, hist, cumulative);
      imlua_newarrayulong(L, hist, 65535, 0);
    }
    break;

  default:
    luaL_argerror(L, 1, "data_type can be byte or ushort only");
    break;
  }

  return 1;
}

/*****************************************************************************\
 im.CalcGrayHistogram(src_image, cumulative)
\*****************************************************************************/
static int imluaCalcGrayHistogram (lua_State *L)
{
  unsigned long hist[256];
  imImage* src_image = imlua_checkimage(L, 1);
  int cumulative = lua_toboolean(L, 2);

  imlua_checkdatatype(L, 1, src_image, IM_BYTE);
  if (src_image->color_space >= IM_CMYK)
    luaL_argerror(L, 1, "color space can be RGB, Gray, Binary or Map only");

  imCalcGrayHistogram(src_image, hist, cumulative);
  imlua_newarrayulong(L, hist, 256, 0);

  return 1;
}

/*****************************************************************************\
 im.CalcImageStatistics(src_image)
\*****************************************************************************/
static int imluaCalcImageStatistics (lua_State *L)
{
  imStats stats;
  imImage *image = imlua_checkimage(L, 1);

  if (image->data_type == IM_CFLOAT)
    luaL_argerror(L, 1, "data type can NOT be of type cfloat");

  imCalcImageStatistics(image, &stats);

  lua_newtable(L);
  lua_pushstring(L, "max");      lua_pushnumber(L, stats.max);      lua_settable(L, -3);
  lua_pushstring(L, "min");      lua_pushnumber(L, stats.min);      lua_settable(L, -3);
  lua_pushstring(L, "positive"); lua_pushnumber(L, stats.positive); lua_settable(L, -3);
  lua_pushstring(L, "negative"); lua_pushnumber(L, stats.negative); lua_settable(L, -3);
  lua_pushstring(L, "zeros");    lua_pushnumber(L, stats.zeros);    lua_settable(L, -3);
  lua_pushstring(L, "mean");     lua_pushnumber(L, stats.mean);     lua_settable(L, -3);
  lua_pushstring(L, "stddev");   lua_pushnumber(L, stats.stddev);   lua_settable(L, -3);
  return 1;
}

/*****************************************************************************\
 im.CalcHistogramStatistics(src_image)
\*****************************************************************************/
static int imluaCalcHistogramStatistics (lua_State *L)
{
  imStats stats;
  imImage *image = imlua_checkimage(L, 1);

  imlua_checkdatatype(L, 1, image, IM_BYTE);

  imCalcHistogramStatistics(image, &stats);

  lua_newtable(L);
  lua_pushstring(L, "max");      lua_pushnumber(L, stats.max);      lua_settable(L, -3);
  lua_pushstring(L, "min");      lua_pushnumber(L, stats.min);      lua_settable(L, -3);
  lua_pushstring(L, "positive"); lua_pushnumber(L, stats.positive); lua_settable(L, -3);
  lua_pushstring(L, "negative"); lua_pushnumber(L, stats.negative); lua_settable(L, -3);
  lua_pushstring(L, "zeros");    lua_pushnumber(L, stats.zeros);    lua_settable(L, -3);
  lua_pushstring(L, "mean");     lua_pushnumber(L, stats.mean);     lua_settable(L, -3);
  lua_pushstring(L, "stddev");   lua_pushnumber(L, stats.stddev);   lua_settable(L, -3);
  return 1;
}

/*****************************************************************************\
 im.CalcHistoImageStatistics
\*****************************************************************************/
static int imluaCalcHistoImageStatistics (lua_State *L)
{
  int* median;
  int* mode;

  imImage *image = imlua_checkimage(L, 1);

  imlua_checkdatatype(L, 1, image, IM_BYTE);

  median = (int*)malloc(sizeof(int)*image->depth);
  mode = (int*)malloc(sizeof(int)*image->depth);

  imCalcHistoImageStatistics(image, median, mode);

  imlua_newarrayint (L, median, image->depth, 0);
  imlua_newarrayint (L, mode, image->depth, 0);

  free(median);
  free(mode);

  return 2;
}

/*****************************************************************************\
 Image Analysis
\*****************************************************************************/

/*****************************************************************************\
 im.AnalyzeFindRegions(src_image, dst_image, connect, touch_border)
\*****************************************************************************/
static int imluaAnalyzeFindRegions (lua_State *L)
{
  imImage* src_image = imlua_checkimage(L, 1);
  imImage* dst_image = imlua_checkimage(L, 2);
  int connect = luaL_checkint(L, 3);
  int touch_border = lua_toboolean(L, 4);

  imlua_checkcolorspace(L, 1, src_image, IM_BINARY);
  imlua_checktype(L, 2, dst_image, IM_GRAY, IM_USHORT);

  luaL_argcheck(L, (connect == 4 || connect == 8), 3, "invalid connect value, must be 4 or 8");
  lua_pushnumber(L, imAnalyzeFindRegions(src_image, dst_image, connect, touch_border));
  return 1;
}

static int iGetMax(imImage* image)
{
  int max = 0;
  int i;

  imushort* data = (imushort*)image->data[0];
  for (i = 0; i < image->count; i++)
  {
    if (*data > max)
      max = *data;

    data++;
  }

  return max;
}

static int imlua_checkregioncount(lua_State *L, int narg, imImage* image)
{
  if (lua_isnoneornil(L, narg)) return iGetMax(image);
  else return (int)luaL_checknumber(L, narg);
}


/*****************************************************************************\
 im.AnalyzeMeasureArea(image, [count])
\*****************************************************************************/
static int imluaAnalyzeMeasureArea (lua_State *L)
{
  int count;
  int *area;

  imImage* image = imlua_checkimage(L, 1);

  imlua_checktype(L, 1, image, IM_GRAY, IM_USHORT);

  count = imlua_checkregioncount(L, 2, image);
  area = (int*) malloc(sizeof(int) * count);

  imAnalyzeMeasureArea(image, area, count);

  imlua_newarrayint(L, area, count, 0);
  free(area);

  return 1;
}

/*****************************************************************************\
 im.AnalyzeMeasurePerimArea(image)
\*****************************************************************************/
static int imluaAnalyzeMeasurePerimArea (lua_State *L)
{
  int count;
  float *perimarea;

  imImage* image = imlua_checkimage(L, 1);

  imlua_checktype(L, 1, image, IM_GRAY, IM_USHORT);

  count = imlua_checkregioncount(L, 2, image);
  perimarea = (float*) malloc(sizeof(float) * count);

  imAnalyzeMeasurePerimArea(image, perimarea);

  imlua_newarrayfloat (L, perimarea, count, 0);
  free(perimarea);

  return 1;
}

/*****************************************************************************\
 im.AnalyzeMeasureCentroid(image, [area], [count])
\*****************************************************************************/
static int imluaAnalyzeMeasureCentroid (lua_State *L)
{
  int count;
  float *cx, *cy;
  int *area;

  imImage* image = imlua_checkimage(L, 1);

  imlua_checktype(L, 1, image, IM_GRAY, IM_USHORT);

  area = imlua_toarrayint(L, 2, &count, 0);
  count = imlua_checkregioncount(L, 3, image);

  cx = (float*) malloc (sizeof(float) * count);
  cy = (float*) malloc (sizeof(float) * count);

  imAnalyzeMeasureCentroid(image, area, count, cx, cy);

  imlua_newarrayfloat(L, cx, count, 0);
  imlua_newarrayfloat(L, cy, count, 0);

  if (area)
    free(area);
  free(cx);
  free(cy);

  return 2;
}

/*****************************************************************************\
 im.AnalyzeMeasurePrincipalAxis(image, [area], [cx], [cy])
\*****************************************************************************/
static int imluaAnalyzeMeasurePrincipalAxis (lua_State *L)
{
  int count;
  float *cx, *cy;
  int *area;
  float *major_slope, *major_length, *minor_slope, *minor_length;

  imImage* image = imlua_checkimage(L, 1);

  imlua_checktype(L, 1, image, IM_GRAY, IM_USHORT);

  area = imlua_toarrayint(L, 2, &count, 0);
  cx = imlua_toarrayfloat(L, 3, NULL, 0);
  cy = imlua_toarrayfloat(L, 4, NULL, 0);
  count = imlua_checkregioncount(L, 5, image);

  major_slope = (float*) malloc (sizeof(float) * count);
  major_length = (float*) malloc (sizeof(float) * count);
  minor_slope = (float*) malloc (sizeof(float) * count);
  minor_length = (float*) malloc (sizeof(float) * count);

  imAnalyzeMeasurePrincipalAxis(image, area, cx, cy, count, major_slope, major_length, minor_slope, minor_length);

  imlua_newarrayfloat(L, major_slope, count, 0);
  imlua_newarrayfloat(L, major_length, count, 0);
  imlua_newarrayfloat(L, minor_slope, count, 0);
  imlua_newarrayfloat(L, minor_length, count, 0);

  if (area)
    free(area);
  if (cx)
    free(cx);
  if (cy)
    free(cy);

  free(major_slope);
  free(major_length);
  free(minor_slope);
  free(minor_length);

  return 4;
}

/*****************************************************************************\
 im.AnalyzeMeasureHoles
\*****************************************************************************/
static int imluaAnalyzeMeasureHoles (lua_State *L)
{
  int holes_count, count;
  int connect;
  int *area = NULL;
  float *perim = NULL;

  imImage* image = imlua_checkimage(L, 1);

  imlua_checktype(L, 1, image, IM_GRAY, IM_USHORT);

  connect = luaL_checkint(L, 2);
  count = imlua_checkregioncount(L, 3, image);

  area = (int*) malloc (sizeof(int) * count);
  perim = (float*) malloc (sizeof(float) * count);

  imAnalyzeMeasureHoles(image, connect, &holes_count, area, perim);

  lua_pushnumber(L, holes_count);
  imlua_newarrayint(L, area, holes_count, 0);
  imlua_newarrayfloat(L, perim, holes_count, 0);

  if (area)
    free(area);
  if (perim)
    free(perim);

  return 3;
}

/*****************************************************************************\
 im.AnalyzeMeasurePerimeter(image, [count])
\*****************************************************************************/
static int imluaAnalyzeMeasurePerimeter (lua_State *L)
{
  int count;
  float *perim;

  imImage* image = imlua_checkimage(L, 1);

  imlua_checktype(L, 1, image, IM_GRAY, IM_USHORT);

  count = imlua_checkregioncount(L, 2, image);
  perim = (float*) malloc(sizeof(float) * count);

  imAnalyzeMeasurePerimeter(image, perim, count);

  imlua_newarrayfloat(L, perim, count, 0);

  free(perim);

  return 1;
}

/*****************************************************************************\
 im.ProcessPerimeterLine(src_image, dst_image)
\*****************************************************************************/
static int imluaProcessPerimeterLine (lua_State *L)
{
  imImage* src_image = imlua_checkimage(L, 1);
  imImage* dst_image = imlua_checkimage(L, 2);

  luaL_argcheck(L, (src_image->data_type < IM_FLOAT), 1, "image data type can be integer only");
  imlua_match(L, src_image, dst_image);

  imProcessPerimeterLine(src_image, dst_image);
  return 0;
}

/*****************************************************************************\
 im.ProcessRemoveByArea(src_image, dst_image, connect, start_size, end_size, inside)
\*****************************************************************************/
static int imluaProcessRemoveByArea (lua_State *L)
{
  imImage* src_image = imlua_checkimage(L, 1);
  imImage* dst_image = imlua_checkimage(L, 2);
  int connect = luaL_checkint(L, 3);
  int start_size = luaL_checkint(L, 4);
  int end_size = luaL_checkint(L, 5);
  int inside = lua_toboolean(L, 6);

  imlua_checkcolorspace(L, 1, src_image, IM_BINARY);
  imlua_match(L, src_image, dst_image);
  luaL_argcheck(L, (connect == 4 || connect == 8), 3, "invalid connect value, must be 4 or 8");

  imProcessRemoveByArea(src_image, dst_image, connect, start_size, end_size, inside);
  return 0;
}

/*****************************************************************************\
 im.ProcessFillHoles(src_image, dst_image, connect)
\*****************************************************************************/
static int imluaProcessFillHoles (lua_State *L)
{
  imImage* src_image = imlua_checkimage(L, 1);
  imImage* dst_image = imlua_checkimage(L, 2);
  int connect = luaL_checkint(L, 3);

  imlua_checkcolorspace(L, 1, src_image, IM_BINARY);
  imlua_match(L, src_image, dst_image);
  luaL_argcheck(L, (connect == 4 || connect == 8), 3, "invalid connect value, must be 4 or 8");

  imProcessFillHoles(src_image, dst_image, connect);
  return 0;
}

static void imlua_checkhoughsize(lua_State *L, imImage* image, imImage* hough_image, int param)
{
#define IMSQR(_x) (_x*_x)
  int hough_rmax;
  if (hough_image->width != 180)
    luaL_argerror(L, param, "invalid image width");

  hough_rmax = (int)(sqrt((double)(IMSQR(image->width) + IMSQR(image->height)))/2.0);
  if (hough_image->height != 2*hough_rmax+1)
    luaL_argerror(L, param, "invalid image height");
}

/*****************************************************************************\
 im.ProcessHoughLines(src_image, dst_image)
\*****************************************************************************/
static int imluaProcessHoughLines (lua_State *L)
{
  imImage* src_image = imlua_checkimage(L, 1);
  imImage* dst_image = imlua_checkimage(L, 2);

  imlua_checkcolorspace(L, 1, src_image, IM_BINARY);
  imlua_checktype(L, 2, dst_image, IM_GRAY, IM_INT);
  imlua_checkhoughsize(L, src_image, dst_image, 2);

  lua_pushboolean(L, imProcessHoughLines(src_image, dst_image));
  return 0;
}

/*****************************************************************************\
 im.ProcessHoughLinesDraw(src_image, hough_points, dst_image)
\*****************************************************************************/
static int imluaProcessHoughLinesDraw (lua_State *L)
{
  imImage* src_image = imlua_checkimage(L, 1);
  imImage* hough_points = imlua_checkimage(L, 3);
  imImage* dst_image = imlua_checkimage(L, 4);
  imImage* hough = NULL;
  if (lua_isuserdata(L, 2))
  {
    hough = imlua_checkimage(L, 2);
    imlua_checktype(L, 2, hough, IM_GRAY, IM_INT);
    imlua_checkhoughsize(L, src_image, hough, 2);
  }

  imlua_checktype(L, 1, src_image, IM_GRAY, IM_BYTE);
  imlua_checkcolorspace(L, 3, hough_points, IM_BINARY);
  imlua_checkhoughsize(L, src_image, hough_points, 3);
  imlua_matchsize(L, src_image, dst_image);

  lua_pushnumber(L, imProcessHoughLinesDraw(src_image, hough, hough_points, dst_image));
  return 0;
}

/*****************************************************************************\
 im.ProcessDistanceTransform(src_image, dst_image)
\*****************************************************************************/
static int imluaProcessDistanceTransform (lua_State *L)
{
  imImage* src_image = imlua_checkimage(L, 1);
  imImage* dst_image = imlua_checkimage(L, 2);

  imlua_checkcolorspace(L, 1, src_image, IM_BINARY);
  imlua_checkdatatype(L, 2, dst_image, IM_FLOAT);
  imlua_matchsize(L, src_image, dst_image);

  imProcessDistanceTransform(src_image, dst_image);
  return 0;
}

/*****************************************************************************\
 im.ProcessRegionalMaximum(src_image, dst_image)
\*****************************************************************************/
static int imluaProcessRegionalMaximum (lua_State *L)
{
  imImage* src_image = imlua_checkimage(L, 1);
  imImage* dst_image = imlua_checkimage(L, 2);

  imlua_checktype(L, 1, src_image, IM_GRAY, IM_FLOAT);
  imlua_checkcolorspace(L, 2, dst_image, IM_BINARY);
  imlua_matchsize(L, src_image, dst_image);

  imProcessRegionalMaximum(src_image, dst_image);
  return 0;
}

/*****************************************************************************\
 Image Resize
\*****************************************************************************/

/*****************************************************************************\
 im.ProcessReduce(src_image, dst_image, order)
\*****************************************************************************/
static int imluaProcessReduce (lua_State *L)
{
  imImage* src_image = imlua_checkimage(L, 1);
  imImage* dst_image = imlua_checkimage(L, 2);
  int order = luaL_checkint(L, 3);

  imlua_matchcolor(L, src_image, dst_image);
  luaL_argcheck(L, (order == 0 || order == 1), 3, "invalid order, must be 0 or 1");

  lua_pushboolean(L, imProcessReduce(src_image, dst_image, order));
  return 1;
}

/*****************************************************************************\
 im.ProcessResize(src_image, dst_image, order)
\*****************************************************************************/
static int imluaProcessResize (lua_State *L)
{
  imImage* src_image = imlua_checkimage(L, 1);
  imImage* dst_image = imlua_checkimage(L, 2);
  int order = luaL_checkint(L, 3);

  imlua_matchcolor(L, src_image, dst_image);
  luaL_argcheck(L, (order == 0 || order == 1 || order == 3), 3, "invalid order, must be 0, 1 or 3");

  lua_pushboolean(L, imProcessResize(src_image, dst_image, order));
  return 1;
}

/*****************************************************************************\
 im.ProcessReduceBy4(src_image, dst_image)
\*****************************************************************************/
static int imluaProcessReduceBy4 (lua_State *L)
{
  imImage* src_image = imlua_checkimage(L, 1);
  imImage* dst_image = imlua_checkimage(L, 2);

  imlua_matchcolor(L, src_image, dst_image);
  luaL_argcheck(L,
    dst_image->width == (src_image->width / 2) &&
    dst_image->height == (src_image->height / 2), 3, "destiny image size must be source image width/2, height/2");

  imProcessReduceBy4(src_image, dst_image);
  return 0;
}

/*****************************************************************************\
 im.ProcessCrop(src_image, dst_image, xmin, ymin)
\*****************************************************************************/
static int imluaProcessCrop (lua_State *L)
{
  imImage* src_image = imlua_checkimage(L, 1);
  imImage* dst_image = imlua_checkimage(L, 2);
  int xmin = luaL_checkint(L, 3);
  int ymin = luaL_checkint(L, 4);

  imlua_matchcolor(L, src_image, dst_image);
  luaL_argcheck(L, xmin >= 0 && xmin < src_image->width, 3, "xmin must be >= 0 and < width");
  luaL_argcheck(L, ymin >= 0 && ymin < src_image->height, 4, "ymin must be >= 0 and < height");
  luaL_argcheck(L, dst_image->width <= (src_image->width - xmin), 2, "destiny image size must be smaller than source image width-xmin");
  luaL_argcheck(L, dst_image->height <= (src_image->height - ymin), 2, "destiny image size must be smaller than source image height-ymin");

  imProcessCrop(src_image, dst_image, xmin, ymin);
  return 0;
}

/*****************************************************************************\
 im.ProcessInsert(src_image, region_image, dst_image, xmin, ymin)
\*****************************************************************************/
static int imluaProcessInsert (lua_State *L)
{
  imImage* src_image = imlua_checkimage(L, 1);
  imImage* region_image = imlua_checkimage(L, 2);
  imImage* dst_image = imlua_checkimage(L, 3);
  int xmin = luaL_checkint(L, 4);
  int ymin = luaL_checkint(L, 5);

  imlua_matchcolor(L, src_image, dst_image);
  luaL_argcheck(L, xmin >= 0 && xmin < src_image->width, 3, "xmin must be >= 0 and < width");
  luaL_argcheck(L, ymin >= 0 && ymin < src_image->height, 3, "ymin must be >= 0 and < height");

  imProcessInsert(src_image, region_image, dst_image, xmin, ymin);
  return 0;
}

/*****************************************************************************\
 im.ProcessAddMargins(src_image, dst_image, xmin, ymin)
\*****************************************************************************/
static int imluaProcessAddMargins (lua_State *L)
{
  imImage* src_image = imlua_checkimage(L, 1);
  imImage* dst_image = imlua_checkimage(L, 2);
  int xmin = luaL_checkint(L, 3);
  int ymin = luaL_checkint(L, 4);

  imlua_matchcolor(L, src_image, dst_image);
  luaL_argcheck(L, dst_image->width >= (src_image->width + xmin), 2, "destiny image size must be greatter or equal than source image width+xmin, height+ymin");
  luaL_argcheck(L, dst_image->height >= (src_image->height + ymin), 2, "destiny image size must be greatter or equal than source image width+xmin, height+ymin");

  imProcessAddMargins(src_image, dst_image, xmin, ymin);
  return 0;
}



/*****************************************************************************\
 Geometric Operations
\*****************************************************************************/

/*****************************************************************************\
 im.ProcessCalcRotateSize
\*****************************************************************************/
static int imluaProcessCalcRotateSize (lua_State *L)
{
  int new_width, new_height;

  int width = luaL_checkint(L, 1);
  int height = luaL_checkint(L, 2);
  double cos0 = (double) luaL_checknumber(L, 3);
  double sin0 = (double) luaL_checknumber(L, 4);

  imProcessCalcRotateSize(width, height, &new_width, &new_height, cos0, sin0);
  lua_pushnumber(L, new_width);
  lua_pushnumber(L, new_height);
  return 2;
}

/*****************************************************************************\
 im.ProcessRotate
\*****************************************************************************/
static int imluaProcessRotate (lua_State *L)
{
  imImage *src_image = imlua_checkimage(L, 1);
  imImage *dst_image = imlua_checkimage(L, 2);
  double cos0 = (double) luaL_checknumber(L, 3);
  double sin0 = (double) luaL_checknumber(L, 4);
  int order = luaL_checkint(L, 5);

  imlua_matchcolor(L, src_image, dst_image);
  luaL_argcheck(L, (order == 0 || order == 1 || order == 3), 5, "invalid order, must be 0, 1 or 3");

  lua_pushboolean(L, imProcessRotate(src_image, dst_image, cos0, sin0, order));
  return 1;
}

/*****************************************************************************\
 im.ProcessRotateRef
\*****************************************************************************/
static int imluaProcessRotateRef (lua_State *L)
{
  imImage *src_image = imlua_checkimage(L, 1);
  imImage *dst_image = imlua_checkimage(L, 2);
  double cos0 = (double) luaL_checknumber(L, 3);
  double sin0 = (double) luaL_checknumber(L, 4);
  int x = luaL_checkint(L, 5);
  int y = luaL_checkint(L, 6);
  int to_origin = lua_toboolean(L, 7);
  int order = luaL_checkint(L, 8);

  imlua_matchcolor(L, src_image, dst_image);
  luaL_argcheck(L, (order == 0 || order == 1 || order == 3), 5, "invalid order, must be 0, 1, or 3");

  lua_pushboolean(L, imProcessRotateRef(src_image, dst_image, cos0, sin0, x, y, to_origin, order));
  return 1;
}

/*****************************************************************************\
 im.ProcessRotate90
\*****************************************************************************/
static int imluaProcessRotate90 (lua_State *L)
{
  imImage *src_image = imlua_checkimage(L, 1);
  imImage *dst_image = imlua_checkimage(L, 2);
  int dir = lua_toboolean(L, 3);

  imlua_matchcolor(L, src_image, dst_image);
  luaL_argcheck(L, dst_image->width == src_image->height && dst_image->height == src_image->width, 2, "destiny width and height must have the source height and width");
  luaL_argcheck(L, (dir == -1 || dir == 1), 3, "invalid dir, can be -1 or 1 only");

  imProcessRotate90(src_image, dst_image, dir);
  return 0;
}

/*****************************************************************************\
 im.ProcessRotate180
\*****************************************************************************/
static int imluaProcessRotate180 (lua_State *L)
{
  imImage *src_image = imlua_checkimage(L, 1);
  imImage *dst_image = imlua_checkimage(L, 2);

  imlua_match(L, src_image, dst_image);

  imProcessRotate180(src_image, dst_image);
  return 0;
}

/*****************************************************************************\
 im.ProcessMirror
\*****************************************************************************/
static int imluaProcessMirror (lua_State *L)
{
  imImage *src_image = imlua_checkimage(L, 1);
  imImage *dst_image = imlua_checkimage(L, 2);

  imlua_match(L, src_image, dst_image);

  imProcessMirror(src_image, dst_image);
  return 0;
}

/*****************************************************************************\
 im.ProcessFlip
\*****************************************************************************/
static int imluaProcessFlip (lua_State *L)
{
  imImage *src_image = imlua_checkimage(L, 1);
  imImage *dst_image = imlua_checkimage(L, 2);

  imlua_match(L, src_image, dst_image);

  imProcessFlip(src_image, dst_image);
  return 0;
}

/*****************************************************************************\
 im.ProcessInterlaceSplit
\*****************************************************************************/
static int imluaProcessInterlaceSplit (lua_State *L)
{
  imImage *src_image = imlua_checkimage(L, 1);
  imImage *dst_image1 = imlua_checkimage(L, 2);
  imImage *dst_image2 = imlua_checkimage(L, 3);

  imlua_matchcolor(L, src_image, dst_image1);
  imlua_matchcolor(L, src_image, dst_image2);
  luaL_argcheck(L, dst_image1->width == src_image->width && dst_image2->width == src_image->width, 2, "destiny width must be equal to source width");

  if (src_image->height%2)
  {
    int dst_height1 = src_image->height/2 + 1;
    luaL_argcheck(L, dst_image1->height == dst_height1, 2, "destiny1 height must be equal to source height/2+1 if height odd");
  }
  else
    luaL_argcheck(L, dst_image1->height == src_image->height/2, 2, "destiny1 height must be equal to source height/2 if height even");

  luaL_argcheck(L, dst_image2->height == src_image->height/2, 2, "destiny2 height must be equal to source height/2");

  imProcessInterlaceSplit(src_image, dst_image1, dst_image2);
  return 0;
}

/*****************************************************************************\
 im.ProcessRadial
\*****************************************************************************/
static int imluaProcessRadial (lua_State *L)
{
  imImage *src_image = imlua_checkimage(L, 1);
  imImage *dst_image = imlua_checkimage(L, 2);
  float k1 = (float) luaL_checknumber(L, 3);
  int order = luaL_checkint(L, 4);

  imlua_match(L, src_image, dst_image);
  luaL_argcheck(L, (order == 0 || order == 1 || order == 3), 4, "invalid order");

  lua_pushboolean(L, imProcessRadial(src_image, dst_image, k1, order));
  return 1;
}

/*****************************************************************************\
 im.ProcessSwirl
\*****************************************************************************/
static int imluaProcessSwirl(lua_State *L)
{
  imImage *src_image = imlua_checkimage(L, 1);
  imImage *dst_image = imlua_checkimage(L, 2);
  float k1 = (float) luaL_checknumber(L, 3);
  int order = luaL_checkint(L, 4);

  imlua_match(L, src_image, dst_image);
  luaL_argcheck(L, (order == 0 || order == 1 || order == 3), 4, "invalid order, can be 0, 1 or 3");

  lua_pushboolean(L, imProcessSwirl(src_image, dst_image, k1, order));
  return 1;
}

static void imlua_checknotcfloat(lua_State *L, imImage *image, int index)
{
  if (image->data_type == IM_CFLOAT)
    luaL_argerror(L, index, "image data type can NOT be cfloat");
}


/*****************************************************************************\
 Morphology Operations for Gray Images
\*****************************************************************************/

/*****************************************************************************\
 im.ProcessGrayMorphConvolve
\*****************************************************************************/
static int imluaProcessGrayMorphConvolve (lua_State *L)
{
  imImage *src_image = imlua_checkimage(L, 1);
  imImage *dst_image = imlua_checkimage(L, 2);
  imImage *kernel = imlua_checkimage(L, 3);
  int ismax = lua_toboolean(L, 4);

  imlua_checknotcfloat(L, src_image, 1);
  imlua_match(L, src_image, dst_image);
  imlua_checkdatatype(L, 3, kernel, IM_INT);
  imlua_matchsize(L, src_image, kernel);

  lua_pushboolean(L, imProcessGrayMorphConvolve(src_image, dst_image, kernel, ismax));
  return 1;
}

/*****************************************************************************\
 im.ProcessGrayMorphErode
\*****************************************************************************/
static int imluaProcessGrayMorphErode (lua_State *L)
{
  imImage *src_image = imlua_checkimage(L, 1);
  imImage *dst_image = imlua_checkimage(L, 2);
  int kernel_size = luaL_checkint(L, 3);

  imlua_checknotcfloat(L, src_image, 1);
  imlua_match(L, src_image, dst_image);

  lua_pushboolean(L, imProcessGrayMorphErode(src_image, dst_image, kernel_size));
  return 1;
}

/*****************************************************************************\
 im.ProcessGrayMorphDilate
\*****************************************************************************/
static int imluaProcessGrayMorphDilate (lua_State *L)
{
  imImage *src_image = imlua_checkimage(L, 1);
  imImage *dst_image = imlua_checkimage(L, 2);
  int kernel_size = luaL_checkint(L, 3);

  imlua_checknotcfloat(L, src_image, 1);
  imlua_match(L, src_image, dst_image);

  lua_pushboolean(L, imProcessGrayMorphDilate(src_image, dst_image, kernel_size));
  return 1;
}

/*****************************************************************************\
 im.ProcessGrayMorphOpen
\*****************************************************************************/
static int imluaProcessGrayMorphOpen (lua_State *L)
{
  imImage *src_image = imlua_checkimage(L, 1);
  imImage *dst_image = imlua_checkimage(L, 2);
  int kernel_size = luaL_checkint(L, 3);

  imlua_checknotcfloat(L, src_image, 1);
  imlua_match(L, src_image, dst_image);

  lua_pushboolean(L, imProcessGrayMorphOpen(src_image, dst_image, kernel_size));
  return 1;
}

/*****************************************************************************\
 im.ProcessGrayMorphClose
\*****************************************************************************/
static int imluaProcessGrayMorphClose (lua_State *L)
{
  imImage *src_image = imlua_checkimage(L, 1);
  imImage *dst_image = imlua_checkimage(L, 2);
  int kernel_size = luaL_checkint(L, 3);

  imlua_checknotcfloat(L, src_image, 1);
  imlua_match(L, src_image, dst_image);

  lua_pushboolean(L, imProcessGrayMorphClose(src_image, dst_image, kernel_size));
  return 1;
}

/*****************************************************************************\
 im.ProcessGrayMorphTopHat
\*****************************************************************************/
static int imluaProcessGrayMorphTopHat (lua_State *L)
{
  imImage *src_image = imlua_checkimage(L, 1);
  imImage *dst_image = imlua_checkimage(L, 2);
  int kernel_size = luaL_checkint(L, 3);

  imlua_checknotcfloat(L, src_image, 1);
  imlua_match(L, src_image, dst_image);

  lua_pushboolean(L, imProcessGrayMorphTopHat(src_image, dst_image, kernel_size));
  return 1;
}

/*****************************************************************************\
 im.ProcessGrayMorphWell
\*****************************************************************************/
static int imluaProcessGrayMorphWell (lua_State *L)
{
  imImage *src_image = imlua_checkimage(L, 1);
  imImage *dst_image = imlua_checkimage(L, 2);
  int kernel_size = luaL_checkint(L, 3);

  imlua_checknotcfloat(L, src_image, 1);
  imlua_match(L, src_image, dst_image);

  lua_pushboolean(L, imProcessGrayMorphWell(src_image, dst_image, kernel_size));
  return 1;
}

/*****************************************************************************\
 im.ProcessGrayMorphGradient
\*****************************************************************************/
static int imluaProcessGrayMorphGradient (lua_State *L)
{
  imImage *src_image = imlua_checkimage(L, 1);
  imImage *dst_image = imlua_checkimage(L, 2);
  int kernel_size = luaL_checkint(L, 3);

  imlua_checknotcfloat(L, src_image, 1);
  imlua_match(L, src_image, dst_image);

  lua_pushboolean(L, imProcessGrayMorphGradient(src_image, dst_image, kernel_size));
  return 1;
}



/*****************************************************************************\
 Morphology Operations for Binary Images
\*****************************************************************************/

/*****************************************************************************\
 im.ProcessBinMorphConvolve
\*****************************************************************************/
static int imluaProcessBinMorphConvolve (lua_State *L)
{
  imImage *src_image = imlua_checkimage(L, 1);
  imImage *dst_image = imlua_checkimage(L, 2);
  imImage *kernel = imlua_checkimage(L, 3);
  int hit_white = lua_toboolean(L, 4);
  int iter = luaL_checkint(L, 5);

  imlua_checkcolorspace(L, 1, src_image, IM_BINARY);
  imlua_match(L, src_image, dst_image);
  imlua_checkdatatype(L, 3, kernel, IM_INT);
  imlua_matchsize(L, src_image, kernel);

  lua_pushboolean(L, imProcessBinMorphConvolve(src_image, dst_image, kernel, hit_white, iter));
  return 1;
}

/*****************************************************************************\
 im.ProcessBinMorphErode
\*****************************************************************************/
static int imluaProcessBinMorphErode (lua_State *L)
{
  imImage *src_image = imlua_checkimage(L, 1);
  imImage *dst_image = imlua_checkimage(L, 2);
  int kernel_size = luaL_checkint(L, 3);
  int iter = luaL_checkint(L, 4);

  imlua_checkcolorspace(L, 1, src_image, IM_BINARY);
  imlua_match(L, src_image, dst_image);

  lua_pushboolean(L, imProcessBinMorphErode(src_image, dst_image, kernel_size, iter));
  return 1;
}

/*****************************************************************************\
 im.ProcessBinMorphDilate
\*****************************************************************************/
static int imluaProcessBinMorphDilate (lua_State *L)
{
  imImage *src_image = imlua_checkimage(L, 1);
  imImage *dst_image = imlua_checkimage(L, 2);
  int kernel_size = luaL_checkint(L, 3);
  int iter = luaL_checkint(L, 4);

  imlua_checkcolorspace(L, 1, src_image, IM_BINARY);
  imlua_match(L, src_image, dst_image);

  lua_pushboolean(L, imProcessBinMorphDilate(src_image, dst_image, kernel_size, iter));
  return 1;
}

/*****************************************************************************\
 im.ProcessBinMorphOpen
\*****************************************************************************/
static int imluaProcessBinMorphOpen (lua_State *L)
{
  imImage *src_image = imlua_checkimage(L, 1);
  imImage *dst_image = imlua_checkimage(L, 2);
  int kernel_size = luaL_checkint(L, 3);
  int iter = luaL_checkint(L, 4);

  imlua_checkcolorspace(L, 1, src_image, IM_BINARY);
  imlua_match(L, src_image, dst_image);

  lua_pushboolean(L, imProcessBinMorphOpen(src_image, dst_image, kernel_size, iter));
  return 1;
}

/*****************************************************************************\
 im.ProcessBinMorphClose
\*****************************************************************************/
static int imluaProcessBinMorphClose (lua_State *L)
{
  imImage *src_image = imlua_checkimage(L, 1);
  imImage *dst_image = imlua_checkimage(L, 2);
  int kernel_size = luaL_checkint(L, 3);
  int iter = luaL_checkint(L, 4);

  imlua_checkcolorspace(L, 1, src_image, IM_BINARY);
  imlua_match(L, src_image, dst_image);

  lua_pushboolean(L, imProcessBinMorphClose(src_image, dst_image, kernel_size, iter));
  return 1;
}

/*****************************************************************************\
 im.ProcessBinMorphOutline
\*****************************************************************************/
static int imluaProcessBinMorphOutline (lua_State *L)
{
  imImage *src_image = imlua_checkimage(L, 1);
  imImage *dst_image = imlua_checkimage(L, 2);
  int kernel_size = luaL_checkint(L, 3);
  int iter = luaL_checkint(L, 4);

  imlua_checkcolorspace(L, 1, src_image, IM_BINARY);
  imlua_match(L, src_image, dst_image);

  lua_pushboolean(L, imProcessBinMorphOutline(src_image, dst_image, kernel_size, iter));
  return 1;
}

/*****************************************************************************\
 im.ProcessBinMorphThin
\*****************************************************************************/
static int imluaProcessBinMorphThin (lua_State *L)
{
  imImage *src_image = imlua_checkimage(L, 1);
  imImage *dst_image = imlua_checkimage(L, 2);

  imlua_checkcolorspace(L, 1, src_image, IM_BINARY);
  imlua_match(L, src_image, dst_image);

  imProcessBinMorphThin(src_image, dst_image);
  return 0;
}



/*****************************************************************************\
 Rank Convolution Operations
\*****************************************************************************/

/*****************************************************************************\
 im.ProcessMedianConvolve
\*****************************************************************************/
static int imluaProcessMedianConvolve (lua_State *L)
{
  imImage *src_image = imlua_checkimage(L, 1);
  imImage *dst_image = imlua_checkimage(L, 2);
  int kernel_size = luaL_checkint(L, 3);

  imlua_checknotcfloat(L, src_image, 1);
  imlua_match(L, src_image, dst_image);

  lua_pushboolean(L, imProcessMedianConvolve(src_image, dst_image, kernel_size));
  return 1;
}

/*****************************************************************************\
 im.ProcessRangeConvolve
\*****************************************************************************/
static int imluaProcessRangeConvolve (lua_State *L)
{
  imImage *src_image = imlua_checkimage(L, 1);
  imImage *dst_image = imlua_checkimage(L, 2);
  int kernel_size = luaL_checkint(L, 3);

  imlua_checknotcfloat(L, src_image, 1);
  imlua_match(L, src_image, dst_image);

  lua_pushboolean(L, imProcessRangeConvolve(src_image, dst_image, kernel_size));
  return 1;
}

/*****************************************************************************\
 im.ProcessRankClosestConvolve
\*****************************************************************************/
static int imluaProcessRankClosestConvolve (lua_State *L)
{
  imImage *src_image = imlua_checkimage(L, 1);
  imImage *dst_image = imlua_checkimage(L, 2);
  int kernel_size = luaL_checkint(L, 3);

  imlua_checknotcfloat(L, src_image, 1);
  imlua_match(L, src_image, dst_image);

  lua_pushboolean(L, imProcessRankClosestConvolve(src_image, dst_image, kernel_size));
  return 1;
}

/*****************************************************************************\
 im.ProcessRankMaxConvolve
\*****************************************************************************/
static int imluaProcessRankMaxConvolve (lua_State *L)
{
  imImage *src_image = imlua_checkimage(L, 1);
  imImage *dst_image = imlua_checkimage(L, 2);
  int kernel_size = luaL_checkint(L, 3);

  imlua_checknotcfloat(L, src_image, 1);
  imlua_match(L, src_image, dst_image);

  lua_pushboolean(L, imProcessRankMaxConvolve(src_image, dst_image, kernel_size));
  return 1;
}

/*****************************************************************************\
 im.ProcessRankMinConvolve
\*****************************************************************************/
static int imluaProcessRankMinConvolve (lua_State *L)
{
  imImage *src_image = imlua_checkimage(L, 1);
  imImage *dst_image = imlua_checkimage(L, 2);
  int kernel_size = luaL_checkint(L, 3);

  imlua_checknotcfloat(L, src_image, 1);
  imlua_match(L, src_image, dst_image);

  lua_pushboolean(L, imProcessRankMinConvolve(src_image, dst_image, kernel_size));
  return 1;
}


/*****************************************************************************\
 Convolution Operations
\*****************************************************************************/

static void imlua_checkkernel(lua_State *L, imImage* kernel, int index)
{
  imlua_checkcolorspace(L, index, kernel, IM_GRAY);
  luaL_argcheck(L, kernel->data_type == IM_INT || kernel->data_type == IM_FLOAT, index, "kernel data type can be int or float only");
}

/*****************************************************************************\
 im.ProcessConvolve
\*****************************************************************************/
static int imluaProcessConvolve (lua_State *L)
{
  imImage *src_image = imlua_checkimage(L, 1);
  imImage *dst_image = imlua_checkimage(L, 2);
  imImage *kernel = imlua_checkimage(L, 3);

  imlua_match(L, src_image, dst_image);
  imlua_checkkernel(L, kernel, 3);

  lua_pushboolean(L, imProcessConvolve(src_image, dst_image, kernel));
  return 1;
}

/*****************************************************************************\
 im.ProcessConvolveDual
\*****************************************************************************/
static int imluaProcessConvolveDual (lua_State *L)
{
  imImage *src_image = imlua_checkimage(L, 1);
  imImage *dst_image = imlua_checkimage(L, 2);
  imImage *kernel1 = imlua_checkimage(L, 3);
  imImage *kernel2 = imlua_checkimage(L, 4);

  imlua_match(L, src_image, dst_image);
  imlua_checkkernel(L, kernel1, 3);
  imlua_checkkernel(L, kernel2, 4);

  lua_pushboolean(L, imProcessConvolveDual(src_image, dst_image, kernel1, kernel2));
  return 1;
}

/*****************************************************************************\
 im.ProcessConvolveRep
\*****************************************************************************/
static int imluaProcessConvolveRep (lua_State *L)
{
  imImage *src_image = imlua_checkimage(L, 1);
  imImage *dst_image = imlua_checkimage(L, 2);
  imImage *kernel = imlua_checkimage(L, 3);
  int count = luaL_checkint(L, 4);

  imlua_match(L, src_image, dst_image);
  imlua_checkkernel(L, kernel, 3);

  lua_pushboolean(L, imProcessConvolveRep(src_image, dst_image, kernel, count));
  return 1;
}

/*****************************************************************************\
 im.ProcessConvolveSep
\*****************************************************************************/
static int imluaProcessConvolveSep (lua_State *L)
{
  imImage *src_image = imlua_checkimage(L, 1);
  imImage *dst_image = imlua_checkimage(L, 2);
  imImage *kernel = imlua_checkimage(L, 3);

  imlua_match(L, src_image, dst_image);
  imlua_checkkernel(L, kernel, 3);

  lua_pushboolean(L, imProcessConvolveSep(src_image, dst_image, kernel));
  return 1;
}

/*****************************************************************************\
 im.ProcessCompassConvolve
\*****************************************************************************/
static int imluaProcessCompassConvolve (lua_State *L)
{
  imImage *src_image = imlua_checkimage(L, 1);
  imImage *dst_image = imlua_checkimage(L, 2);
  imImage *kernel = imlua_checkimage(L, 3);

  imlua_checknotcfloat(L, src_image, 1);
  imlua_match(L, src_image, dst_image);
  imlua_checkkernel(L, kernel, 3);

  lua_pushboolean(L, imProcessCompassConvolve(src_image, dst_image, kernel));
  return 1;
}

/*****************************************************************************\
 im.ProcessRotateKernel
\*****************************************************************************/
static int imluaProcessRotateKernel (lua_State *L)
{
  imProcessRotateKernel(imlua_checkimage(L, 1));
  return 0;
}

/*****************************************************************************\
 im.ProcessDiffOfGaussianConvolve
\*****************************************************************************/
static int imluaProcessDiffOfGaussianConvolve (lua_State *L)
{
  imImage *src_image = imlua_checkimage(L, 1);
  imImage *dst_image = imlua_checkimage(L, 2);
  float stddev1 = (float) luaL_checknumber(L, 3);
  float stddev2 = (float) luaL_checknumber(L, 4);

  if (src_image->data_type == IM_BYTE || src_image->data_type == IM_USHORT)
  {
    imlua_matchcolor(L, src_image, dst_image);
    imlua_checkdatatype(L, 2, dst_image, IM_INT);
  }
  else
    imlua_match(L, src_image, dst_image);

  lua_pushboolean(L, imProcessDiffOfGaussianConvolve(src_image, dst_image, stddev1, stddev2));
  return 1;
}

/*****************************************************************************\
 im.ProcessLapOfGaussianConvolve
\*****************************************************************************/
static int imluaProcessLapOfGaussianConvolve (lua_State *L)
{
  imImage *src_image = imlua_checkimage(L, 1);
  imImage *dst_image = imlua_checkimage(L, 2);
  float stddev = (float) luaL_checknumber(L, 3);

  if (src_image->data_type == IM_BYTE || src_image->data_type == IM_USHORT)
  {
    imlua_matchcolor(L, src_image, dst_image);
    imlua_checkdatatype(L, 2, dst_image, IM_INT);
  }
  else
    imlua_match(L, src_image, dst_image);

  lua_pushboolean(L, imProcessLapOfGaussianConvolve(src_image, dst_image, stddev));
  return 1;
}

/*****************************************************************************\
 im.ProcessMeanConvolve
\*****************************************************************************/
static int imluaProcessMeanConvolve (lua_State *L)
{
  imImage *src_image = imlua_checkimage(L, 1);
  imImage *dst_image = imlua_checkimage(L, 2);
  int kernel_size = luaL_checkint(L, 3);

  imlua_match(L, src_image, dst_image);

  lua_pushboolean(L, imProcessMeanConvolve(src_image, dst_image, kernel_size));
  return 1;
}

/*****************************************************************************\
 im.ProcessBarlettConvolve
\*****************************************************************************/
static int imluaProcessBarlettConvolve (lua_State *L)
{
  imImage *src_image = imlua_checkimage(L, 1);
  imImage *dst_image = imlua_checkimage(L, 2);
  int kernel_size = luaL_checkint(L, 3);

  imlua_match(L, src_image, dst_image);

  lua_pushboolean(L, imProcessBarlettConvolve(src_image, dst_image, kernel_size));
  return 1;
}

/*****************************************************************************\
 im.ProcessGaussianConvolve
\*****************************************************************************/
static int imluaProcessGaussianConvolve (lua_State *L)
{
  imImage *src_image = imlua_checkimage(L, 1);
  imImage *dst_image = imlua_checkimage(L, 2);
  float stddev = (float) luaL_checknumber(L, 3);

  imlua_match(L, src_image, dst_image);

  lua_pushboolean(L, imProcessGaussianConvolve(src_image, dst_image, stddev));
  return 1;
}

/*****************************************************************************\
 im.ProcessPrewittConvolve
\*****************************************************************************/
static int imluaProcessPrewittConvolve (lua_State *L)
{
  imImage *src_image = imlua_checkimage(L, 1);
  imImage *dst_image = imlua_checkimage(L, 2);

  imlua_match(L, src_image, dst_image);

  lua_pushboolean(L, imProcessPrewittConvolve(src_image, dst_image));
  return 1;
}

/*****************************************************************************\
 im.ProcessSplineEdgeConvolve
\*****************************************************************************/
static int imluaProcessSplineEdgeConvolve (lua_State *L)
{
  imImage *src_image = imlua_checkimage(L, 1);
  imImage *dst_image = imlua_checkimage(L, 2);

  imlua_match(L, src_image, dst_image);

  lua_pushboolean(L, imProcessSplineEdgeConvolve(src_image, dst_image));
  return 1;
}

/*****************************************************************************\
 im.ProcessSobelConvolve
\*****************************************************************************/
static int imluaProcessSobelConvolve (lua_State *L)
{
  imImage *src_image = imlua_checkimage(L, 1);
  imImage *dst_image = imlua_checkimage(L, 2);

  imlua_match(L, src_image, dst_image);

  lua_pushboolean(L, imProcessSobelConvolve(src_image, dst_image));
  return 1;
}

/*****************************************************************************\
 im.ProcessZeroCrossing
\*****************************************************************************/
static int imluaProcessZeroCrossing (lua_State *L)
{
  imImage *src_image = imlua_checkimage(L, 1);
  imImage *dst_image = imlua_checkimage(L, 2);

  luaL_argcheck(L, src_image->data_type == IM_INT || src_image->data_type == IM_FLOAT, 1, "image data type can be int or float only");
  imlua_match(L, src_image, dst_image);

  imProcessZeroCrossing(src_image, dst_image);
  return 0;
}

/*****************************************************************************\
 im.ProcessCanny
\*****************************************************************************/
static int imluaProcessCanny (lua_State *L)
{
  imImage *src_image = imlua_checkimage(L, 1);
  imImage *dst_image = imlua_checkimage(L, 2);
  float stddev = (float) luaL_checknumber(L, 3);

  imlua_checktype(L, 1, src_image, IM_GRAY, IM_BYTE);
  imlua_match(L, src_image, dst_image);

  imProcessCanny(src_image, dst_image, stddev);
  return 0;
}

/*****************************************************************************\
 im.ProcessUnsharp
\*****************************************************************************/
static int imluaProcessUnsharp(lua_State *L)
{
  imImage *src_image = imlua_checkimage(L, 1);
  imImage *dst_image = imlua_checkimage(L, 2);
  float p1 = (float)luaL_checknumber(L, 3);
  float p2 = (float)luaL_checknumber(L, 4);
  float p3 = (float)luaL_checknumber(L, 5);

  imlua_match(L, src_image, dst_image);

  imProcessUnsharp(src_image, dst_image, p1, p2, p3);
  return 0;
}

/*****************************************************************************\
 im.ProcessSharp
\*****************************************************************************/
static int imluaProcessSharp(lua_State *L)
{
  imImage *src_image = imlua_checkimage(L, 1);
  imImage *dst_image = imlua_checkimage(L, 2);
  float p1 = (float)luaL_checknumber(L, 3);
  float p2 = (float)luaL_checknumber(L, 4);

  imlua_match(L, src_image, dst_image);

  imProcessSharp(src_image, dst_image, p1, p2);
  return 0;
}

/*****************************************************************************\
 im.ProcessSharpKernel
\*****************************************************************************/
static int imluaProcessSharpKernel(lua_State *L)
{
  imImage *src_image = imlua_checkimage(L, 1);
  imImage *kernel = imlua_checkimage(L, 2);
  imImage *dst_image = imlua_checkimage(L, 3);
  float p1 = (float)luaL_checknumber(L, 4);
  float p2 = (float)luaL_checknumber(L, 5);

  imlua_match(L, src_image, dst_image);

  imProcessSharpKernel(src_image, kernel, dst_image, p1, p2);
  return 0;
}

/*****************************************************************************\
 im.GaussianStdDev2Repetitions
\*****************************************************************************/
static int imluaGaussianKernelSize2StdDev(lua_State *L)
{
  lua_pushnumber(L, imGaussianKernelSize2StdDev((int)luaL_checknumber(L, 1)));
  return 1;
}

/*****************************************************************************\
 im.GaussianStdDev2KernelSize
\*****************************************************************************/
static int imluaGaussianStdDev2KernelSize (lua_State *L)
{
  lua_pushnumber(L, imGaussianStdDev2KernelSize((float)luaL_checknumber(L, 1)));
  return 1;
}



/*****************************************************************************\
 Arithmetic Operations
\*****************************************************************************/

/*****************************************************************************\
 im.ProcessUnArithmeticOp
\*****************************************************************************/
static int imluaProcessUnArithmeticOp (lua_State *L)
{
  imImage *src_image = imlua_checkimage(L, 1);
  imImage *dst_image = imlua_checkimage(L, 2);
  int op = luaL_checkint(L, 3);

  imlua_matchcolorspace(L, src_image, dst_image);

  imProcessUnArithmeticOp(src_image, dst_image, op);
  return 0;
}

/*****************************************************************************\
 im.ProcessArithmeticOp
\*****************************************************************************/
static int imluaProcessArithmeticOp (lua_State *L)
{
  imImage *src_image1 = imlua_checkimage(L, 1);
  imImage *src_image2 = imlua_checkimage(L, 2);
  imImage *dst_image = imlua_checkimage(L, 3);
  int op = luaL_checkint(L, 4);

  imlua_match(L, src_image1, src_image2);
  imlua_matchsize(L, src_image1, dst_image);
  imlua_matchsize(L, src_image2, dst_image);

  switch (src_image1->data_type)
  {
  case IM_BYTE:
    luaL_argcheck(L,
      dst_image->data_type == IM_BYTE ||
      dst_image->data_type == IM_USHORT ||
      dst_image->data_type == IM_INT ||
      dst_image->data_type == IM_FLOAT,
      2, "source image is byte, destiny image data type can be byte, ushort, int and float only.");
    break;
  case IM_USHORT:
    luaL_argcheck(L,
      dst_image->data_type == IM_USHORT ||
      dst_image->data_type == IM_INT ||
      dst_image->data_type == IM_FLOAT,
      2, "source image is ushort, destiny image data type can be ushort, int and float only.");
    break;
  case IM_INT:
    luaL_argcheck(L,
      dst_image->data_type == IM_INT ||
      dst_image->data_type == IM_FLOAT,
      2, "source image is int, destiny image data type can be int and float only.");
    break;
  case IM_FLOAT:
    luaL_argcheck(L,
      dst_image->data_type == IM_FLOAT,
      2, "source image is float, destiny image data type can be float only.");
    break;
  case IM_CFLOAT:
    luaL_argcheck(L,
      dst_image->data_type == IM_CFLOAT,
      2, "source image is cfloat, destiny image data type can be cfloat only.");
    break;
  }

  imProcessArithmeticOp(src_image1, src_image2, dst_image, op);
  return 0;
}

/*****************************************************************************\
 im.ProcessArithmeticConstOp
\*****************************************************************************/
static int imluaProcessArithmeticConstOp (lua_State *L)
{
  imImage *src_image = imlua_checkimage(L, 1);
  float src_const = (float) luaL_checknumber(L, 2);
  imImage *dst_image = imlua_checkimage(L, 3);
  int op = luaL_checkint(L, 4);

  imlua_matchsize(L, src_image, dst_image);

  switch (src_image->data_type)
  {
  case IM_BYTE:
    luaL_argcheck(L,
      dst_image->data_type == IM_BYTE ||
      dst_image->data_type == IM_USHORT ||
      dst_image->data_type == IM_INT ||
      dst_image->data_type == IM_FLOAT,
      2, "source image is byte, destiny image data type can be byte, ushort, int and float only.");
    break;
  case IM_USHORT:
    luaL_argcheck(L,
      dst_image->data_type == IM_USHORT ||
      dst_image->data_type == IM_INT ||
      dst_image->data_type == IM_FLOAT,
      2, "source image is ushort, destiny image data type can be ushort, int and float only.");
    break;
  case IM_INT:
    luaL_argcheck(L,
      dst_image->data_type == IM_INT ||
      dst_image->data_type == IM_FLOAT,
      2, "source image is int, destiny image data type can be int and float only.");
    break;
  case IM_FLOAT:
    luaL_argcheck(L,
      dst_image->data_type == IM_FLOAT,
      2, "source image is float, destiny image data type can be float only.");
    break;
  case IM_CFLOAT:
    luaL_argcheck(L,
      dst_image->data_type == IM_CFLOAT,
      2, "source image is cfloat, destiny image data type can be cfloat only.");
    break;
  }

  imProcessArithmeticConstOp(src_image, src_const, dst_image, op);
  return 0;
}

/*****************************************************************************\
 im.ProcessBlendConst
\*****************************************************************************/
static int imluaProcessBlendConst (lua_State *L)
{
  imImage *src_image1 = imlua_checkimage(L, 1);
  imImage *src_image2 = imlua_checkimage(L, 2);
  imImage *dst_image = imlua_checkimage(L, 3);
  float alpha = (float) luaL_checknumber(L, 4);

  imlua_match(L, src_image1, src_image2);
  imlua_match(L, src_image1, dst_image);

  imProcessBlendConst(src_image1, src_image2, dst_image, alpha);
  return 0;
}

/*****************************************************************************\
 im.ProcessBlend
\*****************************************************************************/
static int imluaProcessBlend (lua_State *L)
{
  imImage *src_image1 = imlua_checkimage(L, 1);
  imImage *src_image2 = imlua_checkimage(L, 2);
  imImage *alpha_image = imlua_checkimage(L, 3);
  imImage *dst_image = imlua_checkimage(L, 4);

  imlua_match(L, src_image1, src_image2);
  imlua_match(L, src_image1, dst_image);
  imlua_matchdatatype(L, src_image1, alpha_image);

  imProcessBlend(src_image1, src_image2, alpha_image, dst_image);
  return 0;
}

/*****************************************************************************\
 im.ProcessCompose
\*****************************************************************************/
static int imluaProcessCompose(lua_State *L)
{
  imImage *src_image1 = imlua_checkimage(L, 1);
  imImage *src_image2 = imlua_checkimage(L, 2);
  imImage *dst_image = imlua_checkimage(L, 3);

  imlua_match(L, src_image1, src_image2);
  imlua_match(L, src_image1, dst_image);

  imProcessCompose(src_image1, src_image2, dst_image);
  return 0;
}

/*****************************************************************************\
 im.ProcessSplitComplex
\*****************************************************************************/
static int imluaProcessSplitComplex (lua_State *L)
{
  imImage *src_image = imlua_checkimage(L, 1);
  imImage *dst_image1 = imlua_checkimage(L, 2);
  imImage *dst_image2 = imlua_checkimage(L, 3);
  int polar = lua_toboolean(L, 4);

  imlua_checkdatatype(L, 1, src_image, IM_CFLOAT);
  imlua_checkdatatype(L, 2, dst_image1, IM_FLOAT);
  imlua_checkdatatype(L, 3, dst_image2, IM_FLOAT);
  imlua_matchcolorspace(L, src_image, dst_image1);
  imlua_matchcolorspace(L, src_image, dst_image2);

  imProcessSplitComplex(src_image, dst_image1, dst_image2, polar);
  return 0;
}

/*****************************************************************************\
 im.ProcessMergeComplex
\*****************************************************************************/
static int imluaProcessMergeComplex (lua_State *L)
{
  imImage *src_image1 = imlua_checkimage(L, 1);
  imImage *src_image2 = imlua_checkimage(L, 2);
  imImage *dst_image = imlua_checkimage(L, 3);
  int polar = lua_toboolean(L, 4);

  imlua_checkdatatype(L, 1, src_image1, IM_FLOAT);
  imlua_checkdatatype(L, 2, src_image2, IM_FLOAT);
  imlua_checkdatatype(L, 3, dst_image, IM_CFLOAT);
  imlua_matchcolorspace(L, src_image1, src_image2);
  imlua_matchcolorspace(L, src_image1, dst_image);

  imProcessMergeComplex(src_image1, src_image2, dst_image, polar);
  return 0;
}

/*****************************************************************************\
 im.ProcessMultipleMean
\*****************************************************************************/
static int imluaProcessMultipleMean (lua_State *L)
{
  int i, src_image_count;
  imImage *dst_image;
  imImage **src_image_list;

  luaL_checktype(L, 1, LUA_TTABLE);
  src_image_count = imlua_getn(L, 1);

  src_image_list = (imImage**)malloc(sizeof(imImage*)*src_image_count);

  for (i = 0; i < src_image_count; i++)
  {
    lua_rawgeti(L, 1, i+1);
    src_image_list[i] = imlua_checkimage(L, -1);
  }

  dst_image = imlua_checkimage(L, 2);

  for (i = 0; i < src_image_count; i++)
  {
    int check = imImageMatchDataType(src_image_list[i], dst_image);
    if (!check) free(src_image_list);
    imlua_matchcheck(L, check, "images must have the same size and data type");
  }

  imProcessMultipleMean((const imImage**)src_image_list, src_image_count, dst_image);
  free(src_image_list);
  return 0;
}

/*****************************************************************************\
 im.ProcessMultipleStdDev
\*****************************************************************************/
static int imluaProcessMultipleStdDev (lua_State *L)
{
  int i, src_image_count, check;
  imImage *dst_image, *mean_image;
  imImage **src_image_list;

  if (!lua_istable(L, 1))
    luaL_argerror(L, 1, "must be a table");

  lua_pushstring(L, "table");
#if LUA_VERSION_NUM > 501
  lua_pushglobaltable(L);
#else
  lua_gettable(L, LUA_GLOBALSINDEX);
#endif
  lua_pushstring(L, "getn");
  lua_gettable(L, -2);
  src_image_count = luaL_checkint(L, -1);
  lua_pop(L, 1);

  src_image_list = (imImage**) malloc(src_image_count * sizeof(imImage*));

  for (i = 0; i < src_image_count; i++)
  {
    lua_rawgeti(L, 1, i+1);
    src_image_list[i] = imlua_checkimage(L, -1);
  }

  mean_image = imlua_checkimage(L, 2);
  dst_image = imlua_checkimage(L, 3);

  for (i = 0; i < src_image_count; i++)
  {
    check = imImageMatchDataType(src_image_list[i], dst_image);
    if (!check) free(src_image_list);
    imlua_matchcheck(L, check, "images must have the same size and data type");
  }
  check = imImageMatchDataType(mean_image, dst_image);
  if (!check) free(src_image_list);
  imlua_matchcheck(L, check, "images must have the same size and data type");

  imProcessMultipleStdDev((const imImage**)src_image_list, src_image_count, mean_image, dst_image);
  free(src_image_list);
  return 0;
}

/*****************************************************************************\
 im.ProcessAutoCovariance
\*****************************************************************************/
static int imluaProcessAutoCovariance (lua_State *L)
{
  imImage *src_image = imlua_checkimage(L, 1);
  imImage *mean_image = imlua_checkimage(L, 2);
  imImage *dst_image = imlua_checkimage(L, 3);

  imlua_match(L, src_image, mean_image);
  imlua_matchcolorspace(L, src_image, dst_image);
  imlua_checkdatatype(L, 3, dst_image, IM_FLOAT);

  lua_pushboolean(L, imProcessAutoCovariance(src_image, mean_image, dst_image));
  return 1;
}

/*****************************************************************************\
 im.ProcessMultiplyConj
\*****************************************************************************/
static int imluaProcessMultiplyConj (lua_State *L)
{
  imImage *src_image1 = imlua_checkimage(L, 1);
  imImage *src_image2 = imlua_checkimage(L, 2);
  imImage *dst_image = imlua_checkimage(L, 3);

  imlua_match(L, src_image1, src_image2);
  imlua_match(L, src_image1, dst_image);

  imProcessMultiplyConj(src_image1, src_image2, dst_image);
  return 0;
}


/*****************************************************************************\
 Additional Image Quantization Operations
\*****************************************************************************/

/*****************************************************************************\
 im.ProcessQuantizeRGBUniform
\*****************************************************************************/
static int imluaProcessQuantizeRGBUniform (lua_State *L)
{
  imImage *src_image = imlua_checkimage(L, 1);
  imImage *dst_image = imlua_checkimage(L, 2);
  int dither = lua_toboolean(L, 3);

  imlua_checktype(L, 1, src_image, IM_RGB, IM_BYTE);
  imlua_checkcolorspace(L, 2, dst_image, IM_MAP);
  imlua_matchsize(L, src_image, dst_image);

  imProcessQuantizeRGBUniform(src_image, dst_image, dither);
  return 0;
}

/*****************************************************************************\
 im.ProcessQuantizeGrayUniform
\*****************************************************************************/
static int imluaProcessQuantizeGrayUniform (lua_State *L)
{
  imImage *src_image = imlua_checkimage(L, 1);
  imImage *dst_image = imlua_checkimage(L, 2);
  int grays = luaL_checkint(L, 3);

  imlua_checktype(L, 1, src_image, IM_GRAY, IM_BYTE);
  imlua_checktype(L, 2, dst_image, IM_GRAY, IM_BYTE);
  imlua_match(L, src_image, dst_image);

  imProcessQuantizeGrayUniform(src_image, dst_image, grays);
  return 0;
}


/*****************************************************************************\
 Histogram Based Operations
\*****************************************************************************/

/*****************************************************************************\
 im.ProcessExpandHistogram
\*****************************************************************************/
static int imluaProcessExpandHistogram (lua_State *L)
{
  imImage *src_image = imlua_checkimage(L, 1);
  imImage *dst_image = imlua_checkimage(L, 2);
  float percent = (float) luaL_checknumber(L, 3);

  imlua_checkdatatype(L, 1, src_image, IM_BYTE);
  imlua_match(L, src_image, dst_image);
  luaL_argcheck(L, src_image->color_space == IM_RGB || src_image->color_space == IM_GRAY, 1, "color space can be RGB or Gray only");
  luaL_argcheck(L, dst_image->color_space == IM_RGB || dst_image->color_space == IM_GRAY, 2, "color space can be RGB or Gray only");

  imProcessExpandHistogram(src_image, dst_image, percent);
  return 0;
}

/*****************************************************************************\
 im.ProcessEqualizeHistogram
\*****************************************************************************/
static int imluaProcessEqualizeHistogram (lua_State *L)
{
  imImage *src_image = imlua_checkimage(L, 1);
  imImage *dst_image = imlua_checkimage(L, 2);

  imlua_checkdatatype(L, 1, src_image, IM_BYTE);
  imlua_match(L, src_image, dst_image);
  luaL_argcheck(L, src_image->color_space == IM_RGB || src_image->color_space == IM_GRAY, 1, "color space can be RGB or Gray only");
  luaL_argcheck(L, dst_image->color_space == IM_RGB || dst_image->color_space == IM_GRAY, 2, "color space can be RGB or Gray only");

  imProcessEqualizeHistogram(src_image, dst_image);
  return 0;
}



/*****************************************************************************\
 Color Processing Operations
\*****************************************************************************/

/*****************************************************************************\
 im.ProcessSplitYChroma
\*****************************************************************************/
static int imluaProcessSplitYChroma (lua_State *L)
{
  imImage *src_image = imlua_checkimage(L, 1);
  imImage *y_image = imlua_checkimage(L, 2);
  imImage *chroma_image = imlua_checkimage(L, 3);

  imlua_checktype(L, 1, src_image, IM_RGB, IM_BYTE);
  imlua_checktype(L, 2, y_image, IM_GRAY, IM_BYTE);
  imlua_checktype(L, 3, chroma_image, IM_RGB, IM_BYTE);
  imlua_matchsize(L, src_image, y_image);
  imlua_matchsize(L, src_image, chroma_image);

  imProcessSplitYChroma(src_image, y_image, chroma_image);
  return 0;
}

/*****************************************************************************\
 im.ProcessSplitHSI
\*****************************************************************************/
static int imluaProcessSplitHSI (lua_State *L)
{
  imImage *src_image = imlua_checkimage(L, 1);
  imImage *h_image = imlua_checkimage(L, 2);
  imImage *s_image = imlua_checkimage(L, 3);
  imImage *i_image = imlua_checkimage(L, 4);

  imlua_checkcolorspace(L, 1, src_image, IM_RGB);
  luaL_argcheck(L, src_image->data_type == IM_BYTE || src_image->data_type == IM_FLOAT, 1, "data type can be float or byte only");
  imlua_checktype(L, 2, h_image, IM_GRAY, IM_FLOAT);
  imlua_checktype(L, 3, s_image, IM_GRAY, IM_FLOAT);
  imlua_checktype(L, 4, i_image, IM_GRAY, IM_FLOAT);
  imlua_matchsize(L, src_image, h_image);
  imlua_matchsize(L, src_image, s_image);
  imlua_matchsize(L, src_image, i_image);

  imProcessSplitHSI(src_image, h_image, s_image, i_image);
  return 0;
}

/*****************************************************************************\
 im.ProcessMergeHSI
\*****************************************************************************/
static int imluaProcessMergeHSI (lua_State *L)
{
  imImage *h_image = imlua_checkimage(L, 1);
  imImage *s_image = imlua_checkimage(L, 2);
  imImage *i_image = imlua_checkimage(L, 3);
  imImage *dst_image = imlua_checkimage(L, 4);

  imlua_checktype(L, 1, h_image, IM_GRAY, IM_FLOAT);
  imlua_checktype(L, 2, s_image, IM_GRAY, IM_FLOAT);
  imlua_checktype(L, 3, i_image, IM_GRAY, IM_FLOAT);
  imlua_checkcolorspace(L, 4, dst_image, IM_RGB);
  luaL_argcheck(L, dst_image->data_type == IM_BYTE || dst_image->data_type == IM_FLOAT, 4, "data type can be float or byte only");
  imlua_matchsize(L, dst_image, h_image);
  imlua_matchsize(L, dst_image, s_image);
  imlua_matchsize(L, dst_image, i_image);

  imProcessMergeHSI(h_image, s_image, i_image, dst_image);
  return 0;
}

/*****************************************************************************\
 im.ProcessSplitComponents(src_image, { r, g, b} )
\*****************************************************************************/
static int imluaProcessSplitComponents (lua_State *L)
{
  int i, src_depth;
  imImage *src_image = imlua_checkimage(L, 1);
  imImage **dst_image_list;

  luaL_checktype(L, 2, LUA_TTABLE);

  src_depth = src_image->has_alpha? src_image->depth+1: src_image->depth;
  if (imlua_getn(L, 2) != src_depth)
    luaL_error(L, "number of destiny images must match the depth of the source image");

  dst_image_list = (imImage**)malloc(sizeof(imImage*)*src_depth);

  for (i = 0; i < src_depth; i++)
  {
    lua_pushnumber(L, i+1);
    lua_gettable(L, 2);
    dst_image_list[i] = imlua_checkimage(L, -1);
    imlua_checkcolorspace(L, 2, dst_image_list[i], IM_GRAY);  /* if error here, there will be a memory leak */
    lua_pop(L, 1);
  }

  for (i = 0; i < src_depth; i++)
  {
    int check = imImageMatchDataType(src_image, dst_image_list[i]);
    if (!check) free(dst_image_list);
    imlua_matchcheck(L, check, "images must have the same size and data type");
  }

  imProcessSplitComponents(src_image, dst_image_list);

  free(dst_image_list);

  return 0;
}

/*****************************************************************************\
 im.ProcessMergeComponents({r, g, b}, rgb)
\*****************************************************************************/
static int imluaProcessMergeComponents (lua_State *L)
{
  int i, dst_depth;
  imImage** src_image_list;
  imImage *dst_image;

  luaL_checktype(L, 1, LUA_TTABLE);
  dst_image = imlua_checkimage(L, 2);

  dst_depth = dst_image->has_alpha? dst_image->depth+1: dst_image->depth;
  if (imlua_getn(L, 1) != dst_depth)
    luaL_error(L, "number of source images must match the depth of the destination image");

  src_image_list = (imImage**)malloc(sizeof(imImage*)*dst_depth);

  for (i = 0; i < dst_depth; i++)
  {
    lua_pushnumber(L, i+1);
    lua_gettable(L, 1);
    src_image_list[i] = imlua_checkimage(L, -1);
    imlua_checkcolorspace(L, 1, src_image_list[i], IM_GRAY);
    lua_pop(L, 1);
  }

  for (i = 0; i < dst_depth; i++)
  {
    int check = imImageMatchDataType(src_image_list[i], dst_image);
    if (!check) free(src_image_list);
    imlua_matchcheck(L, check, "images must have the same size and data type");
  }

  imProcessMergeComponents((const imImage**)src_image_list, dst_image);

  free(src_image_list);

  return 0;
}

/*****************************************************************************\
 im.ProcessNormalizeComponents
\*****************************************************************************/
static int imluaProcessNormalizeComponents (lua_State *L)
{
  imImage *src_image = imlua_checkimage(L, 1);
  imImage *dst_image = imlua_checkimage(L, 2);

  imlua_checkdatatype(L, 2, dst_image, IM_FLOAT);
  imlua_matchcolorspace(L, src_image, dst_image);

  imProcessNormalizeComponents(src_image, dst_image);
  return 0;
}

/*****************************************************************************\
 im.ProcessReplaceColor
\*****************************************************************************/
static int imluaProcessReplaceColor (lua_State *L)
{
  int src_count, dst_count;
  imImage *src_image = imlua_checkimage(L, 1);
  imImage *dst_image = imlua_checkimage(L, 2);
  float *src_color = imlua_toarrayfloat(L, 3, &src_count, 1);
  float *dst_color = imlua_toarrayfloat(L, 4, &dst_count, 1);

  imlua_checknotcfloat(L, src_image, 1);
  imlua_match(L, src_image, dst_image);
  luaL_argcheck(L, src_count == src_image->depth, 3, "the colors must have the same number of components of the images");
  luaL_argcheck(L, dst_count == src_image->depth, 4, "the colors must have the same number of components of the images");

  imProcessReplaceColor(src_image, dst_image, src_color, dst_color);
  return 0;
}



/*****************************************************************************\
 Logical Arithmetic Operations
\*****************************************************************************/

/*****************************************************************************\
 im.ProcessBitwiseOp
\*****************************************************************************/
static int imluaProcessBitwiseOp (lua_State *L)
{
  imImage *src_image1 = imlua_checkimage(L, 1);
  imImage *src_image2 = imlua_checkimage(L, 2);
  imImage *dst_image = imlua_checkimage(L, 3);
  int op = luaL_checkint(L, 4);

  luaL_argcheck(L, (src_image1->data_type < IM_FLOAT), 1, "image data type can be integer only");
  imlua_match(L, src_image1, src_image2);
  imlua_match(L, src_image1, dst_image);

  imProcessBitwiseOp(src_image1, src_image2, dst_image, op);
  return 0;
}

/*****************************************************************************\
 im.ProcessBitwiseNot
\*****************************************************************************/
static int imluaProcessBitwiseNot (lua_State *L)
{
  imImage *src_image = imlua_checkimage(L, 1);
  imImage *dst_image = imlua_checkimage(L, 2);

  luaL_argcheck(L, (src_image->data_type < IM_FLOAT), 1, "image data type can be integer only");
  imlua_match(L, src_image, dst_image);

  imProcessBitwiseNot(src_image, dst_image);
  return 0;
}

/*****************************************************************************\
 im.ProcessBitMask(src_image, dst_image, mask, op)
\*****************************************************************************/
static int imluaProcessBitMask (lua_State *L)
{
  imImage *src_image = imlua_checkimage(L, 1);
  imImage *dst_image = imlua_checkimage(L, 2);
  unsigned char mask = imlua_checkmask(L, 3);
  int op = luaL_checkint(L, 4);

  imlua_checkdatatype(L, 1, src_image, IM_BYTE);
  imlua_match(L, src_image, dst_image);

  imProcessBitMask(src_image, dst_image, mask, op);
  return 0;
}

/*****************************************************************************\
 im.ProcessBitPlane(src_image, dst_image, plane, reset)
\*****************************************************************************/
static int imluaProcessBitPlane (lua_State *L)
{
  imImage *src_image = imlua_checkimage(L, 1);
  imImage *dst_image = imlua_checkimage(L, 2);
  int plane = luaL_checkint(L, 3);
  int reset = lua_toboolean(L, 4);

  imlua_checkdatatype(L, 1, src_image, IM_BYTE);
  imlua_match(L, src_image, dst_image);

  imProcessBitPlane(src_image, dst_image, plane, reset);
  return 0;
}



/*****************************************************************************\
 Synthetic Image Render
\*****************************************************************************/

/* NOTE: This breaks on multithread */
static lua_State *g_renderState = NULL;
int g_paramCount = 0;

static float imluaRenderFunc (int x, int y, int d, float *param)
{
  lua_State *L = g_renderState;

  luaL_checktype(L, 2, LUA_TFUNCTION);
  lua_pushvalue(L, 2);
  lua_pushnumber(L, x);
  lua_pushnumber(L, y);
  lua_pushnumber(L, d);
  imlua_newarrayfloat(L, param, g_paramCount, 1);

  lua_call(L, 4, 1);

  return (float) luaL_checknumber(L, -1);
}

/*****************************************************************************\
 im.ProcessRenderOp(image, function, name, param, plus)
\*****************************************************************************/
static int imluaProcessRenderOp (lua_State *L)
{
  int count;

  imImage *image = imlua_checkimage(L, 1);
  const char *render_name = luaL_checkstring(L, 3);
  float *param = imlua_toarrayfloat(L, 4, &count, 1);
  int plus = luaL_checkint(L, 5);

  imlua_checknotcfloat(L, image, 1);

  luaL_checktype(L, 2, LUA_TFUNCTION);

  g_renderState = L;
  g_paramCount = count;
  lua_pushboolean(L, imProcessRenderOp(image, imluaRenderFunc, (char*) render_name, param, plus));
  return 1;
}

static float imluaRenderCondFunc (int x, int y, int d, int *cond, float *param)
{
  lua_State *L = g_renderState;

  luaL_checktype(L, 2, LUA_TFUNCTION);
  lua_pushvalue(L, 2);
  lua_pushnumber(L, x);
  lua_pushnumber(L, y);
  lua_pushnumber(L, d);
  imlua_newarrayfloat(L, param, g_paramCount, 1);

  lua_call(L, 4, 2);

  *cond = lua_toboolean(L, -1);
  return (float) luaL_checknumber(L, -2);
}

/*****************************************************************************\
 im.ProcessRenderCondOp(image, function, name, param)
\*****************************************************************************/
static int imluaProcessRenderCondOp (lua_State *L)
{
  int count;

  imImage *image = imlua_checkimage(L, 1);
  const char *render_name = luaL_checkstring(L, 3);
  float *param = imlua_toarrayfloat(L, 4, &count, 1);

  imlua_checknotcfloat(L, image, 1);

  luaL_checktype(L, 2, LUA_TFUNCTION);

  g_renderState = L;
  g_paramCount = count;
  lua_pushboolean(L, imProcessRenderCondOp(image, imluaRenderCondFunc, (char*) render_name, param));
  return 1;
}

/*****************************************************************************\
 im.ProcessRenderAddSpeckleNoise
\*****************************************************************************/
static int imluaProcessRenderAddSpeckleNoise (lua_State *L)
{
  imImage *src_image = imlua_checkimage(L, 1);
  imImage *dst_image = imlua_checkimage(L, 2);
  float percent = (float) luaL_checknumber(L, 3);

  imlua_checknotcfloat(L, src_image, 1);
  imlua_match(L, src_image, dst_image);

  lua_pushboolean(L, imProcessRenderAddSpeckleNoise(src_image, dst_image, percent));
  return 1;
}

/*****************************************************************************\
 im.ProcessRenderAddGaussianNoise
\*****************************************************************************/
static int imluaProcessRenderAddGaussianNoise (lua_State *L)
{
  imImage *src_image = imlua_checkimage(L, 1);
  imImage *dst_image = imlua_checkimage(L, 2);
  float mean = (float) luaL_checknumber(L, 3);
  float stddev = (float) luaL_checknumber(L, 4);

  imlua_checknotcfloat(L, src_image, 1);
  imlua_match(L, src_image, dst_image);

  lua_pushboolean(L, imProcessRenderAddGaussianNoise(src_image, dst_image, mean, stddev));
  return 1;
}

/*****************************************************************************\
 im.ProcessRenderAddUniformNoise
\*****************************************************************************/
static int imluaProcessRenderAddUniformNoise (lua_State *L)
{
  imImage *src_image = imlua_checkimage(L, 1);
  imImage *dst_image = imlua_checkimage(L, 2);
  float mean = (float) luaL_checknumber(L, 3);
  float stddev = (float) luaL_checknumber(L, 4);

  imlua_checknotcfloat(L, src_image, 1);
  imlua_match(L, src_image, dst_image);

  lua_pushboolean(L, imProcessRenderAddUniformNoise(src_image, dst_image, mean, stddev));
  return 1;
}

/*****************************************************************************\
 im.ProcessRenderRandomNoise
\*****************************************************************************/
static int imluaProcessRenderRandomNoise (lua_State *L)
{
  imImage *image = imlua_checkimage(L, 1);
  imlua_checknotcfloat(L, image, 1);
  lua_pushboolean(L, imProcessRenderRandomNoise(image));
  return 1;
}

/*****************************************************************************\
 im.ProcessRenderConstant(image, [count])
\*****************************************************************************/
static int imluaProcessRenderConstant (lua_State *L)
{
  int i;
  float *value = NULL;

  imImage *image = imlua_checkimage(L, 1);
  int count = image->depth;

  imlua_checknotcfloat(L, image, 1);

  if (lua_istable(L, 2))
  {
    value = (float*) malloc (sizeof(float) * count);

    for (i = 0; i < count; i++)
    {
      lua_rawgeti(L, 2, i+1);
      value[i] = (float) lua_tonumber(L, -1);
      lua_pop(L, 1);
    }
  }

  lua_pushboolean(L, imProcessRenderConstant(image, value));

  if (value)
    free(value);

  return 1;
}

/*****************************************************************************\
 im.ProcessRenderWheel
\*****************************************************************************/
static int imluaProcessRenderWheel (lua_State *L)
{
  imImage *image = imlua_checkimage(L, 1);
  int int_radius = luaL_checkint(L, 2);
  int ext_radius = luaL_checkint(L, 3);

  imlua_checknotcfloat(L, image, 1);

  lua_pushboolean(L, imProcessRenderWheel(image, int_radius, ext_radius));
  return 1;
}

/*****************************************************************************\
 im.ProcessRenderCone
\*****************************************************************************/
static int imluaProcessRenderCone (lua_State *L)
{
  imImage *image = imlua_checkimage(L, 1);
  int radius = luaL_checkint(L, 2);

  imlua_checknotcfloat(L, image, 1);

  lua_pushboolean(L, imProcessRenderCone(image, radius));
  return 1;
}

/*****************************************************************************\
 im.ProcessRenderTent
\*****************************************************************************/
static int imluaProcessRenderTent (lua_State *L)
{
  imImage *image = imlua_checkimage(L, 1);
  int width = luaL_checkint(L, 2);
  int height = luaL_checkint(L, 3);

  imlua_checknotcfloat(L, image, 1);

  lua_pushboolean(L, imProcessRenderTent(image, width, height));
  return 1;
}

/*****************************************************************************\
 im.ProcessRenderRamp
\*****************************************************************************/
static int imluaProcessRenderRamp (lua_State *L)
{
  imImage *image = imlua_checkimage(L, 1);
  int start = luaL_checkint(L, 2);
  int end = luaL_checkint(L, 3);
  int dir = luaL_checkint(L, 4);

  imlua_checknotcfloat(L, image, 1);

  lua_pushboolean(L, imProcessRenderRamp(image, start, end, dir));
  return 1;
}

/*****************************************************************************\
 im.ProcessRenderBox
\*****************************************************************************/
static int imluaProcessRenderBox (lua_State *L)
{
  imImage *image = imlua_checkimage(L, 1);
  int width = luaL_checkint(L, 2);
  int height = luaL_checkint(L, 3);

  imlua_checknotcfloat(L, image, 1);

  lua_pushboolean(L, imProcessRenderBox(image, width, height));
  return 1;
}

/*****************************************************************************\
 im.ProcessRenderSinc
\*****************************************************************************/
static int imluaProcessRenderSinc (lua_State *L)
{
  imImage *image = imlua_checkimage(L, 1);
  float xperiod = (float) luaL_checknumber(L, 2);
  float yperiod = (float) luaL_checknumber(L, 3);

  imlua_checknotcfloat(L, image, 1);

  lua_pushboolean(L, imProcessRenderSinc(image, xperiod, yperiod));
  return 1;
}

/*****************************************************************************\
 im.ProcessRenderGaussian
\*****************************************************************************/
static int imluaProcessRenderGaussian (lua_State *L)
{
  imImage *image = imlua_checkimage(L, 1);
  float stddev = (float) luaL_checknumber(L, 2);

  imlua_checknotcfloat(L, image, 1);

  lua_pushboolean(L, imProcessRenderGaussian(image, stddev));
  return 1;
}

/*****************************************************************************\
 im.ProcessRenderLapOfGaussian
\*****************************************************************************/
static int imluaProcessRenderLapOfGaussian (lua_State *L)
{
  imImage *image = imlua_checkimage(L, 1);
  float stddev = (float) luaL_checknumber(L, 2);

  imlua_checknotcfloat(L, image, 1);

  lua_pushboolean(L, imProcessRenderLapOfGaussian(image, stddev));
  return 1;
}

/*****************************************************************************\
 im.ProcessRenderCosine
\*****************************************************************************/
static int imluaProcessRenderCosine (lua_State *L)
{
  imImage *image = imlua_checkimage(L, 1);
  float xperiod = (float) luaL_checknumber(L, 2);
  float yperiod = (float) luaL_checknumber(L, 3);

  imlua_checknotcfloat(L, image, 1);

  lua_pushboolean(L, imProcessRenderCosine(image, xperiod, yperiod));
  return 1;
}

/*****************************************************************************\
 im.ProcessRenderGrid
\*****************************************************************************/
static int imluaProcessRenderGrid (lua_State *L)
{
  imImage *image = imlua_checkimage(L, 1);
  int x_space = luaL_checkint(L, 2);
  int y_space = luaL_checkint(L, 3);

  imlua_checknotcfloat(L, image, 1);

  lua_pushboolean(L, imProcessRenderGrid(image, x_space, y_space));
  return 1;
}

/*****************************************************************************\
 im.ProcessRenderChessboard
\*****************************************************************************/
static int imluaProcessRenderChessboard (lua_State *L)
{
  imImage *image = imlua_checkimage(L, 1);
  int x_space = luaL_checkint(L, 2);
  int y_space = luaL_checkint(L, 3);

  imlua_checknotcfloat(L, image, 1);

  lua_pushboolean(L, imProcessRenderChessboard(image, x_space, y_space));
  return 1;
}



/*****************************************************************************\
 Tone Gamut Operations
\*****************************************************************************/

/*****************************************************************************\
 im.ProcessToneGamut
\*****************************************************************************/
static int imluaProcessToneGamut (lua_State *L)
{
  imImage *src_image = imlua_checkimage(L, 1);
  imImage *dst_image = imlua_checkimage(L, 2);
  int op = luaL_checkint(L, 3);
  float *param = NULL;

  imlua_checknotcfloat(L, src_image, 1);
  imlua_match(L, src_image, dst_image);

  param = imlua_toarrayfloat(L, 4, NULL, 1);

  imProcessToneGamut(src_image, dst_image, op, param);

  if (param)
    free(param);

  return 0;
}

/*****************************************************************************\
 im.ProcessUnNormalize
\*****************************************************************************/
static int imluaProcessUnNormalize (lua_State *L)
{
  imImage *src_image = imlua_checkimage(L, 1);
  imImage *dst_image = imlua_checkimage(L, 2);

  imlua_checkdatatype(L, 1, src_image, IM_FLOAT);
  imlua_checkdatatype(L, 2, dst_image, IM_BYTE);
  imlua_matchcolorspace(L, src_image, dst_image);

  imProcessUnNormalize(src_image, dst_image);
  return 0;
}

/*****************************************************************************\
 im.ProcessDirectConv
\*****************************************************************************/
static int imluaProcessDirectConv (lua_State *L)
{
  imImage *src_image = imlua_checkimage(L, 1);
  imImage *dst_image = imlua_checkimage(L, 2);

  luaL_argcheck(L,
    src_image->data_type == IM_USHORT ||
    src_image->data_type == IM_INT ||
    src_image->data_type == IM_FLOAT,
    1, "data type can be ushort, int or float only");
  imlua_checkdatatype(L, 2, dst_image, IM_BYTE);
  imlua_matchsize(L, src_image, dst_image);

  imProcessDirectConv(src_image, dst_image);
  return 0;
}

/*****************************************************************************\
 im.ProcessNegative
\*****************************************************************************/
static int imluaProcessNegative (lua_State *L)
{
  imImage *src_image = imlua_checkimage(L, 1);
  imImage *dst_image = imlua_checkimage(L, 2);

  imlua_checknotcfloat(L, src_image, 1);
  imlua_match(L, src_image, dst_image);

  imProcessNegative(src_image, dst_image);
  return 0;
}



/*****************************************************************************\
 Threshold Operations
\*****************************************************************************/

/*****************************************************************************\
 im.ProcessRangeContrastThreshold
\*****************************************************************************/
static int imluaProcessRangeContrastThreshold (lua_State *L)
{
  imImage *src_image = imlua_checkimage(L, 1);
  imImage *dst_image = imlua_checkimage(L, 2);
  int kernel_size = luaL_checkint(L, 3);
  int min_range = luaL_checkint(L, 4);

  imlua_checktype(L, 1, src_image, IM_GRAY, IM_BYTE);
  luaL_argcheck(L, (src_image->data_type < IM_FLOAT), 1, "image data type can be integer only");
  imlua_checkcolorspace(L, 2, dst_image, IM_BINARY);
  imlua_matchsize(L, src_image, dst_image);

  lua_pushboolean(L, imProcessRangeContrastThreshold(src_image, dst_image, kernel_size, min_range));
  return 1;
}

/*****************************************************************************\
 im.ProcessLocalMaxThreshold
\*****************************************************************************/
static int imluaProcessLocalMaxThreshold (lua_State *L)
{
  imImage *src_image = imlua_checkimage(L, 1);
  imImage *dst_image = imlua_checkimage(L, 2);
  int kernel_size = luaL_checkint(L, 3);
  int min_thres = luaL_checkint(L, 4);

  imlua_checktype(L, 1, src_image, IM_GRAY, IM_BYTE);
  luaL_argcheck(L, (src_image->data_type < IM_FLOAT), 1, "image data type can be integer only");
  imlua_checkcolorspace(L, 2, dst_image, IM_BINARY);
  imlua_matchsize(L, src_image, dst_image);

  lua_pushboolean(L, imProcessLocalMaxThreshold(src_image, dst_image, kernel_size, min_thres));
  return 1;
}

/*****************************************************************************\
 im.ProcessThreshold
\*****************************************************************************/
static int imluaProcessThreshold (lua_State *L)
{
  imImage *src_image = imlua_checkimage(L, 1);
  imImage *dst_image = imlua_checkimage(L, 2);
  int level = luaL_checkint(L, 3);
  int value = luaL_checkint(L, 4);

  imlua_checktype(L, 1, src_image, IM_GRAY, IM_BYTE);
  luaL_argcheck(L, (src_image->data_type < IM_FLOAT), 1, "image data type can be integer only");

  imlua_checkcolorspace(L, 2, dst_image, IM_BINARY);
  imlua_matchsize(L, src_image, dst_image);

  imProcessThreshold(src_image, dst_image, level, value);
  return 0;
}

/*****************************************************************************\
 im.ProcessThresholdByDiff
\*****************************************************************************/
static int imluaProcessThresholdByDiff (lua_State *L)
{
  imImage *src_image1 = imlua_checkimage(L, 1);
  imImage *src_image2 = imlua_checkimage(L, 2);
  imImage *dst_image = imlua_checkimage(L, 3);

  imlua_checktype(L, 1, src_image1, IM_GRAY, IM_BYTE);
  imlua_match(L, src_image1, src_image2);
  imlua_checkcolorspace(L, 2, dst_image, IM_BINARY);
  imlua_matchsize(L, src_image1, dst_image);

  imProcessThresholdByDiff(src_image1, src_image2, dst_image);
  return 0;
}

/*****************************************************************************\
 im.ProcessHysteresisThreshold
\*****************************************************************************/
static int imluaProcessHysteresisThreshold (lua_State *L)
{
  imImage *src_image = imlua_checkimage(L, 1);
  imImage *dst_image = imlua_checkimage(L, 2);
  int low_thres = luaL_checkint(L, 3);
  int high_thres = luaL_checkint(L, 4);

  imlua_checktype(L, 1, src_image, IM_GRAY, IM_BYTE);
  imlua_checkcolorspace(L, 2, dst_image, IM_BINARY);
  imlua_matchsize(L, src_image, dst_image);

  imProcessHysteresisThreshold(src_image, dst_image, low_thres, high_thres);
  return 0;
}

/*****************************************************************************\
 im.ProcessHysteresisThresEstimate
\*****************************************************************************/
static int imluaProcessHysteresisThresEstimate (lua_State *L)
{
  int low_thres, high_thres;

  imImage *src_image = imlua_checkimage(L, 1);

  imlua_checktype(L, 1, src_image, IM_GRAY, IM_BYTE);

  imProcessHysteresisThresEstimate(src_image, &low_thres, &high_thres);
  lua_pushnumber(L, low_thres);
  lua_pushnumber(L, high_thres);

  return 2;
}

/*****************************************************************************\
 im.ProcessUniformErrThreshold
\*****************************************************************************/
static int imluaProcessUniformErrThreshold (lua_State *L)
{
  imImage *src_image = imlua_checkimage(L, 1);
  imImage *dst_image = imlua_checkimage(L, 2);

  imlua_checktype(L, 1, src_image, IM_GRAY, IM_BYTE);
  imlua_checkcolorspace(L, 2, dst_image, IM_BINARY);
  imlua_matchsize(L, src_image, dst_image);

  lua_pushinteger(L, imProcessUniformErrThreshold(src_image, dst_image));
  return 1;
}

/*****************************************************************************\
 im.ProcessDifusionErrThreshold
\*****************************************************************************/
static int imluaProcessDifusionErrThreshold (lua_State *L)
{
  imImage *src_image = imlua_checkimage(L, 1);
  imImage *dst_image = imlua_checkimage(L, 2);
  int level = luaL_checkint(L, 3);

  imlua_checkdatatype(L, 1, src_image, IM_BYTE);
  imlua_checkdatatype(L, 2, dst_image, IM_BYTE);
  imlua_matchcheck(L, src_image->depth == dst_image->depth, "images must have the same depth");
  imlua_matchsize(L, src_image, dst_image);

  imProcessDifusionErrThreshold(src_image, dst_image, level);
  return 0;
}

/*****************************************************************************\
 im.ProcessPercentThreshold
\*****************************************************************************/
static int imluaProcessPercentThreshold (lua_State *L)
{
  imImage *src_image = imlua_checkimage(L, 1);
  imImage *dst_image = imlua_checkimage(L, 2);
  float percent = (float) luaL_checknumber(L, 3);

  imlua_checktype(L, 1, src_image, IM_GRAY, IM_BYTE);
  imlua_checkcolorspace(L, 2, dst_image, IM_BINARY);
  imlua_matchsize(L, src_image, dst_image);

  lua_pushinteger(L, imProcessPercentThreshold(src_image, dst_image, percent));
  return 1;
}

/*****************************************************************************\
 im.ProcessOtsuThreshold
\*****************************************************************************/
static int imluaProcessOtsuThreshold (lua_State *L)
{
  imImage *src_image = imlua_checkimage(L, 1);
  imImage *dst_image = imlua_checkimage(L, 2);

  imlua_checktype(L, 1, src_image, IM_GRAY, IM_BYTE);
  imlua_checkcolorspace(L, 2, dst_image, IM_BINARY);
  imlua_matchsize(L, src_image, dst_image);

  lua_pushinteger(L, imProcessOtsuThreshold(src_image, dst_image));
  return 1;
}

/*****************************************************************************\
 im.ProcessMinMaxThreshold
\*****************************************************************************/
static int imluaProcessMinMaxThreshold (lua_State *L)
{
  imImage *src_image = imlua_checkimage(L, 1);
  imImage *dst_image = imlua_checkimage(L, 2);

  imlua_checkcolorspace(L, 1, src_image, IM_GRAY);
  luaL_argcheck(L, (src_image->data_type < IM_FLOAT), 1, "image data type can be integer only");
  imlua_checkcolorspace(L, 2, dst_image, IM_BINARY);
  imlua_matchsize(L, src_image, dst_image);

  lua_pushinteger(L, imProcessMinMaxThreshold(src_image, dst_image));
  return 1;
}

/*****************************************************************************\
 im.ProcessLocalMaxThresEstimate
\*****************************************************************************/
static int imluaProcessLocalMaxThresEstimate (lua_State *L)
{
  int thres;
  imImage *image = imlua_checkimage(L, 1);

  imlua_checkdatatype(L, 1, image, IM_BYTE);

  imProcessLocalMaxThresEstimate(image, &thres);

  lua_pushnumber(L, thres);
  return 1;
}

/*****************************************************************************\
 im.ProcessSliceThreshold
\*****************************************************************************/
static int imluaProcessSliceThreshold (lua_State *L)
{
  imImage *src_image = imlua_checkimage(L, 1);
  imImage *dst_image = imlua_checkimage(L, 2);

  int start_level = luaL_checkint(L, 3);
  int end_level = luaL_checkint(L, 4);

  imlua_checkcolorspace(L, 1, src_image, IM_GRAY);
  luaL_argcheck(L, (src_image->data_type < IM_FLOAT), 1, "image data type can be integer only");
  imlua_checkcolorspace(L, 2, dst_image, IM_BINARY);
  imlua_matchsize(L, src_image, dst_image);

  imProcessSliceThreshold(src_image, dst_image, start_level, end_level);
  return 0;
}


/*****************************************************************************\
 Special Effects
\*****************************************************************************/

/*****************************************************************************\
 im.ProcessPixelate
\*****************************************************************************/
static int imluaProcessPixelate (lua_State *L)
{
  imImage *src_image = imlua_checkimage(L, 1);
  imImage *dst_image = imlua_checkimage(L, 2);
  int box_size = luaL_checkint(L, 3);

  imlua_checkdatatype(L, 1, src_image, IM_BYTE);
  imlua_match(L, src_image, dst_image);

  imProcessPixelate(src_image, dst_image, box_size);
  return 0;
}

/*****************************************************************************\
 im.ProcessPosterize
\*****************************************************************************/
static int imluaProcessPosterize (lua_State *L)
{
  imImage *src_image = imlua_checkimage(L, 1);
  imImage *dst_image = imlua_checkimage(L, 2);
  int level = luaL_checkint(L, 3);

  imlua_checkdatatype(L, 1, src_image, IM_BYTE);
  imlua_match(L, src_image, dst_image);
  luaL_argcheck(L, (level >= 1 && level <= 7), 3, "invalid level, must be >=1 and <=7");

  imProcessPosterize(src_image, dst_image, level);
  return 0;
}



static const luaL_reg improcess_lib[] = {
  {"CalcRMSError", imluaCalcRMSError},
  {"CalcSNR", imluaCalcSNR},
  {"CalcCountColors", imluaCalcCountColors},
  {"CalcHistogram", imluaCalcHistogram},
  /*{"CalcUShortHistogram", imluaCalcUShortHistogram}, done by imluaCalcHistogram */
  {"CalcGrayHistogram", imluaCalcGrayHistogram},
  {"CalcImageStatistics", imluaCalcImageStatistics},
  {"CalcHistogramStatistics", imluaCalcHistogramStatistics},
  {"CalcHistoImageStatistics", imluaCalcHistoImageStatistics},

  {"AnalyzeFindRegions", imluaAnalyzeFindRegions},
  {"AnalyzeMeasureArea", imluaAnalyzeMeasureArea},
  {"AnalyzeMeasurePerimArea", imluaAnalyzeMeasurePerimArea},
  {"AnalyzeMeasureCentroid", imluaAnalyzeMeasureCentroid},
  {"AnalyzeMeasurePrincipalAxis", imluaAnalyzeMeasurePrincipalAxis},
  {"AnalyzeMeasurePerimeter", imluaAnalyzeMeasurePerimeter},
  {"AnalyzeMeasureHoles", imluaAnalyzeMeasureHoles},

  {"ProcessPerimeterLine", imluaProcessPerimeterLine},
  {"ProcessRemoveByArea", imluaProcessRemoveByArea},
  {"ProcessFillHoles", imluaProcessFillHoles},

  {"ProcessHoughLines", imluaProcessHoughLines},
  {"ProcessHoughLinesDraw", imluaProcessHoughLinesDraw},
  {"ProcessDistanceTransform", imluaProcessDistanceTransform},
  {"ProcessRegionalMaximum", imluaProcessRegionalMaximum},

  {"ProcessReduce", imluaProcessReduce},
  {"ProcessResize", imluaProcessResize},
  {"ProcessReduceBy4", imluaProcessReduceBy4},
  {"ProcessCrop", imluaProcessCrop},
  {"ProcessAddMargins", imluaProcessAddMargins},
  {"ProcessInsert", imluaProcessInsert},

  {"ProcessCalcRotateSize", imluaProcessCalcRotateSize},
  {"ProcessRotate", imluaProcessRotate},
  {"ProcessRotateRef", imluaProcessRotateRef},
  {"ProcessRotate90", imluaProcessRotate90},
  {"ProcessRotate180", imluaProcessRotate180},
  {"ProcessMirror", imluaProcessMirror},
  {"ProcessFlip", imluaProcessFlip},
  {"ProcessRadial", imluaProcessRadial},
  {"ProcessSwirl", imluaProcessSwirl},
  {"ProcessInterlaceSplit", imluaProcessInterlaceSplit},

  {"ProcessGrayMorphConvolve", imluaProcessGrayMorphConvolve},
  {"ProcessGrayMorphErode", imluaProcessGrayMorphErode},
  {"ProcessGrayMorphDilate", imluaProcessGrayMorphDilate},
  {"ProcessGrayMorphOpen", imluaProcessGrayMorphOpen},
  {"ProcessGrayMorphClose", imluaProcessGrayMorphClose},
  {"ProcessGrayMorphTopHat", imluaProcessGrayMorphTopHat},
  {"ProcessGrayMorphWell", imluaProcessGrayMorphWell},
  {"ProcessGrayMorphGradient", imluaProcessGrayMorphGradient},

  {"ProcessBinMorphConvolve", imluaProcessBinMorphConvolve},
  {"ProcessBinMorphErode", imluaProcessBinMorphErode},
  {"ProcessBinMorphDilate", imluaProcessBinMorphDilate},
  {"ProcessBinMorphOpen", imluaProcessBinMorphOpen},
  {"ProcessBinMorphClose", imluaProcessBinMorphClose},
  {"ProcessBinMorphOutline", imluaProcessBinMorphOutline},
  {"ProcessBinMorphThin", imluaProcessBinMorphThin},

  {"ProcessMedianConvolve", imluaProcessMedianConvolve},
  {"ProcessRangeConvolve", imluaProcessRangeConvolve},
  {"ProcessRankClosestConvolve", imluaProcessRankClosestConvolve},
  {"ProcessRankMaxConvolve", imluaProcessRankMaxConvolve},
  {"ProcessRankMinConvolve", imluaProcessRankMinConvolve},

  {"ProcessConvolve", imluaProcessConvolve},
  {"ProcessConvolveDual", imluaProcessConvolveDual},
  {"ProcessConvolveRep", imluaProcessConvolveRep},
  {"ProcessConvolveSep", imluaProcessConvolveSep},
  {"ProcessCompassConvolve", imluaProcessCompassConvolve},
  {"ProcessRotateKernel", imluaProcessRotateKernel},
  {"ProcessDiffOfGaussianConvolve", imluaProcessDiffOfGaussianConvolve},
  {"ProcessLapOfGaussianConvolve", imluaProcessLapOfGaussianConvolve},
  {"ProcessMeanConvolve", imluaProcessMeanConvolve},
  {"ProcessBarlettConvolve", imluaProcessBarlettConvolve},
  {"ProcessGaussianConvolve", imluaProcessGaussianConvolve},
  {"ProcessSobelConvolve", imluaProcessSobelConvolve},
  {"ProcessPrewittConvolve", imluaProcessPrewittConvolve},
  {"ProcessSplineEdgeConvolve", imluaProcessSplineEdgeConvolve},
  {"ProcessZeroCrossing", imluaProcessZeroCrossing},
  {"ProcessCanny", imluaProcessCanny},
  {"ProcessUnsharp", imluaProcessUnsharp},
  {"ProcessSharp", imluaProcessSharp},
  {"ProcessSharpKernel", imluaProcessSharpKernel},
  {"GaussianKernelSize2StdDev", imluaGaussianKernelSize2StdDev},
  {"GaussianStdDev2KernelSize", imluaGaussianStdDev2KernelSize},

  {"ProcessUnArithmeticOp", imluaProcessUnArithmeticOp},
  {"ProcessArithmeticOp", imluaProcessArithmeticOp},
  {"ProcessArithmeticConstOp", imluaProcessArithmeticConstOp},
  {"ProcessBlendConst", imluaProcessBlendConst},
  {"ProcessBlend", imluaProcessBlend},
  {"ProcessCompose", imluaProcessCompose},
  {"ProcessSplitComplex", imluaProcessSplitComplex},
  {"ProcessMergeComplex", imluaProcessMergeComplex},
  {"ProcessMultipleMean", imluaProcessMultipleMean},
  {"ProcessMultipleStdDev", imluaProcessMultipleStdDev},
  {"ProcessAutoCovariance", imluaProcessAutoCovariance},
  {"ProcessMultiplyConj", imluaProcessMultiplyConj},

  {"ProcessQuantizeRGBUniform", imluaProcessQuantizeRGBUniform},
  {"ProcessQuantizeGrayUniform", imluaProcessQuantizeGrayUniform},

  {"ProcessExpandHistogram", imluaProcessExpandHistogram},
  {"ProcessEqualizeHistogram", imluaProcessEqualizeHistogram},

  {"ProcessSplitYChroma", imluaProcessSplitYChroma},
  {"ProcessSplitHSI", imluaProcessSplitHSI},
  {"ProcessMergeHSI", imluaProcessMergeHSI},
  {"ProcessSplitComponents", imluaProcessSplitComponents},
  {"ProcessMergeComponents", imluaProcessMergeComponents},
  {"ProcessNormalizeComponents", imluaProcessNormalizeComponents},
  {"ProcessReplaceColor", imluaProcessReplaceColor},

  {"ProcessBitwiseOp", imluaProcessBitwiseOp},
  {"ProcessBitwiseNot", imluaProcessBitwiseNot},
  {"ProcessBitMask", imluaProcessBitMask},
  {"ProcessBitPlane", imluaProcessBitPlane},

  {"ProcessRenderOp", imluaProcessRenderOp},
  {"ProcessRenderCondOp", imluaProcessRenderCondOp},
  {"ProcessRenderAddSpeckleNoise", imluaProcessRenderAddSpeckleNoise},
  {"ProcessRenderAddGaussianNoise", imluaProcessRenderAddGaussianNoise},
  {"ProcessRenderAddUniformNoise", imluaProcessRenderAddUniformNoise},
  {"ProcessRenderRandomNoise", imluaProcessRenderRandomNoise},
  {"ProcessRenderConstant", imluaProcessRenderConstant},
  {"ProcessRenderWheel", imluaProcessRenderWheel},
  {"ProcessRenderCone", imluaProcessRenderCone},
  {"ProcessRenderTent", imluaProcessRenderTent},
  {"ProcessRenderRamp", imluaProcessRenderRamp},
  {"ProcessRenderBox", imluaProcessRenderBox},
  {"ProcessRenderSinc", imluaProcessRenderSinc},
  {"ProcessRenderGaussian", imluaProcessRenderGaussian},
  {"ProcessRenderLapOfGaussian", imluaProcessRenderLapOfGaussian},
  {"ProcessRenderCosine", imluaProcessRenderCosine},
  {"ProcessRenderGrid", imluaProcessRenderGrid},
  {"ProcessRenderChessboard", imluaProcessRenderChessboard},

  {"ProcessToneGamut", imluaProcessToneGamut},
  {"ProcessUnNormalize", imluaProcessUnNormalize},
  {"ProcessDirectConv", imluaProcessDirectConv},
  {"ProcessNegative", imluaProcessNegative},

  {"ProcessRangeContrastThreshold", imluaProcessRangeContrastThreshold},
  {"ProcessLocalMaxThreshold", imluaProcessLocalMaxThreshold},
  {"ProcessThreshold", imluaProcessThreshold},
  {"ProcessThresholdByDiff", imluaProcessThresholdByDiff},
  {"ProcessHysteresisThreshold", imluaProcessHysteresisThreshold},
  {"ProcessHysteresisThresEstimate", imluaProcessHysteresisThresEstimate},
  {"ProcessUniformErrThreshold", imluaProcessUniformErrThreshold},
  {"ProcessDifusionErrThreshold", imluaProcessDifusionErrThreshold},
  {"ProcessPercentThreshold", imluaProcessPercentThreshold},
  {"ProcessOtsuThreshold", imluaProcessOtsuThreshold},
  {"ProcessMinMaxThreshold", imluaProcessMinMaxThreshold},
  {"ProcessLocalMaxThresEstimate", imluaProcessLocalMaxThresEstimate},
  {"ProcessSliceThreshold", imluaProcessSliceThreshold},

  {"ProcessPixelate", imluaProcessPixelate},
  {"ProcessPosterize", imluaProcessPosterize},

  {NULL, NULL}
};

/*****************************************************************************\
 Constants
\*****************************************************************************/
static const imlua_constant im_process_constants[] = {

  { "UN_EQL", IM_UN_EQL, NULL },
  { "UN_ABS", IM_UN_ABS, NULL },
  { "UN_LESS", IM_UN_LESS, NULL },
  { "UN_INV", IM_UN_INV, NULL },
  { "UN_SQR", IM_UN_SQR, NULL },
  { "UN_SQRT", IM_UN_SQRT, NULL },
  { "UN_LOG", IM_UN_LOG, NULL },
  { "UN_EXP", IM_UN_EXP, NULL },
  { "UN_SIN", IM_UN_SIN, NULL },
  { "UN_COS", IM_UN_COS, NULL },
  { "UN_CONJ", IM_UN_CONJ, NULL },
  { "UN_CPXNORM", IM_UN_CPXNORM, NULL },

  { "BIN_ADD", IM_BIN_ADD, NULL },
  { "BIN_SUB", IM_BIN_SUB, NULL },
  { "BIN_MUL", IM_BIN_MUL, NULL },
  { "BIN_DIV", IM_BIN_DIV, NULL },
  { "BIN_DIFF", IM_BIN_DIFF, NULL },
  { "BIN_POW", IM_BIN_POW, NULL },
  { "BIN_MIN", IM_BIN_MIN, NULL },
  { "BIN_MAX", IM_BIN_MAX, NULL },

  { "BIT_AND", IM_BIT_AND, NULL },
  { "BIT_OR", IM_BIT_OR, NULL },
  { "BIT_XOR", IM_BIT_XOR, NULL },

  { "GAMUT_NORMALIZE", IM_GAMUT_NORMALIZE, NULL },
  { "GAMUT_POW", IM_GAMUT_POW, NULL },
  { "GAMUT_LOG", IM_GAMUT_LOG, NULL },
  { "GAMUT_EXP", IM_GAMUT_EXP, NULL },
  { "GAMUT_INVERT", IM_GAMUT_INVERT, NULL },
  { "GAMUT_ZEROSTART", IM_GAMUT_ZEROSTART, NULL },
  { "GAMUT_SOLARIZE", IM_GAMUT_SOLARIZE, NULL },
  { "GAMUT_SLICE", IM_GAMUT_SLICE, NULL },
  { "GAMUT_EXPAND", IM_GAMUT_EXPAND, NULL },
  { "GAMUT_CROP", IM_GAMUT_CROP, NULL },
  { "GAMUT_BRIGHTCONT", IM_GAMUT_BRIGHTCONT, NULL },

  { NULL, -1, NULL },
};

/* from imlua_kernel.c */
void imlua_open_kernel(lua_State *L);

int imlua_open_process(lua_State *L)
{
  luaL_register(L, "im", improcess_lib);   /* leave "im" table at the top of the stack */
  imlua_regconstants(L, im_process_constants);

#ifdef IMLUA_USELOH
#include "im_process.loh"
#else
#ifdef IMLUA_USELZH
#include "im_process.lzh"
#else
  luaL_dofile(L, "im_process.lua");
#endif
#endif

  imlua_open_kernel(L);
  return 1;
}

int luaopen_imlua_process(lua_State *L)
{
  return imlua_open_process(L);
}