Дата публикации статьи: 02.07.2006 18:02

ГЛАВА 9. Примеры работы с GDI+ графикой в среде .NET

    На смену библиотеке GDI постепенно приходит GDI+. Самой сильной стороной новой библиотеки GDI+ по сравнению с GDI является, не то что она позволяет работать с графикой лучше, а то что значительно проще. Она легко “переваривает” популярные форматы графических файлов. В GDI+ инкапсулировано много “красивостей” для векторной и битовой графики, но больше всего поражает легкость работы битовой графикой. Для представления технологических процессов часто надо отображать различного рода приборы имеющие шкалу в виде круга или переключатель вращающийся вокруг оси. В случае с векторной графикой эту задачу мы уже решали в главе два из первой части. Для битовой графики поворот стрелки или переключателя вокруг оси представляет не такую и простую задачу в обычном GDI. Такого рода трансформации вообще нельзя произвести без искажений, начинают появляться зубцы у пикселов, необходима писать собственный алгоритм заполнения площади при повороте на углы отличные от прямых. Разработчики GDI+ решили эту задачу с помощью метода DrawImage() в классе Graphics. С помощью этого метода можно масштабировать, выводить определенные части, перемещать или вращать изображения. В нашем случае (для представления приборов с круглыми шкалами) метод DrawImage() в качестве одного из аргумента принимает массива из трех точек. Точки должны соответствовать трем координатам вершин прямоугольника в который вписано, выводимое изображение - левой верхней, нижней и правой верхней. Приложение, демонстрирующее применение метода DrawImage() для поворота растрового изображения находится в папке N21. Программирование для .NET сильно отличается от программирования в VisualBasic6, поэтому не важно на каком из языков VB.NET или C# вы получите первоначальный опыт. Оба эти языка практически равнозначны. Переписать приложение созданное на C# в точно такое же на VB.NET не составляет труда. В папке N21 находятся проекты близнецы написанные на C# и VB.NET и в дальнейшем каждый пример будет представлен на обоих языках. Особо подчеркивается, что разработчики перешедшие с VB на VB.NET приобретают новые возможности связанные с объектно-ориентированным программированием (хотя не кто не мешает использовать старый процедурный подход), поэтому теперь приложение это класс, порожденный от верхнего по иерархии класса System.Windows.Forms.Form (в случае с оконными приложениями). Объявим переменные уровня класса. Для обычного VB это переменные уровня формы, объявленные в разделеGneral после Option Explicit:

Public Class Form1
    Inherits System.Windows.Forms.Form
    Dim fFile As New Bitmap("Uro.bmp")
    Dim mem1 As New Bitmap(215, 212, PixelFormat.Format32bppArgb)
    Dim mem2 As New Bitmap(42, 97, PixelFormat.Format32bppArgb)
    Dim mem3 As New Bitmap(132, 212, PixelFormat.Format32bppArgb)
    Dim mem4 As New Bitmap(166, 212, PixelFormat.Format32bppArgb)
    Dim mem5 As New Bitmap(210, 212, PixelFormat.Format32bppArgb)
    Dim x, x1, x2, y, y1, y2 As Long
    Dim x3, x13, x23, y3, y13, y23 As Long
    Dim x4, x14, x24, y4, y14, y24 As Long
    Dim rad, rad3, rad4 As Double
 Dim ugol, ugol3, ugol4 As Double

Такой же код написанный на C#:

	public class Form1 : System.Windows.Forms.Form
	{
		Bitmap fFile;
		Bitmap mem1;
		Bitmap mem2;
		Bitmap mem3;
		Bitmap mem4;
		Bitmap mem5;
		int x,x1,x2,y,y1,y2;
		int x3,x13,x23,y3,y13,y23;
		int x4,x14,x24,y4,y14,y24;
		double rad;  
		double rad3; 
		double rad4;
		double ugol;
		double ugol3;
		double ugol4; 

    Некоторые отличия в коде не приводятся они второстепенны и относятся только к различиям построением каркасов приложений на языках C# и VB.NET. Построение стандартного каркаса приложения вы должны изучить самостоятельно по одному из самоучителей широко представленных в магазинах. На уровне класса объявлено шесть объектных переменных, инкапсулирующих битовую карту. Эти переменные также являются классами и в соответствии с правилами ООП (объектно-ориентированное программирование) имеют конструкторы, необходимые для правильного создания в памяти объектов класса. В первая битовая карта (судя по конструктору) создается из файла, а остальные пять создают в памяти области с размерами, указанными в первых двух аргументах конструктора. Каждый пиксел этих областей имеет формат RGB с альфа каналом требует для своего представления 32 бита. Для создания объекта класса приложения, так же нужен конструктор. Даже если мы его не используем для правильного создания объекта по правилам ООП он все равно нужен. Используя при объявлении переменных ключевое слово New для VB.NET мы ничего не указали в конструкторе и он пустой:

    Public Sub New()
        MyBase.New()
        InitializeComponent()
    End Sub

Конструктор C# выглядит следующим образом:

public Form1()
	{
		InitializeComponent();
		fFile = new Bitmap("Uro.bmp");
		mem1 = new  Bitmap(215,212,PixelFormat.Format32bppArgb);
		mem2 = new  Bitmap(42,97,PixelFormat.Format32bppArgb);
		mem3 = new  Bitmap(132,212,PixelFormat.Format32bppArgb);
		mem4 = new  Bitmap(166,212,PixelFormat.Format32bppArgb);
		mem5 = new  Bitmap(210,212,PixelFormat.Format32bppArgb);
		ugol = 0;
		rad = 0;
		ugol3 = 0;
		rad3 = 0;
		ugol4 = 0;
		rad4 = 0;
		}

    Можно сказать, что по сравнению с VB.NET в C# переменные с начало объявили, а потом вынесли в конструктор для правильного создания объекта в памяти.
Оба приложения, как и в VB имеют событие Form1_Load(). Для VB.NET оно выглядит так:

Private Sub Form1_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles MyBase.Load
        ugol = 0
        rad = 0
        ugol3 = 0
        rad3 = 0
        ugol4 = 0
        rad4 = 0
        'Стрелка и циферблат
        Dim red, gren, blue, sum, xb, yb As Long
        For xb = 0 To 215 Step 1
            For yb = 0 To 212 Step 1
                red = fFile.GetPixel(xb, yb).R
                gren = fFile.GetPixel(xb, yb).G
                blue = fFile.GetPixel(xb, yb).B
                sum = red + gren + blue
                If sum < 700 Then
                    mem1.SetPixel(xb, yb, Color.FromArgb(255, red, gren, blue))
                End If
            Next
        Next
        For xb = 216 To 255 Step 1
            For yb = 45 To 140 Step 1
                red = fFile.GetPixel(xb, yb).R
                gren = fFile.GetPixel(xb, yb).G
                blue = fFile.GetPixel(xb, yb).B
                sum = red + gren + blue
                If sum < 700 Then
                    mem2.SetPixel(xb - 216, yb - 45, Color.FromArgb(255, red, gren, blue))
                Else
                    mem2.SetPixel(xb - 216, yb - 45, Color.FromArgb(0, red, gren, blue))
                End If
            Next
        Next
        ''Прибор с кругами1
        For xb = 256 To 388 Step 1
            For yb = 0 To 212 Step 1
                red = fFile.GetPixel(xb, yb).R
                gren = fFile.GetPixel(xb, yb).G
                blue = fFile.GetPixel(xb, yb).B
                sum = red + gren + blue
                If (sum < 700) Then
                    mem3.SetPixel(xb - 256, yb, Color.FromArgb(255, red, gren, blue))
                End If
            Next
        Next
        ''Прибор с кругами2
        For xb = 389 To 555 Step 1
            For yb = 0 To 212 Step 1
                red = fFile.GetPixel(xb, yb).R
                gren = fFile.GetPixel(xb, yb).G
                blue = fFile.GetPixel(xb, yb).B
                sum = red + gren + blue
                If (sum < 700) Then
                    mem4.SetPixel(xb - 389, yb, Color.FromArgb(255, red, gren, blue))
                End If
            Next
        Next
        ''Прибор с кругами3
        For xb = 561 To 771 Step 1
            For yb = 0 To 212 Step 1
                red = fFile.GetPixel(xb, yb).R
                gren = fFile.GetPixel(xb, yb).G
                blue = fFile.GetPixel(xb, yb).B
                sum = red + gren + blue
                If (sum < 700) Then		
                    mem5.SetPixel(xb - 561, yb, Color.FromArgb(255, red, gren, blue))
                End If
            Next
        Next
    End Sub

Такой же код написанный на C#:

private void Form1_Load(object sender, System.EventArgs e)
{
//Стрелка и циферблат
int red,gren,blue,sum;
for (int xb = 0 ;xb < 215; xb += 1)
{
for (int yb = 0 ;yb < 212; yb += 1)
	{
		red	= fFile.GetPixel(xb,yb).R;
		gren = fFile.GetPixel(xb,yb).G;
		blue = fFile.GetPixel(xb,yb).B;
		sum = red + gren + blue;
		if (sum < 700) 
		{		
			mem1.SetPixel(xb,yb,Color.FromArgb(255,red,gren,blue));
		}
		else
		{
			mem1.SetPixel(xb,yb,Color.FromArgb(0,red,gren,blue));
		}
	}
}
//
for (int xb = 216 ;xb < 255; xb += 1)
{
for (int yb = 45 ;yb < 140; yb += 1)
	{
		red	= fFile.GetPixel(xb,yb).R;
		gren = fFile.GetPixel(xb,yb).G;
		blue = fFile.GetPixel(xb,yb).B;
		sum = red + gren + blue;
		if (sum < 700) 
		{		
			mem2.SetPixel(xb-216,yb-45,Color.FromArgb(255,red,gren,blue));
		}
		else
		{
			mem2.SetPixel(xb-216,yb-45,Color.FromArgb(0,red,gren,blue));
		}
	}
}
//Прибор с кругами1
for (int xb = 256 ;xb < 388; xb += 1)
{
	for (int yb = 0 ;yb < 212; yb += 1)
	{
		red	= fFile.GetPixel(xb,yb).R;
		gren = fFile.GetPixel(xb,yb).G;
		blue = fFile.GetPixel(xb,yb).B;
		sum = red + gren + blue;
		if (sum < 700) 
		{		
			mem3.SetPixel(xb-256,yb,Color.FromArgb(255,red,gren,blue));
		}
		else
		{
			mem3.SetPixel(xb-256,yb,Color.FromArgb(0,red,gren,blue));
		}
	}
}
//Прибор с кругами2
for (int xb = 389 ;xb < 555; xb += 1)
{
	for (int yb = 0 ;yb < 212; yb += 1)
	{
		red	= fFile.GetPixel(xb,yb).R;
		gren = fFile.GetPixel(xb,yb).G;
		blue = fFile.GetPixel(xb,yb).B;
		sum = red + gren + blue;
		if (sum < 700) 
		{		
			mem4.SetPixel(xb-389,yb,Color.FromArgb(255,red,gren,blue));
		}
		else
		{
				mem4.SetPixel(xb-389,yb,Color.FromArgb(0,red,gren,blue));
		}
	}
}
//Прибор с кругами3
for (int xb = 561 ;xb < 771; xb += 1)
{
	for (int yb = 0 ;yb < 212; yb += 1)
	{
		red	= fFile.GetPixel(xb,yb).R;
		gren = fFile.GetPixel(xb,yb).G;
		blue = fFile.GetPixel(xb,yb).B;
		sum = red + gren + blue;
		if (sum < 700) 
		{		
			mem5.SetPixel(xb-561,yb,Color.FromArgb(255,red,gren,blue));
		}
		else
		{
			mem5.SetPixel(xb-561,yb,Color.FromArgb(0,red,gren,blue));
		}
	}
}
}

    Точно так же, как и в VB оба приложение имеют событие Form1_Paint(). В этом событии сохраняется состояние графических объектов, необходимое для обновления окна после того, как окно станет не действительным. Так как в данном случае используется поворот необходимо применить тригонометрические функции. Вращению подвергаются три точки прямоугольной области в которое вписано изображение. Обратите внимание на первые строчки из события orm1_Paint() они являются аналогом получения контекста устройства, только теперь контекст стал объектом и называется Graphics:

    Private Sub Form1_Paint(ByVal sender As Object, ByVal e As System.Windows.Forms.PaintEventArgs) Handles MyBase.Paint
        Dim g As Graphics
        g = e.Graphics
        Dim rect As New Rectangle(0, 0, 215, 212)
        g.DrawImage(mem1, 0, 0, rect, GraphicsUnit.Pixel)
        x = CLng((Math.Cos(rad) * -20) - (Math.Sin(rad) * -93) + 105)
        y = CLng((Math.Sin(rad) * -20) + (Math.Cos(rad) * -93) + 105)
        x1 = CLng((Math.Cos(rad) * 20) - (Math.Sin(rad) * -93) + 105)
        y1 = CLng((Math.Sin(rad) * 20) + (Math.Cos(rad) * -93) + 105)
        x2 = CLng((Math.Cos(rad) * -20) - (Math.Sin(rad) * 2) + 105)
        y2 = CLng((Math.Sin(rad) * -20) + (Math.Cos(rad) * 2) + 105)
        Dim Toska() As Point = {New Point(x, y), New Point(x1, y1), New Point(x2, y2)}
        g.DrawImage(mem2, Toska)
        ''
        x3 = CLng((Math.Cos(rad3) * -66) - (Math.Sin(rad3) * -106) + 354)
        y3 = CLng((Math.Sin(rad3) * -66) + (Math.Cos(rad3) * -106) + 105)
        x13 = CLng((Math.Cos(rad3) * 66) - (Math.Sin(rad3) * -106) + 354)
        y13 = CLng((Math.Sin(rad3) * 66) + (Math.Cos(rad3) * -106) + 105)
        x23 = CLng((Math.Cos(rad3) * -66) - (Math.Sin(rad3) * 106) + 354)
        y23 = CLng((Math.Sin(rad3) * -66) + (Math.Cos(rad3) * 106) + 105)
        Dim Toska1() As Point = {New Point(x3, y3), New Point(x13, y13), New Point(x23, y23)}
        g.DrawImage(mem3, Toska1)
        ''
        x4 = CLng((Math.Cos(rad4) * -83) - (Math.Sin(rad4) * -106) + 354)
        y4 = CLng((Math.Sin(rad4) * -83) + (Math.Cos(rad4) * -106) + 105)
        x14 = CLng((Math.Cos(rad4) * 83) - (Math.Sin(rad4) * -106) + 354)
        y14 = CLng((Math.Sin(rad4) * 83) + (Math.Cos(rad4) * -106) + 105)
        x24 = CLng((Math.Cos(rad4) * -83) - (Math.Sin(rad4) * 106) + 354)
        y24 = CLng((Math.Sin(rad4) * -83) + (Math.Cos(rad4) * 106) + 105)

        Dim Toska2() As Point = {New Point(x4, y4), New Point(x14, y14), New Point(x24, y24)}
        g.DrawImage(mem4, Toska2)
        g.DrawImage(mem5, 250, 0, New Rectangle(0, 0, 210, 212), GraphicsUnit.Pixel)

    End Sub

Такой же код написанный на C#:

private void Form1_Paint(object sender, System.Windows.Forms.PaintEventArgs e)
{
Graphics g = e.Graphics;
//
Rectangle rect2 = new Rectangle (0,0,215,212);
g.DrawImage(mem1,0,0,rect2,GraphicsUnit.Pixel);
//
x = (int)((Math.Cos(rad)*-20) - (Math.Sin(rad)*-93) + 105);
y = (int)((Math.Sin(rad)*-20) + (Math.Cos(rad)*-93)+ 105);
x1 = (int)((Math.Cos(rad)*20) - (Math.Sin(rad)*-93)+ 105);
y1 = (int)((Math.Sin(rad)*20) + (Math.Cos(rad)*-93)+ 105);
x2 = (int)((Math.Cos(rad)*-20) - (Math.Sin(rad)*2)+ 105);
y2 = (int)((Math.Sin(rad)*-20) + (Math.Cos(rad)*2)+ 105);
g.DrawImage(mem2, new Point[]{new Point(x,y),new Point(x1,y1),new Point(x2,y2)});
//
x3 = (int)((Math.Cos(rad3)*-66) - (Math.Sin(rad3)*-106) + 354);
y3 = (int)((Math.Sin(rad3)*-66) + (Math.Cos(rad3)*-106)+ 105);
x13 = (int)((Math.Cos(rad3)*66) - (Math.Sin(rad3)*-106)+ 354);
y13 = (int)((Math.Sin(rad3)*66) + (Math.Cos(rad3)*-106)+ 105);
x23 = (int)((Math.Cos(rad3)*-66) - (Math.Sin(rad3)*106)+ 354);
y23 = (int)((Math.Sin(rad3)*-66) + (Math.Cos(rad3)*106)+ 105);
g.DrawImage(mem3, new Point[]{new Point(x3,y3),new Point(x13,y13),new Point(x23,y23)});
x4 = (int)((Math.Cos(rad4)*-83) - (Math.Sin(rad4)*-106) + 354);
y4 = (int)((Math.Sin(rad4)*-83) + (Math.Cos(rad4)*-106)+ 105);
x14 = (int)((Math.Cos(rad4)*83) - (Math.Sin(rad4)*-106)+ 354);
y14 = (int)((Math.Sin(rad4)*83) + (Math.Cos(rad4)*-106)+ 105);
x24 = (int)((Math.Cos(rad4)*-83) - (Math.Sin(rad4)*106)+ 354);
y24 = (int)((Math.Sin(rad4)*-83) + (Math.Cos(rad4)*106)+ 105);
g.DrawImage(mem4, new Point[]{new Point(x4,y4),new Point(x14,y14),new Point(x24,y24)});
Rectangle rect5 = new Rectangle (0,0,210,212);
g.DrawImage(mem5,250,0,rect5,GraphicsUnit.Pixel);
}

    На форме представлены три кнопки с событиях которых изменяются углы. Код в событиях кнопок практически совпадает с кодом в событии Form1_Paint(). Единственным принципиальным отличием является способ получения объекта Graphics. Для C# этот код имеет вид:
Graphics gg3 = Graphics.FromHwnd(this.Handle).
Для VB.NET тот же самый код имеет вид:

Dim gg3 As Graphics
gg3 = Me.CreateGraphics()

    Нажимая на кнопки в событиях изменяются значения переменных уровня класса формы, что изменяет угол поворота трех точек из функции DrawImage(), который представлен в радианах - rad = ugol / 562.2. На рисунке 1 приложение представлено в работе.


Рис. 1

    Если на первом приборе дрожание заметно не сильно, то на втором дрожание существенно, хотя конечно не критично. Как было показано выше средства DirectX позволяют избежать не желательного дрожания.
    В следующей главе будет рассмотрена последняя на сегодняшний день версия DirectX9, которая написана с учетом платформы .NET, но уже без оглядки на обычный VB. Фирма Microsoft не будет развивать обычный Visual Basic, поэтому версии DirectX девять “заточенной” под него не существует.