Using System.Drawing2D LinearGradientBrush to Fill Ellipse; Unexpected Results

47 Views Asked by At

Hello stackoverflow!

I am attempting to fill an ellipse with a LinearGradientBrush to "simulate" the dark and sun "sides" of a planet. Variable ovalCenter is the 3D vector (System.Numerics) of the position of the planet with respect to the Sun. The method FBody.Unit returns a unit vector.

I compute the darkest point on the edge of the oval and (facing directly away from the sun) the lightest point on the edge of the oval (directly facing sun). These points are stored in darkSidePoint and sunSidePoint respectively.

I have checked the values of X, Y, and Z of these vectors and know they are correct.

I set a color for the "dark point" to Black and the color of the "light point" to the specified color of the planet. These colors are stored in darkSideColor and sunSideColor respectively.

I then created PointF structures for the "dark point" and the "light point" (darkSide and sunSide).

Given the start and end PointF structures and the colors, I create a LinearGradientBrush (bl).

I use the LinearGradientBrush and the ovalRect in calling Graphics.FillEllipse().

Here is the code:

Vector3 u = FBody.Unit(ovalCenter);
Vector3 fromBodyCenterToEdge = u * ovalRect.Width / 2.0f;
Vector3 darkSidePoint = ovalCenter + fromBodyCenterToEdge;
Vector3 sunSidePoint = ovalCenter - fromBodyCenterToEdge;

PointF darkSide = new PointF(darkSidePoint.X, darkSidePoint.Y);
PointF sunSide = new PointF(sunSidePoint.X, sunSidePoint.Y); 
Color darkSideColor = Color.Black;
Color sunSideColor = penBody.Color;

LinearGradientBrush bl = new LinearGradientBrush(darkSide, sunSide, darkSideColor, sunSideColor);

g.FillEllipse(bl, ovalRect);

My expectation was that the oval would be filled and appear with a gradient fill that started in Black at "dark point" and transition smoothly to "Body Color" (for example, Brown) and look something like this:

enter image description here

Instead, I see something like this:

enter image description here

Any thoughts from the community on what I am doing wrong?

The following are the variable values (from Watch, in VS2022):

enter image description here

I followed guidance (as best I could understand) from MSDN on-line help regarding LinearGradientBrush.

2

There are 2 best solutions below

2
Gerry Schmitz On BEST ANSWER

From a "debug" point of view, you need to display the "graphics values" (e.g. points) you're generating. The values of the different elements are related; and just showing code doesn't provide any insight.

0
DMP On

With thanks to #Paul-Jan, the issue was the presence of scaling/transform of the Graphics context. The code was changed as below and appears to have resolved the issue:

Vector3 u = FBody.Unit(bodyCenter);
float bodyRadius = (float)Math.Sqrt(2.0) * bodyRect.Width / 2.0f;
Vector3 fromBodyCenterToEdge = u * bodyRadius;
Vector3 darkSidePoint = bodyCenter + fromBodyCenterToEdge;
Vector3 sunSidePoint = bodyCenter - fromBodyCenterToEdge;

PointF darkestPt = new PointF(darkSidePoint.X, darkSidePoint.Y);
PointF brightestPt = new PointF(sunSidePoint.X, sunSidePoint.Y); 
Color darkColor = Color.Black;
Color brightColor = penBody.Color;

/*
 * added/modified code to address gradient fill issues with transformed Graphics context
 */

PointF[] deviceColorPts = [darkestPt, brightestPt];
g.TransformPoints(CoordinateSpace.Device, CoordinateSpace.World, deviceColorPts);

PointF[] deviceBodyRectCorners = [bodyRect.Location, new PointF(bodyRect.Right, bodyRect.Bottom)];
g.TransformPoints(CoordinateSpace.Device, CoordinateSpace.World, deviceBodyRectCorners);

GraphicsState gs = g.Save();
g.ResetTransform();

LinearGradientBrush bl = new LinearGradientBrush(deviceColorPts[0], deviceColorPts[1], darkColor, brightColor);

RectangleF deviceBodyRect = new RectangleF(deviceBodyRectCorners[0], new SizeF(deviceBodyRectCorners[1].X - deviceBodyRectCorners[0].X,
                                                                                         deviceBodyRectCorners[1].Y - deviceBodyRectCorners[0].Y));

g.FillEllipse(bl, deviceBodyRect);

g.Restore(gs);

These changes resulted in nice results:

enter image description here

My thanks!