/**************************************************************************\ * * Copyright (c) 1998 Microsoft Corporation * * Abstract: * * Engine solid fill routines. * * Revision History: * * 12/11/1998 andrewgo * Created it. * \**************************************************************************/ #include "precomp.hpp" /**************************************************************************\ * * Function Description: * * Outputs a single span within a raster as a solid color. * Is called by the rasterizer. * * Arguments: * * [IN] y - the Y value of the raster being output * [IN] leftEdge - the DDA class of the left edge * [IN] rightEdge - the DDA class of the right edge * * Return Value: * * GpStatus - Ok * * Created: * * 12/15/1998 DCurtis * \**************************************************************************/ GpStatus DpOutputSolidColorSpan::OutputSpan( INT y, INT xMin, INT xMax // xMax is exclusive ) { INT width = xMax - xMin; FillMemoryInt32(Scan->NextBuffer(xMin, y, width), width, Argb); return Ok; } /**************************************************************************\ * * Function Description: * * Fills a path. This distributes to the individual brush fill method. * * Arguments: * * [IN] context - the context (matrix and clipping) * [IN] surface - the surface to fill * [IN] drawBounds - the surface bounds * [IN] path - the path to fill * [IN] brush - the brush to use * * Return Value: * * GpStatus - Ok or failure status * * Created: * * 01/21/1999 ikkof * \**************************************************************************/ GpStatus DpDriver::FillPath( DpContext *context, DpBitmap *surface, const GpRect *drawBounds, const DpPath *path, const DpBrush *brush ) { GpStatus status = GenericError; const GpBrush *gpBrush = GpBrush::GetBrush(brush); BOOL noTransparentPixels = (!context->AntiAliasMode) && (gpBrush->IsOpaque()); DpScanBuffer scan( surface->Scan, this, context, surface, noTransparentPixels); if (scan.IsValid()) { if (brush->Type == BrushTypeSolidColor) { GpColor color(brush->SolidColor.GetValue()); DpOutputSolidColorSpan output(color.GetPremultipliedValue(), &scan); status = RasterizePath(path, &context->WorldToDevice, path->GetFillMode(), context->AntiAliasMode, FALSE, &output, &context->VisibleClip, drawBounds); } else { // If there is a shrinking world to device transform when using // a path gradient, then scale the brush and the path to be // in device units. This eliminates the need to create potentially // very large gradients or textures. // Only handle positive scale because some of our driver rectangle // filling code can't handle negative rects. Doing ABS preserves // the sign of the input world coordinate rectangles/path. REAL scaleX = REALABS(context->WorldToDevice.GetM11()); REAL scaleY = REALABS(context->WorldToDevice.GetM22()); DpOutputSpan * output = NULL; if (brush->Type == BrushTypePathGradient && context->WorldToDevice.IsTranslateScale() && REALABS(scaleX) > REAL_EPSILON && REALABS(scaleY) > REAL_EPSILON && (REALABS(scaleX) < 1.0f || REALABS(scaleY) < 1.0f)) { // I don't like the following hack for magically getting // a GpBrush from a DpBrush, but DpOutputSpan already does this... GpBrush * gpbrush = GpBrush::GetBrush( (DpBrush *)(brush)); GpPathGradient *scaledBrush = (GpPathGradient*)(gpbrush->Clone()); if (scaledBrush == NULL) { return OutOfMemory; } // Scale the cloned brush's path and bounding rect into // device units. scaledBrush->ScalePath(scaleX,scaleY); REAL mOrig[6]; context->WorldToDevice.GetMatrix(mOrig); context->WorldToDevice.Scale(1.0f/scaleX, 1.0f/scaleY); output = DpOutputSpan::Create(scaledBrush->GetDeviceBrush(), &scan, context, drawBounds); if (output != NULL) { GpPath *scalePath = ((GpPath*)path)->Clone(); if (scalePath != NULL) { GpMatrix scaleMatrix (scaleX, 0.0f, 0.0f, scaleY, 0.0f, 0.0f); scalePath->Transform(&scaleMatrix); status = RasterizePath(scalePath, &context->WorldToDevice, path->GetFillMode(), context->AntiAliasMode, FALSE, output, &context->VisibleClip, drawBounds); delete scalePath; } else { status = OutOfMemory; } delete output; } else { status = OutOfMemory; } delete scaledBrush; context->WorldToDevice.SetMatrix(mOrig); } else { output = DpOutputSpan::Create(brush, &scan, context, drawBounds); if (output != NULL) { status = RasterizePath(path, &context->WorldToDevice, path->GetFillMode(), context->AntiAliasMode, FALSE, output, &context->VisibleClip, drawBounds); delete output; } } } } return status; } /**************************************************************************\ * * Function Description: * * Draws a path. This distributes to the individual pen draw method. * * Arguments: * * [IN] context - the context (matrix and clipping) * [IN] surface - the surface to draw to * [IN] drawBounds - the surface bounds * [IN] path - the path to stroke * [IN] pen - the pen to use * * Return Value: * * GpStatus - Ok or failure status * * Created: * * 01/06/1999 ikkof * \**************************************************************************/ GpStatus DpDriver::StrokePath( DpContext *context, DpBitmap *surface, const GpRect *drawBounds, const DpPath *path, const DpPen *pen ) { GpStatus status = GenericError; const DpBrush *brush = pen->Brush; REAL dpiX = (context->GetDpiX() > 0) ? (context->GetDpiX()) : (Globals::DesktopDpiX); BOOL isOnePixelWide = pen->IsOnePixelWide(&context->WorldToDevice, dpiX) && pen->IsCenterNoAnchor(); BOOL isOnePixelWideOpaque = isOnePixelWide && (brush->Type == BrushTypeSolidColor) && (brush->SolidColor.IsOpaque()) && !(context->AntiAliasMode); BOOL isOnePixelWideSolid = isOnePixelWide && pen->IsSimple(); // We have a special fast-path for doing single-pixel-wide, // solid color, opaque, aliased lines: // !!! [asecchia] RAID 239905. // The single pixel wide optimized code has significant rounding problems // that are particularly problematic on bezier curves. // Bezier curves tend to be enumerated in a particular way that causes // the SolidStrokePathOnePixel code to enumerate the line segments backward // hitting badly tested end point conditions, though the problem seems // to be pervasive. // The general rasterizer does not have these problems but is about // 30% slower for single pixel solid lines. // Turn on the optimization only for polylines until this is fixed. if (isOnePixelWideOpaque && isOnePixelWideSolid && !path->HasCurve()) { return SolidStrokePathOnePixel( context, surface, drawBounds, path, pen, TRUE ); } const DpPath* widenedPath; const DpPath* allocatedPath; GpMatrix *transform; GpMatrix identityTransform; if (isOnePixelWideSolid) { // Our RasterizePath code can directly draw a one-pixel-wide solid // line directly: widenedPath = path; allocatedPath = NULL; transform = &context->WorldToDevice; } else { // We have to widen the path before we can give it to the // rasterizer. Generate new path now: REAL dpiX = context->GetDpiX(); REAL dpiY = context->GetDpiY(); if ((dpiX <= 0) || (dpiY <= 0)) { dpiX = Globals::DesktopDpiX; dpiY = Globals::DesktopDpiY; } widenedPath = path->GetFlattenedPath( isOnePixelWideOpaque ? NULL : &context->WorldToDevice, isOnePixelWideOpaque ? Flattened : Widened, pen ); allocatedPath = widenedPath; transform = &identityTransform; if (!widenedPath) return OutOfMemory; // If this line is aliased, opaque and dashed, dash it now and pass the // dashed path to the single pixel stroking code. if (isOnePixelWideOpaque && pen->DashStyle != DashStyleSolid) { DpPath *dashPath = NULL; dashPath = ((GpPath*)widenedPath)->CreateDashedPath(pen, NULL, dpiX, dpiY, 1.0f, FALSE /* don't need caps in 1 px wide case */); if (!dashPath) { delete widenedPath; return OutOfMemory; } Status status = SolidStrokePathOnePixel(context, surface, drawBounds, dashPath, pen, FALSE); delete dashPath; delete widenedPath; return status; } } const GpBrush *gpBrush = GpBrush::GetBrush(brush); BOOL noTransparentPixels = (!context->AntiAliasMode) && (gpBrush->IsOpaque()); DpScanBuffer scan(surface->Scan, this, context, surface, noTransparentPixels); if (scan.IsValid()) { if (brush->Type == BrushTypeSolidColor) { GpColor color(brush->SolidColor.GetValue()); DpOutputSolidColorSpan output(color.GetPremultipliedValue(), &scan); status = RasterizePath(widenedPath, transform, widenedPath->GetFillMode(), context->AntiAliasMode, isOnePixelWideSolid, &output, &context->VisibleClip, drawBounds); } else { DpOutputSpan * output = DpOutputSpan::Create(brush, &scan, context, drawBounds); if (output != NULL) { status = RasterizePath(widenedPath, transform, widenedPath->GetFillMode(), context->AntiAliasMode, isOnePixelWideSolid, output, &context->VisibleClip, drawBounds); delete output; } } } if (allocatedPath) { delete allocatedPath; } return status; } /**************************************************************************\ * * Function Description: * * Fills a region. This distributes to the individual brush fill method. * * Arguments: * * [IN] context - the context (matrix and clipping) * [IN] surface - the surface to fill * [IN] drawBounds - the surface bounds * [IN] region - the region to fill * [IN] brush - the brush to use * * Return Value: * * GpStatus - Ok or failure status * * Created: * * 02/25/1999 DCurtis * \**************************************************************************/ GpStatus DpDriver::FillRegion( DpContext *context, DpBitmap *surface, const GpRect *drawBounds, const DpRegion *region, const DpBrush *brush ) { GpStatus status = GenericError; const GpBrush *gpBrush = GpBrush::GetBrush(brush); DpScanBuffer scan( surface->Scan, this, context, surface, gpBrush->IsOpaque()); if (scan.IsValid()) { DpOutputSpan * output = DpOutputSpan::Create(brush, &scan, context, drawBounds); if (output != NULL) { DpClipRegion * clipRegion = &(context->VisibleClip); GpRect clipBounds; GpRect * clipBoundsPointer = NULL; DpRegion::Visibility visibility; visibility = clipRegion->GetRectVisibility( drawBounds->X, drawBounds->Y, drawBounds->X + drawBounds->Width, drawBounds->Y + drawBounds->Height); switch (visibility) { default: // Need to clip clipRegion->GetBounds(&clipBounds); clipBoundsPointer = &clipBounds; clipRegion->InitClipping(output, drawBounds->Y); status = region->Fill(clipRegion, clipBoundsPointer); break; case DpRegion::TotallyVisible: // No clipping needed status = region->Fill(output, clipBoundsPointer); break; case DpRegion::Invisible: status = Ok; break; } delete output; clipRegion->EndClipping(); } } return status; } GpStatus DpDriver::MoveBits( DpContext *context, DpBitmap *surface, const GpRect *drawBounds, const GpRect *dstRect, const GpPoint *srcPoint ) { return(GenericError); } GpStatus DpDriver::Lock( DpBitmap *surface, const GpRect *drawBounds, INT *stride, // [OUT] - Returned stride VOID **bits // [OUT] - Returned pointer to bits ) { return(Ok); } VOID DpDriver::Unlock( DpBitmap *surface ) { } /**************************************************************************\ * * Function Description: * * Engine version of routine to fill rectangles. * This is not limited to filling solid color. * * Arguments: * * [IN] - DDI parameters. * * Return Value: * * TRUE if successful. * * History: * * 01/13/1999 ikkof * Created it. * \**************************************************************************/ // !!![andrewgo] What is this doing in a file called "solidfill.cpp"? GpStatus DpDriver::FillRects( DpContext *context, DpBitmap *surface, const GpRect *drawBounds, INT numRects, const GpRectF *rects, const DpBrush *brush ) { GpStatus status = Ok; GpBrushType type = brush->Type; const GpBrush *gpBrush = GpBrush::GetBrush(brush); DpScanBuffer scan( surface->Scan, this, context, surface, gpBrush->IsOpaque()); if(!scan.IsValid()) { return(GenericError); } DpOutputSpan * output = DpOutputSpan::Create(brush, &scan, context, drawBounds); if(output == NULL) return(GenericError); DpRegion::Visibility visibility = DpRegion::TotallyVisible; DpClipRegion * clipRegion = NULL; if (context->VisibleClip.GetRectVisibility( drawBounds->X, drawBounds->Y, drawBounds->GetRight(), drawBounds->GetBottom()) != DpRegion::TotallyVisible) { clipRegion = &(context->VisibleClip); clipRegion->InitClipping(output, drawBounds->Y); } GpMatrix *worldToDevice = &context->WorldToDevice; const GpRectF * rect = rects; INT y; for (INT i = numRects; i != 0; i--, rect++) { // We have to check for empty rectangles in world space (because // after the transform they might have flipped): if ((rect->Width > 0) && (rect->Height > 0)) { GpPointF points[4]; points[0].X = rect->X; points[0].Y = rect->Y; points[1].X = rect->X + rect->Width; points[1].Y = rect->Y + rect->Height; // FillRects only ever gets called when a scaling transform: // !!![ericvan] printing code calls this to render the brush onto a rectangle, // but the transform in effect may not be TranslateScale // !!![andrewgo] Yeah but then isn't the printer case completely // broken when there is an arbitrary transform?!? ASSERT(context->IsPrinter || worldToDevice->IsTranslateScale()); worldToDevice->Transform(points, 2); INT left; INT right; // convert to INT the same way the GDI+ rasterizer does // so we get the same rounding error in both places. if (points[0].X <= points[1].X) { left = RasterizerCeiling(points[0].X); right = RasterizerCeiling(points[1].X); // exclusive } else { left = RasterizerCeiling(points[1].X); right = RasterizerCeiling(points[0].X); // exclusive } // Since right is exclusive, we don't draw anything // if left >= right. INT width = right - left; INT top; INT bottom; if (points[0].Y <= points[1].Y) { top = RasterizerCeiling(points[0].Y); bottom = RasterizerCeiling(points[1].Y); // exclusive } else { top = RasterizerCeiling(points[1].Y); bottom = RasterizerCeiling(points[0].Y); // exclusive } // Since bottom is exclusive, we don't draw anything // if top >= bottom. if ((width > 0) && (top < bottom)) { GpRect clippedRect; if(clipRegion) { visibility = clipRegion->GetRectVisibility( left, top, right, bottom, &clippedRect); } switch (visibility) { case DpRegion::ClippedVisible: left = clippedRect.X; top = clippedRect.Y; right = clippedRect.GetRight(); bottom = clippedRect.GetBottom(); width = right - left; // FALLTHRU case DpRegion::TotallyVisible: for (y = top; y < bottom; y++) { output->OutputSpan(y, left, right); } break; case DpRegion::PartiallyVisible: for (y = top; y < bottom; y++) { clipRegion->OutputSpan(y, left, right); } break; case DpRegion::Invisible: break; } } } } if (clipRegion != NULL) { clipRegion->EndClipping(); } delete output; return status; }