

static char *rcsid = "imConv.c";


#include <math.h>
#include <limits.h>

#include "imConv.h"
#include "rgb-hsv.h"

#include "imConv-macros.h"


extern ImageType imConvGoodConversion(ImageType type, ImageType oktypes)
{
  ImageType ordering[] = { IMAGE_BINARY, IMAGE_GRAY, IMAGE_SHORT, IMAGE_LONG, IMAGE_FLOAT,
                           IMAGE_DOUBLE, IMAGE_RGB, IMAGE_RGBF, IMAGE_HSV };
  int numt = sizeof(ordering)/sizeof(ImageType);
  int i, j;

  for (i = 0; i < numt  &&  ordering[i] != type; i++)
    ;

  if (i >= numt  ||  ordering[i] != type) {
    return 0;
  }

  for (j = i; j < numt; j++) {               /* try to promote */
    if (ordering[j] & oktypes) {
      return ordering[j];
    }
  }

  for (j = i; j >= 0; j--) {                 /* else demote */
    if (ordering[j] & oktypes) {
      return ordering[j];
    }
  }

  return 0;                                  /* else fail */
}


static boolean case_binary(AnyImage img, AnyImage out, double mxval)
{
  switch (imGetType(out)) {
  case IMAGE_BINARY: DOBINARY(BinaryImage, 0, 1);
  case IMAGE_GRAY:   DOBINARY(GrayImage, 0, mxval);
  case IMAGE_SHORT:  DOBINARY(ShortImage, 0, mxval);
  case IMAGE_LONG:   DOBINARY(LongImage, 0, mxval);
  case IMAGE_FLOAT:  DOBINARY(FloatImage, 0.0, mxval);
  case IMAGE_DOUBLE: DOBINARY(DoubleImage, 0.0, mxval);
  case IMAGE_RGB:
    DOBINARY(RGBImage, ((RGB) {0, 0, 0}), ((RGB) {(uchar) mxval, (uchar) mxval,
                                                    (uchar) mxval}));
  case IMAGE_RGBF:
    DOBINARY(RGBFloatImage, ((RGBFloat) {0.0, 0.0, 0.0}), ((RGBFloat) {mxval, mxval,
                                                                         mxval}));
  case IMAGE_HSV:
    DOBINARY(HSVImage, ((HSV) {-1.0, 0.0, 0.0}), ((HSV) {-1.0, 0.0, mxval}));

  default:
    return TRUE;
    break;
  }

  return FALSE;
}


#define CASE_SCALAR(NAME, ITYPE)							 \
  static boolean NAME(AnyImage img, AnyImage out, double mxval,				 \
                      double thresh, ImConvMode mode)					 \
  {											 \
    double imax, imin, iabs, idif;							 \
    boolean positive = (mode == ImConvPositive);					 \
    boolean direct = (mode == ImConvDirect);						 \
											 \
    iabs = imax = imin = idif = 0;                                                       \
    if (direct) {									 \
      thresh = 1.0;									 \
    }											 \
    else {										 \
      imMaxAndMinNoInf((ITYPE) img, imax, imin);					 \
      iabs = MAX(ABS(imax), ABS(imin));							 \
      idif = imax - imin;								 \
    }											 \
											 \
    switch (imGetType(out)) {								 \
    case IMAGE_BINARY: THRESHOLD(ITYPE);						 \
    case IMAGE_GRAY:   CONVERT(ITYPE, GrayImage,     1,        ROUNDER,   SCLRASS, BOUND, 0, 255);	 \
    case IMAGE_SHORT:  CONVERT(ITYPE, ShortImage,    positive, ROUNDER,   SCLRASS, IDENT3, --, --);	 \
    case IMAGE_LONG:   CONVERT(ITYPE, LongImage,     positive, ROUNDER,   SCLRASS, IDENT3, --, --);	 \
    case IMAGE_FLOAT:  CONVERT(ITYPE, FloatImage,    positive, IDENTITY,  SCLRASS, IDENT3, --, --);	 \
    case IMAGE_DOUBLE: CONVERT(ITYPE, DoubleImage,   positive, IDENTITY,  SCLRASS, IDENT3, --, --);	 \
    case IMAGE_RGB:    CONVERT(ITYPE, RGBImage,      1,        ROUNDER,   RGBASS, BOUND, 0, 255);	 \
    case IMAGE_RGBF:   CONVERT(ITYPE, RGBFloatImage, positive, IDENTITY,  RGBASS, IDENT3, --, --);	 \
    case IMAGE_HSV:    CONVERT(ITYPE, HSVImage,      1,        IDENTITY,  HSVASS, IDENT3, --, --);	 \
											 \
    default:										 \
      return TRUE;									 \
      break;										 \
    }											 \
											 \
    return FALSE;									 \
  } 


CASE_SCALAR(case_gray, GrayImage)
CASE_SCALAR(case_short, ShortImage)
CASE_SCALAR(case_long, LongImage)
CASE_SCALAR(case_float, FloatImage)
CASE_SCALAR(case_double, DoubleImage)


static boolean case_rgb(AnyImage img, AnyImage out, double mxval,
                        double thresh, ImConvMode mode)
{
  double imax, imin, iabs, idif;
  RGB RGBmax, RGBmin;
  boolean positive = (mode == ImConvPositive);
  boolean direct = (mode == ImConvDirect);

  iabs = imax = imin = idif = 0;
  if (direct) {
    thresh = 1.0;
  }
  else {
    imMaxAndMinNoInf((RGBImage) img, RGBmax, RGBmin);
    imax = RED_WEIGHT * RGBmax.r  +  GREEN_WEIGHT * RGBmax.g  +  BLUE_WEIGHT * RGBmax.b;
    imin = RED_WEIGHT * RGBmin.r  +  GREEN_WEIGHT * RGBmin.g  +  BLUE_WEIGHT * RGBmin.b;
    iabs = MAX(ABS(imax), ABS(imin));
    idif = imax - imin;
  }
  
  switch (imGetType(out)) {
  case IMAGE_BINARY: RGBTHRESH(RGBImage);
  case IMAGE_GRAY:   RGBTOSCLR(RGBImage, GrayImage,     1,        ROUNDER, BOUND, 0, 255);
  case IMAGE_SHORT:  RGBTOSCLR(RGBImage, ShortImage,    positive, ROUNDER, IDENT3,  --, --);
  case IMAGE_LONG:   RGBTOSCLR(RGBImage, LongImage,     positive, ROUNDER, IDENT3,  --, --);
  case IMAGE_FLOAT:  RGBTOSCLR(RGBImage, FloatImage,    positive, IDENTITY, IDENT3,  --, --);
  case IMAGE_DOUBLE: RGBTOSCLR(RGBImage, DoubleImage,   positive, IDENTITY, IDENT3,  --, --);
  case IMAGE_RGB:    RGBTORGB(RGBImage,  RGBImage,      1,        ROUNDER, BOUND, 0, 255);
  case IMAGE_RGBF:   RGBTORGB(RGBImage,  RGBFloatImage, positive, IDENTITY, IDENT3,  --, --);
  case IMAGE_HSV:    MAPFUNC(RGBImage,   HSVImage, RGBtoHSV);

  default:
    return TRUE;
    break;
  }

  return FALSE;
}


static boolean case_rgbf(AnyImage img, AnyImage out, double mxval,
                         double thresh, ImConvMode mode)
{
  double imax, imin, iabs, idif;
  RGBFloat RGBmax, RGBmin;
  boolean positive = (mode == ImConvPositive);
  boolean direct = (mode == ImConvDirect);
    
  iabs = imax = imin = idif = 0;
  if (direct) {
    thresh = 1.0;
  }
  else {
    imMaxAndMinNoInf((RGBFloatImage) img, RGBmax, RGBmin);
    imax = RED_WEIGHT * RGBmax.r  +  GREEN_WEIGHT * RGBmax.g  +  BLUE_WEIGHT * RGBmax.b;
    imin = RED_WEIGHT * RGBmin.r  +  GREEN_WEIGHT * RGBmin.g  +  BLUE_WEIGHT * RGBmin.b;
    iabs = MAX(ABS(imax), ABS(imin));
    idif = imax - imin;
  }
  
  switch (imGetType(out)) {
  case IMAGE_BINARY: RGBTHRESH(RGBFloatImage);
  case IMAGE_GRAY:   RGBTOSCLR(RGBFloatImage, GrayImage,     1,        ROUNDER, BOUND, 0, 255);
  case IMAGE_SHORT:  RGBTOSCLR(RGBFloatImage, ShortImage,    positive, ROUNDER, IDENT3, --, --);
  case IMAGE_LONG:   RGBTOSCLR(RGBFloatImage, LongImage,     positive, ROUNDER, IDENT3, --, --);
  case IMAGE_FLOAT:  RGBTOSCLR(RGBFloatImage, FloatImage,    positive, IDENTITY, IDENT3, --, --);
  case IMAGE_DOUBLE: RGBTOSCLR(RGBFloatImage, DoubleImage,   positive, IDENTITY, IDENT3, --, --);
  case IMAGE_RGB:    RGBTORGB(RGBFloatImage,  RGBImage,      1,        ROUNDER, BOUND, 0, 255);
  case IMAGE_RGBF:   RGBTORGB(RGBFloatImage,  RGBFloatImage, positive, IDENTITY, IDENT3, --, --);
  case IMAGE_HSV:    MAPFUNC(RGBFloatImage,   HSVImage, RGBFtoHSV);

  default:
    return TRUE;
    break;
  }

  return FALSE;
}
    

static boolean case_hsv(AnyImage img, AnyImage out, double mxval,
                        double thresh, ImConvMode mode)
{
  AnyImage temp;
  
  switch (imGetType(out)) {
  case IMAGE_BINARY: 
  case IMAGE_GRAY:   
  case IMAGE_SHORT:  
  case IMAGE_LONG:   
  case IMAGE_FLOAT:  
  case IMAGE_DOUBLE: 
    if ((temp = imConv(img, IMAGE_RGBF, mode: mode)) == imNULL) {
      return TRUE;
    }
    if ((imConv(temp, imGetType(out), mxval: mxval, thresh: thresh,
                mode: mode, into: out)) == imNULL) {
      imFree(temp);
      return TRUE;
    }
    break;

    
  case IMAGE_RGB:    MAPFUNC(HSVImage,   RGBImage,      HSVtoRGB)
  case IMAGE_RGBF:   MAPFUNC(HSVImage,   RGBFloatImage, HSVtoRGBF);
  case IMAGE_HSV:    MAPFUNC(HSVImage,   HSVImage,      IDENTITY);

  default:
    return TRUE;
    break;
  }

  return FALSE;
}

    




AnyImage im_Conv_(void *im, ImageHeader *ih, ImageType type, kwArgDecl(KW_imConv))
{
  kwDefault(thresh, 0.0);
  kwDefault(mxval,  0.0);
  kwDefault(mode,   ImConvNormal);
  kwDefaultN(out, into, imNULL);

  AnyImage img = ih->anyim;
  boolean got_mxval = kwSpecified(mxval);
  boolean error, made_out = (out == imNULL);

  assert(im == (void *) img);
  
  switch (type) {                            /* setup & check mxval/thresh */
  case IMAGE_BINARY:                         /* this case already hit by kwDefault() */
    break;

  case IMAGE_GRAY:
    mxval = got_mxval ? mxval : 255.0;
    mxval = BOUND(mxval, 0.0, 255.0);
    break;

  case IMAGE_SHORT:
    mxval = got_mxval ? mxval : 1000.0;
    mxval = MIN(ABS(mxval), SHRT_MAX);
    break;

  case IMAGE_LONG:
    mxval = got_mxval ? mxval : 1000.0;
    mxval = MIN(ABS(mxval), LONG_MAX);
    break;

  case IMAGE_FLOAT:
    mxval = got_mxval ? mxval : 1.0;
#ifdef LINUX
    mxval = MIN(ABS(mxval), FLT_MAX);
#else
    mxval = MIN(ABS(mxval), max_normal());
#endif
    break;

  case IMAGE_DOUBLE:
    mxval = got_mxval ? mxval : 1.0;
#ifdef LINUX
    mxval = MIN(ABS(mxval), DBL_MAX);
#else
    mxval = MIN(ABS(mxval), max_normal());
#endif
    break;

  case IMAGE_RGB:
    mxval = got_mxval ? mxval : 255.0;
    mxval = BOUND(mxval, 0.0, 255.0);
    break;

  case IMAGE_RGBF:
    mxval = got_mxval ? mxval : 1.0;
#ifdef LINUX
    mxval = MIN(ABS(mxval), FLT_MAX);
#else
    mxval = MIN(ABS(mxval), max_normal());
#endif
    break;
    
  case IMAGE_HSV:
    mxval = 1.0;
    break;

  default:
    panic("imConv: invalid type to convert to");
    break;
  }


  if (out == imNULL) {
    if ((out = (AnyImage) imNewOffset(type, imGetWidth(img), imGetHeight(img),
                                      imGetXBase(img), imGetYBase(img))) == imNULL) {
      return imNULL;
    }
  }
  else {
    if (imGetType(out)  != type  ||
        imGetWidth(out) != imGetWidth(img)  ||
        imGetWidth(out) != imGetWidth(img)) {
      return imNULL;
    }
  }
  

  switch (imGetType(img)) {                  /* outer case on input image type */
  case IMAGE_BINARY: error = case_binary(img, out, (mode==ImConvDirect) ? 1.0 : mxval); break;
  case IMAGE_GRAY:   error = case_gray(img, out, mxval, thresh, mode);   break;
  case IMAGE_FLOAT:  error = case_float(img, out, mxval, thresh, mode);  break;
  case IMAGE_DOUBLE: error = case_double(img, out, mxval, thresh, mode); break;
  case IMAGE_LONG:   error = case_long(img, out, mxval, thresh, mode);   break;
  case IMAGE_SHORT:  error = case_short(img, out, mxval, thresh, mode);  break;
  case IMAGE_RGB:    error = case_rgb(img, out, mxval, thresh, mode);    break;
  case IMAGE_RGBF:   error = case_rgbf(img, out, mxval, thresh, mode);   break;
  case IMAGE_HSV:    error = case_hsv(img, out, mxval, thresh, mode);    break;
    
  default:
    error = TRUE;
    break;
  }

  if (error) {
    if (made_out) imFree(out);
    return imNULL;
  }
  
  return out;  
}





