2

Is it possible to fill a N-gon, for example a 4 point polygon, that has a different color at each point and have GDI+ to do the color blending? I am looking for something like this:

3-point gradient

but for 4 point shapes.

TaW
  • 53,122
  • 8
  • 69
  • 111
kagelos
  • 423
  • 8
  • 19
  • 1
    No, this will not work in one stroke. You can use a series of strokes and fill it pixel row by row.. See [here](http://stackoverflow.com/questions/30339553/fill-panel-with-gradient-in-three-colors/30341521?s=2|0.5512#30341521) for a simple example. (See the bottom of the answer!) For n-point polygon (assuming convex shape) you woud have to frist create a set of gradients that go along all egdes. Then you can use them to create the brushes needed to fill.. – TaW Oct 29 '15 at 13:38
  • I get your idea, but for a not axis - aligned shape I am not sure if this will work. I would expect blank pixels. I think I need a rasterizer here that will go through all the pixels. – kagelos Oct 29 '15 at 13:52
  • No, you would have to go through all pixel rows, one by one, each stroke going horizontally. Build two Color Arrays, each the size of the total height and fill them by going through the relevant edges.. along the way store the x-values in two more arrays. Then use each color pair to draw the lines..Axis-alignment should not be an issue, no matter which axis you think of.. – TaW Oct 29 '15 at 13:59
  • A trivial way to do it is to fill the bounding rectangle, using a GraphicsPath for the shape to set the Graphics.Clip property. But you'll probably say, "that's not *quite* what I want". – Hans Passant Oct 29 '15 at 15:32
  • Actually, using a PathGradientBrush(points[] and its SurroundColors one gets reall close, esp. if the polygon is regular.. – TaW Oct 29 '15 at 20:58

2 Answers2

4

After playing around with Hans' idea of using a path filling, I think this will actually be the best way to solve this.

However neither a GraphicsPath nor Clipping nor the bounding rectangle are used. Instead we use a special overload that takes a point array; those points are matched with the SurroundColors property of a PathGradientBrush; this way the colors don't change radially as usually but stay pinned to the corner points..

Here are six examples:

enter image description hereenter image description hereenter image description hereenter image description hereenter image description here enter image description here

The Color list is not exactly taken from a spectrum:

List<Color> colors = new List<Color>()
    { Color.Blue, Color.Lime, Color.Red, Color.Magenta , Color.MediumOrchid,
      Color.MediumSeaGreen, Color.LightSeaGreen, Color.LightSteelBlue, Color.DarkCyan};

and I use a simple structure to hold a Point and a Color:

struct cPoint
{
    public Point pt; 
    public Color col;
    public cPoint(int x, int y, Color c)
    { pt = new Point(x,y); col = c;}
}

My form has a PictureBox and a NumericUpDown. The NuD calls the pictureBox.Paint:

private void numericUpDown1_ValueChanged(object sender, EventArgs e)
{
    pictureBox1.Invalidate();
}

..which calls two functions:

private void pictureBox1_Paint(object sender, PaintEventArgs e)
{
    points = getPolyList(colors.Take( (int) numericUpDown1.Value).ToList(),
                         pictureBox2.ClientSize);
    DrawPolyGradient(e.Graphics, points, pictureBox2.ClientRectangle);
}

The list of cPoints is created with simple trigonometry, which works fine for regular polygons:

List<cPoint> getPolyList(List<Color> colors, Size size)
{
    int number = colors.Count;
    List<cPoint> cPoints = new List<cPoint>();

    int x2 = size.Width / 2;
    int y2 = size.Height / 2;

    for (int i = 0; i < number; i++)
    {
        double a = Math.PI / 180f * (i * 360d / number - 90);
        int x = x2 + (int)( Math.Cos(a) * (x2 - 15)); // don't overdraw
        int y = y2 + (int)( Math.Sin(a) * (x2 - 15)); // don't overdraw
        cPoints.Add(new cPoint(x, y, colors[i]));
    }
    return cPoints;
}

The drawing code is in a function of its own:

void DrawPolyGradient(Graphics G,  List<cPoint> cPoints, Rectangle bounds)
{
    int r = 0; int g = 0; int b = 0; int c = cPoints.Count;
    foreach (Color col in cPoints.Select(x => x.col))
    { r += col.R; g += col.G; b += col.B; }
    Color centercolor = Color.FromArgb(r / c, r / c, r / c);

    PathGradientBrush brush = new PathGradientBrush(cPoints.Select(x => x.pt).ToArray());

    brush.CenterPoint = new PointF(bounds.Width / 2, bounds.Height / 2);
    brush.CenterColor = centercolor;

    brush.SurroundColors = cPoints.Select(x => x.col).ToArray();

    G.FillRectangle(brush, bounds);
}
  • It starts with calculating the CenterColor
  • And setting the CenterPoint
  • Next it creates a special PathGradientBrush using an overload that does not take a GraphicsPath but a Point array!

  • These points will correspond to the SurroundColors of the brush.

Especially the larger numbers could not be produced by my first idea of connecting the points along the edges with a simple LinearGradientBrush. Many of those lines would miss the influence of the corners in between.

TaW
  • 53,122
  • 8
  • 69
  • 111
0

Can't add a comment so adding a minor fix to @TaW solution:

Color centercolor = Color.FromArgb(r / c, r / c, r / c);

Should be

Color centercolor = Color.FromArgb(r / c, g / c, b / c);

Thrawn
  • 1
  • 1
  • This does not provide an answer to the question. Once you have sufficient [reputation](https://stackoverflow.com/help/whats-reputation) you will be able to [comment on any post](https://stackoverflow.com/help/privileges/comment); instead, [provide answers that don't require clarification from the asker](https://meta.stackexchange.com/questions/214173/why-do-i-need-50-reputation-to-comment-what-can-i-do-instead). - [From Review](/review/low-quality-posts/21160473) – JoelC Oct 17 '18 at 17:07
  • @JoelC There's nothing wrong with referring to another answer, pointing out fixes or optimizations. Even if the user did have the privilege to comment, code is better to read in an answer of its own. – Filburt Oct 18 '18 at 07:54