Постановка задачи. Часто бывает необходимо изображение разделить на две составляющие: предметы (движущиеся) и фон (неподвижный), или наоборот, неподвижный забор с дырками в нем и некоторые предметы, двигающиеся за ним. Форма дырок в заборе может быть произвольная, как и форма движущихся предметов.
В предыдущем примере была продемонстрирована возможность задания произвольных контуров предметов, теперь мы перейдем к изучению возможностей задания произвольных областей. Защита части холста от изменений бывает необходима для алгоритмов анимации.
Для демонстрации работы с областями выполним подготовительные действия:
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-го появился ромб в основании мишени. После покраски результирующей области мишень будет выглядеть так:
Комбинируя методы и области, мы сможем получать области произвольной конфигурации.
А теперь защитим область, определяемую мишенью, от изменений (“заборчик”), используя метод холста 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 и рассмотренные в примере методы, мы можем осуществлять разделение предметов и фона.
Пора перейти к рассмотрению способов анимации. И первая задача «Шар на бильярдном столе«.
NEW: Наш Чат, в котором вы можете обсудить любые вопросы, идеи, поделиться опытом или связаться с администраторами.