🎨 Графическое программирование на C#

← Вернуться к модулю 8

Урок 8.5: Калейдоскопические эффекты

Модуль 8 ⏱️ ~1 час

Симметрия и трансформации

Калейдоскоп строится из повторяющихся сегментов, которые поворачиваются вокруг центра. Каждый сегмент содержит одинаковый рисунок, отражённый или вращённый.

📝 Пример задания — калейдоскоп

8 одинаковых сегментов поворачиваются вокруг центра через g.Save() / g.Restore(). Внутри каждого сегмента рисуются концентрические окружности и расходящиеся линии:

using System;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Windows.Forms;

namespace CSharpGraphicsApp
{
    public class Task25_Kaleidoscope : Form
    {
        private float rotationAngle = 0;
        private readonly Timer timer = new Timer();
        private int segments = 8;

        public Task25_Kaleidoscope()
        {
            Text = "Задание 25 — Калейдоскоп (Kaleidoscope)";
            Size = new Size(700, 700);
            BackColor = Color.Black;
            DoubleBuffered = true;
            Paint += Form_Paint;

            timer.Interval = 16;
            timer.Tick += (s, e) => { rotationAngle += 0.03f; Invalidate(); };
            timer.Start();
            FormClosed += (s, e) => timer.Stop();
        }

        private void Form_Paint(object sender, PaintEventArgs e)
        {
            e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
            e.Graphics.Clear(Color.Black);

            int cx = ClientSize.Width / 2;
            int cy = ClientSize.Height / 2;
            int maxRadius = (int)Math.Min(cx, cy);
            float anglePerSegment = 360f / segments;

            for (int seg = 0; seg < segments; seg++)
            {
                var state = e.Graphics.Save();
                e.Graphics.TranslateTransform(cx, cy);
                e.Graphics.RotateTransform(seg * anglePerSegment);
                DrawSegmentPattern(e.Graphics, maxRadius);
                e.Graphics.Restore(state);
            }

            e.Graphics.FillEllipse(Brushes.White, cx - 10, cy - 10, 20, 20);
            e.Graphics.DrawString($"Сегменты: {segments} | Угол: {rotationAngle:F1}°",
                new Font("Segoe UI", 10, FontStyle.Bold), Brushes.Cyan, 10, 10);
        }

        private void DrawSegmentPattern(Graphics g, int maxRadius)
        {
            Color[] colors = { Color.Red, Color.Cyan, Color.Yellow, Color.Lime, Color.Magenta };

            for (int i = 0; i < 5; i++)
            {
                float r = maxRadius * (i + 1) / 5f;
                Color color = colors[i % colors.Length];
                using (var brush = new SolidBrush(Color.FromArgb(150, color)))
                    g.FillEllipse(brush, -r / 2, -r / 2, r, r);
                using (var pen = new Pen(color, 2))
                    g.DrawEllipse(pen, -r / 2, -r / 2, r, r);
            }

            for (int i = 0; i < 8; i++)
            {
                float angle = (float)(i * Math.PI / 4 + rotationAngle * 0.1f);
                float ex = maxRadius * (float)Math.Cos(angle);
                float ey = maxRadius * (float)Math.Sin(angle);
                using (var pen = new Pen(Color.White, 1))
                    g.DrawLine(pen, 0, 0, ex, ey);
            }
        }
    }
}

Практика

  • Нарисуйте базовый узор внутри одного сектора.
  • Скопируйте его вокруг центра, применяя RotateTransform.
  • Добавьте отражения и меняйте цвета.
  • Анимируйте вращение всего узора.

Задание по аналогии: Калейдоскоп

По аналогии с Task25_Kaleidoscope создайте анимированный калейдоскоп с N сегментами.

  1. Объявите поля float rotationAngle = 0 и int segments = 8. В Timer_Tick увеличивайте rotationAngle += 0.03f.
  2. В Form_Paint вычислите anglePerSegment = 360f / segments. В цикле по сегментам сохраняйте состояние через g.Save(), вызывайте TranslateTransform(cx, cy) и RotateTransform(seg * anglePerSegment).
  3. В методе DrawSegmentPattern рисуйте узор относительно точки (0,0): концентрические полупрозрачные окружности и расходящиеся белые линии.
  4. После рисования узора восстанавливайте состояние через g.Restore(state) — это отменит TranslateTransform для следующего сегмента.
  5. Дополнительно: добавьте TrackBar для изменения числа сегментов (4, 6, 8, 12) и посмотрите, как меняется симметрия.

Пример результата выполнения задания:

Результат Task25

Скриншот программы (Task25)

💡 Совет: Всегда используйте пару Save() / Restore() при временных трансформациях. Не используйте ResetTransform() — это сбросит все накопленные трансформации, а не только последнюю.

Проверка

Что даёт зеркальное отражение сегментов? Почему важно сохранять симметрию в калейдоскопе?