/**************************************************************************\ * * Copyright (c) 1998 Microsoft Corporation * * Module Name: * * One-pixel-wide solid anti-aliased lines * * Abstract: * * Draws anti-aliased solid-color lines which are one pixel wide. * Supports clipping against complex clipping regions. * * History: * * 3/31/1999 AMatos * Created it. * 08/17/1999 AGodfrey * Separated aliased from antialiased. * \**************************************************************************/ #include "precomp.hpp" #pragma optimize("a", on) // Antialiased lines are usually drawn using aarasterizer.cpp // rather than aaline.cpp. If aaline.cpp is to be used, define // AAONEPIXELLINE_SUPPORT #ifdef AAONEPIXELLINE_SUPPORT //------------------------------------------------------------------------ // Global array that stores all the different options of drawing functions. // If the order of the functions change, the offset constants must also // change. //------------------------------------------------------------------------ #define FUNC_X_MAJOR 0 #define FUNC_Y_MAJOR 1 #define FUNC_CLIP_OFFSET 2 typedef VOID (OnePixelLineDDAAntiAliased::*DDAFunc)(DpScanBuffer*); DDAFunc gDrawFunctions[] = { OnePixelLineDDAAntiAliased::DrawXMajor, OnePixelLineDDAAntiAliased::DrawYMajor, OnePixelLineDDAAntiAliased::DrawXMajorClip, OnePixelLineDDAAntiAliased::DrawYMajorClip, }; //------------------------------------------------------------------------ // Constants used for manipulating fixed point and doing all the bitwise // operations on the aliased and antialiased DDA. I know some of these // are already defined elsewhere, but I do it again here as it might be nice to // keep this independent of the rest of gdiplus. //------------------------------------------------------------------------ // Fixed point #define RealToFix GpRealToFix4 #define FBITS 4 #define FMASK 0xf #define FINVMASK 0xfffffff0 #define FSIZE 16 #define FHALF 8 #define FHALFMASK 7 // Antialiasing constants #define MAXALPHA 255 #define MAXERROR 0x08000000 #define TESTABOVE 0xf8000000 #define TESTBELOW 0x07ffffff #define MAXHALF 0x04000000 #define CONVERTALPHA 19 /**************************************************************************\ * * Function Description: * * Does all the DDA setup that is common to aliased and antialiased * lines. * * Arguments: * * [IN] point1 - end point * [IN] point2 - end point * [IN] drawLast - FALSE if the line is to be end-exclusive * Return Value: * * Returns TRUE if the drawing should continue, meaning the line * has non-zero length. * * Created: * * 03/31/1999 AMatos * \**************************************************************************/ BOOL OnePixelLineDDAAntiAliased::SetupCommon( GpPointF *point1, GpPointF *point2, BOOL drawLast ) { // Turn the points into fixed 28.4 INT x1 = RealToFix(point1->X); INT x2 = RealToFix(point2->X); REAL rDeltaX = point2->X - point1->X; REAL rDeltaY = point2->Y - point1->Y; if( rDeltaX == 0 && rDeltaY == 0 ) { return FALSE; } INT xDir = 1; if(rDeltaX < 0) { rDeltaX = -rDeltaX; xDir = -1; } INT y1 = RealToFix(point1->Y); INT y2 = RealToFix(point2->Y); INT yDir = 1; if( rDeltaY < 0) { rDeltaY = -rDeltaY; yDir = -1; } Flipped = FALSE; if( rDeltaY >= rDeltaX ) { // y-major InvDelta = 1.0F/rDeltaY; // Invert the endpoints if necessary if(yDir == -1) { INT tmp = y1; y1 = y2; y2 = tmp; tmp = x1; x1 = x2; x2 = tmp; xDir = -xDir; Flipped = TRUE; } // Determine the Slope Slope = xDir*rDeltaX*InvDelta; // Initialize the Start and End points IsXMajor = FALSE; MajorStart = y1; MajorEnd = y2; MinorStart = x1; MinorEnd = x2; MinorDir = xDir; // This will help us for the AntiAliased x-major case. SwitchFirstLast = 1; // Mark that we'll use the y-major functions. DrawFuncIndex = FUNC_Y_MAJOR; } else { // x-major InvDelta = 1.0F/rDeltaX; // Invert the endpoints if necessary if(xDir == -1) { INT tmp = x1; x1 = x2; x2 = tmp; tmp = y1; y1 = y2; y2 = tmp; yDir = -yDir; Flipped = TRUE; } Slope = yDir*rDeltaY*InvDelta; // Initialize the rest IsXMajor = TRUE; MajorStart = x1; MajorEnd = x2; MinorStart = y1; MinorEnd = y2; MinorDir = yDir; // This will help us for the AntiAliased x-major case. SwitchFirstLast = MinorDir; // Mark that we'll use the x-major functions. DrawFuncIndex = FUNC_X_MAJOR; } // Initialize the Deltas. In fixed point. DMajor = MajorEnd - MajorStart; DMinor = (MinorEnd - MinorStart)*MinorDir; // Mark if we're drawing end-exclusive IsEndExclusive = drawLast; return TRUE; } /**************************************************************************\ * * Function Description: * * Does the part of the DDA setup that is specific for anti-aliased lines. * * Arguments: * Return Value: * * Always returns TRUE. It must return a BOOL because it must have the * same signature as the aliased case. * * Created: * * 03/31/1999 AMatos * \**************************************************************************/ BOOL OnePixelLineDDAAntiAliased::SetupAntiAliased() { const REAL maxError = MAXERROR; // Find the integer major positions for the beginning and // the end of the line. INT major, minor; INT majorEnd, minorEnd; major = (MajorStart + FHALF) >> FBITS; majorEnd = (MajorEnd + FHALF) >> FBITS; // Check for the simple case of a one pixel long line if(majorEnd == major) { AlphaFirst = (MAXALPHA*(MajorEnd - MajorStart)*MinorDir) >> FBITS; MajorStart = major; MajorEnd = majorEnd; MinorStart = (MinorStart + FHALF) >> FBITS; return TRUE; } // Store the fraction of the first pixel covered due to // the start point. FracStart = (major << FBITS) - MajorStart; // Advance the minor coordinate to the integer major MinorStart += GpFloor(Slope*FracStart); // Calculate the length across the line in the minor direction INT halfWidth = RealToFix(LineLength*InvDelta) >> 1; // Make sure thar startX and endX don't end up being the // same pixel, which our code does not handle. Theoretically // this cannot happen when the width of the line is 1, but // let's make sure it doesn't happen because of some roundoff // Error. if( halfWidth < FHALF ) { halfWidth = FHALF; } INT endMinor = MinorEnd + MinorDir*halfWidth; // Calculate the Error up from the Slope. It needs to be that // way so that the Error up will work when the 0-1 interval // is mapped to the interval 0 to 0x8000000. See comments below. ErrorUp = GpFloor(Slope*maxError); ErrorDown = MinorDir*MAXERROR; // For a given aa one pixel wide line, there can be up to three pixels // baing painted across the line. We call these the first, middle and // last lines. So all variable with such prefixes refer to one // of these three. firstX and lastX are the positions of these lines. // In the x-major case, unlike the y-major, we might need to switch // who is the first and who is the second line depending on the // direction, so that the order that each line fills the scan // remains the same. That's why we multiply halfWidth by yDir. halfWidth *= SwitchFirstLast; MinorFirst = MinorStart - halfWidth; MinorLast = MinorStart + halfWidth; // Calculate the initial Error. The Error is mapped so that 1 is // taken to MAXERROR. So we find how mush we are into the // pixel in X, which is a number between 0 and 16 (n.4). We then // multiply this by MAXERROR and shift it from fized point. Finally we add // MAXHALF so that the 0-1 interval is mapped to 0 to MAXERROR // instead of from -MAXHALF and MAXHALF . const INT convError = MAXERROR >> FBITS; ErrorFirst = (MinorFirst - ((MinorFirst + FHALF) & FINVMASK))* convError + MAXHALF; ErrorLast = (MinorLast - ((MinorLast + FHALF) & FINVMASK))* convError + MAXHALF ; // Now calculate the alpha's for the first pixel. This is // done from the Error. Since the Error is between // 0 and MAXERROR-1, if we shift it back by 19 (CONVERTALPHA) // we have a number between 0 and 255. We the multiply by // yFrac which takes into account that the end of the line // also cuts the coverage down. At the end we convert from // 28.4. alphaFirst is the alpha of for the first pixel across the // aa line, alpha Mid is for the middle if there is one, and // AlphaLast is for the last pixel. FracStart = FracStart + FHALF; // Convert from 28.4 rounding MinorFirst = (MinorFirst + FHALF) >> FBITS; MinorLast = (MinorLast + FHALF) >> FBITS; // Store the fraction for the last pixel FracEnd = MajorEnd - (majorEnd << FBITS) + FHALF; // Store the initial values in integer coordinates MajorStart = major; MajorEnd = majorEnd; MinorStart = MinorFirst; MinorEnd = (endMinor + FHALF) >> FBITS; // Now do some initializations specific for the x-major and // y-major cases. These can't be done in the drawing routine // because those are reused during clipping. if(!IsXMajor) { // Calculate the coverage values at the initial pixel. AlphaFirst = ((MAXALPHA - (ErrorFirst >> CONVERTALPHA))* FracStart) >> FBITS; AlphaLast = ((ErrorLast >> CONVERTALPHA)*FracStart) >> FBITS; AlphaMid = (MAXALPHA*FracStart) >> FBITS; } else { // Depending if we are going up or down, the alpha is calculated // a different way from the coverage. In each case we want to // estimate the coverage as the area from the current position to // the end of the pixel, but which end varies. This is stored // in the following biases. We don't have to do this for the // y-major line because of the switch between first and last line // explained above. AlphaBiasLast = ((1 - MinorDir) >> 1)*TESTBELOW; AlphaBiasFirst = ((1 + MinorDir) >> 1)*TESTBELOW; AlphaFirst = ((AlphaBiasFirst - MinorDir*ErrorFirst)*FracStart) >> FBITS; AlphaLast = ((AlphaBiasLast + MinorDir*ErrorLast)*FracStart) >> FBITS; // If there is a middle line on the first X value, take xFrac into // account. Otherwise, the middle line's alpha is always MAXALPHA. if(MinorDir*(MinorLast - MinorFirst) < 2) { AlphaMid = MAXALPHA; } else { AlphaMid = MAXALPHA*FracStart >> FBITS; } // Both the first and last DDAs start with the same // major positions, given by the first pixel. MajorFirst = MajorLast = MajorStart; } return TRUE; } /**************************************************************************\ * * Function Description: * * Draws a y major anti-aliased line. Does not support clipping, it assumes that * it is completely inside any clipping area. * * Arguments: * * [IN] DpScanBuffer - The scan buffer for accessing the surface. * Return Value: * * * Created: * * 03/31/1999 AMatos * \**************************************************************************/ VOID OnePixelLineDDAAntiAliased::DrawYMajor( DpScanBuffer *scan ) { ARGB *buffer; // Treat the special case where the line is just // one pixel long. if( MajorEnd == MajorStart) { buffer = scan->NextBuffer( MinorStart, MajorStart, 1); *buffer = GpColor::PremultiplyWithCoverage(Color, static_cast(AlphaFirst)); return; } // Get the number of pixels not counting the last one. // Which requires special endpoint treatment. INT numPixels = MajorEnd - MajorStart; BOOL endDone = FALSE; // There can be two or three pixels across the line INT pixelWidth = MinorLast - MinorFirst + 1; while(numPixels) { numPixels--; last_pixel: // Get the scanline buffer buffer buffer = scan->NextBuffer(MinorFirst, MajorStart, pixelWidth); // Write the value of the first DDA *buffer++ = GpColor::PremultiplyWithCoverage(Color, static_cast(AlphaFirst)); // If there is a middle line, write its value. if(pixelWidth > 2) { *buffer++ = GpColor::PremultiplyWithCoverage(Color, static_cast(AlphaMid)); } // Write the value of the last (2nd or 3rd) DDA *buffer++ = GpColor::PremultiplyWithCoverage(Color, static_cast(AlphaLast)); // Update the errors of both DDAs ErrorFirst+= ErrorUp; ErrorLast += ErrorUp; MajorStart++; if(ErrorFirst & TESTABOVE) { ErrorFirst -= ErrorDown; MinorFirst += MinorDir; } if(ErrorLast & TESTABOVE) { ErrorLast -= ErrorDown; MinorLast += MinorDir; } // Calculate the new alphas for the next scan, and // the new line width. AlphaFirst = MAXALPHA - (ErrorFirst >> CONVERTALPHA); AlphaLast = (ErrorLast >> CONVERTALPHA); AlphaMid = MAXALPHA; pixelWidth = MinorLast - MinorFirst + 1; } // The last scan requires special treatment since its coverage // must be multiplied my the stored end coverage. So so this // multiplication and go back to the body of the loop above // to draw the last scan. if(!endDone) { AlphaFirst = (AlphaFirst*FracEnd) >> FBITS; AlphaLast = (AlphaLast*FracEnd) >> FBITS; AlphaMid = (AlphaMid*FracEnd) >> FBITS; endDone = TRUE; goto last_pixel; } } /**************************************************************************\ * * Function Description: * * Draws a x major anti-aliased line. Does not support clipping, it assumes that * it is completely inside any clipping area. * * Arguments: * * [IN] DpScanBuffer - The scan buffer for accessing the surface. * Return Value: * * * Created: * * 03/31/1999 AMatos * \**************************************************************************/ VOID OnePixelLineDDAAntiAliased::DrawXMajor( DpScanBuffer *scan ) { ARGB *buffer; INT maxWidth = scan->GetSurface()->Width; // Treat the special case where the line is just // one pixel long. if( MajorEnd == MajorStart) { buffer = scan->NextBuffer( MajorStart, MinorStart, 1); *buffer = GpColor::PremultiplyWithCoverage(Color, static_cast(AlphaFirst)); return; } // For an x-major one-pixel wide line, there can be up to // three different scans being painted for the same x // position. But in our case we can't draw to these all at // the same time since some surfaces can only be accessed // one scan at a time. So the algorithm used here does all the // drawing to one scan at each time. But on the first scan, only // the first line should be drawn, on the second one both the // first and middle (if there is a middle) and only then all // the lines. So correct the Error of the last line so that // it'll only be drawn when we are at the second or third scan line. // Also correct the alpha since it'll also be crecremented for // each scan line. ErrorLast += MinorDir*(MinorLast - MinorFirst)*ErrorDown; AlphaLast += (MinorLast - MinorFirst)*ErrorDown; // Get the pointer to the buffer buffer = scan->NextBuffer(MajorLast, MinorStart, maxWidth); INT width = 0; INT alpha; INT middleMajor; while(MajorLast <= MajorEnd) { // Fill the scan with the portion corresponding to the // last line, which shoudl comes first on the scan. This is // why we use the class member SwitchFirstLast, so we can decide // based on the line direction which DDA will be the first and last // so that the last one (paradoxically) always comes first on the // scan. Keep doing it untill the last line chages scan. Check for // the end to multiply by the last pixel's coverage. while(!(ErrorLast & TESTABOVE)) { if(MajorLast == MajorEnd) { AlphaLast = (AlphaLast*FracEnd) >> FBITS; // Increment the error to correct for the // decrementing below, since we didn't leave the // loop because the error became above 0. ErrorLast += ErrorDown; } *buffer++ = GpColor::PremultiplyWithCoverage(Color, static_cast(AlphaLast >> CONVERTALPHA)); ErrorLast += ErrorUp; AlphaLast = AlphaBiasLast + MinorDir*ErrorLast; width++; MajorLast++; } // We changed scans on the last DDA, so update the errors ErrorLast -= ErrorDown; AlphaLast -= MinorDir*ErrorDown; // Fill in the middle part if there is one middleMajor = MajorLast; while(middleMajor < MajorFirst) { if( middleMajor == MajorEnd) { AlphaMid = (AlphaMid*FracEnd) >> FBITS; } *buffer++ = GpColor::PremultiplyWithCoverage(Color, static_cast(AlphaMid)); AlphaMid = MAXALPHA; width++; middleMajor++; } // Fill the scan with the portion corresponding to the // first line, which comes last. Keep doing it untill the // last line chages scan. while(!(ErrorFirst & TESTABOVE)) { if(MajorFirst == MajorEnd) { AlphaFirst = (AlphaFirst*FracEnd) >> FBITS; // Since we can have at most three more scans // increment ErrorFirst so that we never go in here again ErrorFirst += 4*ErrorDown; } *buffer++ = GpColor::PremultiplyWithCoverage( Color, static_cast(AlphaFirst >> CONVERTALPHA)); ErrorFirst += ErrorUp; AlphaFirst = AlphaBiasFirst - MinorDir*ErrorFirst; width++; MajorFirst++; } // Update the errors on the first scan ErrorFirst -= ErrorDown; AlphaFirst += MinorDir*ErrorDown; // Write the buffer and update the minor variables scan->UpdateWidth(width); MinorStart += MinorDir; if (MajorLast <= MajorEnd) { buffer = scan->NextBuffer(MajorLast, MinorStart, maxWidth); } width = 0; } scan->UpdateWidth(width); } /**************************************************************************\ * * Function Description: * * Draws a y major line taking clipping into account. It uses the member * variables MajorIn, MajorOut, MinorIn, MinorOut of the class as the * clip rectangle. It advances untill the line is in the clip rectangle and * draws untill it gets out or the end point is reached. In the first case, * it leaves the DDA in a state so that it can be called again with another * clipping rectangle. * * Arguments: * * [IN] DpScanBuffer - The scan buffer for accessing the surface. * Return Value: * * * Created: * * 03/31/1999 AMatos * \**************************************************************************/ VOID OnePixelLineDDAAntiAliased::DrawYMajorClip( DpScanBuffer *scan ) { ARGB *buffer; // Treat the special case where the line is just // one pixel long. if( MajorEnd == MajorStart) { // Check if the point is inside the rectangle if((MajorStart >= MajorIn) && (MajorStart <= MajorOut) && ((MinorStart - MinorIn)*MinorDir >= 0) && ((MinorOut - MinorStart)*MinorDir >= 0)) { buffer = scan->NextBuffer( MinorStart, MajorStart, 1); *buffer = GpColor::PremultiplyWithCoverage(Color, static_cast(AlphaFirst)); } return; } // Align the major start coordinate with the edge of the // cliprectangle INT numScans = MajorIn - MajorStart; while(numScans > 0) { ErrorFirst+= ErrorUp; ErrorLast += ErrorUp; MajorStart++; numScans--; if(ErrorFirst & MAXERROR) { ErrorFirst -= ErrorDown; MinorFirst += MinorDir; } if(ErrorLast & MAXERROR) { ErrorLast -= ErrorDown; MinorLast += MinorDir; } // Calculate the new alphas for the next line, and // the width. AlphaFirst = MAXALPHA - (ErrorFirst >> CONVERTALPHA); AlphaLast = (ErrorLast >> CONVERTALPHA); AlphaMid = MAXALPHA; } // Save the end values INT saveMajor2 = MajorEnd; INT saveFracEnd = FracEnd; // If the end major coordinate is outside of the rectangle, // mark that the DDA should stop at the edge if(MajorEnd > MajorOut) { MajorEnd = MajorOut; FracEnd = FSIZE; } // Number of pixels to draw, not counting the last INT numPixels = MajorEnd - MajorStart; BOOL endDone = FALSE; // There can be two or three pixels across the line INT pixelWidth = MinorLast - MinorFirst + 1; // Do the DDA loop. Two loops are implemented here. The // first one is used in the case that the x coordinate of // the rectangle is close enough to the constant-y edges // of the clip rectangle. In this case, it's a pain, since // we have to check each pixel that we are writing if it's // not outside. Thus, as soon as we notice that we are // far from the edges we go to the other loop that doesn't // check all that. All it checks is if it got close enough // to the other edge, in which case it comes back to this // loop, using the label last_part. firstOutDist, firstInDist, // lastOutDist and lastInDist keeps track of the number of // pixels between the first and last DDAs and the In and // Out y-constant edges of the rectangle. INT firstOutDist = (MinorOut - MinorFirst)*MinorDir; last_part: INT firstInDist = (MinorFirst - MinorIn)*MinorDir; INT lastInDist = (MinorLast - MinorIn)*MinorDir; INT lastOutDist = (MinorOut - MinorLast)*MinorDir; while(numPixels > 0) { numPixels--; last_pixel: // Check if it's ok to write the first pixel if(firstInDist >= 0 && firstOutDist >= 0) { buffer = scan->NextBuffer(MinorFirst, MajorStart, 1); *buffer++ = GpColor::PremultiplyWithCoverage(Color, static_cast(AlphaFirst)); } else { // If the first DDA is out, and we are going in the // positive direction, then the whole line is out and // we are done if(firstOutDist < 0 && MinorDir == 1) { goto end; } } // If the line has 3 pixels across if(pixelWidth > 2) { // Check if it's ok to write the second pixel if(firstInDist >= -MinorDir && firstOutDist >= MinorDir) { buffer = scan->NextBuffer(MinorFirst+1, MajorStart, 1); *buffer++ = GpColor::PremultiplyWithCoverage(Color, static_cast(AlphaMid)); } } // Now check if it's ok to write the last one if(lastInDist >= 0 && lastOutDist >= 0) { buffer = scan->NextBuffer(MinorLast, MajorStart, 1); *buffer++ = GpColor::PremultiplyWithCoverage(Color, static_cast(AlphaLast)); } else { // If the first DDA is out, and we are going in the // negative direction, then the whole line is out and // we are done if(lastOutDist < 0 && MinorDir == -1) { goto end; } } // Update the errors ErrorFirst+= ErrorUp; ErrorLast += ErrorUp; MajorStart++; if(ErrorFirst & TESTABOVE) { ErrorFirst -= ErrorDown; MinorFirst += MinorDir; firstInDist++; firstOutDist--; } if(ErrorLast & TESTABOVE) { ErrorLast -= ErrorDown; MinorLast += MinorDir; lastInDist++; lastOutDist--; } // Calculate the new alphas for the next line, and // the width. AlphaFirst = MAXALPHA - (ErrorFirst >> CONVERTALPHA); AlphaLast = (ErrorLast >> CONVERTALPHA); AlphaMid = MAXALPHA; pixelWidth = MinorLast - MinorFirst + 1; // Check to see if we can 'upgrade' to the next loop if(firstInDist >= 3 && firstOutDist >= 3) { break; } } while(numPixels > 0) { numPixels--; // Get the scanline buffer buffer buffer = scan->NextBuffer(MinorFirst, MajorStart, pixelWidth); // Write the value of the first DDA *buffer++ = GpColor::PremultiplyWithCoverage(Color, static_cast(AlphaFirst)); // If there is a middle line, write its value. if(pixelWidth > 2) { *buffer++ = GpColor::PremultiplyWithCoverage(Color, static_cast(AlphaMid)); } // Write the value of the last (2nd or 3rd) DDA *buffer++ = GpColor::PremultiplyWithCoverage(Color, static_cast(AlphaLast)); // Update the DDA ErrorFirst+= ErrorUp; ErrorLast += ErrorUp; MajorStart++; if(ErrorFirst & TESTABOVE) { ErrorFirst -= ErrorDown; MinorFirst += MinorDir; firstOutDist--; } if(ErrorLast & TESTABOVE) { ErrorLast -= ErrorDown; MinorLast += MinorDir; } // Calculate the new alphas for the next line, and // the width. AlphaFirst = MAXALPHA - (ErrorFirst >> CONVERTALPHA); AlphaLast = (ErrorLast >> CONVERTALPHA); AlphaMid = MAXALPHA; pixelWidth = MinorLast - MinorFirst + 1; // Now check if it's time to go to the other loop // because we are too close to the out edge if(firstOutDist < 3) { goto last_part; } } // Now if we haven't gotten here yet, do the last pixel // and go once more through the loop. if(!endDone) { AlphaFirst = (AlphaFirst*FracEnd) >> FBITS; AlphaLast = (AlphaLast*FracEnd) >> FBITS; AlphaMid = (AlphaMid*FracEnd) >> FBITS; endDone = TRUE; goto last_pixel; } end: MajorEnd = saveMajor2; FracEnd = saveFracEnd; } /**************************************************************************\ * * Function Description: * * Draws a x major line taking clipping into account. It uses the member * variables MajorIn, MajorOut, MinorIn, MinorOut of the class as the * clip rectangle. It advances untill the line is in the clip rectangle and * draws untill it gets out or the end point is reached. In the first case, * it leaves the DDA in a state so that it can be called again with another * clipping rectangle. * * Arguments: * * [IN] DpScanBuffer - The scan buffer for accessing the surface. * Return Value: * * * Created: * * 03/31/1999 AMatos * \**************************************************************************/ VOID OnePixelLineDDAAntiAliased::DrawXMajorClip( DpScanBuffer *scan ) { ARGB *buffer; INT maxWidth = scan->GetSurface()->Width; // Treat the special case where the line is just // one pixel long. if( MajorEnd == MajorStart) { // Check to see if the point is inside the rectangle if((MajorStart >= MajorIn) && (MajorStart <= MajorOut) && ((MinorStart - MinorIn)*MinorDir >= 0) && ((MinorOut - MinorStart)*MinorDir >= 0)) { buffer = scan->NextBuffer( MajorStart, MinorStart, 1); *buffer = GpColor::PremultiplyWithCoverage(Color, static_cast(AlphaFirst)); } return; } // Save the real end and its fraction INT saveMajor2 = MajorEnd; INT saveFracEnd = FracEnd; // If the end major coordinate is out, mark that we must stop // before. Also make the fraction be one, since the last // one drawn now should not have a fraction if(MajorOut < MajorEnd) { MajorEnd = MajorOut; FracEnd = FSIZE; } // Advance until the last DDA is in the right scan line and // is aligned with the In y-constant edge of the rectnagle INT numScans = (MinorIn - MinorLast)*MinorDir; while((numScans > 0 && MajorLast <= MajorEnd) || MajorLast < MajorIn) { ErrorLast += ErrorUp; if(ErrorLast & TESTABOVE) { ErrorLast -= ErrorDown; MinorLast += MinorDir; numScans--; } MajorLast++; // Calculate the alpha for the current pixel AlphaLast = AlphaBiasLast + MinorDir*ErrorLast; } // Do the same for the first DDA numScans = (MinorIn - MinorFirst)*MinorDir; while((numScans > 0 && MajorFirst <= MajorEnd) || MajorFirst < MajorIn) { ErrorFirst += ErrorUp; if(ErrorFirst & TESTABOVE) { ErrorFirst -= ErrorDown; MinorFirst += MinorDir; numScans--; } MajorFirst++; AlphaFirst = AlphaBiasFirst - MinorDir*ErrorFirst; } // If there is no middle line in the first x-position, // make the middle alpha full, since the start coverage // won't apply if((MinorLast - MinorFirst) < 2) { AlphaMid = MAXALPHA; } MinorStart = MinorFirst; // The same way that was done in the non-clipping case, // mock arround with the error so we won't draw the // last DDA until the first DDA is in the same scan line, // or has caught up. We need to adjust the alpha and minor // positions for this DDA to, so that when we start // drawing they will have the right value ErrorLast += MinorDir*(MinorLast - MinorFirst)*ErrorDown; AlphaLast += (MinorLast - MinorFirst)*ErrorDown; MinorLast -= (MinorLast - MinorFirst); // Get the pointer to the buffer buffer = scan->NextBuffer(MajorLast, MinorStart, maxWidth); INT width = 0; INT alpha; INT middleMajor; while(MajorLast <= MajorEnd) { // Fill the scan with the portion corresponding to the // last line, which should come first. Keep doing it // until the last line changes scan. while(!(ErrorLast & TESTABOVE)) { // Check if we passed or are at the last pixel if(MajorLast >= MajorEnd) { if(MajorLast == MajorEnd) { // If we are at, just update the alpha AlphaLast = (AlphaLast*FracEnd) >> FBITS; } else { // If we passed, we don't want to draw anymore. // Just adjust the error, alpha and minor so they // will be right when they are corrected after this // loop for the next scan ErrorLast += ErrorDown; AlphaLast -= MinorDir*ErrorDown; MinorLast -= MinorDir; break; } } *buffer++ = GpColor::PremultiplyWithCoverage(Color, static_cast(AlphaLast >> CONVERTALPHA)); ErrorLast += ErrorUp; AlphaLast = AlphaBiasLast + MinorDir*ErrorLast; width++; MajorLast++; } // Correct the values for the next scan ErrorLast -= ErrorDown; AlphaLast -= MinorDir*ErrorDown; MinorLast += MinorDir; // Fill in the middle part. middleMajor = MajorLast; while(middleMajor < MajorFirst) { if( middleMajor == MajorEnd) { AlphaMid = (AlphaMid*FracEnd) >> FBITS; } *buffer++ = GpColor::PremultiplyWithCoverage(Color, static_cast(AlphaMid)); AlphaMid = MAXALPHA; width++; middleMajor++; } // Fill the scan with the portion corresponding to the // first line, which should come first. Keep doing it // until the last line changes scan. while(!(ErrorFirst & TESTABOVE)) { // Check for the end pixel, just like we // did for the last DDA if(MajorFirst >= MajorEnd) { if(MajorFirst == MajorEnd) { AlphaFirst = (AlphaFirst*FracEnd) >> FBITS; } else { ErrorFirst += ErrorDown; AlphaFirst -= MinorDir*ErrorDown; MinorFirst -= MinorDir; break; } } *buffer++ = GpColor::PremultiplyWithCoverage( Color, static_cast(AlphaFirst >> CONVERTALPHA)); ErrorFirst += ErrorUp; AlphaFirst = AlphaBiasFirst - MinorDir*ErrorFirst; width++; MajorFirst++; } // Correct the values for the next scan ErrorFirst -= ErrorDown; AlphaFirst += MinorDir*ErrorDown; MinorFirst += MinorDir; scan->UpdateWidth(width); // Check to see if we have come to the end of the rectangle // through the minor coordinate crossing the Out edge // in the x-constant direction if(MinorStart == MinorOut) { MinorStart += MinorDir; break; } // Update the minor coordinate and get the next buffer // if we aren't done yet. MinorStart += MinorDir; if (MajorLast <= MajorEnd) { buffer = scan->NextBuffer(MajorLast, MinorStart, maxWidth); } width = 0; } scan->UpdateWidth(width); // Restore the old values MajorEnd = saveMajor2; FracEnd = saveFracEnd; } //-------------------------------------------------------------------- // Auxiliary functions //-------------------------------------------------------------------- /**************************************************************************\ * * Function Description: * * Clips the line against a rectangle. It assumes that the line endpoints * are stored in the class in floating point format. This sets an * order in which this function can be called. It must be after the * SetupCommon function and before the specific setups for antialiasing * and aliasing. This is a pain, but it's better than requirering on of * these to have to know about clipping. The clipping here is done by * using the Slope and InvSlope members of the class to advance the * endpoints to the rectangle edges. Thus the function also assumes that * Slope and InvSlope have been calculated. * * Arguments: * * [IN] clipRect - The rectangle to clip against * Return Value: * * * Created: * * 03/31/1999 AMatos * \**************************************************************************/ BOOL OnePixelLineDDAAntiAliased::ClipRectangle( const GpRect* clipRect ) { INT clipBottom, clipTop, clipLeft, clipRight; // Set the major and minor edges ef the clipping // region, converting to fixed point 28.4. Note that // we don't convert to the pixel center, but to a // that goes all the way up to the pixel edges. This // makes a difference for antialiasing. We don't go all // the way to the edge since some rounding rules could // endup lihgting the next pixel outside of the clipping // area. That's why we add/subtract 7 instead of 8. // The right and bottom are exclusive. INT majorMin = (clipRect->GetLeft() << FBITS) - FHALFMASK; INT majorMax = ((clipRect->GetRight() - 1) << FBITS) + FHALFMASK; INT minorMax = ((clipRect->GetBottom() - 1) << FBITS) + FHALFMASK; INT minorMin = (clipRect->GetTop() << FBITS) - FHALFMASK; if(!IsXMajor) { INT tmp; tmp = majorMin; majorMin = minorMin; minorMin = tmp; tmp = majorMax; majorMax = minorMax; minorMax = tmp; } // First clip in the major coordinate BOOL minOut, maxOut; minOut = MajorStart < majorMin; maxOut = MajorEnd > majorMax; if( minOut || maxOut ) { if(MajorStart > majorMax || MajorEnd < majorMin) { return FALSE; } if(minOut) { MinorStart += GpFloor((majorMin - MajorStart)*Slope); MajorStart = majorMin; } if(maxOut) { MinorEnd += GpFloor((majorMax - MajorEnd)*Slope); MajorEnd = majorMax; // If we clipped the last point, we don't need to be IsEndExclusive // anymore, as the last point now is not the line's last // point but some in the middle. IsEndExclusive = FALSE; } } // Now clip the minor coordinate INT *pMajor1, *pMinor1, *pMajor2, *pMinor2; if(MinorDir == 1) { pMajor1 = &MajorStart; pMajor2 = &MajorEnd; pMinor1 = &MinorStart; pMinor2 = &MinorEnd; } else { pMajor1 = &MajorEnd; pMajor2 = &MajorStart; pMinor1 = &MinorEnd; pMinor2 = &MinorStart; } minOut = *pMinor1 < minorMin; maxOut = *pMinor2 > minorMax; if(minOut || maxOut) { if(*pMinor1 > minorMax || *pMinor2 < minorMin) { return FALSE; } if(minOut) { *pMajor1 += GpFloor((minorMin - *pMinor1)*InvSlope); *pMinor1 = minorMin; } if(maxOut) { *pMajor2 += GpFloor((minorMax - *pMinor2)*InvSlope); *pMinor2 = minorMax; // If we clipped the last point, we don't need to be endExclusive // anymore, as the last point now is not the line's last // point but some in the middle. IsEndExclusive = FALSE; } } return(TRUE); } /**************************************************************************\ * * Function Description: * * Draws a one-pixe-wide line with a solid color. Calls on the * OnePixelLineDDAAntiAliased class to do the actual drawing. * * Arguments: * * [IN] scan - The DpScanBuffer to access the drawing surface * [IN] clipRect - A single rectangle that includes all the clipping * region. If there is no clipping, should be set to NULL. * [IN] clipRegionIn - A complex clipping region. If the clipping region is * simple, this should be NULL, and clipRect will be used. * [IN] point1 - line end point * [IN] point2 - line end point * [IN] inColor - the solid color * [IN] drawLast - FALSE if the line is to be end-exclusive. * [IN] antiAliased - TRUE if the line should be antialiased. * * Return Value: * * GpStatus - Ok or failure status * * Created: * * 03/31/1999 AMatos * \**************************************************************************/ GpStatus DrawSolidLineOnePixelAntiAliased( DpScanBuffer *scan, const GpRect *clipRect, const DpClipRegion* clipRegionIn, GpPointF *point1, GpPointF *point2, ARGB inColor, BOOL drawLast ) { // Take out the const for now because the Enumeration method // is not const. DpClipRegion *clipRegion = const_cast(clipRegionIn); // Setup the common part of the DDA OnePixelLineDDAAntiAliased dda; if(!dda.SetupCommon(point1, point2, drawLast)) { return Ok; } // Calculate the length of the line. Since we only use // it to determine the width, it shouldn't matter that // we convert the deltas from 28.4 before the multiplication. INT d1 = dda.DMajor >> FBITS; INT d2 = dda.DMinor >> FBITS; dda.LineLength = (REAL)sqrt((double)(d1*d1 + d2*d2)); // Store the color, not premultiplied dda.Color = inColor; // Now handle the different clipping cases if(!clipRect) { // This is easy, there is no clipping so just draw. if(!dda.SetupAntiAliased()) { return Ok; } (dda.*(gDrawFunctions[dda.DrawFuncIndex]))(scan); return Ok; } else { // The inverse of the Slope might be needed. // Can't use the inverse slope if the slope is zero. if(dda.Slope==0.0F) { dda.InvSlope=0.0F; } else { dda.InvSlope = (1.0F/dda.Slope); } // First of all clip against the bounding rectangle if(!dda.ClipRectangle(clipRect)) { return Ok; } // Do the specific setup if(!dda.SetupAntiAliased()) { return Ok; } // For each clip rectangle we store it's limits in // an array of four elements. We then index this array using // the variables below which depend on the slope and // direction of the line in the following way: majorIn is edge crossed // to go into the rect in the major direction, majorOut is the edge // crossed to go out of the rect in the major direction, and so on. // The same for xIn, xOut, yIn, yOut. INT majorIn, majorOut, minorIn, minorOut; INT xIn, xOut, yIn, yOut; // Direction to enumerate the rectangles which depends on the // line DpClipRegion::Direction enumDirection; INT clipBounds[4]; // We store all our info in terms of major and minor // direction, but to deal with cliping rectangles we // need to know them in terms of x and y, so calculate // xDir, yDir, the advance slope. REAL xAdvanceRate; INT xDir, yDir; INT yEndLine; // If the line crosses a span completely, (xStart, yStart) // is the position where it enters the span and (xEnd, yEnd) // is the position that it leaves. If it starts inside the // span, then (xStart, yStart) is the start point REAL yStart, xStart, xEnd, yEnd; if(dda.IsXMajor) { // Calculate the in-out indices majorIn = xIn = 0; majorOut = xOut = 2; if(dda.MinorDir == 1) { minorIn = 1; minorOut = 3; enumDirection = DpClipRegion::TopLeftToBottomRight; } else { minorIn = 3; minorOut = 1; enumDirection = DpClipRegion::BottomLeftToTopRight; } yIn = minorIn; yOut = minorOut; // Make (xStart, yStart) be the initial point yStart = (REAL)dda.MinorStart; xStart = (REAL)dda.MajorStart; xAdvanceRate = dda.InvSlope; xDir = 1; yDir = dda.MinorDir; yEndLine = dda.MinorEnd; } else { majorIn = yIn = 1; majorOut = yOut = 3; if(dda.MinorDir == 1) { minorIn = 0; minorOut = 2; enumDirection = DpClipRegion::TopLeftToBottomRight; } else { minorIn = 2; minorOut = 0; enumDirection = DpClipRegion::TopRightToBottomLeft; } xIn = minorIn; xOut = minorOut; // Make (xStart, yStart) be the initial point yStart = (REAL)dda.MajorStart; xStart = (REAL)dda.MinorStart; xAdvanceRate = dda.Slope; xDir = dda.MinorDir; yDir = 1; yEndLine = dda.MajorEnd; } // Update the drawing function to the correct // slipping version dda.DrawFuncIndex += FUNC_CLIP_OFFSET; if(!clipRegion) { // In this case there is only a single rect, so just // draw clipped to that // Store the rectangle in an array so we can atribute the // right values to the MajorIn, majorOut, etc... variables. // Remember that bottom and right are exclusive. clipBounds[0] = clipRect->GetLeft(); clipBounds[1] = clipRect->GetTop(); clipBounds[2] = clipRect->GetRight() - 1; clipBounds[3] = clipRect->GetBottom() - 1; dda.MajorIn = clipBounds[majorIn]; dda.MajorOut = clipBounds[majorOut]; dda.MinorIn = clipBounds[minorIn]; dda.MinorOut = clipBounds[minorOut]; (dda.*(gDrawFunctions[dda.DrawFuncIndex]))(scan); return Ok; } else { BOOL agregating = FALSE; INT agregateBounds[4]; // We have a complex clipping region. So what we'll do // is clip against each individual rectangle in the // cliping region. clipRegion->StartEnumeration(GpFloor(yStart), enumDirection); GpRect rect; // Get the first rectangle. INT numRects = 1; clipRegion->Enumerate(&rect, numRects); clipBounds[0] = rect.GetLeft(); clipBounds[1] = rect.GetTop(); clipBounds[2] = rect.GetRight() - 1; clipBounds[3] = rect.GetBottom() - 1; // Store the y position into the span INT currSpanYMin = clipBounds[yIn]; // We need some special treatment for the case where the // line is horizontal, since is this case it's not going // to cross different spans. And it it's not in the current // span, it's totally clipped out. if(dda.IsXMajor && dda.ErrorUp == 0) { if(yStart >= clipBounds[1] && yStart <= clipBounds[3]) { xStart = (REAL)dda.MajorStart; xEnd = (REAL)dda.MajorEnd; } else { return Ok; } } else { if(yStart < clipBounds[1] || yStart > clipBounds[3]) { xStart = xStart + (clipBounds[yIn] - yStart)*xAdvanceRate; yStart = (REAL)clipBounds[yIn]; } xEnd = xStart + (clipBounds[yOut] - yStart)*xAdvanceRate; } yEnd = (REAL)clipBounds[yOut]; while(1) { // Get to the first rectangle on the span that crosses the // line while((xStart - clipBounds[xOut])*xDir > 0) { numRects = 1; clipRegion->Enumerate(&rect, numRects); clipBounds[0] = rect.GetLeft(); clipBounds[1] = rect.GetTop(); clipBounds[2] = rect.GetRight() - 1; clipBounds[3] = rect.GetBottom() - 1; if(numRects != 1) { goto draw_agregated; } if(clipBounds[yIn] != currSpanYMin) { goto process_next_span; } } // Draw on all the rectangles that intersect the // line if((xStart - clipBounds[xIn])*xDir > 0 && (clipBounds[xOut] - xEnd)*xDir > 0) { if(agregating) { if((clipBounds[xIn] - agregateBounds[xIn])*xDir < 0) { agregateBounds[xIn] = clipBounds[xIn]; } if((clipBounds[xOut] - agregateBounds[xOut])*xDir > 0) { agregateBounds[xOut] = clipBounds[xOut]; } agregateBounds[yOut] = clipBounds[yOut]; } else { agregateBounds[0] = clipBounds[0]; agregateBounds[1] = clipBounds[1]; agregateBounds[2] = clipBounds[2]; agregateBounds[3] = clipBounds[3]; agregating = TRUE; } } else { if(agregating) { dda.MajorIn = agregateBounds[majorIn]; dda.MajorOut = agregateBounds[majorOut]; dda.MinorIn = agregateBounds[minorIn]; dda.MinorOut = agregateBounds[minorOut]; (dda.*(gDrawFunctions[dda.DrawFuncIndex]))(scan); agregating = FALSE; } while((xEnd - clipBounds[xIn])*xDir > 0) { dda.MajorIn = clipBounds[majorIn]; dda.MajorOut = clipBounds[majorOut]; dda.MinorIn = clipBounds[minorIn]; dda.MinorOut = clipBounds[minorOut]; (dda.*(gDrawFunctions[dda.DrawFuncIndex]))(scan); if(dda.MajorStart > dda.MajorEnd) { return Ok; } numRects = 1; clipRegion->Enumerate(&rect, numRects); clipBounds[0] = rect.GetLeft(); clipBounds[1] = rect.GetTop(); clipBounds[2] = rect.GetRight() - 1; clipBounds[3] = rect.GetBottom() - 1; if(numRects != 1) { goto draw_agregated; } if(clipBounds[yIn] != currSpanYMin) { goto process_next_span; } } } // Get to the next span while(clipBounds[yIn] == currSpanYMin) { numRects = 1; clipRegion->Enumerate(&rect, numRects); clipBounds[0] = rect.GetLeft(); clipBounds[1] = rect.GetTop(); clipBounds[2] = rect.GetRight() - 1; clipBounds[3] = rect.GetBottom() - 1; if(numRects != 1) { goto draw_agregated; } } process_next_span: if((clipBounds[yIn] - yEndLine)*yDir > 0) { // We are done. goto draw_agregated; } if((clipBounds[yIn] - yEnd)*yDir == 1) { xStart = xEnd; } else { if(agregating) { dda.MajorIn = agregateBounds[majorIn]; dda.MajorOut = agregateBounds[majorOut]; dda.MinorIn = agregateBounds[minorIn]; dda.MinorOut = agregateBounds[minorOut]; (dda.*(gDrawFunctions[dda.DrawFuncIndex]))(scan); if(dda.MajorStart > dda.MajorEnd) { return Ok; } agregating = FALSE; } xStart = xStart + (clipBounds[yIn] - yStart)*xAdvanceRate; } yStart = (REAL)clipBounds[yIn]; xEnd = xStart + (clipBounds[yOut] - yStart)*xAdvanceRate; yEnd = (REAL)clipBounds[yOut]; currSpanYMin = GpFloor(yStart); } draw_agregated: if(agregating) { dda.MajorIn = agregateBounds[majorIn]; dda.MajorOut = agregateBounds[majorOut]; dda.MinorIn = agregateBounds[minorIn]; dda.MinorOut = agregateBounds[minorOut]; (dda.*(gDrawFunctions[dda.DrawFuncIndex]))(scan); } } } return Ok; } #endif // AAONEPIXELLINE_SUPPORT #pragma optimize("a", off)