Previous | Table of Contents | Next |
A couple of run-length slice implementation details yet remain. First is the matter of how error-term turnover is detected. This is done in much the same way as it is with standard Bresenhams: The error term is maintained as a negative valve and advances for each step; when the error term reaches 0, its time to add an extra pixel to the current run. This means that we only have to test for carry after advancing the error term to determine whether or not to add an extra pixel to each run. (Actually, the code in this chapter tests for the error term being greater than zero, but the assembly code in the next chapter will use the very efficient carry approach.)
The second and more difficult detail is balancing the runs so that theyre centered around the ideal line, and therefore draw the same pixels that standard Bresenhams would draw. If we just drew full-length runs from the start, wed end up with an unbalanced line, as shown in Figure 36.5. Instead, we have to split the initial pixel plus one full run as evenly as possible between the first and last runs of the line, and adjust the initial error term appropriately for the initial half-run.
The initial error term is advanced by one-half of the normal per-step fractional advance, because the initial step is only one-half pixel along the minor axis. This half-step gets us exactly halfway between the initial pixel and the next pixel along the minor axis. All the error-term adjustments are scaled up by two times precisely so that we can scale up this halved error term for the initial run by two times, and thereby make it an integer.
The other trick here is that if an odd number of pixels are allocated between the first and last partial runs, well end up with an odd pixel, since we are unable to draw a half-pixel. This odd pixel is accounted for by adding half a pixel to the error term.
Thats all there is to run-length slice line drawing; the partial first and last runs are the only tricky part. Listing 36.1 is a run-length slice implementation in C. This is not an optimized implementation, nor is it meant to be; this listing is provided so that you can see how the run-length slice algorithm works. In the next chapter, Ill move on to an optimized version, but for now, Listing 36.1 will make it much easier to grasp the principles of run-length slice drawing, and to understand the optimized code Ill present in the next chapter.
Figure 36.5 Balancing run-length slice lines: a) unbalanced; b) balanced.
LISTING 36.1 L36-1.C
/* Run-length slice line drawing implementation for mode 0x13, the VGAs 320x200 256-color mode. Not optimized! Tested with Borland C++ in the small model. */ #include <dos.h> #define SCREEN_WIDTH 320 #define SCREEN_SEGMENT 0xA000 void DrawHorizontalRun(char far **ScreenPtr, int XAdvance, int RunLength, int Color); void DrawVerticalRun(char far **ScreenPtr, int XAdvance, int RunLength, int Color); /* Draws a line between the specified endpoints in color Color. */ void LineDraw(int XStart, int YStart, int XEnd, int YEnd, int Color) { int Temp, AdjUp, AdjDown, ErrorTerm, XAdvance, XDelta, YDelta; int WholeStep, InitialPixelCount, FinalPixelCount, i, RunLength; char far *ScreenPtr; /* Well always draw top to bottom, to reduce the number of cases we have to handle, and to make lines between the same endpoints draw the same pixels */ if (YStart > YEnd) { Temp = YStart; YStart = YEnd; YEnd = Temp; Temp = XStart; XStart = XEnd; XEnd = Temp; } /* Point to the bitmap address first pixel to draw */ ScreenPtr = MK_FP(SCREEN_SEGMENT, YStart * SCREEN_WIDTH + XStart); /* Figure out whether were going left or right, and how far were going horizontally */ if ((XDelta = XEnd - XStart) < 0) { XAdvance = -1; XDelta = -XDelta; } else { XAdvance = 1; } /* Figure out how far were going vertically */ YDelta = YEnd - YStart; /* Special-case horizontal, vertical, and diagonal lines, for speed and to avoid nasty boundary conditions and division by 0 */ if (XDelta == 0) { /* Vertical line */ for (i=0; i<=YDelta; i++) { *ScreenPtr = Color; ScreenPtr += SCREEN_WIDTH; } return; } if (YDelta == 0) { /* Horizontal line */ for (i=0; i<=XDelta; i++) { *ScreenPtr = Color; ScreenPtr += XAdvance; } return; } if (XDelta == YDelta) { /* Diagonal line */ for (i=0; i<=XDelta; i++) { *ScreenPtr = Color; ScreenPtr += XAdvance + SCREEN_WIDTH; } return; } /* Determine whether the line is X or Y major, and handle accordingly */ if (XDelta >= YDelta) { /* X major line */ /* Minimum # of pixels in a run in this line */ WholeStep = XDelta / YDelta; /* Error term adjust each time Y steps by 1; used to tell when one extra pixel should be drawn as part of a run, to account for fractional steps along the X axis per 1-pixel steps along Y */ AdjUp = (XDelta % YDelta) * 2; /* Error term adjust when the error term turns over, used to factor out the X step made at that time */ AdjDown = YDelta * 2; /* Initial error term; reflects an initial step of 0.5 along the Y axis */ ErrorTerm = (XDelta % YDelta) - (YDelta * 2); /* The initial and last runs are partial, because Y advances only 0.5 for these runs, rather than 1. Divide one full run, plus the initial pixel, between the initial and last runs */ InitialPixelCount = (WholeStep / 2) + 1; FinalPixelCount = InitialPixelCount; /* If the basic run length is even and theres no fractional advance, we have one pixel that could go to either the initial or last partial run, which well arbitrarily allocate to the last run */ if ((AdjUp == 0) && ((WholeStep & 0x01) == 0)) { InitialPixelCount--; } /* If therere an odd number of pixels per run, we have 1 pixel that cant be allocated to either the initial or last partial run, so well add 0.5 to error term so this pixel will be handled by the normal full-run loop */ if ((WholeStep & 0x01) != 0) { ErrorTerm += YDelta; } /* Draw the first, partial run of pixels */ DrawHorizontalRun(&ScreenPtr, XAdvance, InitialPixelCount, Color); /* Draw all full runs */ for (i=0; i<(YDelta-1); i++) { RunLength = WholeStep; /* run is at least this long */ /* Advance the error term and add an extra pixel if the error term so indicates */ if ((ErrorTerm += AdjUp) > 0) { RunLength++; ErrorTerm -= AdjDown; /* reset the error term */ } /* Draw this scan lines run */ DrawHorizontalRun(&ScreenPtr, XAdvance, RunLength, Color); } /* Draw the final run of pixels */ DrawHorizontalRun(&ScreenPtr, XAdvance, FinalPixelCount, Color); return; } else { /* Y major line */ /* Minimum # of pixels in a run in this line */ WholeStep = YDelta / XDelta; /* Error term adjust each time X steps by 1; used to tell when 1 extra pixel should be drawn as part of a run, to account for fractional steps along the Y axis per 1-pixel steps along X */ AdjUp = (YDelta % XDelta) * 2; /* Error term adjust when the error term turns over, used to factor out the Y step made at that time */ AdjDown = XDelta * 2; /* Initial error term; reflects initial step of 0.5 along the X axis */ ErrorTerm = (YDelta % XDelta) - (XDelta * 2); /* The initial and last runs are partial, because X advances only 0.5 for these runs, rather than 1. Divide one full run, plus the initial pixel, between the initial and last runs */ InitialPixelCount = (WholeStep / 2) + 1; FinalPixelCount = InitialPixelCount; /* If the basic run length is even and theres no fractional advance, we have 1 pixel that could go to either the initial or last partial run, which well arbitrarily allocate to the last run */ if ((AdjUp == 0) && ((WholeStep & 0x01) == 0)) { InitialPixelCount--; } /* If there are an odd number of pixels per run, we have one pixel that cant be allocated to either the initial or last partial run, so well add 0.5 to the error term so this pixel will be handled by the normal full-run loop */ if ((WholeStep & 0x01) != 0) { ErrorTerm += XDelta; } /* Draw the first, partial run of pixels */ DrawVerticalRun(&ScreenPtr, XAdvance, InitialPixelCount, Color); /* Draw all full runs */ for (i=0; i<(XDelta-1); i++) { RunLength = WholeStep; /* run is at least this long */ /* Advance the error term and add an extra pixel if the error term so indicates */ if ((ErrorTerm += AdjUp) > 0) { RunLength++; ErrorTerm -= AdjDown; /* reset the error term */ } /* Draw this scan lines run */ DrawVerticalRun(&ScreenPtr, XAdvance, RunLength, Color); } /* Draw the final run of pixels */ DrawVerticalRun(&ScreenPtr, XAdvance, FinalPixelCount, Color); return; } } /* Draws a horizontal run of pixels, then advances the bitmap pointer to the first pixel of the next run. */ void DrawHorizontalRun(char far **ScreenPtr, int XAdvance, int RunLength, int Color) { int i; char far *WorkingScreenPtr = *ScreenPtr; for (i=0; i<RunLength; i++) { *WorkingScreenPtr = Color; WorkingScreenPtr += XAdvance; } /* Advance to the next scan line */ WorkingScreenPtr += SCREEN_WIDTH; *ScreenPtr = WorkingScreenPtr; } /* Draws a vertical run of pixels, then advances the bitmap pointer to the first pixel of the next run. */ void DrawVerticalRun(char far **ScreenPtr, int XAdvance, int RunLength, int Color) { int i; char far *WorkingScreenPtr = *ScreenPtr; for (i=0; i<RunLength; i++) { *WorkingScreenPtr = Color; WorkingScreenPtr += SCREEN_WIDTH; } /* Advance to the next column */ WorkingScreenPtr += XAdvance; *ScreenPtr = WorkingScreenPtr; }
Previous | Table of Contents | Next |