/*
 * $Id: imlines.cxx,v 4.3 2004/06/23 08:22:48 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.
 * */


/*------------------------------------------------------------------------
 *
 * imlines.C
 *
 * This file contains code to draw line objects that remember what
 * was under them when they first got drawn. Not such an easy matter...
 *
 * Hugues Talbot	 4 Apr 1998
 *      
 *-----------------------------------------------------------------------*/

#include "imview.hxx"
#include "imlines.hxx"
#include "imageIO.hxx"
#include "imageViewer.hxx"
#include "imDrawPoint.hxx"

extern imageViewer *mainViewer;
extern imageIO     *IOBlackBox;


// default constructor
imline::imline(void)
    : Fl_Object(0,0,0,0,0)
{
    firstdrawn_ = true;
    xorval_ = DEFAULT_XOR_FG;
    selected_ = false;
    linecolour_ = FL_WHITE;
}

// normal destructor
// erase line
imline::~imline(void)
{
    dbgprintf("imline destructor called\n");
    // is this really needed?
    //undraw();
}

// copy constructor
imline::imline(const imline &l)
    : Fl_Object(l.x1_, l.y1_, abs(l.x2_-l.x1_), abs(l.y2_-l.y1_), 0)
{
    x1_ = l.x1_;
    x2_ = l.x2_;
    y1_ = l.y1_;
    y2_ = l.y2_;
    style_ = l.style_;
    firstdrawn_ = l.firstdrawn_;
    xorval_ = l.xorval_;
    selected_ = l.selected_;
    linecolour_ = l.linecolour_;
    
    // what about the pointqueue?
    // at this point it is normally empty: no need to copy it...
    
    return;
}

// a real line
imline::imline(int x1, int y1, int x2, int y2, linestyle style,  int xorval, Fl_Color linecolour)
    : Fl_Object(x1, y1, abs(x2-x1+1), abs(y2-y1+1), 0)
{
    x1_ = x1;
    y1_ = y1;
    x2_ = x2;
    y2_ = y2;
    style_ = style;
    firstdrawn_ = true;
    xorval_ = xorval;
    selected_ = false;
    linecolour_ = linecolour;
    
    // sanity check
    if (x1_ < 0) x1_ = 0;
    if (x1_ >= IOBlackBox->imageWidth()) x1_ = IOBlackBox->imageWidth()-1;
    if (x2_ < 0) x2_ = 0;
    if (x2_ >= IOBlackBox->imageWidth()) x2_ = IOBlackBox->imageWidth()-1;
    if (y1_ < 0) y1_ = 0;
    if (y1_ >= IOBlackBox->imageHeight()) y1_ = IOBlackBox->imageHeight()-1;
    if (y2_ < 0) y2_ = 0;
    if (y2_ >= IOBlackBox->imageHeight()) y2_ = IOBlackBox->imageHeight()-1; 

    dbgprintf("imline being defined\n");
    if (style_ == STYLE_SPARSE_COLOUR) {
	// enqueue the image data
	int delta= bresinit();
	impoint aPoint;
	do {
	    // this is not too elegant here. We rely on the structure of imdata
	    // Bjarne would scream!
	    int id = IOBlackBox->imageDepth();
	    if (id != 1) {
		uchar *dp;
		dp = IOBlackBox->imageData() + id * delta;
		aPoint.R = *dp;
		aPoint.G = *(dp+1);
		aPoint.B = *(dp+2);
	    } else {
		aPoint.R = aPoint.G = aPoint.B = *(IOBlackBox->imageData() + delta);
	    }
	    // enqueue this point
	    pointqueue.push_back(aPoint);
	} while ((delta = bresgetnext()) > 0);
    }
}

// I wonder if this is the thing to do...
void imline::draw_xorline(void)
{
    int truex1 = mainViewer->xOnWindow(x1_);
    int truex2 = mainViewer->xOnWindow(x2_);
    int truey1 = mainViewer->yOnWindow(y1_);
    int truey2 = mainViewer->yOnWindow(y2_);

#ifdef WIN32
    int old = SetROP2(fl_gc, R2_NOT);
    fl_line(truex1, truey1, truex2, truey2);
    SetROP2(fl_gc, old);
#elif defined(MACOSX_CARBON)
    PenMode( patXor );
    fl_line(truex1, truey1, truex2, truey2);
    PenMode( patCopy );
#else
    XSetFunction(fl_display, fl_gc, GXxor);
    XSetForeground(fl_display, fl_gc, xorval_);
    XDrawLine(fl_display, fl_window, fl_gc,
	      truex1, truey1,
	      truex2, truey2);
    XSetFunction(fl_display, fl_gc, GXcopy);
#endif

}

void imline::draw_filledLine(void)
{
    int truex1 = mainViewer->xOnWindow(x1_);
    int truex2 = mainViewer->xOnWindow(x2_);
    int truey1 = mainViewer->yOnWindow(y1_);
    int truey2 = mainViewer->yOnWindow(y2_);

    fl_line(truex1, truey1, truex2, truey2);
}

void imline::draw_BWLine(void)
{
    int truex1 = mainViewer->xOnWindow(x1_);
    int truex2 = mainViewer->xOnWindow(x2_);
    int truey1 = mainViewer->yOnWindow(y1_);
    int truey2 = mainViewer->yOnWindow(y2_);

    
    // at least one of the following lines should
    // always be visible no matter what the orientation
    // of the line.
    if (selected())
	fl_color(FL_RED);
    else 
	fl_color(FL_BLACK);
    fl_line(truex1+1, truey1+1, truex2+1, truey2+1);
    fl_line(truex1-1, truey1, truex2-1, truey2);
    // the white should be written after so that is
    // is also always visible
    if (selected())
	fl_color(FL_GREEN);
    else
	fl_color(FL_WHITE);
    fl_line(truex1, truey1, truex2, truey2);
    
}

void imline::draw(void)
{
    // define a clipping path
    fl_push_no_clip();
    fl_clip(mainViewer->x(),
	    mainViewer->y(),
	    mainViewer->visibleWidth(),
	    mainViewer->visibleHeight());
    
    if (style_ == STYLE_SPARSE_COLOUR) {
        fl_color(linecolour_);

	// just draw all the points one by one
	int delta = bresinit();
	do {
	    im_point(mainViewer->xOnWindow(xpos_),
		     mainViewer->yOnWindow(ypos_));
	} while ((delta = bresgetnext()) > 0);

	
	//fl_pop_clip();
    } else if (style_ == STYLE_XOR) {
	draw_xorline(); // between the known points.
	if (firstdrawn_) {
	    firstdrawn_ = false;
	} else {
	    draw_xorline(); // the first draw had erased the line...
	}
    } else if (style_ == STYLE_FILLED_COLOUR) {
	fl_color(linecolour_);
	draw_filledLine();
    }  else {
	draw_BWLine();
    }

    // undefine the clipping path
    fl_pop_clip();
    fl_pop_clip();
    
    return;
}

void imline::undraw(void)
{
    // check that there is something to erase
    if (!firstdrawn_) {
	fl_push_no_clip();
	fl_clip(mainViewer->x(),
		mainViewer->y(),
		mainViewer->visibleWidth(),
		mainViewer->visibleHeight());
	
	if (style_ == STYLE_SPARSE_COLOUR) {
	    // empty the queue
	    int delta = bresinit();
	    impoint aPoint;
	    do {
		if (!pointqueue.empty()) {
		    aPoint = pointqueue.front();
		    fl_color(aPoint.R, aPoint.G, aPoint.B);
		    im_point(mainViewer->xOnWindow(xpos_),
			     mainViewer->yOnWindow(ypos_));
		    // delete this point now.
		    pointqueue.pop_front();
		}
	    } while ((delta = bresgetnext()) > 0);
	} else if (style_ == STYLE_XOR) {
	    // this is simple:
	    dbgprintf("Erasing line between (%d,%d) and (%d,%d)\n",
		      x1_, y1_, x2_, y2_);
	    draw_xorline(); // between the known points.
	}

	fl_pop_clip();
	fl_pop_clip();
    }

    return;
}

// the well-known Bresenham algorithm in a flexible incarnation
int imline::bresinit(void)
{
    sizex_ = IOBlackBox->imageWidth();
    dx_ = x2_ - x1_;
    dy_ = y2_ - y1_;
    if (dx_ < 0) dx_ = -dx_;
    if (dy_ < 0) dy_ = -dy_;
    if (x2_ < x1_)
	incx_ = -1;
    else
	incx_ = 1;
    if (y2_ < y1_)
	incy_ = -1;
    else
	incy_ = 1;

    xpos_ = x1_;
    ypos_ = y1_;

    if (dx_ > dy_) {
	e_ = 2*dy_ - dx_;
	inc1_ = 2*(dy_-dx_);
	inc2_ = 2*dy_;
    } else {
	e_ = 2*dx_ - dy_;
	inc1_ = 2*(dx_-dy_);
	inc2_ = 2*dx_;
    }
    i_ = 0;
    
    return (xpos_ + ypos_ * sizex_);
}

int imline::bresgetnext(void)
{
    if (dx_ > dy_) {
	if (++i_ >= dx_)
	    return -1;
	else {
	    if (e_ >= 0) {
		ypos_ += incy_;
		e_ += inc1_;
	    }
	    else e_ += inc2_;
	    xpos_ += incx_;
	    return (xpos_ + ypos_ * sizex_);
	}
    } else {
	if (++i_ >= dy_)
	    return -1;
	else {
	    if (e_ >= 0) {
		xpos_ += incx_;
		e_ += inc1_;
	    }
	    else e_ += inc2_;
	    ypos_ += incy_;
	    return (xpos_ + ypos_ * sizex_);
	}
    }
}

