/*
 * $Id: transferFunction.cxx,v 4.0 2003/04/28 14:40:09 hut66au Exp $
 *
 * Imview, the portable image analysis application
 * http://www.cmis.csiro.au/Hugues.Talbot/imview
 * ----------------------------------------------------------
 *
 *  Imview is an attempt to provide an image display application
 *  suitable for professional image analysis. It was started in
 *  1997 and is mostly the result of the efforts of Hugues Talbot,
 *  Image Analysis Project, CSIRO Mathematical and Information
 *  Sciences, with help from others (see the CREDITS files for
 *  more information)
 *
 *  Imview is Copyrighted (C) 1997-2001 by Hugues Talbot and was
 *  supported in parts by the Australian Commonwealth Science and 
 *  Industry Research Organisation. Please see the COPYRIGHT file 
 *  for full details. Imview also includes the contributions of 
 *  many others. Please see the CREDITS file for full details.
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *  
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *  
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA.
 * */

/*------------------------------------------------------------------------
 *
 * The long-awaited contrast/brightness/gamma control panel
 *
 * Hugues Talbot	 3 May 1998
 *      
 *-----------------------------------------------------------------------*/

#include <stdio.h>
#include <stdlib.h>
#include <FL/math.h>
#include "imview.hxx"
#include "imageIO.hxx"
#include "transferFunction.hxx"

extern imageIO *IOBlackBox;
extern transfer *transferEditorPanel;
extern int applyTransferToAll;

extern double persistentGamma;

transfer::transfer()
{
    dbgprintf("Transfer function dialog created\n");
    transferWindow = 0; // fluid depends on that.
    transferBox = 0;
    transferHistoBox = 0;
    transferFunctionBox = 0;
    return;
}

transfer::~transfer()
{
    if (transferHistoBox != 0)
	delete transferHistoBox;

    return;
}

void transfer::setDefaults()
{
    static char buff[100];
    
    static const Fl_Menu_Item displayMenu[] = {
	{"Transfer function", 0, (Fl_Callback *)chosendisplay_cb, (void *)TRANSFER_DSP},
	{"Histogram", 0, (Fl_Callback *)chosendisplay_cb, (void *)HISTOGRAM_DSP},
	{0}
    };

    dbgprintf("Setting up defaults for transfer panel\n");
    displayChoice->menu(displayMenu);

    // set range, etc, for sliders...
    contrastSlider->minimum(1.0);
    contrastSlider->maximum(-1.05);
    contrastSlider->value(0.5);
    contrastSlider->step(0.005);

    brightnessSlider->minimum(0.0);
    brightnessSlider->maximum(1.0);
    brightnessSlider->value(0.5);
    brightnessSlider->step(0.005);

    gammaSlider->minimum(1.0);
    gammaSlider->maximum(-1.0);
    gammaSlider->value(log(persistentGamma));  // this is what I mean by persistent
    gammaSlider->step(0.01);
    
	
    sprintf(buff, "%g", persistentGamma);
    gammaInput->value(buff);
    sprintf(buff, "255");
    topInput->value(buff);
    rightInput->value(buff);
    sprintf(buff, "0");
    bottomInput->value(buff);
    leftInput->value(buff);

    // allocate and set the parameters of the histobox;
    if (transferHistoBox == 0) {
	int x,y,w,h;
	x = transferBox->x();
	y = transferBox->y();
	w = transferBox->w();
	h = transferBox->h();
	// same dimensions
	transferHistoBox = new myTransferHistoBox(x,y,w,h);
	// same box
	transferHistoBox->box(transferBox->box());
	// same colour
	transferHistoBox->color(transferBox->color());
	// same user data
	transferHistoBox->user_data((void *)this);
    }

    if (transferFunctionBox == 0) {
	int x,y,w,h;
	x = transferBox->x();
	y = transferBox->y();
	w = transferBox->w();
	h = transferBox->h();
	// same dimensions
	transferFunctionBox = new myTransferFunctionBox(x,y,w,h);
	// same box
	transferFunctionBox->box(transferBox->box());
	// same colour
	transferFunctionBox->color(transferBox->color());
	// same user data
	transferFunctionBox->user_data((void *)this);
    }

    // replace the generic transferBox by the one we want
    transferWindow->remove(transferBox);
    transferWindow->add(transferFunctionBox);
    memoryBox = transferBox; // the one allocated by fluid, we don't want it.
    // but we can't delete it...
    transferBox = transferFunctionBox;
    
    setDrawingParms();

    if (persistentGamma != 1.0) {
	dbgprintf("Forcing the recalculation of the transfer map \n");
	transferBox->forceBuildMap();
	dbgprintf("Forcing redisplay of the main image \n");
	transferBox->causeMainWindowRedraw();
	// force the recalculation of the map LUT
    }
    
    return;
}

void transfer::setTransfer()
{
    if (transferBox == transferHistoBox) {
	transferWindow->remove(transferHistoBox);
	transferWindow->add(transferFunctionBox);
	transferBox = transferFunctionBox;
    }
    // copy the changes in parameters that may have
    // occured with the other type of display...
    setDrawingParms();
    transferWindow->redraw();
}

void transfer::setHisto()
{
    if (transferBox == transferFunctionBox) {
	transferWindow->remove(transferFunctionBox);
	transferWindow->add(transferHistoBox);
	transferBox = transferHistoBox;
    }
    // copy the changes in parameters that may have
    // occured with the other type of display...
    setDrawingParms(); 
    transferHistoBox->makeHisto();
    transferWindow->redraw();
}

// compute the drawing parms from the Input (top,bottom, etc) values.
void transfer::computeDrawingParms()
{
    int topv, bottomv, rightv, leftv;
    double newbrightness = 0.5, newcontrast = 0.5;

    topv = atoi(topInput->value());
    bottomv = atoi(bottomInput->value());
    rightv = atoi(rightInput->value());
    leftv = atoi(leftInput->value());
    if (topv < 0) {
	topv = 0;
	topInput->value("0");
    }
    if (topv > 255) {
	topv = 255;
	topInput->value("255");
    }
    if (bottomv < 0) {
	bottomv = 0;
	bottomInput->value("0");
    }
    if (bottomv > 255) {
	bottomv = 255;
	bottomInput->value("255");
    }
    if (leftv < 0) {
	leftv = 0;
	leftInput->value("0");
    }
    if (leftv > 255) {
	leftv = 255;
	leftInput->value("255");
    }
    if (rightv < 0) {
	rightv = 0;
	rightInput->value("0");
    }
    if (rightv > 255) {
	rightv = 255;
	rightInput->value("255");
    }

    if ((leftv == 0) && (rightv == 255)) {
	if (topv > bottomv) {
	    newcontrast = atan(255.0/((double)topv-bottomv))*2.0/M_PI;
	    newbrightness = ((double)topv+bottomv)/(2*255.0);
	} 
    } else if ((leftv == 255) && (rightv == 0)) {
	if ( topv > bottomv) {
	    newcontrast = atan(-255.0/((double)topv-bottomv))*2.0/M_PI;
	    newbrightness = ((double)topv+bottomv)/(2*255.0);
	}
    } else {
	newcontrast = atan((double)(rightv-leftv)/(255.0))*2.0/M_PI;
	if (rightv > leftv)
	    newbrightness = (255.0 - 2*leftv)/(2*((double)rightv - leftv));
	else if (leftv > rightv)
	    newbrightness = (255.0 - 2*leftv)/(2*((double)leftv -rightv));
	else
	    newbrightness = 0.5; // doesnt' matter.
    }

    //newcontrast = floor(newcontrast*200.0)/200.0; // to get the expected precision
    //newbrightness = floor(newbrightness*200.0)/200.0;
    dbgprintf("Brightness: %g, Contrast: %g\n", newbrightness, newcontrast);

    contrastSlider->value(newcontrast);
    brightnessSlider->value(newbrightness);
    
    setDrawingParms();
    transferWindow->redraw();

    // this doesn't seem to work. Why?
    // ensureImageRedraw(true);
    
    return;
}
    
void transfer::setDrawingParms()
{
    // redo this ASAP
    transferBox->setParms(contrastSlider->value(),
			 brightnessSlider->value(),
			 atof(gammaInput->value()));
    return;
}

void transfer::redrawWindowIfNeeded()
{
    if (transferWindow->visible()) {
	dbgprintf("Forcing the recalculation of the map LUT\n");
	transferBox->forceBuildMap();
        transferWindow->redraw();
	// force the recalculation of the map LUT
    }

    return;
}

void transfer::show()
{
    transferWindow->show();
}

void transfer::hide()
{
    transferWindow->hide();
}


void transfer::setTopValue(int t)
{
    char cval[10];
    dbgprintf("Top value being set to %d\n", t);
    sprintf(cval, "%d", t);
    topInput->value(cval);

    return;
}

void transfer::setBottomValue(int t)
{
    char cval[10];
    dbgprintf("Bottom  value being set to %d\n", t);
    sprintf(cval, "%d", t);
    bottomInput->value(cval);

    return;
}

void transfer::setLeftValue(int t)
{
    char cval[10];
    sprintf(cval, "%d", t);
    dbgprintf("Left  value being set to %d\n", t);
    leftInput->value(cval);

    return;
}

void transfer::setRightValue(int t)
{
    char cval[10];
    sprintf(cval, "%d", t);
    dbgprintf("Right  value being set to %d\n", t);
    rightInput->value(cval);
//  This ugly bit of code is now thankfully useless.
//
//    if (transferWindow && transferWindow->shown()) {
//	transferWindow->make_current();
//	rightInput->draw();
//    }
    return;
}


// callbacks associated with objects on the dialog
void chosendisplay_cb(Fl_Menu_ *, void *value)
{
    long val = (long)value;

    switch (val) {
      case TRANSFER_DSP:
	transferEditorPanel->setTransfer();
	break;
      case HISTOGRAM_DSP:
	transferEditorPanel->setHisto();
	break;
      default:
	errprintf("Unknown value from choice menu: %d\n",
		  val);
	break;
    }
    return;
}

// callbacks associated with this dialog
void brightnessslider_cb(Fl_Slider*, transfer *panel)
{
    dbgprintf("Brightness slider callback\n");

    if (Fl::pushed() == 0) {
	dbgprintf("Button Released!\n");
	panel->ensureImageRedraw(true);
    }
    panel->setDrawingParms();

    return;
}

void contrastslider_cb(Fl_Slider *, transfer *panel)
{
    dbgprintf("Contrast slider callback\n");

    if (Fl::pushed() == 0) {
	dbgprintf("Button Released!\n");
	panel->ensureImageRedraw(true);
    }

    panel->setDrawingParms();

    return;
}

void displaychoice_cb(Fl_Choice*, transfer*)
{
    dbgprintf("Display choice  callback\n");
}

void gammainput_cb(Fl_Float_Input *theInput, transfer *panel)
{
    double eval, val;
    const char  *cval;

    dbgprintf("Gamma input callback\n");
    
    cval = theInput->value();
    eval = atof(cval);
    if (eval > 0) {
	val = log(eval);
    } else {
	char cval[10];
	val = -1;
	sprintf(cval,"%1.4g", exp(-1));
	panel->setGammaInputValue((const char *)cval);
    }

    panel->setGammaSliderValue((float)val);
    panel->ensureImageRedraw(true);
    panel->setDrawingParms();
    
    return;
}

void gammaslider_cb(Fl_Slider *theSlider, transfer *panel)
{
    double val, eval;
    char  cval[20];

    dbgprintf("Gamma slider callback\n");
    
    if (Fl::pushed() == 0) {
	dbgprintf("Button Released!\n");
	panel->ensureImageRedraw(true);
    }


    val = (double)theSlider->value();
    eval = exp(val);
    sprintf(cval, "%1.4g", eval); 

    panel->setGammaInputValue(cval);
    panel->setDrawingParms();
    
    return;
}

void bottominput_cb(Fl_Int_Input *theInput, transfer *panel)
{
    panel->ensureImageRedraw(true);
    panel->computeDrawingParms();
    return;
}

void topinput_cb(Fl_Int_Input *theInput, transfer *panel)
{
    dbgprintf("Top input callback\n");
    panel->ensureImageRedraw(true);
    panel->computeDrawingParms();
    return;
}

void leftinput_cb(Fl_Int_Input *theInput, transfer *panel)
{
    panel->ensureImageRedraw(true);
    panel->computeDrawingParms();
    return;
}

void rightinput_cb(Fl_Int_Input *theInput, transfer *panel)
{
    panel->ensureImageRedraw(true);
    panel->computeDrawingParms();
    return;
}

void resetbutton_cb(Fl_Button*, transfer *panel)
{
    char buf[100];
    // this is very simple...
    dbgprintf("Reset button pressed\n");
    sprintf(buf, "%g", persistentGamma);
    panel->setGammaInputValue(buf);
    panel->setGammaSliderValue(0);
    panel->setContrastSliderValue(0.5);
    panel->setBrightnessSliderValue(0.5);

    panel->computeDrawingParms();
    panel->setDrawingParms();
//    panel->ensureImageRedraw(true); // we want the result to be shown on the image
    
    return;
}

void normalisebutton_cb(Fl_Button*, transfer *panel)
{
    int spp, val, minval, maxval;
    long nbpix;
    unsigned char *p, *end;
    double slope, startp;
    
    nbpix = IOBlackBox->imageWidth() * IOBlackBox->imageHeight();
    spp = IOBlackBox->imageDepth();

    maxval = 0;
    minval = 255;
    
    if (spp == 1) {
	p = IOBlackBox->imageData();
	end = p + nbpix;
	
	while (p != end) {
	    if (*p > maxval)
		maxval = *p;
	    if (*p < minval)
		minval = *p;
	    p++;
	}
    } else if (spp == 3) {
	p = IOBlackBox->imageData();
	end = p + spp * nbpix;
	// colour is a bit more conservative:
	// we take the min and max of all 3 component together
	// otherwise overflow occurs
	while (p != end) {
	    val = *p++;
	    if (val > maxval)
		maxval = val;
	    if (val < maxval)
		minval = val;
	}
    }

    dbgprintf("Min val:%d, max val: %d\n", minval, maxval);

    // now compute the slope parameters, etc
    if (maxval != minval)
	slope = 2*atan(255.0/(maxval-minval))/M_PI;
    else
	slope = 1;
    
    startp = ((double)maxval+minval)/(512);

    // this is now pretty simple...
    panel->setContrastSliderValue(slope);
    panel->setBrightnessSliderValue(startp);
    panel->ensureImageRedraw(true); // we want the result to be shown on the image
    
    panel->setDrawingParms();
    return;
}

void applytoallbutton_cb(Fl_Check_Button *b, transfer *panel)
{
    applyTransferToAll = b->value();
    switch (applyTransferToAll) {
      case 0:
	dbgprintf("Will apply tranfer function only to currently displayed image\n");
	break;
      default:
	dbgprintf("Will apply transfer function to all image/components, etc\n");
	break;
    }
    return;
}

void okbutton_cb(Fl_Return_Button*, transfer *panel)
{
    // this is very simple...
    panel->hide();
}
