Рисование и защита произвольных областей на холсте

Постановка задачи. Часто бывает необходимо изображение разделить на две составляющие: предметы (движущиеся) и фон (неподвижный), или наоборот, неподвижный забор с дырками в нем и некоторые предметы, двигающиеся за ним. Форма дырок в заборе может быть произвольная, как и форма движущихся предметов.

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

Для демонстрации работы с областями выполним подготовительные действия:

1) Кроме библиотеки System.Drawing нам понадобится ее дополнение — библиотека System.Drawing.Drawing2D.
2) На форме размером 900х400 поместим две кнопки «Сбросить» и «Восстановить».
3) В папку с программой  поместим файл ris1.jpg с картинкой фона-пейзажа.
4) Зададим свойство формы StartPosition=Manual для размещения формы в верхнем левом углу экрана.
5) Создадим обработчики событий  Form1_Click(), button1_Click() и button2_Click().
6) В классе Form1 объявим объектGraphics g;   // холст.
7) В конструкторе Form1() добавим кроме вставляемого автоматически оператора InitializeComponent(); еще два оператора:
g = Graphics.FromHwnd(this.Handle);  // инициализация холста g.SmoothingMode = SmoothingMode.AntiAlias; // сглаживание
Примечание. Перечисление (enum) SmoothingMode, заданное в библиотеке System.Drawing.Drawing2D, указывает, применяется ли сглаживание для линий и кривых и границ заполненных областей. Принимает несколько возможных значений, в частности: AntiAlias — сглаживание, Default — без сглаживания.

Класс Region

Класс Region (область) описывает внутреннюю часть графической формы, состоящей из прямоугольников и замкнутых контуров. Например, для задания всей прямоугольной области формы — объекта target достаточно записать два оператора:
Rectangle rect = new Rectangle(0,0,this.Width,this.Height);
Region target = new Region(rect);
Здесь первый из них создает объект «прямоугольник» (rect) с размерами, равными размеру окна. Второй объект (target) создается с использованием первого объекта (rect), с которым далее будем выполнять различные преобразования.

Объект romb класса Region можно задать также более универсальным способом через объект gp класса GraphicsPath, представляющего собой последовательность соединенных линий и кривых:
GraphicsPath gp = new GraphicsPath(pt, typ);
Region romb = new System.Drawing.Region(gp);
В свою очередь, объект gp определяется массивом точек (Point[] pt) и массивом типов соединений (byte [] typ).
Размерность массивов на единицу больше числа точек, определяющих объект gp (пусть первая точка совпадает с последней).

В примере (для простоты понимания) такой областью выбран ромб, который можно задать координатами его центра (x, y)  и длинами половин его диагоналей (dx, dy). Тогда с помощью метода (см. его код в тексте файла Form1.cs)
public Region romb(int x, int y, int dx, int dy)
можно задавать области в виде ромбов, например:
Region rombR = romb(300,200,100,150); //внутренняя часть формы.

Аффинные преобразования

К аффинным преобразованиям на плоскости относятся отражения, повороты (вращения), растяжения/сжатия и сдвиги. Используя методы класса Matrix из библиотеки System.Drawing.Drawing2D вы можете легко эти преобразования выполнять.

Сначала задается  единичная матрица аффинных преобразований:
Matrix matr = new Matrix();

Для поворота на 45 градусов по часовой стрелке относительно точки (300,200) изменим матрицу (метод RotateAt()):
matr.RotateAt(45, new Point(300, 200)); 
После чего выполним поворот нашей фигуры, например, rombR:
rombR.Transform(matr);      

Аналогично выполним растяжение-сжатие по x и y:
matr = new Matrix();  
matr.Scale(1.0f, 0.8f);  
rombR.Transform(matr); 

Наш  rombR преобразовался в прямоугольник.

Создадим еще две области — два ромба:
Region rombG = romb(300, 100, 150, 20); //  зеленый ромб
Region rombB = romb(300, 210, 200, 30); //  синий ромб

Логические операции над областями

А теперь, внимание, главный фокус! Используя области target, rombR, rombG, rombB, построим и закрасим мишень:
target.Intersect(rombR);
target.Xor(rombG);
target.Union(rombB);
g.FillRegion(new SolidBrush(Color.Red), target);
// Покраска

Напомним, что исходный объект — target, это весь холст (поверхность формы), rombR аффинным преобразованием мы превратили в прямоугольник, основанием мишени служит rombB, а шляпой — rombG.

Для получения мишени использованы три базовых метода (три базовые операции, известные из теории множеств): пересечение (Intersect), разность (Xor)  и объединение (Union). После 1 оператора наша мишень стала прямоугольником (точки, принадлежащие одновременно и холсту и прямоугольнику), после 2-го оделась шляпа, но была исключена область пересечения шляпы и прямоугольника, после 3-го появился ромб в основании мишени. мишень После покраски результирующей области мишень будет выглядеть так: мишень_0

Комбинируя методы и области, мы сможем получать области произвольной конфигурации.

А теперь защитим область, определяемую мишенью, от изменений (“заборчик”), используя метод холста g:
g.ExcludeClip(target);
который обновляет вырезанную область, определяемую target. Теперь область мишени, исключая внутреннюю полость, для изменений недоступна.

Оформим это в методе private void Form1_Click(object sender, EventArgs e).
В тексте метода указаны также другие операторы с комментариями, которые могут быть Вами изучены.
В обработчике private void button1_Click(object sender, EventArgs e)   (кнопка «Сбросить») оператор g.ResetClip(); сбрасывает вырезанную область и делает ее бесконечной. Операторы:  Image image = Image.FromFile(«ris1.jpg»); g.DrawImage(Image.FromFile(«ris1.jpg»), 0, 0); позволяют отобразить фон.
Отметим, что если уберем оператор g.ResetClip(); (проще всего, закомментируйте его), то на вырезанной области рисунка не будет.
Копирование части экрана выполним, используя структуры Point и Size: Point sourse = new Point(180, 270);      // источник
Point destination = new Point(0, 0);     // приемник
Size size = new Size();                  // размер   
size.Width = 120;
size.Height = 120;
g.CopyFromScreen(sourse, destination, size); // копирование 

В обработчике button2_Click() (кнопка «Восстановить») укажем один оператор: g.Clear(BackColor); который очищает всю невырезанную поверхность и выполняет ее заливку цветом фона.

Текст файла Form1.cs представлен ниже.

// Рисование и защита произвольных областей на холсте
// Для формы задать StartPosition=Manual 
using System;
using System.Drawing;
using System.Drawing.Drawing2D;  // добавлено
using System.Windows.Forms;
namespace защита областей
{ 
   public partial class Form1 : Form
   {
      Graphics g; // холст
      public Form1()
      {
         InitializeComponent();
         g = Graphics.FromHwnd(this.Handle);  // холст
         // сглаживание:
         g.SmoothingMode = SmoothingMode.AntiAlias;
      }
      
      // пример задания области "ромб"
      public Region romb(int x, int y, int dx, int dy)
      {
         int [,] pts = { {x,y-dy},{x+dx,y},{x,y+dy},{x-dx,y},{x,y-dy} };
         // ромб через массивы
         Point []pt = new Point[5];      // массив точек
         byte [] typ = new byte [5];     // массив соединений
         for (int p = 0; p < 5; p++)
         {
            pt[p].X = pts[p, 0];
            pt[p].Y = pts[p, 1];
            typ[p] = (byte)PathPointType.Line;         
         };
         GraphicsPath gp = new GraphicsPath(pt, typ); 
         Region romb = new System.Drawing.Region(gp);
         return romb;
      }
      // пример работы с областями
      private void Form1_Click(object sender, EventArgs e)
      {
         // Задание областей
         Region rombR = romb(300,200,100,150);  // красный ромб
         Matrix matr = new Matrix(); // Единичная матрица
         matr.RotateAt(45, new Point(300, 200)); // поворот на 45 
         // градусов по часовой стрелке относительно (x,y)
         rombR.Transform(matr);    // трансформация
         matr = new Matrix();
         matr.Scale(1.0f, 0.8f);  // растяжение-сжатие по осям
         rombR.Transform(matr);   // трансформация
         matr = new Matrix();
         matr.Shear(0.0f, 0.0f);  // сдвиги:коэффициенты по х и y
         rombR.Transform(matr);   // трансформация
         Region rombG = romb(300, 100, 150, 20); //  зеленый ромб
         Region rombB = romb(300, 210, 200, 30); //  синий ромб
         Rectangle rect = new Rectangle(0,0,this.Width,this.Height);
         // прямоугольник
         Region target = new Region(rect); // некая заготовка - окно
         // Формирование мишени - ro
         target.Intersect(rombR);
         target.Xor(rombG);
         target.Union(rombB);
         //  Покраска мишени
         g.FillRegion(new SolidBrush(Color.Red), target);
         // g.FillRegion(new SolidBrush(Color.Blue), rombB);
         // g.FillRegion(new SolidBrush(Color.Green), rombG);
         // Защита
         g.ExcludeClip(target);   //  обновляет вырезанную область, чтобы исключить из нее часть, определяемую target - "все кроме"

      // Изучите действие еще трех операторов - убрав "//" :   
         // g.IntersectClip(target); //  обновляет вырезанную область, включая в нее пересечение текущей области и указанной структуры
         // тогда будет все защищено.
         //  g.SetClip(rombG, CombineMode.Union); // объединение
         // вырезанных областей g+romb->g
         //  g.SetClip(rombB, CombineMode.Xor);
      }

      //  Сбросить защиту, показать фон, копировать часть экрана
      private void button1_Click(object sender, EventArgs e)
      {
         g.ResetClip();  // сбрасывает вырезанную область(ти)
                         // и делает ее бесконечной
         Image image = Image.FromFile("ris1.jpg"); // из файла
         g.DrawImage(Image.FromFile("ris1.jpg"), 0, 0);   
         // отобразить фон (убрав g.ResetClip(); - 
         // на вырезанной области рисунка не будет) 
         // копирование части экрана 
         Point sourse = new Point(180, 270);  // источник 
         Point destination = new Point(0, 0); // приемник 
         Size size = new Size();  // размер 
         size.Width = 120; 
         size.Height = 120; 
         g.CopyFromScreen(sourse, destination, size); // копирование 
      }

      private void button2_Click(object sender, EventArgs e)
      //  Восстановить 
      { 
         g.Clear(Color.BackColor);  // очищает всю невырезанную 
                         //поверхность и выполняет ее заливку 
         //  g.Dispose();  //  освобождает ресурсы g насовсем 
      }
   }
 }

После нажатия кнопки «Сбросить» и клика на форме получим следующее изображение:
фео
где красным на фоне рисунка наблюдается мишень, а в левом верхнем углу — копия купола с нижней части фото.
После нажатия клавиши «Восстановить» фон (кроме мишени) очистится.
После нажатия клавиши «Сбросить» мишень исчезнет и появится рисунок.

После клика на форме появится на фоне рисунка мишень.
Таким образом, если Вы определили область, как Region target, то для ее защиты достаточно применить оператор  g.ExcludeClip(target);  Вся остальная область будет рассматриваться как фон, на котором можно рисовать.
Последующее применение оператора g.IntersectClip(target); обеспечит защиту всего изображения (удалите комментарий перед этим оператором!).
Если после этого оператора добавите g.SetClip(target, CombineMode.Xor); (он также в тексте закомментирован), то защищенной областью станет весь фон кроме мишени (задается параметром CombineMode.Xor), а рисование станет возможным только в области мишени.

Вывод. Используя объекты класса Region и рассмотренные в примере  методы, мы можем осуществлять разделение предметов и фона.

Пора перейти к рассмотрению способов анимации. И первая задача «Шар на бильярдном столе«.

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

Ваш e-mail не будет опубликован. Обязательные поля помечены *