/**************************************************************************\
*
* Copyright (c) 1998  Microsoft Corporation
*
* Abstract:
*
*   Implementation of GpBrush class
*
* Revision History:
*
*   12/09/1998 davidx
*       Flesh out brush interfaces.
*
*   12/08/1998 andrewgo
*       Initial placeholders.
*
\**************************************************************************/

#include "precomp.hpp"

// For GetData and SetData methods
#define GDIP_BRUSHFLAGS_PATH                0x00000001
#define GDIP_BRUSHFLAGS_TRANSFORM           0x00000002
#define GDIP_BRUSHFLAGS_PRESETCOLORS        0x00000004
#define GDIP_BRUSHFLAGS_BLENDFACTORS        0x00000008
#define GDIP_BRUSHFLAGS_BLENDFACTORSH       GDIP_BRUSHFLAGS_BLENDFACTORS
#define GDIP_BRUSHFLAGS_BLENDFACTORSV       0x00000010
#define GDIP_BRUSHFLAGS_BLENDFACTORS0       GDIP_BRUSHFLAGS_BLENDFACTORSH
#define GDIP_BRUSHFLAGS_BLENDFACTORS1       GDIP_BRUSHFLAGS_BLENDFACTORSV
#define GDIP_BRUSHFLAGS_BLENDFACTORS2       0x00000020
#define GDIP_BRUSHFLAGS_FOCUSSCALES         0x00000040
#define GDIP_BRUSHFLAGS_ISGAMMACORRECTED    0x00000080

// Defined in path.cpp
extern BOOL 
IsRectanglePoints(
    const GpPointF* points,
    INT count,
    const GpMatrix * matrix,
    GpRectF * transformedBounds
    );

GpStatus
GpElementaryBrush::MultiplyTransform(const GpMatrix& matrix,
                                   GpMatrixOrder order)
{
    GpStatus    status = Ok;

    if (matrix.IsInvertible())
    {
        if (order == MatrixOrderPrepend)
        {
            DeviceBrush.Xform.Prepend(matrix);
        }
        else
        {
            DeviceBrush.Xform.Append(matrix);
        }
        UpdateUid();
    }
    else
        status = InvalidParameter;

    return status;
}


/**************************************************************************\
*
* Function Description:
*
*   Calculate the brush transform from a starting point and two directions.
*
* Arguments:
*
*   [OUT] m         - matrix coefficients
*   [OUT] width     - width (the length of dP1)
*   [OUT] height    - height (the length of dP2)
*   [IN] p0         - the starting point of the brush.
*   [IN] dP1        - the vector to represent the transformed x-direction.
*   [IN] dP2        - the vector to represent the transformed y-direction.
*
* Return Vaule:
*
*   TRUE if the transform matrix is non-degenerate.
*   Otherwise returns FALSE.
*
* History:
*
*   06/03/1999 ikkof
*       Created it.
*
\**************************************************************************/

BOOL getLineGradientTransform(
    REAL* m,
    REAL* width,
    REAL* height,
    const GpPointF& p0,
    const GpPointF& dP1,
    const GpPointF& dP2
    )
{
    // Make sure the flat API has correctly set the FPU.

    FPUStateSaver::AssertMode();

    REAL l1 = dP1.X*dP1.X + dP1.Y*dP1.Y;
    REAL l2 = dP2.X*dP2.X + dP2.Y*dP2.Y;
    REAL test = dP1.X*dP2.Y - dP1.Y*dP2.X;

    if(l1 == 0 || l2 == 0 || test == 0)
        return FALSE;

    l1 = REALSQRT(l1);
    l2 = REALSQRT(l2);
    m[0] = TOREAL(dP1.X/l1);    // M11
    m[1] = TOREAL(dP1.Y/l1);    // M12
    m[2] = TOREAL(dP2.X/l2);   // M21
    m[3] = TOREAL(dP2.Y/l2);    // M22
    m[4] = TOREAL(p0.X - p0.X*m[0] - p0.Y*m[2]);    // Dx
    m[5] = TOREAL(p0.Y - p0.X*m[1] - p0.Y*m[3]);    // Dy

    *width = l1;
    *height = l2;

    return TRUE;
}

static GpStatus
LinearGradientRectFromPoints(
    const GpPointF& point1,
    const GpPointF& point2,
    GpRectF &       rect
    )
{
    // If the API specifies two coincident points, we
    // can't get information for the gradient, so we
    // fail the construction here.

    if( IsClosePointF(point1, point2) )
    {
        return InvalidParameter;
    }

    // Compute the bounding rectangle of the two input points.

    rect.X = min(point1.X, point2.X);
    rect.Y = min(point1.Y, point2.Y);
    rect.Width = REALABS(point1.X-point2.X);
    rect.Height = REALABS(point1.Y-point2.Y);

    // eliminate degenerate rectangles when the two
    // input points form a horizontal or vertical line.

    // This is a very odd way of coercing a 1d linear gradient
    // into a rect gradient and avoiding later matrix computation error
    // when we try get an affine warp between this rectangle and a
    // reference rectangle.

    if( IsCloseReal(point1.X, point2.X) )
    {
        rect.X -= rect.Height/2;
        rect.Width = rect.Height;
    }

    if( IsCloseReal(point1.Y, point2.Y) )
    {
        rect.Y -= rect.Width/2;
        rect.Height = rect.Width;
    }
    return Ok;
}

static GpStatus 
CalcLinearGradientXform(
    REAL                angle,
    BOOL                isAngleScalable,
    const GpRectF&      rect,
    GpMatrix&           xform
    )
{
    GpPointF p0, dP1, dP2;

    angle = GpModF(angle, 360);

    INT zone;
    REALD deltaTheta;
    const REALD degreeToRadian = 3.1415926535897932/180;

    if(angle < 90)
    {
        zone = 0;
        deltaTheta = angle;
    }
    else if(angle < 180)
    {
        zone = 1;
        deltaTheta = 180 - angle;
    }
    else if(angle < 270)
    {
        zone = 2;
        deltaTheta = angle - 180;
    }
    else
    {
        zone = 3;
        deltaTheta = 360 - angle;
    }

    REALD s, c;

    deltaTheta *= degreeToRadian;

    s = sin(deltaTheta);
    c = cos(deltaTheta);

    // d0 is the distance between p0 and the starting corner of the
    // original rectangle.
    // d1 and d2 is the length of dP1 and dP2, respectively.

    REALD top, left, w, h, d0, d1, d2;
    REALD x0, y0;   // Starting corner of the original rectangle.
    GpPointD norm;  // Direction of dP1.

    // Direction of dP2 = (-norm.Y, norm.X) which is 90 degree rotation
    // of dP1.

    if(!isAngleScalable)
    {
        left = rect.X;
        top = rect.Y;
        w = rect.Width;
        h = rect.Height;
    }
    else
    {
        // Scale to (0, 0, 1, 1) rectangle.

        top = 0.0;
        left = 0.0;
        w = 1.0;
        h = 1.0;
    }

    switch(zone)
    {
    case 0:
        d0 = w*s;
        norm.X = c;
        norm.Y = s;

        x0 = left;
        y0 = top;
        break;

    case 1:
        d0 = h*c;
        norm.X = - c;
        norm.Y = s;

        x0 = left + w;
        y0 = top;
        break;

    case 2:
        d0 = w*s;
        norm.X = - c;
        norm.Y = - s;

        x0 = left + w;
        y0 = top + h;
        break;

    case 3:
        d0 = h*c;
        norm.X = c;
        norm.Y = - s;

        x0 = left;
        y0 = top + h;
        break;
    }

    d2 = w*s + h*c;
    d1 = w*c + h*s;
    p0.X = TOREAL(x0 + d0*norm.Y);
    p0.Y = TOREAL(y0 - d0*norm.X);
    dP1.X = TOREAL(d1*norm.X);
    dP1.Y = TOREAL(d1*norm.Y);
    dP2.X = TOREAL(- d2*norm.Y);
    dP2.Y = TOREAL(d2*norm.X);

    if(isAngleScalable)
    {
        // Scale back.

        p0.X = rect.Width*p0.X + rect.X;
        p0.Y = rect.Height*p0.Y + rect.Y;

        dP1.X *= rect.Width;
        dP1.Y *= rect.Height;
        dP2.X *= rect.Width;
        dP2.Y *= rect.Height;
    }

    // Set up the transform.

    GpPointF points[3];

    points[0] = p0;
    points[1].X = p0.X + dP1.X;
    points[1].Y = p0.Y + dP1.Y;
    points[2].X = p0.X + dP2.X;
    points[2].Y = p0.Y + dP2.Y;

    GpStatus status;

    if(xform.InferAffineMatrix(&points[0], rect) == Ok)
    {
        return Ok;
    }
    return InvalidParameter;
}

GpStatus
GpLineGradient::ChangeLinePoints(
    const GpPointF&     point1,
    const GpPointF&     point2,
    BOOL                isAngleScalable
    )
{
    GpStatus    status;
    GpRectF     rect;
    
    if ((status = LinearGradientRectFromPoints(point1, point2, rect)) != Ok)
    {
        return status;
    }

    REAL        angle = GetAngleFromPoints(point1, point2);
    GpMatrix    xform;

    if ((status = CalcLinearGradientXform(angle, isAngleScalable, rect, xform)) == Ok)
    {
        DeviceBrush.Xform           = xform;
        DeviceBrush.Rect            = rect;
        DeviceBrush.IsAngleScalable = isAngleScalable;
        DeviceBrush.Points[0]       = point1;
        DeviceBrush.Points[1]       = point2;
        return Ok;
    }
    return status;
}

GpLineGradient::GpLineGradient(
    const GpPointF& point1,
    const GpPointF& point2,
    const GpColor& color1,
    const GpColor& color2,
    GpWrapMode wrapMode
    )
{
    // Make sure the flat API has correctly set the FPU.

    FPUStateSaver::AssertMode();

    REAL angle;
    GpRectF rect;

    if (LinearGradientRectFromPoints(point1, point2, rect) != Ok)
    {
        SetValid(FALSE);
        return;
    }

    // Compute the angle of the line formed by point1 and point2.
    // Note atan2 is only undefined if dP.Y == 0.0 and dP.X == 0.0
    // and then it returns 0 radians. We take care of that case separately
    // (above).
    // Also, atan2 correctly computes the quadrant from the two input points.

    GpPointF dP = point2 - point1;
    double rad = atan2((double)(dP.Y), (double)(dP.X));

    SetLineGradient(
        point1,
        point2,
        rect,
        color1,
        color2,

        // why aren't we working in radians???

        (REAL)(rad*180.0/3.1415926535897932),
        FALSE,
        wrapMode
    );
}


/**************************************************************************\
*
* Function Description:
*
*   Creates a LineGradient which is defined by the rectangle.
*
* Arguments:
*
*   [IN] rect       - the rectangle to define this gradient.
*   [IN] color1     - the color of the start point.
*   [IN] color2     - the color of the end point.
*   [IN] mode       - the line gradient mode
*   [IN] wrapMode   - the wrap mode of this brush.
*
* The start and end points of this gradient is defined as follows
* according to the line gradient mode:
*
*       mode                        start point     end point
*    -------------------------------------------------------------
*    LineGradientHorizontal         top-left        top-right
*    LineGradientVertical           top-right       bottom-right
*    LineGradientForwardDiagonal    top-left        bottom-right
*    LineGradientBackwardDiagonal   bottom-left     top-right
*
*
* History:
*
*   06/03/1999 ikkof
*       Created it.
*
\**************************************************************************/

GpLineGradient::GpLineGradient(
    const GpRectF& rect,
    const GpColor& color1,
    const GpColor& color2,
    LinearGradientMode mode,
    GpWrapMode wrapMode
    )
{
    // Make sure the flat API has correctly set the FPU.

    FPUStateSaver::AssertMode();

    BOOL isAngleScalable = TRUE;
    REAL angle = 0;
    GpPointF    point1;
    GpPointF    point2;

    switch(mode)
    {
    case LinearGradientModeHorizontal:
        angle = 0;
        point1.X = rect.X;
        point1.Y = (rect.Y + rect.GetBottom()) / 2.0f;
        point2.X = rect.GetRight();
        point2.Y = point1.Y;
        break;

    case LinearGradientModeVertical:
        angle = 90;
        point1.X = (rect.X + rect.GetRight()) / 2.0f;
        point1.Y = rect.Y;
        point2.X = point1.X;
        point2.Y = rect.GetBottom();
        break;

    case LinearGradientModeForwardDiagonal:
        angle = 45;
        point1.X = rect.X;
        point1.Y = rect.Y;
        point2.X = rect.GetRight();
        point2.Y = rect.GetBottom();
        break;

    case LinearGradientModeBackwardDiagonal:
        angle = 135;
        point1.X = rect.GetRight();
        point1.Y = rect.Y;
        point2.X = rect.X;
        point2.Y = rect.GetBottom();
        break;

    default:
        // No such a case.
        ASSERT(0);

        SetValid(FALSE);
        return;
    }

    SetLineGradient(
        point1,
        point2,
        rect,
        color1,
        color2,
        angle,
        isAngleScalable,
        wrapMode);
}


/**************************************************************************\
*
* Function Description:
*
*   Creates a LineGradient which is defined by the rectangle.
*
* Arguments:
*
*   [IN] rect       - the rectangle to define this gradient.
*   [IN] color1     - the color of the start point.
*   [IN] color2     - the color of the end point.
*   [IN] angle      - the angle of the gradient
*   [IN] isAngleScalable - TRUE if 45 degree is corner to corner.
*                          The default value is FALSE.
*   [IN] wrapMode   - the wrap mode of this brush.
*
*
*
* History:
*
*   10/06/1999 ikkof
*       Created it.
*
\**************************************************************************/

GpLineGradient::GpLineGradient(
    const GpRectF& rect,
    const GpColor& color1,
    const GpColor& color2,
    REAL angle,
    BOOL isAngleScalable,
    GpWrapMode wrapMode
    )
{
    // Make sure the flat API has correctly set the FPU.

    FPUStateSaver::AssertMode();
    GpPointF    point1;
    GpPointF    point2;
    
    // Not an Office scenario, but need to fix at some point
    // so we can print to PCL better.
    point1.X = point1.Y = point2.X = point2.Y = 0;

    SetLineGradient(
        point1,
        point2,
        rect,
        color1,
        color2,
        angle,
        isAngleScalable,
        wrapMode);
}

GpStatus
GpLineGradient::SetLineGradient(
    const GpPointF& point1,
    const GpPointF& point2,
    const GpRectF& rect,
    const GpColor& color1,
    const GpColor& color2,
    REAL angle,
    BOOL isAngleScalable,
    GpWrapMode wrapMode
    )
{
    // Make sure the flat API has correctly set the FPU.

    FPUStateSaver::AssertMode();

    DeviceBrush.Wrap = wrapMode;
    DeviceBrush.Colors[0] = color1;
    DeviceBrush.Colors[1] = color2;
    DeviceBrush.Colors[2] = color1;
    DeviceBrush.Colors[3] = color2;

    DeviceBrush.BlendCounts[0] = DeviceBrush.BlendCounts[1] = 1;
    DeviceBrush.BlendFactors[0] = DeviceBrush.BlendFactors[1] = NULL;
    DeviceBrush.Falloffs[0] = DeviceBrush.Falloffs[1] = 1;

    GpStatus status;
    
    if (CalcLinearGradientXform(angle, isAngleScalable, rect, DeviceBrush.Xform) == Ok)
    {
        SetValid(TRUE);
        DeviceBrush.Rect = rect;
        DeviceBrush.IsAngleScalable = isAngleScalable;
        DeviceBrush.Points[0] = point1;
        DeviceBrush.Points[1] = point2;
        status = Ok;
    }
    else
    {
        SetValid(FALSE);
        GpMemset(&DeviceBrush.Rect, 0, sizeof(DeviceBrush.Rect));
        GpMemset(DeviceBrush.Points, 0, sizeof(DeviceBrush.Points[0]) * 2);
        DeviceBrush.IsAngleScalable = FALSE;
        status = InvalidParameter;
    }

    return status;
}



GpStatus
GpLineGradient::SetLinePoints(
    const GpPointF& point1,
    const GpPointF& point2
    )
{
    // Make sure the flat API has correctly set the FPU.

    FPUStateSaver::AssertMode();

    GpPointF p0, dP1, dP2;

    p0 = point1;
    dP1.X = point2.X - point1.X;
    dP1.Y = point2.Y - point1.Y;
    dP2.X = - dP1.Y;
    dP2.Y = dP1.X;

    REAL m[6];
    REAL width, height;

    if(getLineGradientTransform(&m[0], &width, &height, p0, dP1, dP2))
    {
        SetValid(TRUE);

        DeviceBrush.Rect.X = p0.X;
        DeviceBrush.Rect.Y = p0.Y;
        DeviceBrush.Rect.Width = width;
        DeviceBrush.Rect.Height = height;
    }
    else
    {
        // Don't change the current state.

        return GenericError;
    }

    DeviceBrush.Xform.SetMatrix(m);
    UpdateUid();
    return Ok;
}


GpStatus
GpLineGradient::GetLinePoints(GpPointF* points)
{
    // Make sure the flat API has correctly set the FPU.

    FPUStateSaver::AssertMode();

    ASSERT(points);
    points[0].X = DeviceBrush.Rect.X;
    points[0].Y = DeviceBrush.Rect.Y;
    points[1].X = DeviceBrush.Rect.X + DeviceBrush.Rect.Width;
    points[1].Y = DeviceBrush.Rect.Y + DeviceBrush.Rect.Height;
    DeviceBrush.Xform.Transform(points, 2);

    return Ok;
}


INT
GpLineGradient::GetPresetBlendCount()
{
    if(DeviceBrush.UsesPresetColors)
        return DeviceBrush.BlendCounts[0];
    else
        return 0;
}

/*
** This returns the premultiplied colors
*/

GpStatus
GpLineGradient::GetPresetBlend(
    GpColor* blendColors,
    REAL* blendPositions,
    INT count)
{
    // Make sure the flat API has correctly set the FPU.

    FPUStateSaver::AssertMode();

    if(!blendColors || !blendPositions || count <= 1)
        return InvalidParameter;

    if(DeviceBrush.UsesPresetColors &&
       DeviceBrush.PresetColors &&
       DeviceBrush.BlendPositions[0])
    {
        for(INT i = 0; i < count; i++)
        {
            blendColors[i].SetColor(DeviceBrush.PresetColors[i]);
        }
        GpMemcpy(blendPositions,
                 DeviceBrush.BlendPositions[0],
                 count*sizeof(REAL));

        return Ok;
    }
    else
        return GenericError;
}

GpStatus
GpLineGradient::SetPresetBlend(
            const GpColor* blendColors,
            const REAL* blendPositions,
            INT count)
{
    // Make sure the flat API has correctly set the FPU.

    FPUStateSaver::AssertMode();

    if(!blendColors || !blendPositions || count <= 1)
        return InvalidParameter;

    ARGB* newColors = (ARGB*) GpRealloc(DeviceBrush.PresetColors,
                                        count*sizeof(ARGB));

    if (newColors != NULL)
    {
        DeviceBrush.PresetColors = newColors;
    }
    else
    {
        return OutOfMemory;
    }

    REAL* newPositions = (REAL*) GpRealloc(DeviceBrush.BlendPositions[0],
                                           count*sizeof(REAL));

    if (newPositions != NULL)
    {
        DeviceBrush.BlendPositions[0] = newPositions;
    }
    else
    {
        return OutOfMemory;
    }

    GpFree(DeviceBrush.BlendFactors[0]);

    // DeviceBrush.BlendFactors[1] is always NULL for LineGradient.
    DeviceBrush.BlendFactors[0] = NULL;

    DeviceBrush.UsesPresetColors = TRUE;

    for(INT i = 0; i < count; i++)
    {
        newColors[i] = blendColors[i].GetValue();
    }
    GpMemcpy(newPositions, blendPositions, count*sizeof(REAL));
    DeviceBrush.BlendCounts[0] = count;
    UpdateUid();
    return Ok;
}

/**************************************************************************\
*
* Function Description:
*
*   Blend any transparent colors in this brush with white.  Note that
*   colors are premultiplied, since they will become fully opaque.
*
* Arguments:
*
* Return Value:
*
*   GpStatus - Ok or failure status
*
\**************************************************************************/

GpStatus GpLineGradient::BlendWithWhite()
{
    if (DeviceBrush.UsesPresetColors)
    {
        GpColor color;
        
        for (INT i=0; i<DeviceBrush.BlendCounts[0]; i++)
        {
            color.SetValue(GpColor::ConvertToPremultiplied(DeviceBrush.PresetColors[i]));
            color.BlendOpaqueWithWhite();
            DeviceBrush.PresetColors[i] = color.GetValue();
        }
        return Ok;
    }
    else
    {
        return GpRectGradient::BlendWithWhite();
    }
}

BOOL
GpPathGradient::IsRectangle() const
{
    BOOL result = FALSE;

    if (DeviceBrush.PointsPtr != NULL)
        result = IsRectanglePoints(DeviceBrush.PointsPtr, DeviceBrush.Count, NULL, NULL);
    else
    {
        GpPath* path = static_cast<GpPath*> (DeviceBrush.Path);
        if(path)
            result = path->IsRectangle(NULL);
    }

    return result;
}

INT
GpPathGradient::GetPresetBlendCount() const
{
    if(DeviceBrush.UsesPresetColors)
        return DeviceBrush.BlendCounts[0];
    else
        return 0;
}

/*
** This returns the premultiplied colors
*/

GpStatus
GpPathGradient::GetPresetBlend(
    GpColor* blendColors,
    REAL* blendPositions,
    INT count) const
{
    if(!blendColors || !blendPositions || count <= 1)
        return InvalidParameter;

    if(DeviceBrush.UsesPresetColors && DeviceBrush.PresetColors && DeviceBrush.BlendPositions[0])
    {
        // Users will obtain the preset colors as radial blend colors.
        // 0 position means the center location and 1 position means the
        // the outer edge.  In order to convert those colors and position arrays
        // from the weight factor arrays in PathGradient,
        // we must invert the order of the returned arrays.

        for(INT i = 0; i < count; i++)
        {
            blendColors[count - 1 -i].SetColor(DeviceBrush.PresetColors[i]);
            blendPositions[count - 1 -i] = TOREAL(1.0 - DeviceBrush.BlendPositions[0][i]);
        }

        return Ok;
    }
    else
        return GenericError;
}

GpStatus
GpPathGradient::SetPresetBlend(
            const GpColor* blendColors,
            const REAL* blendPositions,
            INT count)
{
    if(!blendColors || !blendPositions || count <= 1)
        return InvalidParameter;

    ARGB* newColors = (ARGB*) GpRealloc(DeviceBrush.PresetColors, count*sizeof(ARGB));

    if (newColors != NULL)
    {
        DeviceBrush.PresetColors = newColors;
    }
    else
    {
        return OutOfMemory;
    }

    REAL* newPositions = (REAL*) GpRealloc(DeviceBrush.BlendPositions[0], count*sizeof(REAL));

    if (newPositions != NULL)
    {
        DeviceBrush.BlendPositions[0] = newPositions;
    }
    else
    {
        return OutOfMemory;
    }

    GpFree(DeviceBrush.BlendFactors[0]);
    DeviceBrush.BlendFactors[0] = NULL;

    DeviceBrush.UsesPresetColors = TRUE;

    // Users will supply the preset colors as radial blend colors.
    // 0 position means the center location and 1 position means the
    // the outer edge.  In order to convert those colors and position arrays
    // to the weight factor arrays in PathGradient,
    // we must invert the order of the given arrays.

    for(INT i = 0; i < count; i++)
    {
        // PresetColors are stored non-premultiplied.
        newColors[count - 1 - i] = blendColors[i].GetValue();
        newPositions[count - 1 - i] = TOREAL(1.0 - blendPositions[i]);
    }

    DeviceBrush.BlendCounts[0] = count;
    UpdateUid();
    return Ok;
}

//==================================================================
// Copy constructors
//==================================================================

GpElementaryBrush::GpElementaryBrush(const GpElementaryBrush *brush)
{
    if(brush && brush->IsValid())
    {
        // !!! [asecchia] we should really be asking the DeviceBrush to
        // copy it's members instead of duplicating the code all over
        // the place. Current code is error prone - each subclass is has to 
        // know all about how to copy and what has or hasn't been updated on
        // the chain down to it's ancestor.
        
        DeviceBrush.Xform = brush->DeviceBrush.Xform;
        DeviceBrush.Wrap = brush->DeviceBrush.Wrap;
        DeviceBrush.IsGammaCorrected = brush->DeviceBrush.IsGammaCorrected;
        
        SetValid(brush->IsValid());
    }
    else
        SetValid(FALSE);
}

GpTexture::GpTexture(
    const GpTexture *brush
    ) : GpElementaryBrush(brush)
{
    if(brush && brush->IsValid())
    {
        const DpBrush* devBrush = &(brush->DeviceBrush);

        InitializeBrush(brush->Image, devBrush->Wrap, NULL);
        SetTransform(devBrush->Xform);
    }
    else
        SetValid(FALSE);
}

GpRectGradient::GpRectGradient(
    const GpRectGradient *brush
    )  : GpGradientBrush(brush)
{
    if(brush && brush->IsValid())
    {
        const DpBrush* devBrush = &(brush->DeviceBrush);

        InitializeBrush(
            devBrush->Rect,
            &(devBrush->Colors[0]),
            devBrush->Wrap
            );

        SetTransform(devBrush->Xform);

        SetHorizontalBlend(
            devBrush->BlendFactors[0],
            devBrush->BlendPositions[0],
            devBrush->BlendCounts[0]
            );

        SetVerticalBlend(
            devBrush->BlendFactors[1],
            devBrush->BlendPositions[1],
            devBrush->BlendCounts[1]
            );

    }
    else
        SetValid(FALSE);
}

GpLineGradient::GpLineGradient(
    const GpLineGradient *brush
    ) : GpRectGradient(brush)
{
    if(brush && brush->IsValid())
    {
        // Copy the preset colors.
        // !!! [asecchia] why isn't this handled in a uniform way?
        const DpBrush* devBrush = &(brush->DeviceBrush);

        DeviceBrush.Points[0]       = devBrush->Points[0];
        DeviceBrush.Points[1]       = devBrush->Points[1];
        DeviceBrush.IsAngleScalable = devBrush->IsAngleScalable;

        if(devBrush->UsesPresetColors)
        {
            SetPresetBlend(
                (GpColor*)(devBrush->PresetColors),
                (REAL*)(devBrush->BlendPositions[0]),
                devBrush->BlendCounts[0]
            );
        }
    }
}

GpPathGradient::GpPathGradient(
    const GpPathGradient *brush
    ) : GpGradientBrush(brush)
{
    if(brush && brush->IsValid())
    {
        const DpBrush* devBrush = &(brush->DeviceBrush);

        // If a path exists for the brush, use that for initialization.
        // Otherwise, use the points collection.
        if (devBrush->Path != NULL)
        {
            DefaultBrush();
            DeviceBrush.Wrap = devBrush->Wrap;
            DeviceBrush.Path = devBrush->Path->ClonePath();
            PrepareBrush();
        }
        else
        {
            InitializeBrush(devBrush->PointsPtr, 
                            devBrush->Count, 
                            devBrush->Wrap);
        }

        if(IsValid())
        {
            SetTransform(devBrush->Xform);

            SetCenterPoint(devBrush->Points[0]);
            SetCenterColor(devBrush->Colors[0]);
            SetSurroundColors(devBrush->ColorsPtr);
            DeviceBrush.Falloffs[0] = devBrush->Falloffs[0];
            DeviceBrush.FocusScaleX = devBrush->FocusScaleX;
            DeviceBrush.FocusScaleY = devBrush->FocusScaleY;
            DeviceBrush.UsesPresetColors = devBrush->UsesPresetColors;
            
            INT blendCount = devBrush->BlendCounts[0];
            DeviceBrush.BlendCounts[0] = blendCount;

            // If we're cloning a brush with preset colors, copy preset colors 
            // and blend positions.  Otherwise, copy the blend factors and
            // blend positions.
            if (devBrush->UsesPresetColors)
            {
                ARGB* newColors = (ARGB*) GpRealloc(DeviceBrush.PresetColors, blendCount*sizeof(ARGB));
                if (newColors != NULL)
                {
                    DeviceBrush.PresetColors = newColors;

                    REAL* newPositions = (REAL*) GpRealloc(DeviceBrush.BlendPositions[0], blendCount*sizeof(REAL));

                    if (newPositions != NULL)
                    {
                        DeviceBrush.BlendPositions[0] = newPositions;
                        GpFree(DeviceBrush.BlendFactors[0]);
                        DeviceBrush.BlendFactors[0] = NULL;
                
                        memcpy(DeviceBrush.PresetColors,
                               devBrush->PresetColors,
                               blendCount*sizeof(ARGB));
                        memcpy(DeviceBrush.BlendPositions[0],
                               devBrush->BlendPositions[0],
                               blendCount*sizeof(REAL));
                    }
                    else
                    {
                        SetValid(FALSE);
                    }
                }
                else
                {
                    SetValid(FALSE);
                }
            }
            else if (devBrush->BlendFactors[0] && devBrush->BlendPositions[0])
            {
                REAL* newFactors = (REAL*) GpRealloc(DeviceBrush.BlendFactors[0], blendCount*sizeof(REAL));
                if (newFactors != NULL)
                {
                    DeviceBrush.BlendFactors[0] = newFactors;

                    REAL* newPositions = (REAL*) GpRealloc(DeviceBrush.BlendPositions[0], blendCount*sizeof(REAL));

                    if (newPositions != NULL)
                    {
                        DeviceBrush.BlendPositions[0] = newPositions;
                
                        memcpy(DeviceBrush.BlendFactors[0],
                               devBrush->BlendFactors[0],
                               blendCount*sizeof(REAL));
                        memcpy(DeviceBrush.BlendPositions[0],
                               devBrush->BlendPositions[0],
                               blendCount*sizeof(REAL));
                    }
                    else
                    {
                        SetValid(FALSE);
                    }
                }
                else
                {
                    SetValid(FALSE);
                }
            }
        }
    }
    else
        SetValid(FALSE);
}

GpHatch::GpHatch(const GpHatch* brush)
{
    if(brush && brush->IsValid())
    {
        const DpBrush* devBrush = &(brush->DeviceBrush);

        InitializeBrush(devBrush->Style,
                    devBrush->Colors[0],
                    devBrush->Colors[1]);
    }
    else
        SetValid(FALSE);
}

/**************************************************************************\
*
* Function Description:
*
*   Getting horizontal falloff / blend-factors for
*   a rectangular gradient brush object
*
* Arguments:
*
*   [OUT] blendFactors - Buffer for returning the horizontal
*               falloff or blend-factors.
*   count - Size of the buffer (in number of REAL elements)
*
* Return Value:
*
*   Status code
*
\**************************************************************************/

GpStatus
GpRectGradient::GetHorizontalBlend(
    REAL* blendFactors,
    REAL* blendPositions,
    INT count
    )
{
    if(!blendFactors || !blendPositions || count < 1)
        return InvalidParameter;

    // Check if the input buffer is big enough

    if (count < DeviceBrush.BlendCounts[0])
        return InsufficientBuffer;

    if (DeviceBrush.BlendCounts[0] == 1)
    {
        // Return falloff parameter

        blendFactors[0] = DeviceBrush.Falloffs[0];
    }
    else
    {
        // Return blend factors

        GpMemcpy(
            blendFactors,
            DeviceBrush.BlendFactors[0],
            DeviceBrush.BlendCounts[0]*sizeof(REAL)
            );
        GpMemcpy(
            blendPositions,
            DeviceBrush.BlendPositions[0],
            DeviceBrush.BlendCounts[0]*sizeof(REAL)
            );
    }

    return Ok;
}


/**************************************************************************\
*
* Function Description:
*
*   Setting horizontal falloff / blend-factors for
*   a rectangular gradient brush object
*
* Arguments:
*
*   [IN] blendFactors - Specify the new blend factors
*   count - Number of elements in the blend factor array
*
* Return Value:
*
*   Status code
*
\**************************************************************************/

GpStatus
GpRectGradient::SetHorizontalBlend(
    const REAL* blendFactors,
    const REAL* blendPositions,
    INT count
    )
{
    if(!blendFactors || !blendPositions || count < 1)
        return InvalidParameter;

    if (count == 1)
    {
        // Setting falloff parameter

        GpFree(DeviceBrush.BlendFactors[0]);
        DeviceBrush.BlendFactors[0] = NULL;
        GpFree(DeviceBrush.BlendPositions[0]);
        DeviceBrush.BlendPositions[0] = NULL;

        if (blendFactors == NULL)
            DeviceBrush.Falloffs[0] = 1;
        else
            DeviceBrush.Falloffs[0] = blendFactors[0];

        DeviceBrush.BlendCounts[0] = 1;
    }
    else
    {
        ASSERT(blendFactors != NULL && blendPositions != NULL);
        
        // blend positions must start at 0.0 and end at 1.0
        
        if (REALABS(blendPositions[0]) > REAL_EPSILON ||
            REALABS(1.0f - blendPositions[count-1]) > REAL_EPSILON)
        {
            return InvalidParameter;
        }

        // Setting blend factors

        REAL* newFactors;
        REAL* newPositions;

        newFactors = (REAL*) GpRealloc(DeviceBrush.BlendFactors[0], count*sizeof(REAL));

        if (newFactors != NULL)
        {
            DeviceBrush.BlendFactors[0] = newFactors;
        }
        else
        {
            return OutOfMemory;
        }

        newPositions = (REAL*) GpRealloc(DeviceBrush.BlendPositions[0], count*sizeof(REAL));

        if (newPositions != NULL)
        {
            DeviceBrush.BlendPositions[0] = newPositions;
        }
        else
        {
            return OutOfMemory;
        }

        if (newFactors == NULL || newPositions == NULL)
            return OutOfMemory;

        GpMemcpy(newFactors, blendFactors, count*sizeof(REAL));
        GpMemcpy(newPositions, blendPositions, count*sizeof(REAL));
        DeviceBrush.BlendCounts[0] = count;
    }

    DeviceBrush.UsesPresetColors = FALSE;
    GpFree(DeviceBrush.PresetColors);
    DeviceBrush.PresetColors = NULL;
    UpdateUid();
    return Ok;
}


/**************************************************************************\
*
* Function Description:
*
*   Getting vertical falloff / blend-factors for
*   a rectangular gradient brush object
*
* Arguments:
*
*   [OUT] blendFactors - Buffer for returning the vertical
*               falloff or blend-factors.
*   count - Size of the buffer (in number of REAL elements)
*
* Return Value:
*
*   Status code
*
\**************************************************************************/

GpStatus
GpRectGradient::GetVerticalBlend(
    REAL* blendFactors,
    REAL* blendPositions,
    INT count
    )
{
    if(!blendFactors || !blendPositions || count < 1)
        return InvalidParameter;

    // Check if the input buffer is big enough

    if (count < DeviceBrush.BlendCounts[1])
        return InsufficientBuffer;

    if (DeviceBrush.BlendCounts[1] == 1)
    {
        // Return falloff parameter

        blendFactors[0] = DeviceBrush.Falloffs[1];
    }
    else
    {
        // Return blend factors

        GpMemcpy(
            blendFactors,
            DeviceBrush.BlendFactors[1],
            DeviceBrush.BlendCounts[1]*sizeof(REAL));
        GpMemcpy(
            blendPositions,
            DeviceBrush.BlendPositions[1],
            DeviceBrush.BlendCounts[1]*sizeof(REAL));
    }

    return Ok;
}


/**************************************************************************\
*
* Function Description:
*
*   Setting vertical falloff / blend-factors for
*   a rectangular gradient brush object
*
* Arguments:
*
*   [IN] blendFactors - Specify the new blend factors
*   count - Number of elements in the blend factor array
*
* Return Value:
*
*   Status code
*
\**************************************************************************/

GpStatus
GpRectGradient::SetVerticalBlend(
    const REAL* blendFactors,
    const REAL* blendPositions,
    INT count
    )
{
    if(!blendFactors || !blendPositions || count < 1)
        return InvalidParameter;

    if (count == 1)
    {
        // Setting falloff parameter

        GpFree(DeviceBrush.BlendFactors[1]);
        DeviceBrush.BlendFactors[1] = NULL;
        GpFree(DeviceBrush.BlendPositions[1]);
        DeviceBrush.BlendPositions[1] = NULL;

        if (blendFactors == NULL)
            DeviceBrush.Falloffs[1] = 1;
        else
            DeviceBrush.Falloffs[1] = blendFactors[0];

        DeviceBrush.BlendCounts[1] = 1;
    }
    else
    {
        ASSERT(blendFactors != NULL && blendPositions != NULL);

        // Setting blend factors

        REAL* newFactors;
        REAL* newPositions;

        newFactors = (REAL*) GpRealloc(DeviceBrush.BlendFactors[1], count*sizeof(REAL));

        if (newFactors != NULL)
        {
            DeviceBrush.BlendFactors[1] = newFactors;
        }
        else
        {
            return OutOfMemory;
        }

        newPositions = (REAL*) GpRealloc(DeviceBrush.BlendPositions[1], count*sizeof(REAL));

        if (newPositions != NULL)
        {
            DeviceBrush.BlendPositions[1] = newPositions;
        }
        else
        {
            return OutOfMemory;
        }

        GpMemcpy(newFactors, blendFactors, count*sizeof(REAL));
        GpMemcpy(newPositions, blendPositions, count*sizeof(REAL));
        DeviceBrush.BlendCounts[1] = count;
    }

    DeviceBrush.UsesPresetColors = FALSE;
    GpFree(DeviceBrush.PresetColors);
    DeviceBrush.PresetColors = NULL;
    UpdateUid();

    return Ok;
}

/**************************************************************************\
*
* Function Description:
*
*   Blend any transparent colors in this brush with white.  Note that colors
*   are converted to premultiplied first, since they will become fully opaque.
*
* Arguments:
*
* Return Value:
*
*   GpStatus - Ok or failure status
*
\**************************************************************************/

GpStatus GpRectGradient::BlendWithWhite()
{
    GpColor color;
    
    for (INT i=0; i<4; i++)
    {
        color.SetValue(DeviceBrush.Colors[i].GetPremultipliedValue());
        color.BlendOpaqueWithWhite();
        DeviceBrush.Colors[i] = color.GetValue();
    }

    return Ok;
}

//--------------------------------------------------------------------------
// Path Gradient
//--------------------------------------------------------------------------

VOID
GpPathGradient::PrepareBrush()
{
    GpPath* path = static_cast<GpPath*> (DeviceBrush.Path);

    if (path)
    {
        DeviceBrush.Count = path->Points.GetCount();
        GpPointF* points = path->Points.GetDataBuffer();

        if(!DeviceBrush.ColorsPtr)
        {
           DeviceBrush.ColorsPtr = (GpColor*)GpMalloc(DeviceBrush.Count*sizeof(GpColor));

           if (DeviceBrush.ColorsPtr != NULL)
              GpMemset(&DeviceBrush.ColorsPtr[0], 255, DeviceBrush.Count*sizeof(GpColor));
        }

        REAL xmin, xmax, ymin, ymax, x0, y0; 

        x0 = xmin = xmax = points[0].X;
        y0 = ymin = ymax = points[0].Y;

        for(INT i = 1; i < DeviceBrush.Count; i++)
        {
            x0 += points[i].X;
            y0 += points[i].Y;
            xmin = min(xmin, points[i].X);
            xmax = max(xmax, points[i].X);
            ymin = min(ymin, points[i].Y);
            ymax = max(ymax, points[i].Y);
        }

        DeviceBrush.Rect.X = xmin;
        DeviceBrush.Rect.Width = xmax - xmin;
        DeviceBrush.Rect.Y = ymin;
        DeviceBrush.Rect.Height = ymax - ymin;

        if(!WrapModeIsValid(DeviceBrush.Wrap) || DeviceBrush.Rect.Width <= 0 || DeviceBrush.Rect.Height <= 0)
            return;

        DeviceBrush.Points[0].X = x0/DeviceBrush.Count;
        DeviceBrush.Points[0].Y = y0/DeviceBrush.Count;

        SetValid(TRUE);
    }
}

GpStatus
GpPathGradient::Flatten(GpMatrix* matrix) const
{
    GpPath* path = static_cast<GpPath*> (DeviceBrush.Path);

    if(!path)
        return Ok;

    if(path->HasCurve())
    {
        INT origCount = DeviceBrush.Count;

        GpStatus status = path->Flatten(
                            const_cast<DynByteArray*>(&FlattenTypes),
                            const_cast<DynPointFArray*> (&FlattenPoints),
                            matrix);

        if(status == Ok)
        {
            DeviceBrush.Count = FlattenPoints.GetCount();
            DeviceBrush.PointsPtr = FlattenPoints.GetDataBuffer();
            
            if ((DeviceBrush.Count > origCount) &&
                (DeviceBrush.ColorsPtr != NULL)) 
            {
                // The colors array is no longer the proper size.  Adjust the
                // size and copy up the last color.  It is the apps responsibility
                // to estimate and specify the correct number of flattened points.
                
                const_cast<GpColor*>(DeviceBrush.ColorsPtr) = (GpColor*) GpRealloc((VOID*)DeviceBrush.ColorsPtr, 
                                                             sizeof(GpColor)*DeviceBrush.Count);

                if (DeviceBrush.ColorsPtr != NULL)
                {
                     GpColor copyColor = (origCount > 1) ? 
                                        DeviceBrush.ColorsPtr[origCount-1] :
                                        GpColor(0xFFFFFFFF);

                     for (INT i=origCount; i<DeviceBrush.Count; i++) 
                     {
                          DeviceBrush.ColorsPtr[i] = copyColor;
                     }
                }
                else
                {
                    return OutOfMemory;
                }
            }
        }
    }
    else
    {
            DeviceBrush.Count = path->GetPointCount();
            DeviceBrush.PointsPtr = const_cast<GpPointF*> (path->GetPathPoints());
    }

    return Ok;
}


GpStatus
GpPathGradient::GetBlend(
    REAL* blendFactors,
    REAL* blendPositions,
    INT count
    ) const
{
    if(!blendFactors || !blendPositions || count < 1)
        return InvalidParameter;

    // Check if the input buffer is big enough

    if (count < DeviceBrush.BlendCounts[0])
        return InsufficientBuffer;

    if (DeviceBrush.BlendCounts[0] == 1)
    {
        // Return falloff parameter

        blendFactors[0] = DeviceBrush.Falloffs[0];
    }
    else
    {
        // Return blend factors

        // Users want to obtain the blend factor as radial blend factors.
        // 0 blend factor means 100 % center color and 0 position means
        // the center location.  In order to return those factor and
        // position arrays, we must invert the weight and position factor
        // arrays stored in this PathGradient class.

        for(INT i = 0; i < DeviceBrush.BlendCounts[0]; i++)
        {
            blendFactors[DeviceBrush.BlendCounts[0] - 1 - i] = TOREAL(1.0 - DeviceBrush.BlendFactors[0][i]);
            blendPositions[DeviceBrush.BlendCounts[0] - 1 - i] = TOREAL(1.0 - DeviceBrush.BlendPositions[0][i]);
        }
    }

    return Ok;
}

GpStatus
GpPathGradient::SetBlend(
    const REAL* blendFactors,
    const REAL* blendPositions,
    INT count
    )
{
    if(!blendFactors || !blendPositions || count < 1)
        return InvalidParameter;

    if (count == 1)
    {
        // Setting falloff parameter

        GpFree(DeviceBrush.BlendFactors[0]);
        DeviceBrush.BlendFactors[0] = NULL;
        GpFree(DeviceBrush.BlendPositions[0]);
        DeviceBrush.BlendPositions[0] = NULL;

        if (blendFactors == NULL)
            DeviceBrush.Falloffs[0] = 1;
        else
            DeviceBrush.Falloffs[0] = blendFactors[0];

        DeviceBrush.BlendCounts[0] = 1;
    }
    else
    {
        // blend positions must start at 0.0 and end at 1.0
        
        if (REALABS(blendPositions[0]) > REAL_EPSILON ||
            REALABS(1.0f - blendPositions[count-1]) > REAL_EPSILON)
        {
            return InvalidParameter;
        }

        // Setting blend factors

        REAL* newFactors;
        REAL* newPositions;

        newFactors = (REAL*) GpRealloc(DeviceBrush.BlendFactors[0], count*sizeof(REAL));

        if (newFactors != NULL)
        {
            DeviceBrush.BlendFactors[0] = newFactors;
        }
        else
        {
            return OutOfMemory;
        }

        newPositions = (REAL*) GpRealloc(DeviceBrush.BlendPositions[0], count*sizeof(REAL));

        if (newPositions != NULL)
        {
            DeviceBrush.BlendPositions[0] = newPositions;
        }
        else
        {
            return OutOfMemory;
        }

        // Users will supply the blend factor as radial blend factors.
        // 0 blend factor means 100 % center color and 0 position means
        // the center location.  In order to convert those factor and position arrays
        // to the weight and position factor arrays in PathGradient,
        // we must invert the given arrays.

        for(INT i = 0; i < count; i++)
        {
            newFactors[count - 1 - i] = TOREAL(1.0 - blendFactors[i]);
            newPositions[count - 1 - i] = TOREAL(1.0 - blendPositions[i]);
        }
        DeviceBrush.BlendCounts[0] = count;
    }

    DeviceBrush.UsesPresetColors = FALSE;
    GpFree(DeviceBrush.PresetColors);
    DeviceBrush.PresetColors = NULL;
    UpdateUid();

    return Ok;
}

GpStatus
GpGradientBrush::GetSigmaBlendArray(
    REAL focus,
    REAL scale,
    INT* count,
    REAL* blendFactors,
    REAL* blendPositions)
{
    // Make sure the FPU is set correctly.

    FPUStateSaver::AssertMode();

    if(!blendFactors || !blendPositions || !count)
        return InvalidParameter;

    // This gives 1/4 of the Sigma array.

    static REAL factors[] =
    {
            0,    59,   120,   182,   247,   314,   383,   454,
          527,   602,   680,   759,   841,   926,  1013,  1102,
         1194,  1288,  1385,  1485,  1587,  1692,  1800,  1911,
         2024,  2141,  2260,  2383,  2508,  2637,  2769,  2904,
         3042,  3183,  3328,  3477,  3628,  3783,  3942,  4104,
         4270,  4439,  4612,  4789,  4969,  5153,  5341,  5533,
         5728,  5928,  6131,  6338,  6549,  6764,  6983,  7206,
         7434,  7665,  7900,  8139,  8382,  8630,  8881,  9136,
         9396,  9660,  9927, 10199, 10475, 10755, 11039, 11327,
        11619, 11916, 12216, 12520, 12828, 13140, 13456, 13776,
        14099, 14427, 14758, 15093, 15431, 15774, 16119, 16469,
        16822, 17178, 17538, 17901, 18267, 18637, 19009, 19385,
        19764, 20146, 20530, 20918, 21308, 21701, 22096, 22494,
        22894, 23297, 23702, 24109, 24518, 24929, 25342, 25756,
        26173, 26591, 27010, 27431, 27853, 28276, 28701, 29126,
        29552, 29979, 30407, 30836, 31264, 31694, 32123, 32553
    };

    if(focus < 0 || focus > 1 || scale < 0 || scale > 1)
        return InvalidParameter;

    if(blendFactors && blendPositions)
    {
        INT i, n;
        scale /= 65536;
        REAL one = 65536;

        if(focus > 0 && focus < 1)
        {
            for(i = 0; i < 128; i++)
            {
                blendFactors[i] = factors[i];
                blendPositions[i] = focus*i/255;
            }
            for(i = 128; i < 256; i++)
            {
                blendFactors[i] = one - factors[255 - i];
                blendPositions[i] = focus*i/255;
            }

            // skip i = 256 since this gives the same data.

            for(i = 257; i < 384; i++)
            {
                blendFactors[i - 1] = one - factors[i - 256];
                blendPositions[i - 1] = TOREAL(focus + (1.0 - focus)*(i - 256)/255);
            }
            for(i = 384; i < 512; i++)
            {
                blendFactors[i - 1] = factors[511 - i];
                blendPositions[i - 1] = TOREAL(focus + (1.0 - focus)*(i - 256)/255);
            }

            // Set n to 511 because we skipped index 256 above to avoid
            // the duplicate 1 entry in the ramp from 0 to 1 to 0.

            n = 511;
        }
        else if(focus == 1)
        {
            for(i = 0; i < 128; i++)
            {
                blendFactors[i] = factors[i];
                blendPositions[i] = TOREAL(i)/255;
            }
            for(i = 128; i < 256; i++)
            {
                blendFactors[i] = one - factors[255 - i];
                blendPositions[i] = TOREAL(i)/255;
            }

            n = 256;
        }
        else    // focus == 0
        {
            for(i = 256; i < 384; i++)
            {
                blendFactors[i - 256] = one - factors[i - 256];
                blendPositions[i - 256] = TOREAL(i - 256)/255;
            }
            for(i = 384; i < 512; i++)
            {
                blendFactors[i - 256] = factors[511 - i];
                blendPositions[i - 256] = TOREAL(i - 256)/255;
            }

            n = 256;
        }

        for(i = 0; i < n; i++)
            blendFactors[i] *= scale;

        *count = n;
        return Ok;
    }
    else
        return InvalidParameter;
}

GpStatus
GpGradientBrush::GetLinearBlendArray(
    REAL focus,
    REAL scale,
    INT* count,
    REAL* blendFactors,
    REAL* blendPositions)
{
    if(!blendFactors || !blendPositions || !count)
        return InvalidParameter;

    if(focus < 0 || focus > 1 || scale < 0 || scale > 1)
        return InvalidParameter;

    if(blendFactors && blendPositions)
    {
        if(focus > 0 && focus < 1)
        {
            blendFactors[0] = 0.0f;
            blendFactors[1] = scale;
            blendFactors[2] = 0.0f;

            blendPositions[0] = 0.0f;
            blendPositions[1] = focus;
            blendPositions[2] = 1.0f;

            *count = 3;
        }
        else if(focus == 1)
        {
            blendFactors[0] = 0.0f;
            blendFactors[1] = scale;

            blendPositions[0] = 0.0f;
            blendPositions[1] = 1.0f;

            *count = 2;
        }
        else    // focus == 0
        {
            blendFactors[0] = scale;
            blendFactors[1] = 0.0f;

            blendPositions[0] = 0.0f;
            blendPositions[1] = 1.0f;

            *count = 2;
        }

        return Ok;
    }
    else
        return InvalidParameter;
}

GpStatus
GpGradientBrush::SetSigmaBlend(
            REAL focus,
            REAL scale)
{
    REAL*   blendFactors = (REAL*) GpMalloc(512*sizeof(REAL));
    REAL*   blendPositions = (REAL*) GpMalloc(512*sizeof(REAL));
    INT     count;
    GpStatus status;

    if(blendFactors && blendPositions)
    {
        status = GetSigmaBlendArray(focus, scale,
                    &count, blendFactors, blendPositions);

        if(status == Ok)
            status = SetBlend(&blendFactors[0], &blendPositions[0], count);
    }
    else
        status = OutOfMemory;

    GpFree(blendFactors);
    GpFree(blendPositions);

    return status;
}

GpStatus
GpGradientBrush::SetLinearBlend(
            REAL focus,
            REAL scale)
{
    REAL    blendFactors[3];
    REAL    blendPositions[3];
    INT     count;

    GpStatus status = GetLinearBlendArray(focus, scale,
                        &count, &blendFactors[0], &blendPositions[0]);

    if(status != Ok)
        return status;

    return SetBlend(&blendFactors[0], &blendPositions[0], count);
}

//--------------------------------------------------------------------------
// Hatch Brush
//--------------------------------------------------------------------------

const BYTE GdipHatchPatterns8bpp[HatchStyleTotal][64] = {
    {    //    HatchStyleHorizontal,                   0
        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    },
    {    //    HatchStyleVertical,                     1
        0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    },
    {    //    HatchStyleForwardDiagonal,              2
        0xff, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80,
        0x80, 0xff, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x80, 0xff, 0x80, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x80, 0xff, 0x80, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x80, 0xff, 0x80, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x80, 0xff, 0x80, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0xff, 0x80,
        0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0xff,
    },
    {    //    HatchStyleBackwardDiagonal,             3
        0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0xff,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0xff, 0x80,
        0x00, 0x00, 0x00, 0x00, 0x80, 0xff, 0x80, 0x00,
        0x00, 0x00, 0x00, 0x80, 0xff, 0x80, 0x00, 0x00,
        0x00, 0x00, 0x80, 0xff, 0x80, 0x00, 0x00, 0x00,
        0x00, 0x80, 0xff, 0x80, 0x00, 0x00, 0x00, 0x00,
        0x80, 0xff, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00,
        0xff, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80,
    },
    {    //    HatchStyleCross,                        4
        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
        0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    },
    {    //    HatchStyleDiagonalCross                 5
        0xff, 0x80, 0x00, 0x00, 0x00, 0x00, 0x80, 0xff,
        0x80, 0xff, 0x80, 0x00, 0x00, 0x80, 0xff, 0x80,
        0x00, 0x80, 0xff, 0x80, 0x80, 0xff, 0x80, 0x00,
        0x00, 0x00, 0x80, 0xff, 0xff, 0x80, 0x00, 0x00,
        0x00, 0x00, 0x80, 0xff, 0xff, 0x80, 0x00, 0x00,
        0x00, 0x80, 0xff, 0x80, 0x80, 0xff, 0x80, 0x00,
        0x80, 0xff, 0x80, 0x00, 0x00, 0x80, 0xff, 0x80,
        0xff, 0x80, 0x00, 0x00, 0x00, 0x00, 0x80, 0xff,
    },
    {    //    HatchStyle05Percent,                    6
        0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    },
    {    //    HatchStyle10Percent,                    7
        0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    },
    {    //    HatchStyle20Percent,                    8
        0xff, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0xff, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    },
    {    //    HatchStyle25Percent,                    9
        0xff, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00,
        0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, 0x00,
        0xff, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00,
        0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, 0x00,
        0xff, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00,
        0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, 0x00,
        0xff, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00,
        0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, 0x00,
    },
    {    //    HatchStyle30Percent,                    10
        0xff, 0x00, 0xff, 0x00, 0xff, 0x00, 0xff, 0x00,
        0x00, 0xff, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00,
        0xff, 0x00, 0xff, 0x00, 0xff, 0x00, 0xff, 0x00,
        0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff,
        0xff, 0x00, 0xff, 0x00, 0xff, 0x00, 0xff, 0x00,
        0x00, 0xff, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00,
        0xff, 0x00, 0xff, 0x00, 0xff, 0x00, 0xff, 0x00,
        0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff,
    },
    {    //    HatchStyle40Percent,                    11
        0xff, 0x00, 0xff, 0x00, 0xff, 0x00, 0xff, 0x00,
        0x00, 0xff, 0x00, 0xff, 0x00, 0xff, 0x00, 0xff,
        0xff, 0x00, 0xff, 0x00, 0xff, 0x00, 0xff, 0x00,
        0x00, 0xff, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff,
        0xff, 0x00, 0xff, 0x00, 0xff, 0x00, 0xff, 0x00,
        0x00, 0xff, 0x00, 0xff, 0x00, 0xff, 0x00, 0xff,
        0xff, 0x00, 0xff, 0x00, 0xff, 0x00, 0xff, 0x00,
        0x00, 0x00, 0x00, 0xff, 0x00, 0xff, 0x00, 0xff,
    },
    {    //    HatchStyle50Percent,                    12
        0xff, 0x00, 0xff, 0x00, 0xff, 0x00, 0xff, 0x00,
        0x00, 0xff, 0x00, 0xff, 0x00, 0xff, 0x00, 0xff,
        0xff, 0x00, 0xff, 0x00, 0xff, 0x00, 0xff, 0x00,
        0x00, 0xff, 0x00, 0xff, 0x00, 0xff, 0x00, 0xff,
        0xff, 0x00, 0xff, 0x00, 0xff, 0x00, 0xff, 0x00,
        0x00, 0xff, 0x00, 0xff, 0x00, 0xff, 0x00, 0xff,
        0xff, 0x00, 0xff, 0x00, 0xff, 0x00, 0xff, 0x00,
        0x00, 0xff, 0x00, 0xff, 0x00, 0xff, 0x00, 0xff,
    },
    {    //    HatchStyle60Percent,                    13
        0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00,
        0x00, 0xff, 0x00, 0xff, 0x00, 0xff, 0x00, 0xff,
        0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff,
        0x00, 0xff, 0x00, 0xff, 0x00, 0xff, 0x00, 0xff,
        0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00,
        0x00, 0xff, 0x00, 0xff, 0x00, 0xff, 0x00, 0xff,
        0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff,
        0x00, 0xff, 0x00, 0xff, 0x00, 0xff, 0x00, 0xff,
    },
    {    //    HatchStyle70Percent,                    14
        0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff,
        0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff,
        0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff,
        0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff,
        0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff,
        0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff,
        0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff,
        0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff,
    },
    {    //    HatchStyle75Percent,                    15
        0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff,
        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
        0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff,
        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
        0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff,
        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
        0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff,
        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
    },
    {    //    HatchStyle80Percent,                    16
        0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0xff,
        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00,
        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
        0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0xff,
        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00,
        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
    },
    {    //    HatchStyle90Percent,                    17
        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
        0xff, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff,
        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
        0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
    },
    {    //    HatchStyleLightDownwardDiagonal,        18
        0xff, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00,
        0x00, 0xff, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00,
        0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, 0x00,
        0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff,
        0xff, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00,
        0x00, 0xff, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00,
        0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, 0x00,
        0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff,
    },
    {    //    HatchStyleLightUpwardDiagonal,          19
        0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff,
        0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, 0x00,
        0x00, 0xff, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00,
        0xff, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff,
        0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, 0x00,
        0x00, 0xff, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00,
        0xff, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00,
    },
    {    //    HatchStyleDarkDownwardDiagonal,         20
        0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00,
        0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00,
        0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff,
        0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff,
        0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00,
        0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00,
        0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff,
        0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff,
    },
    {    //    HatchStyleDarkUpwardDiagonal,           21
        0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff,
        0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00,
        0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00,
        0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff,
        0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff,
        0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00,
        0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00,
        0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff,
    },
    {    //    HatchStyleWideDownwardDiagonal,         22
        0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff,
        0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff,
        0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff,
    },
    {    //    HatchStyleWideUpwardDiagonal,           23
        0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff,
        0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff,
        0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0x00,
        0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0x00, 0x00,
        0x00, 0x00, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00,
        0x00, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00,
        0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00,
        0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff,
    },
    {    //    HatchStyleLightVertical,                24
        0xff, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00,
        0xff, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00,
        0xff, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00,
        0xff, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00,
        0xff, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00,
        0xff, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00,
        0xff, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00,
        0xff, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00,
    },
    {    //    HatchStyleLightHorizontal,              25
        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    },
    {    //    HatchStyleNarrowVertical,               26
        0x00, 0xff, 0x00, 0xff, 0x00, 0xff, 0x00, 0xff,
        0x00, 0xff, 0x00, 0xff, 0x00, 0xff, 0x00, 0xff,
        0x00, 0xff, 0x00, 0xff, 0x00, 0xff, 0x00, 0xff,
        0x00, 0xff, 0x00, 0xff, 0x00, 0xff, 0x00, 0xff,
        0x00, 0xff, 0x00, 0xff, 0x00, 0xff, 0x00, 0xff,
        0x00, 0xff, 0x00, 0xff, 0x00, 0xff, 0x00, 0xff,
        0x00, 0xff, 0x00, 0xff, 0x00, 0xff, 0x00, 0xff,
        0x00, 0xff, 0x00, 0xff, 0x00, 0xff, 0x00, 0xff,
    },
    {    //    HatchStyleNarrowHorizontal,             27
        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    },
    {    //    HatchStyleDarkVertical,                 28
        0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00,
        0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00,
        0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00,
        0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00,
        0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00,
        0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00,
        0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00,
        0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00,
    },
    {    //    HatchStyleDarkHorizontal,               29
        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    },
    {    //    HatchStyleDashedDownwardDiagonal,       30
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0xff, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00,
        0x00, 0xff, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00,
        0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, 0x00,
        0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    },
    {    //    HatchStyleDashedUpwardDiagonal,         31
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff,
        0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, 0x00,
        0x00, 0xff, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00,
        0xff, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    },
    {    //    HatchStyleDashedHorizontal,             32
        0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    },
    {    //    HatchStyleDashedVertical,               33
        0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00,
    },
    {    //    HatchStyleSmallConfetti,                34
        0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00,
        0x00, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x00,
        0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff,
        0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00,
    },
    {    //    HatchStyleLargeConfetti,                35
        0xff, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0xff,
        0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff,
        0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0xff, 0xff,
        0xff, 0xff, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00,
        0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00,
        0xff, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0xff,
    },
    {    //    HatchStyleZigZag,                       36
        0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff,
        0x00, 0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0x00,
        0x00, 0x00, 0xff, 0x00, 0x00, 0xff, 0x00, 0x00,
        0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00,
        0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff,
        0x00, 0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0x00,
        0x00, 0x00, 0xff, 0x00, 0x00, 0xff, 0x00, 0x00,
        0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00,
    },
    {    //    HatchStyleWave,                         37
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00,
        0x00, 0x00, 0xff, 0x00, 0x00, 0xff, 0x00, 0xff,
        0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00,
        0x00, 0x00, 0xff, 0x00, 0x00, 0xff, 0x00, 0xff,
        0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    },
    {    //    HatchStyleDiagonalBrick,                38
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00,
        0x00, 0x00, 0xff, 0x00, 0x00, 0xff, 0x00, 0x00,
        0x00, 0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0x00,
        0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff,
    },
    {    //    HatchStyleHorizontalBrick,              39
        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
        0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
        0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00,
    },
    {    //    HatchStyleWeave,                        40
        0xff, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00,
        0x00, 0xff, 0x00, 0xff, 0x00, 0xff, 0x00, 0x00,
        0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, 0x00,
        0x00, 0xff, 0x00, 0x00, 0x00, 0xff, 0x00, 0xff,
        0xff, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0xff, 0x00, 0xff, 0x00, 0x00,
        0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, 0x00,
        0x00, 0xff, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff,
    },
    {    //    HatchStylePlaid,                        41
        0xff, 0x00, 0xff, 0x00, 0xff, 0x00, 0xff, 0x00,
        0x00, 0xff, 0x00, 0xff, 0x00, 0xff, 0x00, 0xff,
        0xff, 0x00, 0xff, 0x00, 0xff, 0x00, 0xff, 0x00,
        0x00, 0xff, 0x00, 0xff, 0x00, 0xff, 0x00, 0xff,
        0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00,
        0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00,
        0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00,
        0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00,
    },
    {    //    HatchStyleDivot,                        42
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff,
        0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    },
    {    //    HatchStyleDottedGrid,                   43
        0xff, 0x00, 0xff, 0x00, 0xff, 0x00, 0xff, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    },
    {    //    HatchStyleDottedDiamond,                44
        0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    },
    {    //    HatchStyleShingle,                      45
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff,
        0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00,
        0x00, 0xff, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00,
        0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff,
    },
    {    //    HatchStyleTrellis,                      46
        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
        0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00,
        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
        0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff,
        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
        0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00,
        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
        0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff,
    },
    {    //    HatchStyleSphere,                       47
        0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff,
        0xff, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0xff,
        0xff, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff,
        0xff, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff,
        0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff,
        0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00,
        0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00,
        0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00,
    },
    {    //    HatchStyleSmallGrid,                    48
        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
        0xff, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00,
        0xff, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00,
        0xff, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00,
        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
        0xff, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00,
        0xff, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00,
        0xff, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00,
    },
    {    //    HatchStyleSmallCheckerBoard,            49
        0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff,
        0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00,
        0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00,
        0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff,
        0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff,
        0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00,
        0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00,
        0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff,
    },
    {    //    HatchStyleLargeCheckerBoard,            50
        0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00,
        0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00,
        0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00,
        0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff,
        0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff,
        0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff,
        0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff,
    },
    {    //    HatchStyleOutlinedDiamond,              51
        0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x00,
        0x00, 0xff, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00,
        0x00, 0x00, 0xff, 0x00, 0xff, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0xff, 0x00, 0xff, 0x00, 0x00, 0x00,
        0x00, 0xff, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00,
        0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff,
    },
    {    //    HatchStyleSolidDiamond,                 52
        0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00,
        0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00,
        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00,
        0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00,
        0x00, 0x00, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    },
};

VOID
GpHatch::InitializeData()
{
    if ((DeviceBrush.Style >= HatchStyleMin) &&
        (DeviceBrush.Style <= HatchStyleMax))
    {
        GpMemcpy(DeviceBrush.Data, GdipHatchPatterns8bpp[DeviceBrush.Style], 64);
    }
    else
    {
        WARNING1("Bad Hatch Style Value");
        GpMemset(DeviceBrush.Data, 0x00, 64);   // make it transparent
    }
}

/***************************************************************************\
*
*   Equivalence comparsion functions
*
\***************************************************************************/

/**************************************************************************\
*
* Function Description:
*
*   Answer TRUE if brush and the receiver are equivalent (i.e. - they will
*   render indentically)
*
* Arguments:
*
*   [IN] brush - GpBrush, or subclass, to compare this against.
*
* Return Value:
*
*   TRUE if equivalent
*
* Created - 5/28/99 peterost
*
\**************************************************************************/

BOOL
GpHatch::IsEqual(const GpBrush * brush) const
{
    if(!brush)
        return FALSE;

    if (brush == this)
        return TRUE;

    if (GpBrush::IsEqual(brush))
    {
        const GpHatch * hbrush = static_cast<const GpHatch *>(brush);
        return hbrush->DeviceBrush.Style == DeviceBrush.Style &&
               hbrush->DeviceBrush.Colors[0].IsEqual(DeviceBrush.Colors[0]) &&
               hbrush->DeviceBrush.Colors[1].IsEqual(DeviceBrush.Colors[1]);
    }
    else
    {
        return FALSE;
    }
}

/**************************************************************************\
*
* Function Description:
*
*   Answer TRUE if brush and the receiver are equivalent (i.e. - they will
*   render indentically).  RectGradient brushes require all four colors and
*   blend factors to be equal.
*
* Arguments:
*
*   [IN] brush - GpBrush, or subclass, to compare this against.
*
* Return Value:
*
*   TRUE if equivalent
*
* Created - 5/28/99 peterost
*
\**************************************************************************/

BOOL
GpRectGradient::IsEqual(const GpBrush * brush) const
{
    if(!brush)
        return FALSE;

    if (brush == this)
        return TRUE;

    if (GpGradientBrush::IsEqual(brush))
    {
        const GpRectGradient * rbrush = static_cast<const GpRectGradient *>(brush);

        if (rbrush->DeviceBrush.UsesPresetColors == DeviceBrush.UsesPresetColors &&
            rbrush->DeviceBrush.BlendCounts[0] == DeviceBrush.BlendCounts[0] &&
            rbrush->DeviceBrush.BlendCounts[1] == DeviceBrush.BlendCounts[1])
        {
            INT i;

            if (DeviceBrush.UsesPresetColors)
            {
                // For preset colors, only the horizontal blend variables are used.
                for (INT i=0; i<DeviceBrush.BlendCounts[0]; i++)
                {
                    if (rbrush->DeviceBrush.PresetColors[i] != DeviceBrush.PresetColors[i] ||
                        rbrush->DeviceBrush.BlendPositions[0][i] != DeviceBrush.BlendPositions[0][i])
                        return FALSE;
                }

            }
            else
            {
                for (i=0; i<4; i++)
                {
                    if (!rbrush->DeviceBrush.Colors[i].IsEqual(DeviceBrush.Colors[i]))
                        return FALSE;
                }

                if (DeviceBrush.BlendCounts[0] > 1)
                {
                    for (i=0; i<DeviceBrush.BlendCounts[0]; i++)
                    {
                        if (rbrush->DeviceBrush.BlendFactors[0][i] != DeviceBrush.BlendFactors[0][i] ||
                            rbrush->DeviceBrush.BlendPositions[0][i] != DeviceBrush.BlendPositions[0][i])
                            return FALSE;
                    }
                }
                else if (rbrush->DeviceBrush.Falloffs[0] != DeviceBrush.Falloffs[0])
                {
                    return FALSE;
                }

                if (DeviceBrush.BlendCounts[1] > 1)
                {
                    for (i=0; i<DeviceBrush.BlendCounts[1]; i++)
                    {
                        if (rbrush->DeviceBrush.BlendFactors[1][i] != DeviceBrush.BlendFactors[1][i] ||
                            rbrush->DeviceBrush.BlendPositions[1][i] != DeviceBrush.BlendPositions[1][i])
                            return FALSE;
                    }
                }
                else if (rbrush->DeviceBrush.Falloffs[1] != DeviceBrush.Falloffs[1])
                {
                    return FALSE;
                }
            }

            return TRUE;

        }
        else
        {
            return FALSE;
        }
    }
    else
    {
        return FALSE;
    }

}

/**************************************************************************\
*
* Function Description:
*
*   Answer TRUE if brush and the receiver are equivalent (i.e. - they will
*   render indentically).
*
* Arguments:
*
*   [IN] brush - GpBrush, or subclass, to compare this against.
*
* Return Value:
*
*   TRUE if equivalent
*
* Created - 6/2/99 peterost
*
\**************************************************************************/
#if 0
BOOL
GpRadialGradient::IsEqual(const GpBrush * brush) const
{
    if(!brush)
        return FALSE;

    if (brush == this)
        return TRUE;

    if (GpGradientBrush::IsEqual(brush))
    {
        const GpRadialGradient * rbrush = static_cast<const GpRadialGradient *>(brush);
        if (rbrush->DeviceBrush.UsesPresetColors == DeviceBrush.UsesPresetColors &&
            rbrush->DeviceBrush.BlendCounts[0] == DeviceBrush.BlendCounts[0])
        {
            if (DeviceBrush.UsesPresetColors)
            {
                for (INT i=0; i<DeviceBrush.BlendCounts[0]; i++)
                {
                    if (rbrush->DeviceBrush.PresetColors[i] != DeviceBrush.PresetColors[i] ||
                        rbrush->DeviceBrush.BlendPositions[0][i] != DeviceBrush.BlendPositions[0][i])
                        return FALSE;
                }
            }
            else
            {
                if (rbrush->DeviceBrush.Colors[0].IsEqual(DeviceBrush.Colors[0]) &&
                    rbrush->DeviceBrush.Colors[1].IsEqual(DeviceBrush.Colors[1]))
                {
                    if (DeviceBrush.BlendCounts[0] > 1)
                    {
                        for (INT i=0; i<DeviceBrush.BlendCounts[0]; i++)
                        {
                            if (rbrush->DeviceBrush.BlendFactors[0][i] != DeviceBrush.BlendFactors[0][i] ||
                                rbrush->DeviceBrush.BlendPositions[0][i] != DeviceBrush.BlendPositions[0][i])
                                return FALSE;
                        }
                    }
                    else if (rbrush->DeviceBrush.Falloffs[0] != DeviceBrush.Falloffs[0])
                    {
                        return FALSE;
                    }
                }
                else
                {
                    return FALSE;
                }
            }

            return TRUE;
        }
        else
        {
            return FALSE;
        }
    }
    else
    {
        return FALSE;
    }

}

/**************************************************************************\
*
* Function Description:
*
*   Answer TRUE if brush and the receiver are equivalent (i.e. - they will
*   render indentically).
*
* Arguments:
*
*   [IN] brush - GpBrush, or subclass, to compare this against.
*
* Return Value:
*
*   TRUE if equivalent
*
* Created - 6/7/99 peterost
*
\**************************************************************************/

BOOL
GpTriangleGradient::IsEqual(const GpBrush * brush) const
{
    if(!brush)
        return FALSE;

    if (brush == this)
        return TRUE;

    if (GpGradientBrush::IsEqual(brush))
    {
        const GpTriangleGradient * tbrush = static_cast<const GpTriangleGradient *>(brush);
        if (tbrush->DeviceBrush.BlendCounts[0] == DeviceBrush.BlendCounts[0] &&
            tbrush->DeviceBrush.BlendCounts[1] == DeviceBrush.BlendCounts[1] &&
            tbrush->DeviceBrush.BlendCounts[2] == DeviceBrush.BlendCounts[2] &&
            tbrush->DeviceBrush.Rect.Equals(DeviceBrush.Rect))
        {
            INT   i;
            for (i=0; i<3; i++)
            {
                if (tbrush->DeviceBrush.Points[i].X != DeviceBrush.Points[i].X ||
                    tbrush->DeviceBrush.Points[i].Y != DeviceBrush.Points[i].Y ||
                    !(tbrush->DeviceBrush.Colors[i].IsEqual(DeviceBrush.Colors[i])))
                    return FALSE;
            }

            if (DeviceBrush.BlendCounts[0] > 1)
            {
                for (i=0; i<DeviceBrush.BlendCounts[0]; i++)
                {
                    if (tbrush->DeviceBrush.BlendFactors[0][i] != DeviceBrush.BlendFactors[0][i] ||
                        tbrush->DeviceBrush.BlendPositions[0][i] != DeviceBrush.BlendPositions[0][i])
                        return FALSE;
                }
            }
            else if (tbrush->DeviceBrush.Falloffs[0] != DeviceBrush.Falloffs[0])
            {
                return FALSE;
            }

            if (DeviceBrush.BlendCounts[1] > 1)
            {
                for (i=0; i<DeviceBrush.BlendCounts[1]; i++)
                {
                    if (tbrush->DeviceBrush.BlendFactors[1][i] != DeviceBrush.BlendFactors[1][i] ||
                        tbrush->DeviceBrush.BlendPositions[1][i] != DeviceBrush.BlendPositions[1][i])
                        return FALSE;
                }
            }
            else if (tbrush->DeviceBrush.Falloffs[1] != DeviceBrush.Falloffs[1])
            {
                return FALSE;
            }

            if (DeviceBrush.BlendCounts[2] > 1)
            {
                for (i=0; i<DeviceBrush.BlendCounts[2]; i++)
                {
                    if (tbrush->DeviceBrush.BlendFactors[2][i] != DeviceBrush.BlendFactors[2][i] ||
                        tbrush->DeviceBrush.BlendPositions[2][i] != DeviceBrush.BlendPositions[2][i])
                        return FALSE;
                }
            }
            else if (tbrush->DeviceBrush.Falloffs[2] != DeviceBrush.Falloffs[2])
            {
                return FALSE;
            }

            return TRUE;
        }
        else
        {
            return FALSE;
        }
    }
    else
    {
        return FALSE;
    }
}
#endif

/**************************************************************************\
*
* Function Description:
*
*   Answer TRUE if brush and the receiver are equivalent (i.e. - they will
*   render indentically).
*
* Arguments:
*
*   [IN] brush - GpBrush, or subclass, to compare this against.
*
* Return Value:
*
*   TRUE if equivalent
*
* Created - 6/7/99 peterost
*
\**************************************************************************/

BOOL
GpPathGradient::IsEqual(const GpBrush * brush) const
{
    if(!brush)
        return FALSE;

    if (brush == this)
        return TRUE;

    if (GpGradientBrush::IsEqual(brush))
    {
        const GpPathGradient * pbrush = static_cast<const GpPathGradient *>(brush);
        if (pbrush->DeviceBrush.BlendCounts[0] == DeviceBrush.BlendCounts[0] &&
            pbrush->DeviceBrush.Count == DeviceBrush.Count &&
            pbrush->DeviceBrush.OneSurroundColor == DeviceBrush.OneSurroundColor &&
            pbrush->DeviceBrush.UsesPresetColors == DeviceBrush.UsesPresetColors &&
            pbrush->DeviceBrush.Points[0].X == DeviceBrush.Points[0].X &&
            pbrush->DeviceBrush.Points[0].Y == DeviceBrush.Points[0].Y &&
            pbrush->DeviceBrush.Rect.Equals(DeviceBrush.Rect) &&
            pbrush->DeviceBrush.Colors[0].IsEqual(DeviceBrush.Colors[0])
            )
        {
            INT   i;
            for (i=0; i<DeviceBrush.Count; i++)
            {
                if (pbrush->DeviceBrush.PointsPtr[i].X != DeviceBrush.PointsPtr[i].X ||
                    pbrush->DeviceBrush.PointsPtr[i].Y != DeviceBrush.PointsPtr[i].Y ||
                    !(pbrush->DeviceBrush.ColorsPtr[i].IsEqual(DeviceBrush.ColorsPtr[i])))
                    return FALSE;
            }

            if (DeviceBrush.UsesPresetColors)
            {
                for (i=0; i<DeviceBrush.BlendCounts[0]; i++)
                {
                    if (pbrush->DeviceBrush.PresetColors[i] != DeviceBrush.PresetColors[i] ||
                        pbrush->DeviceBrush.BlendPositions[0][i] != DeviceBrush.BlendPositions[0][i])
                        return FALSE;
                }
            }
            else
            {
                if (DeviceBrush.BlendCounts[0] > 1)
                {
                    for (i=0; i<DeviceBrush.BlendCounts[0]; i++)
                    {
                        if (pbrush->DeviceBrush.BlendFactors[0][i] != DeviceBrush.BlendFactors[0][i] ||
                            pbrush->DeviceBrush.BlendPositions[0][i] != DeviceBrush.BlendPositions[0][i])
                            return FALSE;
                    }
                }
                else if (pbrush->DeviceBrush.Falloffs[0] != DeviceBrush.Falloffs[0])
                {
                    return FALSE;
                }
            }
        }

        return TRUE;
    }
    else
    {
        return FALSE;
    }

}


DpOutputSpan*
GpSolidFill::CreateOutputSpan(
    DpScanBuffer *  scan,
    DpContext *context,
    const GpRect *drawBounds)
{
    return new DpOutputSolidColorSpan(
                    DeviceBrush.SolidColor.GetPremultipliedValue(),
                    scan
                    );
}

DpOutputSpan*
GpRectGradient::CreateOutputSpan(
    DpScanBuffer *  scan,
    DpContext *context,
    const GpRect *drawBounds)
{
    DpOutputSpan* span = NULL;

    ARGB argb[4];

    for(INT i = 0; i < 4; i++)
    {
        argb[i] = DeviceBrush.Colors[i].GetValue();
    }

    BOOL isHorizontal = FALSE;
    BOOL isVertical = FALSE;

    if(HasPresetColors() && DeviceBrush.BlendCounts[0] > 1)
        isHorizontal = TRUE;

    if(!isHorizontal && argb[0] == argb[2] && argb[1] == argb[3])
        isHorizontal = TRUE;

    if(!isHorizontal && argb[0] == argb[1] && argb[2] == argb[3])
        isVertical = TRUE;

    if(!isHorizontal && !isVertical)
    {
        span = new DpOutputGradientSpan(this, scan, context);
    }
    else
    {
        // !!![andrewgo] Not sure why a LinearGradient is coming down to us
        //               as BrushRectGrad - if it comes down as a BrushTypeLinearGradient
        //               (as it should) then we don't have to do any of the
        //               above 'isHorizontal', 'isVertical' stuff

        FPUStateSaver fpuState; // Set the rounding mode.

        if ((GetBrushType() == BrushTypeLinearGradient) /*|| (GetBrushType() == BrushRectGrad)*/)
        {
            if (OSInfo::HasMMX)
            {
                span = new DpOutputLinearGradientSpan_MMX(this, scan, context);
            }
            else
            {
                span = new DpOutputLinearGradientSpan(this, scan, context);
            }
        }
        else
        {
            span = new DpOutputOneDGradientSpan(this, scan, context,
                                                isHorizontal, isVertical);
        }
    }

    if (span && !span->IsValid())
    {
        delete span;
        span = NULL;
    }

    return span;
}

#if 0
DpOutputSpan*
GpRadialGradient::CreateOutputSpan(
    DpScanBuffer *  scan,
    DpContext *context,
    const GpRect *drawBounds)
{
    return new DpOutputOneDGradientSpan(
                    this,
                    scan,
                    context
                    );
}


DpOutputSpan*
GpTriangleGradient::CreateOutputSpan(
    DpScanBuffer *  scan,
    DpContext *context,
    const GpRect *drawBounds)
{
    return new DpOutputTriangleGradientSpan(
                    this,
                    scan,
                    context
                    );
}
#endif

DpOutputSpan*
GpPathGradient::CreateOutputSpan(
    DpScanBuffer *  scan,
    DpContext *context,
    const GpRect *drawBounds)
{
    FPUStateSaver::AssertMode();

    DpOutputSpan* span = NULL;
    WrapMode  wrap = DeviceBrush.Wrap;

    // Check to see if a tiled gradient is really needed.  It 
    // is not necessary if the transformed drawbounds fit 
    // entirely within the bounds of the brush rectangle.
    if (drawBounds && wrap != WrapModeClamp)
    {
        GpMatrix inverseXForm = context->WorldToDevice;
        
        if (Ok == inverseXForm.Invert())
        {  
            GpRectF brushRect = DeviceBrush.Rect;
            GpRectF transformRect;
            
            TransformBounds(
                &inverseXForm, 
                (REAL)drawBounds->GetLeft(),
                (REAL)drawBounds->GetTop(),
                (REAL)drawBounds->GetRight(),
                (REAL)drawBounds->GetBottom(),
                &transformRect
            );

            if (brushRect.Contains(transformRect))
            {
                wrap = WrapModeClamp;
            }
        }
    }

    if(wrap == WrapModeClamp)
    {
        if(!DeviceBrush.OneSurroundColor)
        {
            span = new DpOutputPathGradientSpan(
                this,
                scan,
                context
            );

        }
        else
        {
            span = new DpOutputOneDPathGradientSpan(
                this,
                scan,
                context
            );
        }
    }
    else
    {
        INT width, height, ix, iy;

        GpRectF brushRect = DeviceBrush.Rect;

        // Create a texture brush to represent this path gradient brush.
        // We do this by creating a texture as close to device resolution 
        // as we can and computing the transform (brush to world) for the
        // texture brush decomposed into two transforms that take the 
        // brush via device space. The texture brush transform 
        // usually works out to be the inverse of the world to device, so
        // the final texture brush draws with a resultant identity transform
        // regardless of the world to device matrix. (exception when there is 
        // a rotation in the w2d).

        GpPointF worldDestPoints[3];
        worldDestPoints[0].X = brushRect.X ;
        worldDestPoints[0].Y = brushRect.Y;
        worldDestPoints[1].X = worldDestPoints[0].X + brushRect.Width;
        worldDestPoints[1].Y = worldDestPoints[0].Y;
        worldDestPoints[2].X = worldDestPoints[0].X;
        worldDestPoints[2].Y = worldDestPoints[0].Y + brushRect.Height;

        // Take into account transformation by both the brush xform and
        // the world to device. This will handle transforms such as
        // UnitInch and w2d scales.
        
        // First get the destination points in world space by applying the 
        // brush transform.
        
        DeviceBrush.Xform.Transform(worldDestPoints, 3);
        
        GpPointF deviceDestPoints[3];
        GpMemcpy(deviceDestPoints, worldDestPoints, sizeof(worldDestPoints));
        
        // Now get the device space destination points by applying the 
        // world to device transform.
        
        context->WorldToDevice.Transform(deviceDestPoints, 3);
        
        // Compute the bounds in device space.
        
        REAL xmin, xmax, ymin, ymax, nextX, nextY;
        
        xmin = xmax = deviceDestPoints[1].X + 
            deviceDestPoints[2].X - deviceDestPoints[0].X;
            
        ymin = ymax = deviceDestPoints[1].Y + 
            deviceDestPoints[2].Y - deviceDestPoints[0].Y;
        
        for(INT i = 0; i < 3; i++)
        {
            nextX = deviceDestPoints[i].X;
            nextY = deviceDestPoints[i].Y;

            if(nextX < xmin)
                xmin = nextX;
            else if(nextX > xmax)
                xmax = nextX;
            if(nextY < ymin)
                ymin = nextY;
            else if(nextY > ymax)
                ymax = nextY;
        }

        // Set the optimal bitmap bounds.

        ix = GpRound(xmin);
        iy = GpRound(ymin);
        width = GpRound(xmax) - ix;
        height = GpRound(ymax) - iy;
        GpRectF bitmapBounds(0, 0, TOREAL(width), TOREAL(height));

        // Decompose brushRect --> worldDestPoints transform into two matrix.
        // mat1: brushRect --> bitmapBounds (device space)
        // mat2: bitmapBounds --> worldDestPoints

        GpMatrix mat1, mat2;
        mat1.InferAffineMatrix(bitmapBounds, brushRect);
        mat2.InferAffineMatrix(worldDestPoints, bitmapBounds);

        if(width <= 0 || height <= 0)
            return NULL;

        // Create a bitmap which the gradient will be drawn onto.
        // Make it the full width and height of the gradient, even
        // though only a small portion may be used to simplify 
        // handling by downstream functions.
        
        GpBitmap* bitmap = new GpBitmap(width, height, PixelFormat32bppARGB);  

        if(bitmap)
        {
            GpGraphics* g = bitmap->GetGraphicsContext();
            if(g)
            {
                GpLock lock(g->GetObjectLock());
                
                // Set the transform to brushRect --> bitmapBounds.

                g->MultiplyWorldTransform(mat1);

                WrapMode savedWrapMode = DeviceBrush.Wrap;
                DeviceBrush.Wrap = WrapModeClamp;
                GpMatrix savedMat = DeviceBrush.Xform;
                DeviceBrush.Xform.Reset();

                g->FillRect(this, brushRect.X, brushRect.Y,
                            brushRect.Width, brushRect.Height);
                
                DeviceBrush.Wrap = savedWrapMode;
                DeviceBrush.Xform = savedMat;

                if(MorphedBrush)
                    delete MorphedBrush;

                // Create a texuture with a unit tile and set the
                // brush transform to bitmapBounds --> worldDestPoints.

                GpTexture* texture = new GpTexture(bitmap, savedWrapMode);
                
                // span must be NULL at this point. If it's not, we're going 
                // to leak memory when we create it below, or in the case of
                // an error out, we may end up with uninitialized memory
                // being returned to the caller.
                
                ASSERT(span == NULL);
                
                if(texture)
                {
                    texture->MultiplyTransform(mat2);
    
                    span = texture->CreateOutputSpan(scan, context, drawBounds);
                }
                
                // Even if we failed to create the texture, we still want to
                // set a reasonable (NULL) value for MorphedBrush so that we
                // don't have a dangling pointer.
                
                MorphedBrush = texture;
            }
            
            // We're done with this graphics.
            // NOTE: this is explicitly done outside of the scope of the 
            // GpLock object, so that the GpLock (which modifies the graphics
            // in its destructor) doesn't touch freed memory.
            
            delete g;

            bitmap->Dispose();            
        }
    }

    return span;
}

DpOutputSpan*
GpTexture::CreateOutputSpan(
    DpScanBuffer *scan,
    DpContext *context,
    const GpRect *drawBounds)
{
    DpOutputBilinearSpan *textureSpan = NULL;
    GpMatrix brushTransform;
    GpMatrix worldToDevice;

    // Figure out the world-to-device transform:

    worldToDevice = context->WorldToDevice;
    this->GetTransform(&brushTransform);
    worldToDevice.Prepend(brushTransform);

    // Go through our heirarchy of scan drawers:
    if (worldToDevice.IsIntegerTranslate() &&
        ((this->GetWrapMode() == WrapModeTile) ||
         (this->GetWrapMode() == WrapModeClamp)))
    {
        textureSpan = new DpOutputBilinearSpan_Identity(this,
                                                       scan,
                                                       &worldToDevice,
                                                       context);
    }
    else if (OSInfo::HasMMX &&
             GpValidFixed16(DeviceBrush.Rect.Width) &&
             GpValidFixed16(DeviceBrush.Rect.Height))
    {
        textureSpan = new DpOutputBilinearSpan_MMX(this,
                                                  scan,
                                                  &worldToDevice,
                                                  context);
    }

    // Scan drawer creation may fail, so clean up and try one last time
    if ((textureSpan) && !textureSpan->IsValid())
    {
        delete textureSpan;
        textureSpan = NULL;
    }

    if (!textureSpan)
    {
        textureSpan = new DpOutputBilinearSpan(this,
                                              scan,
                                              &worldToDevice,
                                              context);
    }

    if ((textureSpan) && !textureSpan->IsValid())
    {
        delete textureSpan;
        textureSpan = NULL;
    }

    return textureSpan;
}

DpOutputSpan*
GpHatch::CreateOutputSpan(
    DpScanBuffer *  scan,
    DpContext *context,
    const GpRect *drawBounds)
{
    if (StretchFactor == 1)
    {
        return new DpOutputHatchSpan(
                        this,
                        scan,
                        context
                        );
    }
    else
    {
        return new DpOutputStretchedHatchSpan(
                        this,
                        scan,
                        context,
                        StretchFactor
                        );
    }
}

class SolidBrushData : public ObjectTypeData
{
public:
    ARGB        SolidColor;
};

/**************************************************************************\
*
* Function Description:
*
*   Get the brush data.
*
* Arguments:
*
*   [IN] dataBuffer - fill this buffer with the data
*   [IN/OUT] size   - IN - size of buffer; OUT - number bytes written
*
* Return Value:
*
*   GpStatus - Ok or error code
*
* Created:
*
*   9/13/1999 DCurtis
*
\**************************************************************************/
GpStatus
GpSolidFill::GetData(
    IStream *   stream
    ) const
{
    ASSERT (stream != NULL);

    SolidBrushData  brushData;
    brushData.Type       = DeviceBrush.Type;
    brushData.SolidColor = DeviceBrush.SolidColor.GetValue();
    stream->Write(&brushData, sizeof(brushData), NULL);
    return Ok;
}

UINT
GpSolidFill::GetDataSize() const
{
    return sizeof(SolidBrushData);
}

/**************************************************************************\
*
* Function Description:
*
*   Read the brush object from memory.
*
* Arguments:
*
*   [IN] dataBuffer - the data that was read from the stream
*   [IN] size - the size of the data
*
* Return Value:
*
*   GpStatus - Ok or failure status
*
* Created:
*
*   4/26/1999 DCurtis
*
\**************************************************************************/
GpStatus
GpSolidFill::SetData(
    const BYTE *        dataBuffer,
    UINT                size
    )
{
    ASSERT ((GpBrushType)(((SolidBrushData *)dataBuffer)->Type) == BrushTypeSolidColor);

    if (dataBuffer == NULL)
    {
        WARNING(("dataBuffer is NULL"));
        return InvalidParameter;
    }

    if (size < sizeof(SolidBrushData))
    {
        WARNING(("size too small"));
        return InvalidParameter;
    }

    if (!((SolidBrushData *)dataBuffer)->MajorVersionMatches())
    {
        WARNING(("Version number mismatch"));
        return InvalidParameter;
    }

    SetColor(GpColor(((SolidBrushData *)dataBuffer)->SolidColor));

    return Ok;
}

GpStatus
GpSolidFill::ColorAdjust(
    GpRecolor *             recolor,
    ColorAdjustType         type
    )
{
    if(!recolor)
        return InvalidParameter;

    if (type == ColorAdjustTypeDefault)
    {
        type = ColorAdjustTypeBrush;
    }

    ARGB    solidColor32 = Color.GetValue();

    recolor->ColorAdjust(&solidColor32, 1, type);

    this->SetColor(GpColor(solidColor32));
    return Ok;
}

class TextureBrushData : public ObjectTypeData
{
public:
    INT32       Flags;
    INT32       Wrap;
};

/**************************************************************************\
*
* Function Description:
*
*   Get the brush data.
*
* Arguments:
*
*   [IN] dataBuffer - fill this buffer with the data
*   [IN/OUT] size   - IN - size of buffer; OUT - number bytes written
*
* Return Value:
*
*   GpStatus - Ok or error code
*
* Created:
*
*   9/13/1999 DCurtis
*
\**************************************************************************/
GpStatus
GpTexture::GetData(
    IStream *   stream
    ) const
{
    ASSERT (stream != NULL);

    if (Image == NULL)
    {
        WARNING(("Image is NULL"));
        return Ok;
    }

    INT         flags = 0;

    if (DeviceBrush.IsGammaCorrected)
    {
        flags |= GDIP_BRUSHFLAGS_ISGAMMACORRECTED;
    }

    if (!DeviceBrush.Xform.IsIdentity())
    {
        flags |= GDIP_BRUSHFLAGS_TRANSFORM;
    }

    TextureBrushData    brushData;
    brushData.Type  = DeviceBrush.Type;
    brushData.Flags = flags;
    brushData.Wrap  = DeviceBrush.Wrap;
    stream->Write(&brushData, sizeof(brushData), NULL);

    if (flags & GDIP_BRUSHFLAGS_TRANSFORM)
    {
        DeviceBrush.Xform.WriteMatrix(stream);
    }

    return Image->GetData(stream);
}

UINT
GpTexture::GetDataSize() const
{
    if (Image == NULL)
    {
        WARNING(("Image is NULL"));
        return 0;
    }

    UINT    size = sizeof(TextureBrushData);

    if (!DeviceBrush.Xform.IsIdentity())
    {
        size += GDIP_MATRIX_SIZE;
    }

    size += Image->GetDataSize();

    return size;
}

/**************************************************************************\
*
* Function Description:
*
*   Read the brush object from memory.
*
* Arguments:
*
*   [IN] dataBuffer - the data that was read from the stream
*   [IN] size - the size of the data
*
* Return Value:
*
*   GpStatus - Ok or failure status
*
* Created:
*
*   4/26/1999 DCurtis
*
\**************************************************************************/
GpStatus
GpTexture::SetData(
    const BYTE *        dataBuffer,
    UINT                size
    )
{
    ASSERT ((GpBrushType)(((TextureBrushData *)dataBuffer)->Type) == BrushTypeTextureFill);

    if (dataBuffer == NULL)
    {
        WARNING(("dataBuffer is NULL"));
        return InvalidParameter;
    }

    if (size < sizeof(TextureBrushData))
    {
        WARNING(("size too small"));
        return InvalidParameter;
    }

    const TextureBrushData *    brushData;

    brushData = reinterpret_cast<const TextureBrushData *>(dataBuffer);

    if (!brushData->MajorVersionMatches())
    {
        WARNING(("Version number mismatch"));
        return InvalidParameter;
    }

    DeviceBrush.Type             = BrushTypeTextureFill;
    DeviceBrush.Wrap             = (GpWrapMode) brushData->Wrap;
    DeviceBrush.IsGammaCorrected = ((brushData->Flags & GDIP_BRUSHFLAGS_ISGAMMACORRECTED) != 0);

    dataBuffer += sizeof(TextureBrushData);
    size       -= sizeof(TextureBrushData);

    if (brushData->Flags & GDIP_BRUSHFLAGS_TRANSFORM)
    {
        if (size < GDIP_MATRIX_SIZE)
        {
            WARNING(("size too small"));
            return InvalidParameter;
        }
        DeviceBrush.Xform.SetMatrix((REAL *)dataBuffer);
        dataBuffer += GDIP_MATRIX_SIZE;
        size       -= GDIP_MATRIX_SIZE;
    }
    if (Image != NULL)
    {
        Image->Dispose();
        Image = NULL;
    }

    if (size >= sizeof(ObjectTypeData))
    {
        Image = (GpImage *)GpObject::Factory(ObjectTypeImage, (const ObjectData *)dataBuffer, size);

        if (Image != NULL)
        {
            if ((Image->SetData(dataBuffer, size) == Ok) && Image->IsValid() &&
                ((ImageType = Image->GetImageType()) == ImageTypeBitmap))
            {
                GpPageUnit  unit;
                Image->GetBounds(&DeviceBrush.Rect, &unit);
                SetValid(TRUE);
                UpdateUid();
                return Ok;
            }
            Image->Dispose();
            Image = NULL;
        }
    }
    WARNING(("Failure getting image"));
    GpMemset(&DeviceBrush.Rect, 0, sizeof(DeviceBrush.Rect));
    SetValid(FALSE);
    return GenericError;
}

GpStatus
GpTexture::ColorAdjust(
    GpRecolor *             recolor,
    ColorAdjustType         type
    )
{
    if (type == ColorAdjustTypeDefault)
    {
        type = ColorAdjustTypeBrush;
    }

    if (Image != NULL)
    {
        Image->ColorAdjust(recolor, type);
        UpdateUid();
    }

    return Ok;
}


VOID GpTexture::InitializeBrush(
    GpImage* image,
    GpWrapMode wrapMode,
    const GpRectF* rect,
    const GpImageAttributes *imageAttributes)
{
    ASSERT(image && image->IsValid());

    if (!WrapModeIsValid(wrapMode))
    {
        WARNING(("bad wrap mode"));
        goto Failure;
    }

    GpImageType imageType;

    imageType = image->GetImageType();

    if (imageType == ImageTypeBitmap)
    {
        InitializeBrushBitmap(
            static_cast<GpBitmap*>(image),
            wrapMode, rect, imageAttributes
        );
    }
    else if (imageType == ImageTypeMetafile)
    {
        // For now, convert the metafile into a bitmap image and use that to
        // create the brush.

        GpBitmap *  bitmapImage;

        if (rect != NULL)
        {
            // !!! we don't handle this case yet
            if ((rect->X != 0) || (rect->Y != 0))
            {
                WARNING(("No handling for non-zero start in metafiles"));
            }
            // Don't apply the imageAttributes now, because WMF/EMF rendering
            // doesn't support alpha.  So wait until it's been converted to
            // a bitmap to apply the imageAttributes.

            bitmapImage = ((GpMetafile *)image)->GetBitmap(
                            GpRound(rect->Width),
                            GpRound(rect->Height), NULL);
        }
        else
        {
            // Let the metafile decide how big the bitmap should be

            // Don't apply the imageAttributes now, because WMF/EMF rendering
            // doesn't support alpha.  So wait until it's been converted to
            // a bitmap to apply the imageAttributes.

            bitmapImage = ((GpMetafile *)image)->GetBitmap(0, 0, NULL);
        }

        if (bitmapImage != NULL)
        {
            ASSERT (bitmapImage->IsValid());

            InitializeBrushBitmap(bitmapImage, wrapMode, NULL, imageAttributes, TRUE);
            return;
        }
        goto Failure;
    }
    else    // unknown image type
    {
        WARNING(("unknown image type"));
Failure:
        Image = NULL;
        SetValid(FALSE);
    }
}

VOID GpTexture::InitializeBrushBitmap(
    GpBitmap* bitmap,
    GpWrapMode wrapMode,
    const GpRectF* rect,
    const GpImageAttributes *imageAttributes,
    BOOL useBitmap)
{
    DeviceBrush.Type = BrushTypeTextureFill;
    DeviceBrush.Wrap = wrapMode;

    ImageType = ImageTypeBitmap;

    Image = NULL;

    FPUStateSaver fpState;   // Setup the fpu state.

    if (bitmap && bitmap->IsValid())
    {
        GpRect *pRectI = NULL;
        GpRect recti;
        if(rect)
        {
            recti.X = GpRound(rect->X);
            recti.Y = GpRound(rect->Y);
            recti.Width = GpRound(rect->Width);
            recti.Height = GpRound(rect->Height);
            pRectI = &recti;
        }

        if(imageAttributes)
        {
            GpBitmap *dst = NULL;

            if (bitmap->Recolor(
                imageAttributes->recolor, &dst,
                NULL, NULL, pRectI
            ) == Ok)
            {
                Image = dst;
                
                // If useBitmap is TRUE that means the caller has transferred
                // ownership of bitmap to us. In this case, Recolor makes
                // a clone of the bitmap that we're going to use, so we have
                // to free the bitmap passed in and use the clone instead,
                // otherwise we leak.
                
                if(useBitmap)
                {
                    bitmap->Dispose();
                }
            }
        }

        // !!! note that this should be non-premultiplied ARGB.
        //     we'll fix this when we drop premultiplied data [asecchia]
        //     also note that the output of RecolorImage is 32BPP_ARGB

        // if it's not NULL it's because the RecolorImage code cloned it already
        if (Image == NULL)
        {
            if (useBitmap)
            {
                // This is for the case where we constructed a bitmap
                // from a metafile image.
                Image = bitmap;
            }
            else
            {
                #ifdef NO_PREMULTIPLIED_ALPHA
                Image = bitmap->Clone(pRectI, PIXFMT_32BPP_ARGB);
                #else
                Image = bitmap->Clone(pRectI, PIXFMT_32BPP_PARGB);
                #endif
            }
        }
    }

    if (Image && Image->IsValid())
    {
        SetValid(TRUE);

        // Rect is given as a pixel unit in bitmap.

        GpPageUnit unit;
        Image->GetBounds(&DeviceBrush.Rect, &unit);
    }
    else
    {
        SetValid(FALSE);

        GpMemset(&DeviceBrush.Rect,
                 0,
                 sizeof(DeviceBrush.Rect));
    }
}

// See if this texture fill is really a picture fill (with a bitmap, 
// not a metafile).
BOOL 
GpTexture::IsPictureFill(
    const GpMatrix *    worldToDevice,
    const GpRect *      drawBounds
    ) const
{
    ASSERT ((drawBounds->Width > 0) && (drawBounds->Height > 0));

    BOOL        isPictureFill = FALSE;
    GpMatrix    newBrushMatrix;
    
    this->GetTransform(&newBrushMatrix);

    if (worldToDevice != NULL)
    {
        newBrushMatrix.Append(*worldToDevice);
    }

    newBrushMatrix.Translate(
        (REAL)-(drawBounds->X),
        (REAL)-(drawBounds->Y),
        MatrixOrderAppend
    );

    // See if the texture is supposed to fill the drawBounds.  
    // If so, this is a picture fill.
    if (newBrushMatrix.IsTranslateScale())
    {
        Size    size;

        // If the texture is not a bitmap, this returns InvalidParameter.
        if (this->GetBitmapSize(&size) == Ok)
        {
            GpRectF     transformedRect(0.0f, 0.0f, (REAL)size.Width, (REAL)size.Height);
            newBrushMatrix.TransformRect(transformedRect);

            // get the transformed width
            INT     deltaValue = abs(GpRound(transformedRect.Width) - drawBounds->Width);
            
            // We might be off a little because of the pixel offset mode
            // or a matrix that isn't quite right for whatever reason.
            if (deltaValue <= 2)
            {
                // get the transformed height
                deltaValue = abs(GpRound(transformedRect.Height) - drawBounds->Height);

                if (deltaValue <= 2)
                {
                    if ((abs(GpRound(transformedRect.X)) <= 2) &&
                        (abs(GpRound(transformedRect.Y)) <= 2))
                    {
                        isPictureFill = TRUE;
                    }
                }
            }
        }
    }
    return isPictureFill;
}


class RectGradientBrushData : public ObjectTypeData
{
public:
    INT32       Flags;
    INT32       Wrap;
    GpRectF     Rect;
    UINT32      Color0;
    UINT32      Color1;
    UINT32      Color2;
    UINT32      Color3;
};

/**************************************************************************\
*
* Function Description:
*
*   Get the brush data.
*
* Arguments:
*
*   [IN] dataBuffer - fill this buffer with the data
*   [IN/OUT] size   - IN - size of buffer; OUT - number bytes written
*
* Return Value:
*
*   GpStatus - Ok or error code
*
* Created:
*
*   9/13/1999 DCurtis
*
\**************************************************************************/
GpStatus
GpRectGradient::GetData(
    IStream *   stream
    ) const
{
    ASSERT (stream != NULL);

    INT         flags    = 0;

    if (DeviceBrush.IsGammaCorrected)
    {
        flags |= GDIP_BRUSHFLAGS_ISGAMMACORRECTED;
    }

    if (!DeviceBrush.Xform.IsIdentity())
    {
        flags |= GDIP_BRUSHFLAGS_TRANSFORM;
    }

    // Note: can't have both blendFactors and presetColors at the same time
    // PresetColors used for GpLineGradient, but not for GpRectGradient.
    if (DeviceBrush.UsesPresetColors && (DeviceBrush.BlendCounts[0] > 1) && (DeviceBrush.PresetColors != NULL) &&
        (DeviceBrush.BlendPositions[0] != NULL) && (DeviceBrush.BlendFactors[0] == NULL))
    {
        flags |= GDIP_BRUSHFLAGS_PRESETCOLORS;
    }

    if ((DeviceBrush.BlendCounts[0] > 1) && (DeviceBrush.BlendFactors[0] != NULL) && (DeviceBrush.BlendPositions[0] != NULL))
    {
        flags |= GDIP_BRUSHFLAGS_BLENDFACTORSH;
    }

    if ((DeviceBrush.BlendCounts[1] > 1) && (DeviceBrush.BlendFactors[1] != NULL) && (DeviceBrush.BlendPositions[1] != NULL))
    {
        flags |= GDIP_BRUSHFLAGS_BLENDFACTORSV;
    }

    RectGradientBrushData   brushData;
    brushData.Type   = DeviceBrush.Type;
    brushData.Flags  = flags;
    brushData.Wrap   = DeviceBrush.Wrap;
    brushData.Rect   = DeviceBrush.Rect;
    brushData.Color0 = DeviceBrush.Colors[0].GetValue();
    brushData.Color1 = DeviceBrush.Colors[1].GetValue();
    brushData.Color2 = DeviceBrush.Colors[2].GetValue();
    brushData.Color3 = DeviceBrush.Colors[3].GetValue();
    stream->Write(&brushData, sizeof(brushData), NULL);

    if (flags & GDIP_BRUSHFLAGS_TRANSFORM)
    {
        DeviceBrush.Xform.WriteMatrix(stream);
    }

    if (flags & GDIP_BRUSHFLAGS_PRESETCOLORS)
    {
        INT     realSize = DeviceBrush.BlendCounts[0] * sizeof(REAL);
        INT     argbSize = DeviceBrush.BlendCounts[0] * sizeof(ARGB);

        stream->Write(&DeviceBrush.BlendCounts[0], sizeof(INT32), NULL);
        stream->Write(DeviceBrush.BlendPositions[0], realSize, NULL);
        stream->Write(DeviceBrush.PresetColors, argbSize, NULL);
    }

    if (flags & GDIP_BRUSHFLAGS_BLENDFACTORSH)
    {
        INT     realSize = DeviceBrush.BlendCounts[0] * sizeof(REAL);

        stream->Write(&DeviceBrush.BlendCounts[0], sizeof(INT32), NULL);
        stream->Write(DeviceBrush.BlendPositions[0], realSize, NULL);
        stream->Write(DeviceBrush.BlendFactors[0], realSize, NULL);
    }

    if (flags & GDIP_BRUSHFLAGS_BLENDFACTORSV)
    {
        INT     realSize = DeviceBrush.BlendCounts[1] * sizeof(REAL);

        stream->Write(&DeviceBrush.BlendCounts[1], sizeof(INT32), NULL);
        stream->Write(DeviceBrush.BlendPositions[1], realSize, NULL);
        stream->Write(DeviceBrush.BlendFactors[1], realSize, NULL);
    }

    return Ok;
}

UINT
GpRectGradient::GetDataSize() const
{
    UINT        size = sizeof(RectGradientBrushData);

    if (!DeviceBrush.Xform.IsIdentity())
    {
        size += GDIP_MATRIX_SIZE;
    }

    // Note: can't have both blendFactors and presetColors at the same time
    // PresetColors used for GpLineGradient, but not for GpRectGradient.
    if (DeviceBrush.UsesPresetColors && (DeviceBrush.BlendCounts[0] > 1) && (DeviceBrush.PresetColors != NULL) &&
        (DeviceBrush.BlendPositions[0] != NULL) && (DeviceBrush.BlendFactors[0] == NULL))
    {
        size += sizeof(INT32) + ((sizeof(ARGB) + sizeof(REAL)) * DeviceBrush.BlendCounts[0]);
    }

    if ((DeviceBrush.BlendCounts[0] > 1) && (DeviceBrush.BlendFactors[0] != NULL) && (DeviceBrush.BlendPositions[0] != NULL))
    {
        size += sizeof(INT32) + ((sizeof(REAL) + sizeof(REAL)) * DeviceBrush.BlendCounts[0]);
    }

    if ((DeviceBrush.BlendCounts[1] > 1) && (DeviceBrush.BlendFactors[1] != NULL) && (DeviceBrush.BlendPositions[1] != NULL))
    {
        size += sizeof(INT32) + ((sizeof(REAL) + sizeof(REAL)) * DeviceBrush.BlendCounts[1]);
    }

    return size;
}

/**************************************************************************\
*
* Function Description:
*
*   Read the brush object from memory.
*
* Arguments:
*
*   [IN] dataBuffer - the data that was read from the stream
*   [IN] size - the size of the data
*
* Return Value:
*
*   GpStatus - Ok or failure status
*
* Created:
*
*   4/26/1999 DCurtis
*
\**************************************************************************/
GpStatus
GpRectGradient::SetData(
    const BYTE *        dataBuffer,
    UINT                size
    )
{
    ASSERT ((GpBrushType)(((RectGradientBrushData *)dataBuffer)->Type) == BrushTypeLinearGradient);

    if (dataBuffer == NULL)
    {
        WARNING(("dataBuffer is NULL"));
        return InvalidParameter;
    }

    if (size < sizeof(RectGradientBrushData))
    {
        WARNING(("size too small"));
        return InvalidParameter;
    }

    const RectGradientBrushData *   brushData;
    GpColor                         colors[4];

    brushData = reinterpret_cast<const RectGradientBrushData *>(dataBuffer);

    if (!brushData->MajorVersionMatches())
    {
        WARNING(("Version number mismatch"));
        return InvalidParameter;
    }

    colors[0].SetValue(brushData->Color0);
    colors[1].SetValue(brushData->Color1);
    colors[2].SetValue(brushData->Color2);
    colors[3].SetValue(brushData->Color3);

    InitializeBrush(brushData->Rect, colors, (GpWrapMode) brushData->Wrap);

    DeviceBrush.IsGammaCorrected = ((brushData->Flags & GDIP_BRUSHFLAGS_ISGAMMACORRECTED) != 0);

    dataBuffer += sizeof(RectGradientBrushData);
    size       -= sizeof(RectGradientBrushData);

    if (brushData->Flags & GDIP_BRUSHFLAGS_TRANSFORM)
    {
        if (size < GDIP_MATRIX_SIZE)
        {
            WARNING(("size too small"));
            return InvalidParameter;
        }
        DeviceBrush.Xform.SetMatrix((REAL *)dataBuffer);
        dataBuffer += GDIP_MATRIX_SIZE;
        size       -= GDIP_MATRIX_SIZE;
    }

    if (brushData->Flags & GDIP_BRUSHFLAGS_PRESETCOLORS)
    {
        if (size < sizeof(INT32))
        {
            WARNING(("size too small"));
            return InvalidParameter;
        }

        UINT count = ((INT32 *)dataBuffer)[0];
        dataBuffer += sizeof(INT32);
        size       -= sizeof(INT32);

        UINT realSize = count * sizeof(REAL);
        UINT argbSize = count * sizeof(ARGB);

        if (size < (realSize + argbSize))
        {
            WARNING(("size too small"));
            return InvalidParameter;
        }

        ARGB* newColors    = (ARGB*) GpRealloc(DeviceBrush.PresetColors, argbSize);

        if (newColors != NULL)
        {
            // We have to just copy in the ARGB values, because they've already
            // been premultiplied.
            // Actually PresetColors is NON-premultiplied, but this code should
            // still be right because we write them out non-premultiplied too.
            
            GpMemcpy(newColors, dataBuffer + realSize, argbSize);
            DeviceBrush.PresetColors = newColors;

            REAL* newPositions = (REAL*) GpRealloc(DeviceBrush.BlendPositions[0], realSize);

            if (newPositions != NULL)
            {
                GpMemcpy(newPositions, dataBuffer, realSize);
                DeviceBrush.BlendPositions[0] = newPositions;

                GpFree(DeviceBrush.BlendFactors[0]);
                DeviceBrush.BlendFactors[0] = NULL;
                DeviceBrush.UsesPresetColors = TRUE;
                DeviceBrush.BlendCounts[0] = count;
            }
        }

        dataBuffer += (realSize + argbSize);
        size       -= (realSize + argbSize);
    }

    if (brushData->Flags & GDIP_BRUSHFLAGS_BLENDFACTORSH)
    {
        if (size < sizeof(INT32))
        {
            WARNING(("size too small"));
            return InvalidParameter;
        }

        UINT count = ((INT32 *)dataBuffer)[0];
        dataBuffer += sizeof(INT32);
        size       -= sizeof(INT32);

        UINT realSize = count * sizeof(REAL);

        if (size < (2 * realSize))
        {
            WARNING(("size too small"));
            return InvalidParameter;
        }

        this->SetHorizontalBlend((REAL *)(dataBuffer + realSize),(REAL *)dataBuffer, count);
        dataBuffer += (2 * realSize);
        size       -= (2 * realSize);
    }

    if (brushData->Flags & GDIP_BRUSHFLAGS_BLENDFACTORSV)
    {
        if (size < sizeof(INT32))
        {
            WARNING(("size too small"));
            return InvalidParameter;
        }

        UINT count = ((INT32 *)dataBuffer)[0];
        dataBuffer += sizeof(INT32);
        size       -= sizeof(INT32);

        UINT realSize = count * sizeof(REAL);

        if (size < (2 * realSize))
        {
            WARNING(("size too small"));
            return InvalidParameter;
        }

        this->SetVerticalBlend((REAL *)(dataBuffer + realSize), (REAL *)dataBuffer, count);
        dataBuffer += (2 * realSize);
        size       -= (2 * realSize);
    }
    UpdateUid();
    return Ok;
}

GpStatus
GpRectGradient::ColorAdjust(
    GpRecolor *             recolor,
    ColorAdjustType         type
    )
{
    if(!recolor)
        return InvalidParameter;

    if (type == ColorAdjustTypeDefault)
    {
        type = ColorAdjustTypeBrush;
    }

    ARGB    solidColor32[4];

    solidColor32[0] = DeviceBrush.Colors[0].GetValue();
    solidColor32[1] = DeviceBrush.Colors[1].GetValue();
    solidColor32[2] = DeviceBrush.Colors[2].GetValue();
    solidColor32[3] = DeviceBrush.Colors[3].GetValue();

    recolor->ColorAdjust(solidColor32, 4, type);

    DeviceBrush.Colors[0].SetValue(solidColor32[0]);
    DeviceBrush.Colors[1].SetValue(solidColor32[1]);
    DeviceBrush.Colors[2].SetValue(solidColor32[2]);
    DeviceBrush.Colors[3].SetValue(solidColor32[3]);

    if (DeviceBrush.UsesPresetColors && (DeviceBrush.BlendCounts[0] > 1) && (DeviceBrush.PresetColors != NULL))
    {
        recolor->ColorAdjust(DeviceBrush.PresetColors, DeviceBrush.BlendCounts[0], type);
    }

    UpdateUid();
    return Ok;
}

#if 0
class RadialGradientBrushData : public ObjectTypeData
{
public:
    INT32       Flags;
    INT32       Wrap;
    GpRectF     Rect;
    UINT32      CenterColor;
    UINT32      BoundaryColor;
};

/**************************************************************************\
*
* Function Description:
*
*   Get the brush data.
*
* Arguments:
*
*   [IN] dataBuffer - fill this buffer with the data
*   [IN/OUT] size   - IN - size of buffer; OUT - number bytes written
*
* Return Value:
*
*   GpStatus - Ok or error code
*
* Created:
*
*   9/13/1999 DCurtis
*
\**************************************************************************/
GpStatus
GpRadialGradient::GetData(
    IStream *   stream
    ) const
{
    ASSERT (stream != NULL);

    INT         flags    = 0;

    if (DeviceBrush.IsGammaCorrected)
    {
        flags |= GDIP_BRUSHFLAGS_ISGAMMACORRECTED;
    }

    if (!DeviceBrush.Xform.IsIdentity())
    {
        flags |= GDIP_BRUSHFLAGS_TRANSFORM;
    }

    // Note: can't have both blendFactors and presetColors at the same time
    if (DeviceBrush.UsesPresetColors && (DeviceBrush.BlendCounts[0] > 1) && (DeviceBrush.PresetColors != NULL) &&
        (DeviceBrush.BlendPositions[0] != NULL) && (DeviceBrush.BlendFactors[0] == NULL))
    {
        flags |= GDIP_BRUSHFLAGS_PRESETCOLORS;
    }

    if ((DeviceBrush.BlendCounts[0] > 1) && (DeviceBrush.BlendFactors[0] != NULL) && (DeviceBrush.BlendPositions[0] != NULL))
    {
        flags |= GDIP_BRUSHFLAGS_BLENDFACTORS;
    }

    RadialGradientBrushData brushData;
    brushData.Type          = DeviceBrush.Type;
    brushData.Flags         = flags;
    brushData.Wrap          = DeviceBrush.Wrap;
    brushData.Rect          = DeviceBrush.Rect;
    brushData.CenterColor   = DeviceBrush.Colors[0].GetValue();
    brushData.BoundaryColor = DeviceBrush.Colors[1].GetValue();
    stream->Write(&brushData, sizeof(brushData), NULL);

    if (flags & GDIP_BRUSHFLAGS_TRANSFORM)
    {
        DeviceBrush.Xform.WriteMatrix(stream);
    }

    if (flags & GDIP_BRUSHFLAGS_PRESETCOLORS)
    {
        INT     realSize = DeviceBrush.BlendCounts[0] * sizeof(REAL);
        INT     argbSize = DeviceBrush.BlendCounts[0] * sizeof(ARGB);

        stream->Write(&DeviceBrush.BlendCounts[0], sizeof(INT32), NULL);
        stream->Write(DeviceBrush.BlendPositions[0], realSize, NULL);
        stream->Write(DeviceBrush.PresetColors, argbSize, NULL);
    }

    if (flags & GDIP_BRUSHFLAGS_BLENDFACTORS)
    {
        INT     realSize = DeviceBrush.BlendCounts[0] * sizeof(REAL);

        stream->Write(&DeviceBrush.BlendCounts[0], sizeof(INT32), NULL);
        stream->Write(DeviceBrush.BlendPositions[0], realSize, NULL);
        stream->Write(DeviceBrush.BlendFactors[0], realSize, NULL);
    }

    return Ok;
}

UINT
GpRadialGradient::GetDataSize() const
{
    UINT    size = sizeof(RadialGradientBrushData);

    if (!DeviceBrush.Xform.IsIdentity())
    {
        size += GDIP_MATRIX_SIZE;
    }

    // Note: can't have both blendFactors and presetColors at the same time
    if (DeviceBrush.UsesPresetColors && (DeviceBrush.BlendCounts[0] > 1) && (DeviceBrush.PresetColors != NULL) &&
        (DeviceBrush.BlendPositions[0] != NULL) && (DeviceBrush.BlendFactors[0] == NULL))
    {
        size += sizeof(INT32) + ((sizeof(ARGB) + sizeof(REAL)) * DeviceBrush.BlendCounts[0]);
    }

    if ((DeviceBrush.BlendCounts[0] > 1) && (DeviceBrush.BlendFactors[0] != NULL) && (DeviceBrush.BlendPositions[0] != NULL))
    {
        size += sizeof(INT32) + ((sizeof(REAL) + sizeof(REAL)) * DeviceBrush.BlendCounts[0]);
    }

    return size;
}

/**************************************************************************\
*
* Function Description:
*
*   Read the brush object from memory.
*
* Arguments:
*
*   [IN] dataBuffer - the data that was read from the stream
*   [IN] size - the size of the data
*
* Return Value:
*
*   GpStatus - Ok or failure status
*
* Created:
*
*   4/26/1999 DCurtis
*
\**************************************************************************/
GpStatus
GpRadialGradient::SetData(
    const BYTE *        dataBuffer,
    UINT                size
    )
{
//    ASSERT ((GpBrushType)(((RadialGradientBrushData *)dataBuffer)->Type) == BrushTypeRadialGradient);

    if (dataBuffer == NULL)
    {
        WARNING(("dataBuffer is NULL"));
        return InvalidParameter;
    }

    if (size < sizeof(RadialGradientBrushData))
    {
        WARNING(("size too small"));
        return InvalidParameter;
    }

    const RadialGradientBrushData * brushData;
    GpColor                         centerColor;
    GpColor                         boundaryColor;

    brushData = reinterpret_cast<const RadialGradientBrushData *>(dataBuffer);

    if (!brushData->MajorVersionMatches())
    {
        WARNING(("Version number mismatch"));
        return InvalidParameter;
    }

    centerColor.SetValue(brushData->CenterColor);
    boundaryColor.SetValue(brushData->BoundaryColor);

    InitializeBrush(
        brushData->Rect,
        centerColor,
        boundaryColor,
        (GpWrapMode) brushData->Wrap
        );

    DeviceBrush.IsGammaCorrected = ((brushData->Flags & GDIP_BRUSHFLAGS_ISGAMMACORRECTED) != 0);

    dataBuffer += sizeof(RadialGradientBrushData);
    size       -= sizeof(RadialGradientBrushData);

    if (brushData->Flags & GDIP_BRUSHFLAGS_TRANSFORM)
    {
        if (size < GDIP_MATRIX_SIZE)
        {
            WARNING(("size too small"));
            return InvalidParameter;
        }

        DeviceBrush.Xform.SetMatrix((REAL *)dataBuffer);
        dataBuffer += GDIP_MATRIX_SIZE;
        size       -= GDIP_MATRIX_SIZE;
    }

    if (brushData->Flags & GDIP_BRUSHFLAGS_PRESETCOLORS)
    {
        if (size < sizeof(INT32))
        {
            WARNING(("size too small"));
            return InvalidParameter;
        }

        UINT count = ((INT32 *)dataBuffer)[0];
        dataBuffer += sizeof(INT32);
        size       -= sizeof(INT32);

        UINT realSize = count * sizeof(REAL);
        UINT argbSize = count * sizeof(ARGB);

        if (size < (realSize + argbSize))
        {
            WARNING(("size too small"));
            return InvalidParameter;
        }

        ARGB* newColors    = (ARGB*) GpRealloc(DeviceBrush.PresetColors, argbSize);

        if (newColors != NULL)
        {
            // We have to just copy in the ARGB values, because they've already
            // been premultiplied.
            GpMemcpy(newColors, dataBuffer + realSize, argbSize);
            DeviceBrush.PresetColors = newColors;

            REAL* newPositions = (REAL*) GpRealloc(DeviceBrush.BlendPositions[0], realSize);

            if (newPositions != NULL)
            {
                GpMemcpy(newPositions, dataBuffer, realSize);
                DeviceBrush.BlendPositions[0] = newPositions;

                GpFree(DeviceBrush.BlendFactors[0]);
                DeviceBrush.BlendFactors[0] = NULL;
                DeviceBrush.UsesPresetColors = TRUE;
                DeviceBrush.BlendCounts[0] = count;
            }
        }

        dataBuffer += (realSize + argbSize);
        size       -= (realSize + argbSize);
    }

    if (brushData->Flags & GDIP_BRUSHFLAGS_BLENDFACTORS)
    {
        if (size < sizeof(INT32))
        {
            WARNING(("size too small"));
            return InvalidParameter;
        }

        UINT count = ((INT32 *)dataBuffer)[0];
        dataBuffer += sizeof(INT32);
        size       -= sizeof(INT32);

        UINT realSize = count * sizeof(REAL);

        if (size < (2 * realSize))
        {
            WARNING(("size too small"));
            return InvalidParameter;
        }

        this->SetBlend((REAL *)(dataBuffer + realSize), (REAL *)dataBuffer, count);
        dataBuffer += (2 * realSize);
        size       -= (2 * realSize);
    }

    UpdateUid();
    return Ok;
}

GpStatus
GpRadialGradient::ColorAdjust(
    GpRecolor *             recolor,
    ColorAdjustType         type
    )
{
    if(!recolor)
        return InvalidParameter;

    if (type == ColorAdjustTypeDefault)
    {
        type = ColorAdjustTypeBrush;
    }

    ARGB    solidColor32[2];

    solidColor32[0] = DeviceBrush.Colors[0].GetValue();
    solidColor32[1] = DeviceBrush.Colors[1].GetValue();

    recolor->ColorAdjust(solidColor32, 2, type);

    DeviceBrush.Colors[0].SetValue(solidColor32[0]);
    DeviceBrush.Colors[1].SetValue(solidColor32[1]);

    if (DeviceBrush.UsesPresetColors && (DeviceBrush.BlendCounts[0] > 1) && (DeviceBrush.PresetColors != NULL))
    {
        recolor->ColorAdjust(DeviceBrush.PresetColors, DeviceBrush.BlendCounts[0], type);
    }

    UpdateUid();
    return Ok;
}

class TriangleGradientBrushData : public ObjectTypeData
{
public:
    INT32       Flags;
    INT32       Wrap;
    GpPointF    Points[3];
    UINT32      Color0;
    UINT32      Color1;
    UINT32      Color2;
};

/**************************************************************************\
*
* Function Description:
*
*   Get the brush data.
*
* Arguments:
*
*   [IN] dataBuffer - fill this buffer with the data
*   [IN/OUT] size   - IN - size of buffer; OUT - number bytes written
*
* Return Value:
*
*   GpStatus - Ok or error code
*
* Created:
*
*   9/13/1999 DCurtis
*
\**************************************************************************/
GpStatus
GpTriangleGradient::GetData(
    IStream *   stream
    ) const
{
    ASSERT (stream != NULL);

    INT         flags    = 0;

    if (DeviceBrush.IsGammaCorrected)
    {
        flags |= GDIP_BRUSHFLAGS_ISGAMMACORRECTED;
    }

    if (!DeviceBrush.Xform.IsIdentity())
    {
        flags |= GDIP_BRUSHFLAGS_TRANSFORM;
    }

    if ((DeviceBrush.BlendCounts[0] > 1) && (DeviceBrush.BlendFactors[0] != NULL) && (DeviceBrush.BlendPositions[0] != NULL))
    {
        flags |= GDIP_BRUSHFLAGS_BLENDFACTORS0;
    }

    if ((DeviceBrush.BlendCounts[1] > 1) && (DeviceBrush.BlendFactors[1] != NULL) && (DeviceBrush.BlendPositions[1] != NULL))
    {
        flags |= GDIP_BRUSHFLAGS_BLENDFACTORS1;
    }

    if ((DeviceBrush.BlendCounts[2] > 1) && (DeviceBrush.BlendFactors[2] != NULL) && (DeviceBrush.BlendPositions[2] != NULL))
    {
        flags |= GDIP_BRUSHFLAGS_BLENDFACTORS2;
    }

    TriangleGradientBrushData   brushData;
    brushData.Type      = DeviceBrush.Type;
    brushData.Flags     = flags;
    brushData.Wrap      = DeviceBrush.Wrap;
    brushData.Points[0] = DeviceBrush.Points[0];
    brushData.Points[1] = DeviceBrush.Points[1];
    brushData.Points[2] = DeviceBrush.Points[2];
    brushData.Color0    = DeviceBrush.Colors[0].GetValue();
    brushData.Color1    = DeviceBrush.Colors[1].GetValue();
    brushData.Color2    = DeviceBrush.Colors[2].GetValue();
    stream->Write(&brushData, sizeof(brushData), NULL);

    if (flags & GDIP_BRUSHFLAGS_TRANSFORM)
    {
        DeviceBrush.Xform.WriteMatrix(stream);
    }

    if (flags & GDIP_BRUSHFLAGS_BLENDFACTORS0)
    {
        INT     realSize = DeviceBrush.BlendCounts[0] * sizeof(REAL);

        stream->Write(&DeviceBrush.BlendCounts[0], sizeof(INT32), NULL);
        stream->Write(DeviceBrush.BlendPositions[0], realSize, NULL);
        stream->Write(DeviceBrush.BlendFactors[0], realSize, NULL);
    }

    if (flags & GDIP_BRUSHFLAGS_BLENDFACTORS1)
    {
        INT     realSize = DeviceBrush.BlendCounts[1] * sizeof(REAL);

        stream->Write(&DeviceBrush.BlendCounts[1], sizeof(INT32), NULL);
        stream->Write(DeviceBrush.BlendPositions[1], realSize, NULL);
        stream->Write(DeviceBrush.BlendFactors[1], realSize, NULL);
    }

    if (flags & GDIP_BRUSHFLAGS_BLENDFACTORS2)
    {
        INT     realSize = DeviceBrush.BlendCounts[2] * sizeof(REAL);

        stream->Write(&DeviceBrush.BlendCounts[2], sizeof(INT32), NULL);
        stream->Write(DeviceBrush.BlendPositions[2], realSize, NULL);
        stream->Write(DeviceBrush.BlendFactors[2], realSize, NULL);
    }

    return Ok;
}

UINT
GpTriangleGradient::GetDataSize() const
{
    UINT    size = sizeof(RectGradientBrushData);

    if (!DeviceBrush.Xform.IsIdentity())
    {
        size += GDIP_MATRIX_SIZE;
    }

    if ((DeviceBrush.BlendCounts[0] > 1) && (DeviceBrush.BlendFactors[0] != NULL) && (DeviceBrush.BlendPositions[0] != NULL))
    {
        size += sizeof(INT32) + ((sizeof(REAL) + sizeof(REAL)) * DeviceBrush.BlendCounts[0]);
    }

    if ((DeviceBrush.BlendCounts[1] > 1) && (DeviceBrush.BlendFactors[1] != NULL) && (DeviceBrush.BlendPositions[1] != NULL))
    {
        size += sizeof(INT32) + ((sizeof(REAL) + sizeof(REAL)) * DeviceBrush.BlendCounts[1]);
    }

    if ((DeviceBrush.BlendCounts[2] > 1) && (DeviceBrush.BlendFactors[2] != NULL) && (DeviceBrush.BlendPositions[2] != NULL))
    {
        size += sizeof(INT32) + ((sizeof(REAL) + sizeof(REAL)) * DeviceBrush.BlendCounts[2]);
    }

    return size;
}

/**************************************************************************\
*
* Function Description:
*
*   Read the brush object from memory.
*
* Arguments:
*
*   [IN] dataBuffer - the data that was read from the stream
*   [IN] size - the size of the data
*
* Return Value:
*
*   GpStatus - Ok or failure status
*
* Created:
*
*   4/26/1999 DCurtis
*
\**************************************************************************/
GpStatus
GpTriangleGradient::SetData(
    const BYTE *        dataBuffer,
    UINT                size
    )
{
//    ASSERT ((GpBrushType)(((TriangleGradientBrushData *)dataBuffer)->Type) == BrushTypeTriangleGradient);

    if (dataBuffer == NULL)
    {
        WARNING(("dataBuffer is NULL"));
        return InvalidParameter;
    }

    if (size < sizeof(TriangleGradientBrushData))
    {
        WARNING(("size too small"));
        return InvalidParameter;
    }

    const TriangleGradientBrushData *   brushData;
    GpColor                             colors[3];

    brushData = reinterpret_cast<const TriangleGradientBrushData *>(dataBuffer);

    if (!brushData->MajorVersionMatches())
    {
        WARNING(("Version number mismatch"));
        return InvalidParameter;
    }

    colors[0].SetValue(brushData->Color0);
    colors[1].SetValue(brushData->Color1);
    colors[2].SetValue(brushData->Color2);

    InitializeBrush(brushData->Points, colors, (GpWrapMode) brushData->Wrap);

    DeviceBrush.IsGammaCorrected = ((brushData->Flags & GDIP_BRUSHFLAGS_ISGAMMACORRECTED) != 0);

    dataBuffer += sizeof(TriangleGradientBrushData);
    size       -= sizeof(TriangleGradientBrushData);

    if (brushData->Flags & GDIP_BRUSHFLAGS_TRANSFORM)
    {
        if (size < GDIP_MATRIX_SIZE)
        {
            WARNING(("size too small"));
            return InvalidParameter;
        }

        DeviceBrush.Xform.SetMatrix((REAL *)dataBuffer);
        dataBuffer += GDIP_MATRIX_SIZE;
        size       -= GDIP_MATRIX_SIZE;
    }

    if (brushData->Flags & GDIP_BRUSHFLAGS_BLENDFACTORS0)
    {
        if (size < sizeof(INT32))
        {
            WARNING(("size too small"));
            return InvalidParameter;
        }

        UINT count = ((INT32 *)dataBuffer)[0];
        dataBuffer += sizeof(INT32);
        size       -= sizeof(INT32);

        UINT realSize = count * sizeof(REAL);

        if (size < (2 * realSize))
        {
            WARNING(("size too small"));
            return InvalidParameter;
        }

        this->SetBlend0((REAL *)(dataBuffer + realSize), (REAL *)dataBuffer, count);
        dataBuffer += (2 * realSize);
        size       -= (2 * realSize);
    }

    if (brushData->Flags & GDIP_BRUSHFLAGS_BLENDFACTORS1)
    {
        if (size < sizeof(INT32))
        {
            WARNING(("size too small"));
            return InvalidParameter;
        }

        UINT count = ((INT32 *)dataBuffer)[0];
        dataBuffer += sizeof(INT32);
        size       -= sizeof(INT32);

        UINT realSize = count * sizeof(REAL);

        if (size < (2 * realSize))
        {
            WARNING(("size too small"));
            return InvalidParameter;
        }

        this->SetBlend1((REAL *)(dataBuffer + realSize), (REAL *)dataBuffer, count);
        dataBuffer += (2 * realSize);
        size       -= (2 * realSize);
    }

    if (brushData->Flags & GDIP_BRUSHFLAGS_BLENDFACTORS2)
    {
        if (size < sizeof(INT32))
        {
            WARNING(("size too small"));
            return InvalidParameter;
        }

        UINT count = ((INT32 *)dataBuffer)[0];
        dataBuffer += sizeof(INT32);
        size       -= sizeof(INT32);

        UINT realSize = count * sizeof(REAL);

        if (size < (2 * realSize))
        {
            WARNING(("size too small"));
            return InvalidParameter;
        }

        this->SetBlend2((REAL *)(dataBuffer + realSize), (REAL *)dataBuffer, count);
        dataBuffer += (2 * realSize);
        size       -= (2 * realSize);
    }

    UpdateUid();
    return Ok;
}

GpStatus
GpTriangleGradient::ColorAdjust(
    GpRecolor *             recolor,
    ColorAdjustType         type
    )
{
    if(!recolor)
        return InvalidParameter;

    if (type == ColorAdjustTypeDefault)
    {
        type = ColorAdjustTypeBrush;
    }

    ARGB    solidColor32[3];

    solidColor32[0] = DeviceBrush.Colors[0].GetValue();
    solidColor32[1] = DeviceBrush.Colors[1].GetValue();
    solidColor32[2] = DeviceBrush.Colors[2].GetValue();

    recolor->ColorAdjust(solidColor32, 3, type);

    DeviceBrush.Colors[0].SetValue(solidColor32[0]);
    DeviceBrush.Colors[1].SetValue(solidColor32[1]);
    DeviceBrush.Colors[2].SetValue(solidColor32[2]);

    UpdateUid();
    return Ok;
}
#endif

class PathGradientBrushData : public ObjectTypeData
{
public:
    INT32       Flags;
    INT32       Wrap;
    UINT32      CenterColor;
    GpPointF    CenterPoint;
    UINT32      SurroundingColorCount;
};

/**************************************************************************\
*
* Function Description:
*
*   Get the brush data.
*
* Arguments:
*
*   [IN] dataBuffer - fill this buffer with the data
*   [IN/OUT] size   - IN - size of buffer; OUT - number bytes written
*
* Return Value:
*
*   GpStatus - Ok or error code
*
* Created:
*
*   9/13/1999 DCurtis
*
\**************************************************************************/
GpStatus
GpPathGradient::GetData(
    IStream *   stream
    ) const
{
    ASSERT (stream != NULL);

    UINT        pathSize              = 0;
    UINT        surroundingColorCount = DeviceBrush.OneSurroundColor ? 1 : DeviceBrush.Count;
    INT         flags                 = 0;
    GpPath *    path                  = GpPath::GetPath(DeviceBrush.Path);

    if (DeviceBrush.IsGammaCorrected)
    {
        flags |= GDIP_BRUSHFLAGS_ISGAMMACORRECTED;
    }

    if ((DeviceBrush.PointsPtr == NULL) && (path != NULL))
    {
        flags |= GDIP_BRUSHFLAGS_PATH;
        pathSize = path->GetDataSize();
        ASSERT((pathSize & 0x03) == 0);
    }

    if (!DeviceBrush.Xform.IsIdentity())
    {
        flags |= GDIP_BRUSHFLAGS_TRANSFORM;
    }

    // Note: can't have both blendFactors and presetColors at the same time
    if (DeviceBrush.UsesPresetColors && (DeviceBrush.BlendCounts[0] > 1) && (DeviceBrush.PresetColors != NULL) &&
        (DeviceBrush.BlendPositions[0] != NULL) && (DeviceBrush.BlendFactors[0] == NULL))
    {
        flags |= GDIP_BRUSHFLAGS_PRESETCOLORS;
    }

    if ((DeviceBrush.BlendCounts[0] > 1) && (DeviceBrush.BlendFactors[0] != NULL) && (DeviceBrush.BlendPositions[0] != NULL))
    {
        flags |= GDIP_BRUSHFLAGS_BLENDFACTORS;
    }

    if((DeviceBrush.FocusScaleX != 0) || (DeviceBrush.FocusScaleY != 0))
    {
        flags |= GDIP_BRUSHFLAGS_FOCUSSCALES;
    }

    PathGradientBrushData   brushData;
    brushData.Type                  = DeviceBrush.Type;
    brushData.Flags                 = flags;
    brushData.Wrap                  = DeviceBrush.Wrap;
    brushData.CenterColor           = DeviceBrush.Colors[0].GetValue();
    brushData.CenterPoint           = DeviceBrush.Points[0];
    brushData.SurroundingColorCount = surroundingColorCount;
    stream->Write(&brushData, sizeof(brushData), NULL);

    ARGB    argb;

    for (UINT i = 0; i < surroundingColorCount; i++)
    {
        argb = DeviceBrush.ColorsPtr[i].GetValue();
        stream->Write(&argb, sizeof(argb), NULL);
    }

    if (flags & GDIP_BRUSHFLAGS_PATH)
    {
        stream->Write(&pathSize, sizeof(INT32), NULL);
        path->GetData(stream);
    }
    else
    {
        INT     count = DeviceBrush.Count;

        if (DeviceBrush.PointsPtr == NULL)
        {
            count = 0;
        }
        stream->Write(&count, sizeof(INT32), NULL);
        if (count > 0)
        {
            INT pointsSize = count * sizeof(DeviceBrush.PointsPtr[0]);
            stream->Write(DeviceBrush.PointsPtr, pointsSize, NULL);
        }
    }

    if (flags & GDIP_BRUSHFLAGS_TRANSFORM)
    {
        DeviceBrush.Xform.WriteMatrix(stream);
    }

    if (flags & GDIP_BRUSHFLAGS_PRESETCOLORS)
    {
        INT     count = DeviceBrush.BlendCounts[0];
        INT     realSize = count * sizeof(REAL);
        INT     argbSize = count * sizeof(ARGB);

        REAL    *newPositions = (REAL*) GpMalloc(realSize);

        if (newPositions == NULL )
        {
            return OutOfMemory;
        }

        ARGB    *newARGB = (ARGB*) GpMalloc(argbSize);

        if (newARGB == NULL )
        {
            GpFree(newPositions);
            return OutOfMemory;
        }

        GpColor *newPresetColors = new GpColor[count];

        if (newPresetColors == NULL)
        {
            GpFree(newPositions);
            GpFree (newARGB);
            return OutOfMemory;
        }

        // Users will supply the preset colors as radial blend colors.
        // 0 position means the center location and 1 position means the
        // the outer edge.  These are stored inverted internally, so to get back
        // to the original user values, invert again.

        GetPresetBlend(newPresetColors, newPositions, count);

        for (INT i = 0; i < count; i++)
        {
            newARGB[i] = newPresetColors[i].GetValue();
        }

        stream->Write(&count, sizeof(INT32), NULL);
        stream->Write(newPositions, realSize, NULL);
        stream->Write(newARGB, argbSize, NULL);

        GpFree(newPositions);
        GpFree(newARGB);
        delete newPresetColors;
    }

    if (flags & GDIP_BRUSHFLAGS_BLENDFACTORS)
    {
        INT  count = DeviceBrush.BlendCounts[0];
        INT  realSize = count * sizeof(REAL);

        // Users will supply the blend factor as radial blend factors, and these are stored
        // with inverted values.  To get back the original user specified blend factors to
        // store, they must be inverted again.

        REAL *newFactors = (REAL*) GpMalloc(realSize);

        if (newFactors == NULL )
        {
            return OutOfMemory;
        }

        REAL *newPositions = (REAL*) GpMalloc(realSize);

        if (newPositions == NULL )
        {
            GpFree(newFactors);
            return OutOfMemory;
        }

        GetBlend(newFactors, newPositions, count);

        stream->Write(&count, sizeof(INT32), NULL);
        stream->Write(newPositions, realSize, NULL);
        stream->Write(newFactors, realSize, NULL);

        GpFree(newPositions);
        GpFree(newFactors);
    }

    if (flags & GDIP_BRUSHFLAGS_FOCUSSCALES)
    {
        INT     count = 2;
        REAL    focusScale[2];

        focusScale[0] = DeviceBrush.FocusScaleX;
        focusScale[1] = DeviceBrush.FocusScaleY;

        stream->Write(&count, sizeof(INT32), NULL);
        stream->Write(focusScale, 2 * sizeof(REAL), NULL);
    }

    return Ok;
}

UINT
GpPathGradient::GetDataSize() const
{
    UINT        pathSize;
    UINT        surroundingColorCount = DeviceBrush.OneSurroundColor ? 1 : DeviceBrush.Count;
    UINT        size                  = sizeof(PathGradientBrushData) +
                                        (surroundingColorCount * sizeof(ARGB));

    GpPath* path = static_cast<GpPath*> (DeviceBrush.Path);

    if (DeviceBrush.PointsPtr != NULL)
    {
        size += sizeof(INT32) + (DeviceBrush.Count * sizeof(DeviceBrush.PointsPtr[0]));
    }
    else if (path != NULL)
    {
        pathSize = path->GetDataSize();
        ASSERT((pathSize & 0x03) == 0);
        size += sizeof(INT32) + pathSize;
    }

    if (!DeviceBrush.Xform.IsIdentity())
    {
        size += GDIP_MATRIX_SIZE;
    }

    // Note: can't have both blendFactors and presetColors at the same time
    if (DeviceBrush.UsesPresetColors && (DeviceBrush.BlendCounts[0] > 1) && (DeviceBrush.PresetColors != NULL) &&
        (DeviceBrush.BlendPositions[0] != NULL) && (DeviceBrush.BlendFactors[0] == NULL))
    {
        size += sizeof(INT32) + ((sizeof(ARGB) + sizeof(REAL)) * DeviceBrush.BlendCounts[0]);
    }

    if ((DeviceBrush.BlendCounts[0] > 1) && (DeviceBrush.BlendFactors[0] != NULL) && (DeviceBrush.BlendPositions[0] != NULL))
    {
        size += sizeof(INT32) + ((sizeof(REAL) + sizeof(REAL)) * DeviceBrush.BlendCounts[0]);
    }

    if((DeviceBrush.FocusScaleX != 0) || (DeviceBrush.FocusScaleY != 0))
    {
        size += sizeof(INT32) + 2*sizeof(REAL);
    }

    return size;
}

/**************************************************************************\
*
* Function Description:
*
*   Read the brush object from memory.
*
* Arguments:
*
*   [IN] dataBuffer - the data that was read from the stream
*   [IN] size - the size of the data
*
* Return Value:
*
*   GpStatus - Ok or failure status
*
* Created:
*
*   4/26/1999 DCurtis
*
\**************************************************************************/
GpStatus
GpPathGradient::SetData(
    const BYTE *        dataBuffer,
    UINT                size
    )
{
    ASSERT ((GpBrushType)(((PathGradientBrushData *)dataBuffer)->Type) == BrushTypePathGradient);

    if (dataBuffer == NULL)
    {
        WARNING(("dataBuffer is NULL"));
        return InvalidParameter;
    }

    if (size < sizeof(PathGradientBrushData))
    {
        WARNING(("size too small"));
        return InvalidParameter;
    }

    if (DeviceBrush.PointsPtr != NULL)
    {
        GpFree(DeviceBrush.PointsPtr);
        DeviceBrush.PointsPtr = NULL;
    }

    GpPath* path = static_cast<GpPath*> (DeviceBrush.Path);

    if (path != NULL)
    {
        delete path;
        path = NULL;
    }

    const PathGradientBrushData *   brushData;
    ARGB *                          surroundingColors;

    brushData = reinterpret_cast<const PathGradientBrushData *>(dataBuffer);

    if (!brushData->MajorVersionMatches())
    {
        WARNING(("Version number mismatch"));
        return InvalidParameter;
    }

    dataBuffer += sizeof(PathGradientBrushData);
    size       -= sizeof(PathGradientBrushData);

    if (size < (brushData->SurroundingColorCount * sizeof(ARGB)))
    {
        WARNING(("size too small"));
        return InvalidParameter;
    }

    surroundingColors = (ARGB *)dataBuffer;

    dataBuffer += (brushData->SurroundingColorCount * sizeof(ARGB));
    size       -= (brushData->SurroundingColorCount * sizeof(ARGB));

    if (brushData->Flags & GDIP_BRUSHFLAGS_PATH)
    {
        if (size < sizeof(INT32))
        {
            WARNING(("size too small"));
            return InvalidParameter;
        }

        UINT    pathSize = ((INT32 *)dataBuffer)[0];
        dataBuffer += sizeof(INT32);
        size       -= sizeof(INT32);

        DefaultBrush();
        DeviceBrush.Wrap = (GpWrapMode) brushData->Wrap;

        if (size < pathSize)
        {
            WARNING(("size too small"));
            return InvalidParameter;
        }

        path = new GpPath();
        if (path)
        {
            path->SetData(dataBuffer, pathSize);
        }

        DeviceBrush.Path = path;
        PrepareBrush();
        dataBuffer += pathSize;
        size       -= pathSize;
    }
    else
    {
        if (size < sizeof(INT32))
        {
            WARNING(("size too small"));
            return InvalidParameter;
        }

        INT         count = ((INT32 *)dataBuffer)[0];
        dataBuffer += sizeof(INT32);
        size       -= sizeof(INT32);

        if (size < (count * sizeof(GpPointF)))
        {
            WARNING(("size too small"));
            return InvalidParameter;
        }

        InitializeBrush((GpPointF *)dataBuffer, count, (GpWrapMode) brushData->Wrap);
        dataBuffer += (count * sizeof(GpPointF));
        size       -= (count * sizeof(GpPointF));
    }

    DeviceBrush.IsGammaCorrected = ((brushData->Flags & GDIP_BRUSHFLAGS_ISGAMMACORRECTED) != 0);

    SetCenterPoint(brushData->CenterPoint);
    SetCenterColor(GpColor(brushData->CenterColor));

    DeviceBrush.OneSurroundColor = (brushData->SurroundingColorCount == 1);

    if (DeviceBrush.ColorsPtr != NULL)
    {
        for (UINT32 i = 0; i < brushData->SurroundingColorCount; i++)
        {
            SetSurroundColor(GpColor(surroundingColors[i]), i);
        }
        
        // OneSurroundColor requires n colors and they are all set to the 
        // same value. This is a very weird requirement, but that's the way
        // it was written. One color simply isn't enough.
        
        if (i == 1)
        {
            for (i = 1; (INT)i < DeviceBrush.Count; i++)
            {
                DeviceBrush.ColorsPtr[i] = GpColor(surroundingColors[0]);
            }
        }
    }
    
    if (brushData->Flags & GDIP_BRUSHFLAGS_TRANSFORM)
    {
        if (size < GDIP_MATRIX_SIZE)
        {
            WARNING(("size too small"));
            return InvalidParameter;
        }

        DeviceBrush.Xform.SetMatrix((REAL *)dataBuffer);
        dataBuffer += GDIP_MATRIX_SIZE;
        size       -= GDIP_MATRIX_SIZE;
    }

    if (brushData->Flags & GDIP_BRUSHFLAGS_PRESETCOLORS)
    {
        if (size < sizeof(INT32))
        {
            WARNING(("size too small"));
            return InvalidParameter;
        }

        UINT count = ((INT32 *)dataBuffer)[0];
        dataBuffer += sizeof(INT32);
        size       -= sizeof(INT32);

        UINT realSize = count * sizeof(REAL);
        UINT argbSize = count * sizeof(ARGB);

        if (size < (realSize + argbSize))
        {
            WARNING(("size too small"));
            return InvalidParameter;
        }

        ARGB  *argbBuffer = (ARGB*)(dataBuffer + realSize);
        GpColor *colors = new GpColor[count];

        if (colors == NULL)
        {
            return OutOfMemory;
        }

        for (UINT i = 0; i < count; i++)
        {
            colors[i].SetValue(argbBuffer[i]);
        }

        this->SetPresetBlend(colors, (REAL *)dataBuffer, count);

        dataBuffer += (realSize + argbSize);
        size       -= (realSize + argbSize);

        delete colors;
    }

    if (brushData->Flags & GDIP_BRUSHFLAGS_BLENDFACTORS)
    {
        if (size < sizeof(INT32))
        {
            WARNING(("size too small"));
            return InvalidParameter;
        }

        UINT count = ((INT32 *)dataBuffer)[0];
        dataBuffer += sizeof(INT32);
        size       -= sizeof(INT32);

        UINT realSize = count * sizeof(REAL);

        if (size < (2 * realSize))
        {
            WARNING(("size too small"));
            return InvalidParameter;
        }

        this->SetBlend((REAL *)(dataBuffer + realSize), (REAL *)dataBuffer, count);
        dataBuffer += (2 * realSize);
        size       -= (2 * realSize);
    }

    if (brushData->Flags & GDIP_BRUSHFLAGS_FOCUSSCALES)
    {
        if (size < sizeof(INT32))
        {
            WARNING(("size too small"));
            return InvalidParameter;
        }

        INT count = ((INT32 *)dataBuffer)[0];
        dataBuffer += sizeof(INT32);
        size       -= sizeof(INT32);

        if (size < (2 * sizeof(REAL)))
        {
            WARNING(("size too small"));
            return InvalidParameter;
        }

        DeviceBrush.FocusScaleX = ((REAL *) dataBuffer)[0];
        DeviceBrush.FocusScaleY = ((REAL *) dataBuffer)[1];

        dataBuffer += (2 * sizeof(REAL));
        size       -= (2 * sizeof(REAL));
    }

    UpdateUid();
    return Ok;
}

/**************************************************************************\
*
* Function Description:
*
*   Blend any transparent colors in this brush with white. Note that
*   colors are premultiplied, since they will become fully opaque.
*
* Arguments:
*
* Return Value:
*
*   GpStatus - Ok or failure status
*
\**************************************************************************/

GpStatus GpPathGradient::BlendWithWhite()
{        
    DeviceBrush.Colors[0].SetValue(
        GpColor::ConvertToPremultiplied(DeviceBrush.Colors[0].GetValue()));
    DeviceBrush.Colors[0].BlendOpaqueWithWhite();
    
    if (DeviceBrush.UsesPresetColors)
    {
        GpColor color;
        
        for (INT i=0; i<DeviceBrush.BlendCounts[0]; i++)
        {
            color.SetValue(GpColor::ConvertToPremultiplied(DeviceBrush.PresetColors[i]));
            color.BlendOpaqueWithWhite();
            DeviceBrush.PresetColors[i] = color.GetValue();
        }
    }
    else
    {
        for (INT i=0; i<DeviceBrush.Count; i++)
        {
            DeviceBrush.ColorsPtr[i].SetValue(
                GpColor::ConvertToPremultiplied(DeviceBrush.ColorsPtr[i].GetValue()));
            DeviceBrush.ColorsPtr[i].BlendOpaqueWithWhite();
        }
    }
        
    return Ok;
}

/**************************************************************************\
*
* Function Description:
*
*   Set the surround color.
*
* Arguments:
*
*   [IN] color - the color to set.
*   [IN] index - which color to set.
*
* Return Value:
*
*   GpStatus - Ok or failure status
*
\**************************************************************************/

GpStatus GpPathGradient::SetSurroundColor(GpColor& color, INT index)
{
    if(index >= 0 && index < DeviceBrush.Count)
    {
        if(DeviceBrush.OneSurroundColor)
        {
            if(index == 0)
            {
                DeviceBrush.ColorsPtr[0] = color;
                
                // OneSurroundColor requires n colors and they are all set to the 
                // same value. This is a very weird requirement, but that's the way
                // it was written. One color simply isn't enough.
                
                for (INT i = 1; i < DeviceBrush.Count; i++)
                {
                    DeviceBrush.ColorsPtr[i] = GpColor(DeviceBrush.ColorsPtr[0]);
                }
                
                UpdateUid();
            }
            else
            {
                if(DeviceBrush.ColorsPtr[0].GetValue() !=
                   color.GetValue())
                {
                    DeviceBrush.OneSurroundColor = FALSE;
                    DeviceBrush.ColorsPtr[index] = color;
                    UpdateUid();
                }
            }
        }
        else
        {
            DeviceBrush.ColorsPtr[index] = color;
            UpdateUid();
        }

        return Ok;
    }
    else
        return InvalidParameter;
}

/**************************************************************************\
*
* Function Description:
*
*   Set the surround colors.
*
* Arguments:
*
*   [IN] color - the color to set.
*
* Return Value:
*
*   GpStatus - Ok or failure status
*
\**************************************************************************/

GpStatus GpPathGradient::SetSurroundColors(const GpColor* colors)
{
    GpStatus status = InvalidParameter;

    ASSERT(DeviceBrush.Count > 0);

    if(IsValid() && colors && DeviceBrush.Count > 0)
    {
        GpMemcpy(
            DeviceBrush.ColorsPtr,
            colors,
            DeviceBrush.Count*sizeof(GpColor)
        );

        DeviceBrush.OneSurroundColor = TRUE;
        
        INT i = 1;
        ARGB value = colors[0].GetValue();

        while((i < DeviceBrush.Count) && (DeviceBrush.OneSurroundColor))
        {
            if(colors[i].GetValue() != value)
            {
                DeviceBrush.OneSurroundColor = FALSE;
            }

            i++;
        }

        UpdateUid();
        status = Ok;
    }

    return status;
}



GpStatus
GpPathGradient::ColorAdjust(
    GpRecolor *             recolor,
    ColorAdjustType         type
    )
{
    if(!recolor)
        return InvalidParameter;

    if (type == ColorAdjustTypeDefault)
    {
        type = ColorAdjustTypeBrush;
    }

    INT     surroundingColorCount = DeviceBrush.OneSurroundColor ? 1 : DeviceBrush.Count;

    if ((surroundingColorCount > 0) && (DeviceBrush.ColorsPtr != NULL))
    {
        ARGB    solidColor32[32];
        ARGB *  color32 = solidColor32;

        if (surroundingColorCount > 32)
        {
            color32 = new ARGB[surroundingColorCount];
            if (color32 == NULL)
            {
                return OutOfMemory;
            }
        }
        INT     i;

        for (i = 0; i < surroundingColorCount; i++)
        {
            color32[i] = DeviceBrush.ColorsPtr[i].GetValue();
        }

        recolor->ColorAdjust(color32, surroundingColorCount, type);

        for (i = 0; i < surroundingColorCount; i++)
        {
            DeviceBrush.ColorsPtr[i].SetValue(color32[i]);
        }

        if (color32 != solidColor32)
        {
            delete[] color32;
        }
    }

    if (DeviceBrush.UsesPresetColors && (DeviceBrush.BlendCounts[0] > 1) && (DeviceBrush.PresetColors != NULL))
    {
        recolor->ColorAdjust(DeviceBrush.PresetColors, DeviceBrush.BlendCounts[0], type);
    }

    UpdateUid();
    return Ok;
}

class HatchBrushData : public ObjectTypeData
{
public:
    INT32       Style;
    UINT32      ForeColor;
    UINT32      BackColor;
};

/**************************************************************************\
*
* Function Description:
*
*   Get the brush data.
*
* Arguments:
*
*   [IN] dataBuffer - fill this buffer with the data
*   [IN/OUT] size   - IN - size of buffer; OUT - number bytes written
*
* Return Value:
*
*   GpStatus - Ok or error code
*
* Created:
*
*   9/13/1999 DCurtis
*
\**************************************************************************/
GpStatus
GpHatch::GetData(
    IStream *   stream
    ) const
{
    ASSERT (stream != NULL);

    HatchBrushData  brushData;
    brushData.Type      = DeviceBrush.Type;
    brushData.Style     = DeviceBrush.Style;
    brushData.ForeColor = DeviceBrush.Colors[0].GetValue();
    brushData.BackColor = DeviceBrush.Colors[1].GetValue();
    stream->Write(&brushData, sizeof(brushData), NULL);

    return Ok;
}

UINT
GpHatch::GetDataSize() const
{
    return sizeof(HatchBrushData);
}

/**************************************************************************\
*
* Function Description:
*
*   Read the brush object from memory.
*
* Arguments:
*
*   [IN] dataBuffer - the data that was read from the stream
*   [IN] size - the size of the data
*
* Return Value:
*
*   GpStatus - Ok or failure status
*
* Created:
*
*   4/26/1999 DCurtis
*
\**************************************************************************/
GpStatus
GpHatch::SetData(
    const BYTE *        dataBuffer,
    UINT                size
    )
{
    ASSERT ((GpBrushType)(((HatchBrushData *)dataBuffer)->Type) == BrushTypeHatchFill);

    if (dataBuffer == NULL)
    {
        WARNING(("dataBuffer is NULL"));
        return InvalidParameter;
    }

    if (size < sizeof(HatchBrushData))
    {
        WARNING(("size too small"));
        return InvalidParameter;
    }

    const HatchBrushData *      brushData;

    brushData = reinterpret_cast<const HatchBrushData *>(dataBuffer);

    if (!brushData->MajorVersionMatches())
    {
        WARNING(("Version number mismatch"));
        return InvalidParameter;
    }

    InitializeBrush(static_cast<GpHatchStyle>(brushData->Style),
                    GpColor(brushData->ForeColor),
                    GpColor(brushData->BackColor));

    UpdateUid();
    return Ok;
}

GpStatus
GpHatch::ColorAdjust(
    GpRecolor *             recolor,
    ColorAdjustType         type
    )
{
    ASSERT(recolor != NULL);
    if (type == ColorAdjustTypeDefault)
    {
        type = ColorAdjustTypeBrush;
    }

    ARGB    solidColor32[2];

    solidColor32[0] = DeviceBrush.Colors[0].GetValue();

    //!!! bhouse: bug?
    //            seems that this should be BackColor ... I'm making the
    //            change!
//    solidColor32[1] = ForeColor.GetValue();
    solidColor32[1] = DeviceBrush.Colors[1].GetValue();

    recolor->ColorAdjust(solidColor32, 2, type);

    DeviceBrush.Colors[0].SetValue(solidColor32[0]);
    DeviceBrush.Colors[1].SetValue(solidColor32[1]);
    UpdateUid();
    return Ok;
}

static COLORREF
AverageColors(
    const GpColor *     colors,
    INT                 count
    )
{
    REAL    r = 0;
    REAL    g = 0;
    REAL    b = 0;

    if (count > 0)
    {
        for (INT i = 0; i < count; i++)
        {
            r += colors->GetRed();
            g += colors->GetGreen();
            b += colors->GetBlue();
        }

        r /= count;
        g /= count;
        b /= count;
    }

    INT     red   = GpRound(r);
    INT     green = GpRound(g);
    INT     blue  = GpRound(b);

    return RGB(red, green, blue);
}

static COLORREF
AverageColors(
    const GpColor &     color1,
    const GpColor &     color2
    )
{
    REAL    r = ((REAL)((INT)color1.GetRed()  + (INT)color2.GetRed()))   / 2.0f;
    REAL    g = ((REAL)((INT)color1.GetGreen()+ (INT)color2.GetGreen())) / 2.0f;
    REAL    b = ((REAL)((INT)color1.GetBlue() + (INT)color2.GetBlue()))  / 2.0f;

    INT     red   = GpRound(r);
    INT     green = GpRound(g);
    INT     blue  = GpRound(b);

    return RGB(red, green, blue);
}

COLORREF
ToCOLORREF(
    const DpBrush *     deviceBrush
    )
{
    switch (deviceBrush->Type)
    {
    default:
        ASSERT(0);
        // FALLTHRU

    case BrushTypeSolidColor:
        return deviceBrush->SolidColor.ToCOLORREF();

    case BrushTypeHatchFill:
        return AverageColors(deviceBrush->Colors[0],
                             deviceBrush->Colors[1]);

    case BrushTypeTextureFill:
        return RGB(0x80, 0x80, 0x80);

//    case BrushRectGrad:
    case BrushTypeLinearGradient:
        return AverageColors(deviceBrush->Colors, 4);
#if 0
    case BrushRadialGrad:
        return AverageColors(deviceBrush->Colors[0],
                             deviceBrush->Colors[1]);

    case BrushTriangleGrad:
        return AverageColors(deviceBrush->Colors, 3);
#endif

    case BrushTypePathGradient:
        return AverageColors(deviceBrush->Colors[0],
                             deviceBrush->ColorsPtr[0]);
    }
}