Страницы

Поиск по вопросам

суббота, 30 ноября 2019 г.

Как сделать развевающийся радужный флаг?

#c_sharp #net #winforms #анимация


Я добавил на форму таймер (Interval=100, Enabled=true), потом написал вот этот код:

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 RainbowColors = new List {
            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 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);
            }
        }
    }
}


У меня флаг поворачивается, но это совсем не то, что надо. Никак не пойму, как сделать,
чтобы флаг развевался волнами, как обычно флаги рисуют. У Graphics есть всякие RotateTransform
и ScaleTransform, но как сделать из этого волну непонятно. Надо примерно так:



Ещё флаг мерцает почему-то...
    


Ответы

Ответ 1



Вот моя первая попытка со сплайнами. Выглядит не очень, но я попробую улучшить. Корневая часть такова: 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 => p.X) < 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(p.X, p.Y + 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); } } Напоминаю, что для радужных цветов достаточно изменить наполнение массива с цветами. Полный код: public partial class GermanForm : Form { const int WaveHeight = 80; const int LineHeight = 80; static List GermanColors = new List { Color.FromArgb(0x0A, 0x0A, 0x0D), Color.FromArgb(0xC1, 0x12, 0x1C), Color.FromArgb(0xEE, 0xC9, 0x00) }; static List 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 positions = new List(); 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 => p.X) < 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(p.X, p.Y + 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); } } }

Ответ 2



"Фундаментальные сплайны" какие-то мутные и трудноуправляемые, а понятные кривые Безье почему-то можно только рисовать, но нельзя залить ограниченную ими область (или я не нашёл способ), так что для этого примера сойдёт и топорное решение на синусах и многоугольниках. Берём за форму волны или просто синус: Math.Sin(x / 100f) * WaveHeight / 2f или сумму синусов: (Math.Sin(x / 100f) + Math.Sin(x / 70f)) * WaveHeight / 4f а дальше просто строим многоугольник по точкам (y0 используется, чтобы флаг был "прикреплен" к левому краю): 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; } Чтобы анимация не "мерцала", можно рисовать на внеэкранный битмап и копировать на форму сразу всю готовую картинку. В противном случае реальное оображение осью нарисованного может произойти (и происходит) посреди процесса рисования. 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); } } Полный пример: 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 RainbowColors = new List { 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 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); } } }

Комментариев нет:

Отправить комментарий