You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
717 lines
22 KiB
717 lines
22 KiB
/**************************************************************************\
|
|
*
|
|
* 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;
|
|
}
|
|
|