/* * 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); } } }