ctkRegion.c at [e327ead7cb]

File ctkRegion.c artifact 5e9c5af08b part of check-in e327ead7cb


/* 
 * ctkRegion.c (CTk) --
 *
 *	Geometry manipulation routines - regions (free form 2-D shapes),
 *	rectangles, and spans (horizontal segments).
 *
 *	Beware, some of theses routines have special constraints that
 *	are not obvious from their title.  Be sure to examine headers
 *	for constraints.
 *
 * Copyright (c) 1994-1995 Cleveland Clinic Foundation
 *
 * See the file "license.terms" for information on usage and redistribution
 * of this file, and for a DISCLAIMER OF ALL WARRANTIES.
 *
 * @(#) $Id: ctk.shar,v 1.50 1996/01/15 14:47:16 andrewm Exp andrewm $
 */

#include "tkPort.h"
#include "tkInt.h"

/*
 * Notes on Geometry Types.
 *
 * Spans -
 *	Horizontal line segment.  Between left point (inclusive)
 *	and right point (exclusive).  Any span with right <= left
 *	is considered empty.
 *
 * Rectangles -
 *	Analogous to spans top and left points are include,
 *	and right and bottom points are excluded from area
 *	rectangle.  Any rectangle with right <= left or
 *	bottom <= top is considered empty.
 *
 * Region -
 *	Free form area.  Contents of structure are opaque.
 *	!!! Vertical bounds of region cannot increase !!!
 *	The rectangle used with CtkCreateRegion() must have the
 *	maximum top and bottom for the region (the rectangle
 *	may still be empty if right <= left).
 */

/*
 *  Limits for x and y coordinates
 *  (choose these to work with systems where int is 16-bit).
 */
#define COORD_MAX           32767
#define COORD_MIN           -32768


/*
 * Component of a region
 */
typedef struct {
    int left;
    int right;
    int next;           /* Index of next span */
} RegionSpan;
#define NO_SPAN (-1)    /* Invalid index (indicates end of span list) */

struct CtkRegion {
    int top;
    int bottom;
    int free;
    int num_spans;
    RegionSpan *spans;
};

#define CopySpan(dst, src) (memcpy((dst), (src), sizeof(RegionSpan)))
#define FreeSpan(rgnPtr, index) \
	((rgnPtr)->spans[(index)].next \
	= (rgnPtr)->free, (rgnPtr)->free = (index))

/*
 * Private Function Declarations
 */
static void	PseudoUnionSpans _ANSI_ARGS_((int *leftPtr, int *rightPtr,
		    int left2, int right2));
static int	DeleteSpan _ANSI_ARGS_((CtkRegion * rgnPtr, int index,
		    int priorIndex));
static void	AppendSpan _ANSI_ARGS_((CtkRegion * rgnPtr, int index,
		    int left, int right));
static void	PrependSpan _ANSI_ARGS_((CtkRegion * rgnPtr, int index,
		    int left, int right));
static int	AllocSpan _ANSI_ARGS_((CtkRegion * rgnPtr));
static void	MergeSpan _ANSI_ARGS_((CtkRegion * rgnPtr, int left,
		    int right, int y));


/*
 *----------------------------------------------------------------------
 *
 * CtkIntersectSpans -- compute intersection of 2 spans
 *
 *	Compute the intersection of the span (*leftPtr,*rightPtr)
 *	and the span (left2,right2).
 *
 * Results:
 *	Stores the resulting span in `leftPtr' and `rightPtr'.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

void
CtkIntersectSpans(leftPtr, rightPtr, left2, right2)
    int *leftPtr;
    int *rightPtr;
    int left2;
    int right2;
{
    if (*leftPtr < left2)  *leftPtr = left2;
    if (*rightPtr > right2)  *rightPtr = right2;
}

/*
 *----------------------------------------------------------------------
 *
 * PseudoUnionSpans -- compute union of 2 spans
 *
 *	Compute the union of the span (*leftPtr,*rightPtr)
 *	and the span (left2,right2).  Assumes that the spans overlap.
 *	If they don't, the result will contain the area between the
 *	spans also.  (A real union would have to be capable of
 *	returning two disjoint spans.)
 *
 * Results:
 *	Stores the resulting span in `leftPtr' and `rightPtr'.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

static void
PseudoUnionSpans(leftPtr, rightPtr, left2, right2)
    int *leftPtr;
    int *rightPtr;
    int left2;
    int right2;
{
    if (*leftPtr > left2)  *leftPtr = left2;
    if (*rightPtr < right2)  *rightPtr = right2;
}

/*
 *----------------------------------------------------------------------
 *
 * CtkSpanMinusSpan - compute difference of 2 spans
 *
 *	Substract span (subL, subR) from span (srcL, srcR).
 *	(Find segment(s) in first span that do not overlap with
 *	second span.)
 *
 * Results:
 *	Returns the number of resultin spans (0-2).
 *	Stores the resulting span(s) in remsL[] and remsR[].
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

int
CtkSpanMinusSpan(srcL, srcR, subL, subR, remsL, remsR)
    int srcL;
    int srcR;
    int subL;
    int subR;
    int *remsL;
    int *remsR;
{
    int numRems = 0;

    if (srcR <= subL || srcL >= subR)  return (numRems);
    if (srcL < subL) {
	remsL[numRems] = srcL;
	remsR[numRems] = subL;
	numRems += 1;
    }
    if (srcR > subR) {
	remsL[numRems] = subR;
	remsR[numRems] = srcR;
	numRems += 1;
    }
    return (numRems);

}

/*
 *----------------------------------------------------------------------
 *
 * CtkIntersectRects -- compute intersection of two rectangles
 *
 *	Computer overlap between rectangles pointed to by
 *	r1Ptr and r2Ptr.
 *
 * Results:
 *	Stores clipped down rectangle in `r1Ptr'.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

void
CtkIntersectRects(r1Ptr, r2Ptr)
    Ctk_Rect * r1Ptr;
    CONST Ctk_Rect * r2Ptr;
{

    if (r1Ptr->left < r2Ptr->left)  r1Ptr->left = r2Ptr->left;
    if (r1Ptr->top < r2Ptr->top)  r1Ptr->top = r2Ptr->top;
    if (r1Ptr->right > r2Ptr->right)  r1Ptr->right = r2Ptr->right;
    if (r1Ptr->bottom > r2Ptr->bottom)  r1Ptr->bottom = r2Ptr->bottom;

}

/*
 *----------------------------------------------------------------------
 *
 * CtkCreateRegion -- create a new region
 *
 *	Create a new region and initialize it to the area of
 *	`rect'.
 *
 * Results:
 *	Returns pointer to new region.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

CtkRegion *
CtkCreateRegion(rect)
    Ctk_Rect * rect;
{
    CtkRegion * rgnPtr;
    int i;
    rgnPtr = (CtkRegion *) ckalloc(sizeof(CtkRegion));

    rgnPtr->top = rect->top;
    rgnPtr->bottom = rect->bottom;
    rgnPtr->free = NO_SPAN;
    rgnPtr->num_spans = rgnPtr->bottom - rgnPtr->top;
    if (rgnPtr->num_spans <= 0) {
    	rgnPtr->num_spans = 0;
    	rgnPtr->spans = (RegionSpan *) NULL;
    } else {
	rgnPtr->spans = (RegionSpan *)
		ckalloc(rgnPtr->num_spans * sizeof(RegionSpan));
	for (i=0; i < rgnPtr->num_spans; i++) {
	    rgnPtr->spans[i].left = rect->left;
	    rgnPtr->spans[i].right = rect->right;
	    rgnPtr->spans[i].next = NO_SPAN;
	}
    }
    return rgnPtr;
}

/*
 *----------------------------------------------------------------------
 *
 * CtkDestroyRegion - release resources held by a region
 *
 *	Free resources for a region - region may not be referenced
 *	again.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Memory is freed.
 *
 *----------------------------------------------------------------------
 */

void
CtkDestroyRegion(rgnPtr)
    CtkRegion *rgnPtr;
{
    if (rgnPtr->spans) {
	ckfree((char *) rgnPtr->spans);
    }
    ckfree((char *) rgnPtr);
}

/*
 *----------------------------------------------------------------------
 *
 * CtkRegionMinusRect - remove rectangular area from region
 *
 *	Substract area of rectangle `rectPtr' from region `rgnPtr'.
 *
 * Results:
 *	If `wantInter', returns the intersection of the region and
 *	the rectangle (as a new region - use CtkDestroyRegion() to
 *	get rid of it).
 *	Otherwise, returns NULL.
 *
 * Side effects:
 *	Contents of `rgnPtr' is changed.
 *
 *----------------------------------------------------------------------
 */

CtkRegion *
CtkRegionMinusRect(rgnPtr, rectPtr, wantInter)
    CtkRegion *rgnPtr;
    Ctk_Rect *rectPtr;
    int wantInter;
{
    int itop = rectPtr->top;
    int ibottom = rectPtr->bottom;
    RegionSpan *spans = rgnPtr->spans;
    int y;
    int idx;
    int lastIdx;
    int rems;
    int newLefts[2];
    int newRights[2];
    Ctk_Rect emptyRect;
    CtkRegion * intersection = NULL;

    CtkIntersectSpans(&itop, &ibottom, rgnPtr->top, rgnPtr->bottom);
    if (wantInter) {
	emptyRect.left = 0;
	emptyRect.top = itop;
	emptyRect.right = 0;
	emptyRect.bottom = ibottom;
	intersection = (CtkRegion *) CtkCreateRegion(&emptyRect);
    }

    for (y = itop; y < ibottom; y++) {
	lastIdx = NO_SPAN;
	idx = y - rgnPtr->top;
	while (idx != NO_SPAN) {
	    if (spans[idx].left >= rectPtr->right) {
		/*
		 * Remaining spans on this line are right of `rectPtr'
		 */
		break;
	    }
	    if (spans[idx].right <= rectPtr->left) {
		/*
		 * No overlap
		 */
		lastIdx = idx;
		idx = spans[idx].next;
	    } else {
		/*
		 * Rect and span overlap
		 */
		rems = CtkSpanMinusSpan(
		    spans[idx].left, spans[idx].right,
		    rectPtr->left, rectPtr->right,
		    newLefts, newRights);
		if (wantInter) {
		    CtkIntersectSpans(
			&spans[idx].left, &spans[idx].right,
			rectPtr->left, rectPtr->right );
		    MergeSpan(intersection,
			spans[idx].left, spans[idx].right, y);
		}
		switch (rems) {
		case 0:
		    idx = DeleteSpan(rgnPtr, idx, lastIdx);
		    break;
		case 1:
		    spans[idx].left = newLefts[0];
		    spans[idx].right = newRights[0];
		    lastIdx = idx;
		    idx = spans[idx].next;
		    break;
		case 2:
		    spans[idx].left = newLefts[0];
		    spans[idx].right = newRights[0];
		    AppendSpan(rgnPtr, idx, newLefts[1], newRights[1]);
		    spans = rgnPtr->spans;
		    lastIdx = spans[idx].next;
		    idx = spans[lastIdx].next;
		    break;
		}
	    }
	} /* for (idx) */
    } /* for (y) */
    return intersection;
}

/*
 *----------------------------------------------------------------------
 *
 * CtkUnionRegions - merge one region into another
 *
 *	Computes the union of the regions `rgn1Ptr' and `rgn2Ptr',
 *	and stores it in `rgn1Ptr'.
 *	!!! The union cannot increase the top and bottom of `rgn1' !!!
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	`rgn1Ptr' was is (possibly) expanded.
 *
 *----------------------------------------------------------------------
 */

void
CtkUnionRegions(rgn1Ptr, rgn2Ptr)
    CtkRegion *rgn1Ptr;
    CtkRegion *rgn2Ptr;
{
    RegionSpan *spans2 = rgn2Ptr->spans;
    int top2 = rgn2Ptr->top;
    int bottom2 = rgn2Ptr->bottom;
    int y;
    int idx;

    for (y = top2; y < bottom2; y++) {
	idx = y - top2;
	if (spans2[idx].left >= spans2[idx].right) {
	    /* Empty scan line */
	    continue;
	}
	/*
	 * Could eventually expand (repack) region 1 here,
	 * if line is not within the vertical bounds of the
	 * first region.
	 */
	while (idx != NO_SPAN) {
	    MergeSpan(rgn1Ptr, spans2[idx].left, spans2[idx].right, y);
	    idx = spans2[idx].next;
	}
    }
}

/*
 *----------------------------------------------------------------------
 *
 * CtkForEachSpan -- perform function on every span in a region
 *
 *	Executes function `spanProcPtr' for each span in region.
 *	Passes `clientData' and the span as arguments to `spanProcPtr'.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	`spanProcPtr' is executed.
 *
 *----------------------------------------------------------------------
 */

void
CtkForEachSpan(spanProcPtr, clientData, rgnPtr)
    CtkSpanProc *spanProcPtr;
    ClientData clientData;
    CtkRegion *rgnPtr;
{
    RegionSpan *spans = rgnPtr->spans;
    int top = rgnPtr->top;
    int bottom = rgnPtr->bottom;
    int y;
    int idx;

    for (y = top; y < bottom; y++) {
	idx = y - top;
	if (spans[idx].left >= spans[idx].right) {
	    /* Empty scan line */
	    continue;
	}
	while (idx != NO_SPAN) {
	    (*spanProcPtr)(spans[idx].left, spans[idx].right, y, clientData);
	    idx = spans[idx].next;
	}
    }
}

/*
 *----------------------------------------------------------------------
 *
 * CtkForEachIntersectingSpan -- perform function on region/span intersection
 *
 *	Computes the intersection of region `rgnPtr' and the
 *	span `left',`right' at vertical position `y'.  Executes
 *	function `spanProcPtr' for each span in the intersection.
 *	Passes `clientData' and the span as arguments to `spanProcPtr'.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	`spanProcPtr' is executed.
 *
 *----------------------------------------------------------------------
 */

void
CtkForEachIntersectingSpan(spanProcPtr, clientData, left, right, y, rgnPtr)
    CtkSpanProc *spanProcPtr;
    ClientData clientData;
    int left;
    int right;
    int y;
    CtkRegion *rgnPtr;
{
    RegionSpan *spans = rgnPtr->spans;
    int idx;
    int ileft;
    int iright;

    if (y < rgnPtr->top || y >= rgnPtr->bottom)  return;

    for (idx = y - rgnPtr->top; idx != NO_SPAN; idx = spans[idx].next) {
	if (spans[idx].left >= right)  break;
	if (spans[idx].right > left) {
	    /*
	     * Spans overlap.
	     */
	    ileft = spans[idx].left;
	    iright = spans[idx].right;
	    CtkIntersectSpans(&ileft, &iright, left, right);
	    (*spanProcPtr)(ileft, iright, y, clientData);
	}
    } /* for (idx) */
}

/*
 *----------------------------------------------------------------------
 *
 * CtkPointInRegion -- check if point is contained in region
 *
 *	Check if point (x,y) is in the region `rgnPtr'.
 *
 * Results:
 *	Returns 1 if point is in region, otherwise returns 0.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

int
CtkPointInRegion(x, y, rgnPtr)
    int x, y;
    CtkRegion *rgnPtr;
{
    RegionSpan *spans = rgnPtr->spans;
    int idx;

    if (y >= rgnPtr->top && y < rgnPtr->bottom) {
	for (idx = y - rgnPtr->top; idx != NO_SPAN; idx = spans[idx].next) {
	    if (spans[idx].left > x)  break;
	    if (spans[idx].right > x) {
		return 1;
	    }
	}
    }
    return 0;
}

/*
 *----------------------------------------------------------------------
 *
 * CtkRegionGetRect -- compute enclosing rectangle of a region
 *
 *	Compute the smallest rectangle that will enclose the
 *	area of `rgnPtr'.
 *
 * Results:
 *	Stores the resulting rectangle in `rectPtr'.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

void
CtkRegionGetRect(rgnPtr, rectPtr)
    CtkRegion *rgnPtr;
    Ctk_Rect *rectPtr;
{
    RegionSpan *spans = rgnPtr->spans;
    int top = rgnPtr->top;
    int numLines = rgnPtr->bottom - top;
    int topLine;           /* Index of top non-empty scan line */
    int bottomLine;        /* Index of bottom non-empty scan line */
    int left = COORD_MAX;
    int right = COORD_MIN;
    int line;
    int i;

    for (topLine = 0; topLine < numLines; topLine++) {
	if (spans[topLine].left < spans[topLine].right) {
	    /* Non-empty scan line */
	    break;
	}
    }
    for (bottomLine = numLines-1 ; bottomLine >= topLine; bottomLine--) {
	if (spans[bottomLine].left < spans[bottomLine].right) {
	    /* Non-empty scan line */
	    break;
	}
    }
    bottomLine++;

    for (line = topLine; line < bottomLine; line++)
    {
	if (spans[line].left < spans[line].right) {
	    /*
	     *	Non-empty scan line, if it goes outside the current
	     *	left and right bounds, then expand the bounds.
	     */
	    if (spans[line].left < left) {
		left = spans[line].left;
	    }
	    for (i = line; spans[i].next != NO_SPAN; i++);
	    if (spans[i].right > right) {
		right = spans[i].right;
	    }
	}
    }

    if (left < right && topLine < bottomLine) {
	CtkSetRect(rectPtr, left, top+topLine, right, top+bottomLine);
    } else {
	CtkSetRect(rectPtr, 0, 0, 0, 0);
    }
}

/*
 *----------------------------------------------------------------------
 *
 * DeleteSpan -- remove a span from a region
 *
 *	Removes the span at `index' from `rgnPtr'.  `priorIndex'
 *	must point to the preceding span, or be NO_SPAN if this
 *	is the first span of a line.
 *
 * Results:
 *	Index of the next span (one after the deleted one).
 *
 * Side effects:
 *	The span at the specified index is removed, unless it is the
 *	first span of scan line in which case is is set to empty.
 *
 *----------------------------------------------------------------------
 */

static int
DeleteSpan(rgnPtr, index, priorIndex)
    CtkRegion *rgnPtr;
    int index;
    int priorIndex;
{
    int nextIndex = rgnPtr->spans[index].next;

    if (priorIndex == NO_SPAN) {
	if (nextIndex == NO_SPAN) {
	    rgnPtr->spans[index].left = rgnPtr->spans[index].right;
	} else {
	    CopySpan(&rgnPtr->spans[index], &rgnPtr->spans[nextIndex]);
	    FreeSpan(rgnPtr, nextIndex);
	    nextIndex = index;
	}
    } else {
	rgnPtr->spans[priorIndex].next = nextIndex;
	FreeSpan(rgnPtr, index);
    }
    return nextIndex;
}

/*
 *----------------------------------------------------------------------
 *
 * AppendSpan -- add a span to a region
 *
 *	Adds span `left',`right' to region `rgnPtr'
 *	after the span at `index'.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Changes contents of `rgnPtr'.
 *	!!! May change the value of rgnPtr->spans !!!
 *
 *----------------------------------------------------------------------
 */

static void
AppendSpan(rgnPtr, index, left, right)
    CtkRegion * rgnPtr;
    int index;
    int left;
    int right;
{
    int newIndex = AllocSpan(rgnPtr);
    RegionSpan *spans = rgnPtr->spans;

    spans[newIndex].left = left;
    spans[newIndex].right = right;
    spans[newIndex].next = spans[index].next;
    spans[index].next = newIndex;
}

/*
 *----------------------------------------------------------------------
 *
 * PrependSpan -- add a span to a region
 *
 *	Adds span `left',`right' to region `rgnPtr'
 *	before the span at `index'.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Changes contents of `rgnPtr'.
 *	!!! May change the value of rgnPtr->spans !!!
 *
 *----------------------------------------------------------------------
 */

static void
PrependSpan(rgnPtr, index, left, right)
    CtkRegion * rgnPtr;
    int index;
    int left;
    int right;
{
    int newIndex = AllocSpan(rgnPtr);
    RegionSpan *spans = rgnPtr->spans;

    CopySpan(&spans[newIndex], &spans[index]);
    spans[index].left = left;
    spans[index].right = right;
    spans[index].next = newIndex;
    
}

/*
 *----------------------------------------------------------------------
 *
 * AllocSpan -- get a new span for a region
 *
 *	Allocates another span for region `rgnPtr'.
 *
 * Results:
 *	Returns index of new span.
 *
 * Side effects:
 *	!!! May change the value of rgnPtr->spans !!!
 *
 *----------------------------------------------------------------------
 */

static int
AllocSpan(rgnPtr)
    CtkRegion * rgnPtr;
{
    int i;
    int old_num;
    int new_num;

    if (rgnPtr->free == NO_SPAN) {
	/*
	 *  No spans in free list, allocate some more.
	 */
	old_num = rgnPtr->num_spans;
	new_num = old_num + 20;
	rgnPtr->spans = (RegionSpan *)
	    ckrealloc((char *) rgnPtr->spans, (new_num)*sizeof(RegionSpan));
	rgnPtr->num_spans = new_num;

	/*
	 *  Add the new spans (except one) to the regions free list.
	 */
	for (i=old_num+1; i < new_num; i++) {
	    rgnPtr->spans[i-1].next = i;
	}
	rgnPtr->spans[new_num-1].next = NO_SPAN;
	rgnPtr->free = old_num+1;

	/*
	 * Return the remaining new span.
	 */
	return (old_num);
    } else {
	/*
	 *  Spans in free list, return one.
	 */
	i = rgnPtr->free;
	rgnPtr->free = rgnPtr->spans[rgnPtr->free].next;
	return (i);
    }
}

/*
 *----------------------------------------------------------------------
 *
 * MergeSpan -- union a span into a region
 *
 *	Computes the union of region `rgnPtr' and span `left',`right'
 *	at line y, and stores it in `rgnPtr'.
 *	!!! `y' must be within rgnPtr->top and rgnPtr->bottom !!!
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	(Possibly) expands the region `rgnPtr'.
 *
 *----------------------------------------------------------------------
 */

static void
MergeSpan(rgnPtr, left, right, y)
    CtkRegion * rgnPtr;
    int left;
    int right;
    int y;
{
    RegionSpan *spans = rgnPtr->spans;
    int idx = y - rgnPtr->top;
    int lastIdx = NO_SPAN;
    int mergeIdx = NO_SPAN;

    if (y < rgnPtr->top || y > rgnPtr->bottom) {
    	panic("Merge span (y=%d) outside of regions vertical bounds (%d-%d)",
    		y, rgnPtr->top, rgnPtr->bottom);
    }

    if (spans[idx].left >= spans[idx].right) {
	/*
	 *  Empty scan line, replace it with the new span.
	 */
	spans[idx].left = left;
	spans[idx].right = right;
	return;
    }

    while (idx != NO_SPAN) {
	if (spans[idx].right >= left) {
	    /*
	     *	This spans is not left of the merge span.
	     */

	    if (spans[idx].left > right)  break; /* right of merge */

	    if (mergeIdx == NO_SPAN) {
		PseudoUnionSpans(
		    &spans[idx].left, &spans[idx].right, left, right);
		mergeIdx = idx;
	    } else {
		if (spans[mergeIdx].right < spans[idx].right) {
		    spans[mergeIdx].right = spans[idx].right;
		}
		idx = DeleteSpan(rgnPtr, idx, lastIdx);
		continue;
	    }
	}
    
	lastIdx = idx;
	idx = spans[idx].next;
    }

    if (mergeIdx == NO_SPAN) {
	/*
	 *  No merge performed, append merge span to scan line.
	 */
	if (lastIdx == NO_SPAN) {
	    PrependSpan(rgnPtr, idx, left, right);
	} else {
	    AppendSpan(rgnPtr, lastIdx, left, right);
	}
    }
}