 /*
  * Khoros: $Id: zoom.c,v 1.1 1991/05/10 15:59:17 khoros Exp $
  */

#if !defined(lint) && !defined(SABER)
static char rcsid[] = "Khoros: $Id: zoom.c,v 1.1 1991/05/10 15:59:17 khoros Exp $";
#endif

 /*
  * $Log: zoom.c,v $
 * Revision 1.1  1991/05/10  15:59:17  khoros
 * Initial revision
 *
  */ 

/*
 *----------------------------------------------------------------------
 *
 * Copyright 1990, University of New Mexico.  All rights reserved.
 * 
 * Permission to copy and modify this software and its documen-
 * tation only for internal use in your organization is hereby
 * granted, provided that this notice is retained thereon and
 * on all copies.  UNM makes no representations as too the sui-
 * tability and operability of this software for any purpose.
 * It is provided "as is" without express or implied warranty.
 * 
 * UNM DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
 * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FIT-
 * NESS.  IN NO EVENT SHALL UNM BE LIABLE FOR ANY SPECIAL,
 * INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY OTHER DAMAGES WHAT-
 * SOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER
 * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
 * ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PER-
 * FORMANCE OF THIS SOFTWARE.
 * 
 * No other rights, including for example, the right to redis-
 * tribute this software and its documentation or the right to
 * prepare derivative works, are granted unless specifically
 * provided in a separate license agreement.
 *----------------------------------------------------------------------
 */

#include "unmcopyright.h"	 /* Copyright 1990 by UNM */
#include "warpimage.h"


/* >>>>>>>>>>>>>>>>>>>>>>>>>>>>> <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
   >>>>                                                       <<<<
   >>>>		Routines To Control Zoom Mechanism            <<<<
   >>>>                                                       <<<<
   >>>>			pc_zoom()		              <<<<
   >>>>			rb_start()	                      <<<<
   >>>>			rb_move()			      <<<<
   >>>>			rb_end()			      <<<<
   >>>>			redisplay_zoom()		      <<<<
   >>>>			refresh_zoom()			      <<<<
   >>>>			redraw_rubberband()		      <<<<
   >>>>			xvw_resize_ximage()		      <<<<
   >>>>                                                       <<<<
   >>>>                                                       <<<<
   >>>>>>>>>>>>>>>>>>>>>>>>>>>>> <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<  */
   


static int rubberband_started = False;

/************************************************************
*
*  MODULE NAME: pc_zoom
*
*      PURPOSE: pc_zoom is the event handler that gets the button 
*		press event in the display raster window, and decides
*		what to do with it.
*
*        INPUT: widget     -  the widget for the event
*               clientData -  not used
*               event      -  the event
*
*       OUTPUT: none - redisplay_zoom() does the actual refreshing
*
*   WRITTEN BY: Danielle Argiro & Tom Sauer
*
************************************************************/

void pc_zoom(widget, clientData, event, dispatch)
Widget  widget;
caddr_t clientData;
XEvent  *event;
Boolean *dispatch;
{
    char temp[50];
    unsigned long mask;
    DisplayStructure *xvdisplay;
    ZoomStructure *zoom;
    Arg args[MaxArgs];
    Position xoffset, yoffset;
    int i;


    if (src_image->raster == widget)
    {
	xvdisplay = src_image;
	zoom = src_zoom;
    }
    else if (tiepoint_mode == SourceAndDest)
    {
        xvdisplay = dest_image;
        zoom = dest_zoom;
    }

    if (xvdisplay->image == NULL) return;

    i =0;
    XtSetArg(args[i], XtNxoffset, &xoffset); i++;
    XtSetArg(args[i], XtNyoffset, &yoffset); i++;
    XtGetValues(xvdisplay->raster, args, i);

    if (zoom->mode == RubberBand)
    {
       XDrawRectangle(display, XtWindow(xvdisplay->raster), gc_invert, 
	              zoom->min_x - xoffset, zoom->min_y - yoffset, 
		      zoom->xoffset, zoom->yoffset);
       zoom->zoomfactor = DefaultZoomFactor;
       bzero(temp, 50);
       (void) sprintf(temp, "%.2f", zoom->zoomfactor);
       if (zoom->id == SrcZoom)
          xvf_change_input(form, 
          Master_info.WarpImage->Options->src_zoom_factor_index,
                              xvf_floatval_chng, temp, 0);
       else
          xvf_change_input(form, 
          Master_info.WarpImage->Options->dest_zoom_factor_index,
                              xvf_floatval_chng, temp, 0);
    }
	
    zoom->mode = PointClick;
    zoom->min_x = event->xbutton.x + xoffset;
    zoom->min_y = event->xbutton.y + yoffset;

    refresh_zoom(zoom);

    mask = ButtonPressMask;
    XtRemoveEventHandler(src_image->raster, mask, FALSE, pc_zoom, NULL);

    if (tiepoint_mode == SourceAndDest)
        XtRemoveEventHandler(dest_image->raster, mask, FALSE, pc_zoom, NULL);

    point_and_clicking = False;
 
    *dispatch = False;

}


/************************************************************
*
*  MODULE NAME: rb_start
*
*      PURPOSE: rb_start is the event handler that gets the ButtonPress 
*		event in the display raster window, and sets the upper 
*		left hand corner of the box to be rubber banded at that
*		position.
*
*        INPUT: widget     -  the widget for the event
*               clientData -  not used
*               event      -  the event
*
*       OUTPUT: initializes the upper left hand corner of the 
*		box to be rubberbanded
*
*   WRITTEN BY: Danielle Argiro & Tom Sauer
*
************************************************************/

void rb_start(widget, clientData, event, dispatch)
Widget  widget;
caddr_t clientData;
XEvent  *event;
Boolean *dispatch;
{
    int i, x, y;
    Arg args[MaxArgs];
    Position xoffset, yoffset;
  
    if (rubberbanding == False) return;


    x = event->xbutton.x;
    y = event->xbutton.y;

    if (src_image->raster == widget)
    {
        i =0;
        XtSetArg(args[i], XtNxoffset, &xoffset); i++;
        XtSetArg(args[i], XtNyoffset, &yoffset); i++;
        XtGetValues(src_image->raster, args, i);

	if (src_image->image == NULL) return;
        if (src_zoom->mode == RubberBand)
	{

           XDrawRectangle(display, XtWindow(src_image->raster), gc_invert, 
	                  src_zoom->min_x - xoffset, src_zoom->min_y - yoffset, 
			  src_zoom->xoffset, src_zoom->yoffset); 
        }
	src_zoom->xoffset = 0;
	src_zoom->yoffset = 0;
        
	src_zoom->xpos = src_zoom->min_x = x + xoffset;
	src_zoom->ypos = src_zoom->min_y = y + yoffset;
	
	src_zoom->mode = RubberBand;
    }

    else if (tiepoint_mode == SourceAndDest)
    {
        if (dest_image->raster == widget)
        {
            i =0;
            XtSetArg(args[i], XtNxoffset, &xoffset); i++;
            XtSetArg(args[i], XtNyoffset, &yoffset); i++;
            XtGetValues(dest_image->raster, args, i);

	    if (dest_image->image == NULL) return;
            if (dest_zoom->mode == RubberBand)
               XDrawRectangle(display, XtWindow(dest_image->raster), gc_invert, 
	                 dest_zoom->min_x - xoffset, dest_zoom->min_y - yoffset,
			 dest_zoom->xoffset, dest_zoom->yoffset); 
	    dest_zoom->xoffset = 0; 
 	    dest_zoom->yoffset = 0;

            dest_zoom->xpos = dest_zoom->min_x = x + xoffset;
            dest_zoom->ypos = dest_zoom->min_y = y + yoffset;

	    dest_zoom->mode = RubberBand;
        }
    }
    rubberband_started = True;
    *dispatch = False;
}


/************************************************************
*
*  MODULE NAME: rb_move
*
*      PURPOSE: rb_move is the event handler that gets ButtonMotion
*		events, and draws the rubberband from the point set
*		earlier in rb_set
*
*	INPUT:  widget     -  the widget for the event
*               clientData -  not used
*               event      -  the event
*
*       OUTPUT: rubberbands a box 
*
* WRITTEN BY: Tom Sauer & Danielle Argiro
*
*
************************************************************/

void rb_move(widget, clientData, event, dispatch)
Widget  widget;
caddr_t clientData;
XEvent  *event;
Boolean *dispatch;
{
    int width, height; 
    int  x, y, x1, y1, x2, y2, i;
    ZoomStructure *zoom;
    DisplayStructure *xvdisplay;
    Position xoffset, yoffset;
    Arg args[MaxArgs];

    if (rubberbanding == False) return;
    if (rubberband_started == False) return;

    if (src_image->raster == widget)
    {
	zoom = src_zoom;
	xvdisplay = src_image;
    }
    else 
    {
	zoom = dest_zoom;
	xvdisplay = dest_image;
    }

    i =0;
    XtSetArg(args[i], XtNxoffset, &xoffset); i++;
    XtSetArg(args[i], XtNyoffset, &yoffset); i++;
    XtGetValues(xvdisplay->raster, args, i);

    x1 = zoom->xpos - xoffset;
    y1 = zoom->ypos - yoffset;

    x = event->xbutton.x;
    y = event->xbutton.y;

    if (x < 0) x = 0;
    else 
    {
	if (x > xvdisplay->image->row_size) 
	    x = xvdisplay->image->row_size-1;
    }

    if (y < 0) y = 0;
    else 
    {
	if (y > xvdisplay->image->col_size) 
	   y = xvdisplay->image->col_size-1;
    }

    x2 = x;
    y2 = y;

    x = MIN(x1,x2);
    y = MIN(y1,y2);

    XDrawRectangle(display, XtWindow(xvdisplay->raster), gc_invert, 
                   zoom->min_x - xoffset, zoom->min_y - yoffset, 
		   (unsigned int) zoom->xoffset, (unsigned int) zoom->yoffset); 

    zoom->min_x = x + xoffset;
    zoom->min_y = y + yoffset;
    zoom->xoffset = width = MAX(x1,x2) - x;
    zoom->yoffset = height = MAX(y1,y2) - y;

    XDrawRectangle(display, XtWindow(xvdisplay->raster), gc_invert, 
	           x, y, (unsigned int) width, 
		   (unsigned int) height); 

    XFlush(display);
}

/************************************************************
*
*  MODULE NAME: rb_end
*
*      PURPOSE: rb_end  is the event handler that gets the ButtonRelease
*	        event, and ends the rubberbanded zoom box at that point.
*
*       INPUT:  widget     -  the widget for the event
*               clientData -  not used
*               event      -  the event
*
*       OUTPUT: updates the zoom box to the rubberbanded area
*
* WRITTEN BY: Tom Sauer & Danielle Argiro
*
*
************************************************************/

void rb_end(widget, clientData, event, dispatch)
Widget  widget;
caddr_t clientData;
XEvent  *event;
Boolean *dispatch;
{
     if (rubberband_started == False) return;

     if (src_image->raster == widget)
        refresh_zoom(src_zoom);
     else
        refresh_zoom(dest_zoom);

     XtRemoveEventHandler(src_image->raster, ButtonPressMask, FALSE, 
			  rb_start, NULL);
     XtRemoveEventHandler(src_image->raster, ButtonMotionMask, FALSE, 
			  rb_move, NULL);
     XtRemoveEventHandler(src_image->raster, ButtonReleaseMask, FALSE, 
			  rb_end, NULL);

     if (tiepoint_mode == SourceAndDest)
     {
         XtRemoveEventHandler(dest_image->raster, ButtonPressMask, FALSE, 
			      rb_start, NULL);
         XtRemoveEventHandler(dest_image->raster, ButtonMotionMask, FALSE, 
			      rb_move, NULL);
         XtRemoveEventHandler(dest_image->raster, ButtonReleaseMask, FALSE, 
			      rb_end, NULL);
     }

     rubberband_started = False;
     rubberbanding = False;
     *dispatch = False;
}


/************************************************************
*
*  MODULE NAME: redisplay_zoom
*
*      PURPOSE: redisplay_zoom is the event handler that
*		is used to refresh the zoomed image to the
*		zoom window when zoom window is exposed or resized.  
*		calls refresh_zoom() to do the actual displaying.
*
*
*	 INPUT: widget     -  the widget for the event
*		clientData -  not used
*		event      -  the event
*
*       OUTPUT: none - refresh_zoom() does the actual refreshing
*
*   WRITTEN BY: Mark Young
*
************************************************************/


void redisplay_zoom(widget, clientData, event)
Widget	widget;
caddr_t clientData;
XEvent  *event;
{
        ZoomStructure  *zoom = (ZoomStructure *) clientData;

	Window	    root;
	int	    x, y;
	unsigned    int width, height, border_width, depth;


	/*
	 *  Make sure that there is a current image to zoom with.
	 */
	if (zoom->assoc_image == NULL || zoom->ximage == NULL)
	   return;

	if (event->type == Expose && zoom->raster == widget)
	{
	   /*
	    *  refresh the zoom display.
	    */
	   refresh_zoom(zoom);
	}
	else if (event->type == ConfigureNotify)
	{
           if ( !XGetGeometry(XtDisplay(zoom->raster), XtWindow(zoom->raster),
	            &root, &x, &y, &width, &height,  &border_width, &depth))
           {
	      xvf_error_wait("Window does not exist.","redisplay_zoom", NULL);
	      return;
           }

	   if (zoom->ximage->width != width || zoom->ximage->height != height)
	   {
	      XtFree(zoom->ximage->data);
	      zoom->ximage->width = width;
	      zoom->ximage->height = height;
	      zoom->ximage->bytes_per_line = width;
	      zoom->ximage->data = (char *) XtMalloc(width * height);

	      refresh_zoom(zoom);
	   }
	}
	else
	{
	   if (xvd_check_visibility(widget) == False)
	      return;

	   refresh_zoom(zoom);
	}
}




/************************************************************
*
*  MODULE NAME: refresh_zoom
*
*      PURPOSE: refreshes the zoom image in the zoom window.
*
*        INPUT: none - uses the zoom->xpos & zoom->ypos fields
*		       as the x and y position to offset.
*
*	OUTPUT: displays a zoomed image to the pixel window
*
*   WRITTEN BY: Mark Young
*
************************************************************/

refresh_zoom(zoom)
ZoomStructure *zoom;
{
	register  float	  factor;
	unsigned  char	  *data, *line, *imgdata;
	register  int	  xpos, ypos, i, j, k, x, y, width, height;
	int	          num_pixels_width, num_pixels_height;
	char              temp[50];
	Widget 		  raster;
	Arg 		  args[MaxArgs];
	Position          xoffset, yoffset;

	XImage *ximage    = zoom->assoc_ximage;

	if (zoom->id == SourceImage)
           raster = src_image->raster;
        else
           raster = dest_image->raster;

	i = 0;
        XtSetArg(args[i], XtNxoffset, &xoffset);             i++;
        XtSetArg(args[i], XtNyoffset, &yoffset);             i++;
        XtGetValues(raster, args, i);


	/*
	 *  Make sure that there is a current image to zoom with.
	 */
	if (zoom->assoc_image == NULL || zoom->ximage == NULL)
	   return;

	factor   = zoom->zoomfactor;
        zoom->x_zoom_loc = 0;
        zoom->y_zoom_loc = 0;
	x = zoom->min_x;
	y = zoom->min_y;

        if (zoom->mode == RubberBand)
	{
	   if (rubberbanding)
	   {
               factor = MIN(((float) ZoomDisplayWidth / (float) zoom->xoffset), 
			((float) ZoomDisplayHeight / (float) zoom->yoffset));
	
	       zoom->zoomfactor = factor;
	       bzero(temp, 50);
               (void) sprintf(temp, "%.2f", zoom->zoomfactor);
	       if (zoom->id == SrcZoom)
                  xvf_change_input(form, 
	                Master_info.WarpImage->Options->src_zoom_factor_index,
                        xvf_floatval_chng, temp, 0);
	       else
                  xvf_change_input(form, 
	                Master_info.WarpImage->Options->dest_zoom_factor_index,
                        xvf_floatval_chng, temp, 0);
	   }

	   width  = (int) ((float) zoom->xoffset * factor);
	   height = (int) ((float) zoom->yoffset * factor);

	   zoom->x_zoom_loc = (ZoomDisplayWidth - width) / 2;
	   zoom->y_zoom_loc = (ZoomDisplayHeight - height) / 2;
	}
	else  /* zoom->mode = PointClick */
        {
	   width = ZoomDisplayWidth;
	   height = ZoomDisplayHeight;
	   num_pixels_width = (int) ((float) ZoomDisplayWidth / factor);
	   num_pixels_height = (int) ((float) ZoomDisplayHeight / factor);
	   x = x - num_pixels_width / 2;
           if (x < 0)
	   {
              zoom->x_zoom_loc = abs(x);
	      x = 0;
	   }
             
	   y = y - num_pixels_height / 2;
	   if (y < 0)
	   {
              zoom->y_zoom_loc = abs(y);
	      y = 0;
	   }
        }

	/*
	 * need to generate a new ximage for the zoom because of the
	 * change in the width and height of the zoom area. 
	 */

	xvw_resize_ximage(zoom->ximage, width, height);

	data     = (unsigned char *) zoom->ximage->data;
	imgdata  = (unsigned char *) ximage->data;


	i = 0; ypos = y;
	while ((i < height) && (ypos < ximage->height - 1))
	{
	    ypos = y + i/factor;

	       /* save the start of the scan line */
	    line = data;
	    imgdata = (unsigned char *)(ximage->data + ximage->width * ypos);

	    xpos = 0;
	    for (j = 0; (j < width) && (xpos < ximage->width - 1); j++)
	    {
		   xpos = x + j/factor;
		   *(data++) = *(imgdata + xpos);
	    }
	    data = line + width;
	    i++;

	       /* duplicate the rest of the scan lines */
	    for (k = 1; (k < factor) && (i < height); k++, i++)
	    {
		   bcopy(line, data, j);
		   data += width;
	    }
	}
	XClearWindow(display, XtWindow(zoom->raster));
	XPutImage(XtDisplay(zoom->raster), XtWindow(zoom->raster), gc_zoom,
	          zoom->ximage, 0, 0, zoom->x_zoom_loc, zoom->y_zoom_loc, j, i);

		/* 
		 * set the width and height for the zoom now that
		 * we know what it is for the Point and Click mode
		 */
	if (zoom->mode == PointClick)
	{
		zoom->xoffset = j;
		zoom->yoffset = i;
	}

 	XFlush(XtDisplay(zoom->raster));
}


/************************************************************
*
*  MODULE NAME: redraw_rubberband
*
*      PURPOSE: used to redraw the rubberband rectangle when
*		panning around in the image
*
*
*	 INPUT: widget     -  the widget for the event
*		clientData -  not used
*		event      -  the event
*		dispatch   -  the dispatch flag for propagation of this event
*
*       OUTPUT: none
*
*   WRITTEN BY: Tom Sauer & Mark Young
*
************************************************************/


void redraw_rubberband(widget, clientData, event, dispatch)

Widget  widget;
caddr_t clientData;
XEvent  *event;
Boolean *dispatch;
{
	ZoomStructure *zoom = (ZoomStructure *) clientData;

	int      i;
	Widget   raster;
	Arg	 args[MaxArgs];
	Position xoffset, yoffset;


	if (event->type == Expose)
	{
	   if (event->xexpose.count > 0)
	      return;
	}
	else if (event->type == GraphicsExpose)
	{
	   if (event->xgraphicsexpose.count > 0)
	      return;
	}
	else if (event->type != NoExpose)
	   return;

	if (zoom->mode == RubberBand)
	{
	   if (zoom->id == SourceImage)
	      raster = src_image->raster;
	   else
	      raster = dest_image->raster;

	   i = 0;
	   XtSetArg(args[i], XtNxoffset, &xoffset);		i++;
	   XtSetArg(args[i], XtNyoffset, &yoffset);		i++;
	   XtGetValues(raster, args, i);

	   XDrawRectangle(display, XtWindow(raster), gc_invert,
			zoom->min_x - xoffset, zoom->min_y - yoffset,
			zoom->xoffset, zoom->yoffset);
	}
}



/************************************************************
*
*  MODULE NAME: xvw_resize_ximage
*
*      PURPOSE: Takes the ximage that is contained in the xvdisplay structure,
*		and returns a shrinked ximage of the desired width and 
*		height.  This is commonly used when creating an icon
*		or image to zoom in.
*
*        INPUT: display - pointer to the X display structure
*		ximage  - the ximage to subsampled
*		desired_width - desired width of the subsampled image
*		desired_height - desired height of the subsampled image
*
*       OUTPUT:	the new ximage of a new size
*
*    CALLED BY: the application program
*
*   WRITTEN BY: Tom Sauer 
*		Modified from xvd_shrink_ximage written by Mark Young
*
*
*************************************************************/


xvw_resize_ximage(ximage, desired_width, desired_height)

XImage  *ximage;
int	desired_width, desired_height;
{
	unsigned   int width, height, size;

	/*
	 *  Make sure there is a ximage 
	 */
	if (ximage == NULL)
	   return;

	ximage->width = width  = desired_width;
	ximage->height = height = desired_height;

	/*
	 *  Check to see if the image is a dithered image
	 */
	if (ximage->depth == 1)
	{
	   /*
	    * compute the new size of the image and allocate new space
	    */
	   ximage->bytes_per_line = (width + 7)/8;
	   size   = ximage->bytes_per_line * height;
	   free(ximage->data);
	   ximage->data   = (char *) XtMalloc((unsigned) sizeof(char) * size);
        }
	else
	{
	   ximage->bytes_per_line = width;
	   /*
	    * compute the new size of the image and allocate new space
	    */
	   size   = ximage->bytes_per_line * height;
	   free(ximage->data);
           ximage->data   = (char *) XtMalloc((unsigned) sizeof(char) * size);
	}
}
