I added a timer to the form ( Interval=100 , Enabled=true ), then I wrote this code here:

 using System; using System.Collections.Generic; using System.Drawing; using System.Linq; using System.Windows.Forms; namespace LoveWins { public partial class RainbowForm : Form { static List<Color> RainbowColors = new List<Color> { Color.FromArgb(0xE4, 0x03, 0x03), Color.FromArgb(0xFF, 0x8C, 0x00), Color.FromArgb(0xFF, 0xED, 0x00), Color.FromArgb(0x00, 0x80, 0x26), Color.FromArgb(0x00, 0x4D, 0xFF), Color.FromArgb(0x75, 0x07, 0x87), }; static List<SolidBrush> RainbowBrushes = RainbowColors.Select(c => new SolidBrush(c)).ToList(); float angle = 0; public RainbowForm () { InitializeComponent(); } void lgbtFlagTimer_Tick (object sender, EventArgs e) { using (Graphics graphics = CreateGraphics()) { graphics.FillRectangle(Brushes.White, ClientRectangle); graphics.RotateTransform(angle); angle += 1f; for (int i = 0; i < RainbowBrushes.Count; i++) graphics.FillRectangle(RainbowBrushes[i], 0, i * 80, 777, 80); } } } } 

My flag is turning, but this is not at all what is needed. I don’t understand how to make the flag wave like a flag, as usual. Graphics has all sorts of RotateTransform and ScaleTransform , but how to make a wave out of this is not clear. It should be like this:

Another flag flickers for some reason ...

  • No, the transformations for all occasions will not save enough, so that the curved rectangle will not work. You need DrawBeziers or DrawClosedCurve. - VladD
  • @VladD In the help of some kind of "closed cardinal spline" and nothing is clear. What should be the point to get a wave? - Nikolas Hailey
  • @NikolasHailey To prevent the animation from flickering, you must first draw in the bitmap with all the repainting fillings, and then copy the finished result to the form. If you draw directly on the form, then the actual drawing can occur in the middle of working with the context. - Athari
  • @NikolasHailey: Oh. It needs math. Well, or experiment. Maybe someone will come with an example. - VladD
  • one
    Then the easiest way to take some kind of ready-made function: z = sin (x) - y; and textured. - cpp_user

2 answers 2

Here is my first attempt with splines. It does not look very good, but I will try to improve.

The root part is:

 void NewPosition() { firstPositionTarget = new PointF((float)(rnd.NextDouble() * 100 + 50), (float)(rnd.NextDouble() * 50 - 25)); positions.Insert(0, new PointF(0f, 0f)); } void UpdatePositions() { for (int i = positions.Count - 1; i >= 0; i--) { var x = positions[i].X + 10; if (x >= ClientSize.Width + 200) { positions.RemoveAt(i); } else if (i > 0) { positions[i] = new PointF(x, positions[i].Y); } else if (x < firstPositionTarget.X) { positions[i] = new PointF(x, firstPositionTarget.Y * (x / firstPositionTarget.X)); } else { positions[i] = new PointF(x, firstPositionTarget.Y); // add new point NewPosition(); } } if (positions.Count == 0 || positions.Max(p => pX) < ClientSize.Width) positions.Add(new PointF(ClientSize.Width + 200, 0)); } 

This is the calculation of the positions of the reference points of the spline. In fact, for the time being, a new point is chosen at random, because of this the picture is not very plausible.

Drawing:

 void germanFlagTimer_Tick(object sender, EventArgs e) { UpdatePositions(); using (Graphics graphics = Graphics.FromImage(offscreenBitmap)) { graphics.SmoothingMode = SmoothingMode.HighQuality; graphics.FillRectangle(Brushes.White, ClientRectangle); var totalPositions = Enumerable.Range(1, GermanBrushes.Count + 1) .Select(n => n * WaveHeight) .Select(y => new[] { new PointF(0, y) } .Concat(positions.Select(p => new PointF(pX, pY + y))) .ToArray()) .ToList(); for (int i = 0; i < GermanBrushes.Count; i++) { GraphicsPath path = new GraphicsPath(); path.AddCurve(totalPositions[i], tension: 0.5f); path.AddLine(totalPositions[i].Last(), totalPositions[i + 1].Last()); path.AddCurve(totalPositions[i+1].Reverse().ToArray(), tension: 0.5f); path.AddLine(totalPositions[i+1].First(), totalPositions[i].First()); graphics.FillPath(GermanBrushes[i], path); } using (Graphics formGraphics = CreateGraphics()) formGraphics.DrawImage(offscreenBitmap, ClientRectangle); } } 

I remind you that for rainbow colors it is enough to change the filling of the array with flowers.

Full code:

 public partial class GermanForm : Form { const int WaveHeight = 80; const int LineHeight = 80; static List<Color> GermanColors = new List<Color> { Color.FromArgb(0x0A, 0x0A, 0x0D), Color.FromArgb(0xC1, 0x12, 0x1C), Color.FromArgb(0xEE, 0xC9, 0x00) }; static List<SolidBrush> GermanBrushes = GermanColors.Select(c => new SolidBrush(c)).ToList(); Bitmap offscreenBitmap; public GermanForm() { InitializeComponent(); ClientSize = new Size(777, LineHeight * GermanColors.Count + WaveHeight * 2); offscreenBitmap = new Bitmap(ClientSize.Width, ClientSize.Height); NewPosition(); } List<PointF> positions = new List<PointF>(); PointF firstPositionTarget; Random rnd = new Random(); void NewPosition() { firstPositionTarget = new PointF((float)(rnd.NextDouble() * 100 + 50), (float)(rnd.NextDouble() * 50 - 25)); positions.Insert(0, new PointF(0f, 0f)); } const float step = 10; void UpdatePositions() { for (int i = positions.Count - 1; i >= 0; i--) { var x = positions[i].X + step; if (x >= ClientSize.Width + 200) positions.RemoveAt(i); else { if (i > 0) { positions[i] = new PointF(x, positions[i].Y); } else if (x < firstPositionTarget.X) { positions[i] = new PointF(x, firstPositionTarget.Y * (x / firstPositionTarget.X)); } else { positions[i] = new PointF(x, firstPositionTarget.Y); // add new point NewPosition(); } } } if (positions.Count == 0 || positions.Max(p => pX) < ClientSize.Width) positions.Add(new PointF(ClientSize.Width + 200, 0)); } void germanFlagTimer_Tick(object sender, EventArgs e) { UpdatePositions(); using (Graphics graphics = Graphics.FromImage(offscreenBitmap)) { graphics.SmoothingMode = SmoothingMode.HighQuality; graphics.FillRectangle(Brushes.White, ClientRectangle); var totalPositions = Enumerable.Range(1, GermanBrushes.Count + 1) .Select(n => n * WaveHeight) .Select(y => new[] { new PointF(0, y) } .Concat(positions.Select(p => new PointF(pX, pY + y))) .ToArray()) .ToList(); for (int i = 0; i < GermanBrushes.Count; i++) { GraphicsPath path = new GraphicsPath(); path.AddCurve(totalPositions[i], tension: 0.5f); path.AddLine(totalPositions[i].Last(), totalPositions[i + 1].Last()); path.AddCurve(totalPositions[i+1].Reverse().ToArray(), tension: 0.5f); path.AddLine(totalPositions[i+1].First(), totalPositions[i].First()); graphics.FillPath(GermanBrushes[i], path); } using (Graphics formGraphics = CreateGraphics()) formGraphics.DrawImage(offscreenBitmap, ClientRectangle); } } } 
  • "In fact, for the time being, a new kid is randomly selected" VladD, this is cruel;) - Isaev
  • @Isaev: Hmm, that’s yes :) I gotta fix it. - VladD

For some reason, the ā€œFundamental splinesā€ are muddy and difficult to control, and understandable Bezier curves can only be drawn, but you cannot fill in the area bounded by them (or I haven’t found a way), so for this example a clumsy solution will come off on sines and polygons.

We take a waveform or just a sine:

 Math.Sin(x / 100f) * WaveHeight / 2f 

or the sum of sines:

 (Math.Sin(x / 100f) + Math.Sin(x / 70f)) * WaveHeight / 4f 

and then simply build the polygon by the points ( y0 used so that the flag is "attached" to the left edge):

 float y0 = Wave(offset) - WaveHeight; int stride = 777 / Segments + 1; var points = new PointF[stride * 2]; for (int i = 0; i < stride; i++) { float x = i * Segments; points[i] = new PointF(x, Wave(x + offset) - y0); points[2 * stride - i - 1] = new PointF(x, Wave(x + offset) - y0 + LineHeight); } 

and draw each wave:

 for (int i = 0; i < RainbowColors.Count; i++) { graphics.FillPolygon(RainbowBrushes[i], points); for (int j = 0; j < points.Length; j++) points[j].Y += LineHeight; } 

In order for the animation not to "flicker", you can draw on an offscreen bitmap and copy the entire finished picture onto the form at once. Otherwise, the actual image of the axis drawn can occur (and occurs) in the middle of the drawing process.

 Bitmap offscreenBitmap = new Bitmap(width, height); void lgbtFlagTimer_Tick (object sender, EventArgs e) { using (Graphics graphics = Graphics.FromImage(offscreenBitmap)) { // ... // Ń€ŠøŃŃƒŠµŠ¼ на graphics // ... using (Graphics formGraphics = CreateGraphics()) formGraphics.DrawImage(offscreenBitmap, ClientRectangle); } } 

Full example:

 using System; using System.Collections.Generic; using System.Drawing; using System.Drawing.Drawing2D; using System.Linq; using System.Windows.Forms; namespace LoveWins { public partial class RainbowForm : Form { const int Segments = 30; const int WaveHeight = 80; const int LineHeight = 80; static List<Color> RainbowColors = new List<Color> { Color.FromArgb(0xE4, 0x03, 0x03), Color.FromArgb(0xFF, 0x8C, 0x00), Color.FromArgb(0xFF, 0xED, 0x00), Color.FromArgb(0x00, 0x80, 0x26), Color.FromArgb(0x00, 0x4D, 0xFF), Color.FromArgb(0x75, 0x07, 0x87), }; static List<SolidBrush> RainbowBrushes = RainbowColors.Select(c => new SolidBrush(c)).ToList(); int offset; Bitmap offscreenBitmap; public RainbowForm () { InitializeComponent(); ClientSize = new Size(777, LineHeight * 6 + WaveHeight * 2); offscreenBitmap = new Bitmap(ClientSize.Width, ClientSize.Height); } void lgbtFlagTimer_Tick (object sender, EventArgs e) { using (Graphics graphics = Graphics.FromImage(offscreenBitmap)) { graphics.SmoothingMode = SmoothingMode.HighQuality; graphics.FillRectangle(Brushes.White, ClientRectangle); float y0 = Wave(offset) - WaveHeight; int stride = 777 / Segments + 1; var points = new PointF[stride * 2]; for (int i = 0; i < stride; i++) { float x = i * Segments; points[i] = new PointF(x, Wave(x + offset) - y0); points[2 * stride - i - 1] = new PointF(x, Wave(x + offset) - y0 + LineHeight); } for (int i = 0; i < RainbowColors.Count; i++) { graphics.FillPolygon(RainbowBrushes[i], points); for (int j = 0; j < points.Length; j++) points[j].Y += LineHeight; } using (Graphics formGraphics = CreateGraphics()) formGraphics.DrawImage(offscreenBitmap, ClientRectangle); offset -= 10; } } float Wave (float x) { //return (float)(Math.Sin(x / 100f) * WaveHeight / 2f); return (float)((Math.Sin(x / 100f) + Math.Sin(x / 70f)) * WaveHeight / 4f); } } } 
  • Work demo: screencast.com/t/J4NmaH1ju - Nick Volynkin ♦
  • I propose to add graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias; inside using - without this, pixels are visible on the border of the stripes - PashaPash ♦
  • @PashaPash Fixed. - Athari
  • You can use the WinApi ExtFloodFill function to fill an arbitrary region bounded by Bezier curves. Call through PInvoke. - Alexander Petrov
  • @AlexanderPetrov With the smoothing, this thing, as I understand it, is not very friendly ... - Athari