Дата публикации статьи: 10.07.2003 00:00
[введение] BitBlt - важная часть GDI32 API. Если Вы хотите делать игру, вам просто необходимо научиться переносу битов графического образа (Blit) из-за его быстроты. Знакомство с BitBlt будет хорошей стартовой точкой для изучения DirectDraw. Существует два способа, через которые может быть реализован BitBlt. Есть "Плохой" способ - использовать Picture Box, как область памяти для хранения графического образа, это очень медленный способ. "Хороший" способ - это использовать напрямую зарезервированную область памяти для хранения в ней графики. Это позволяет создавать быструю анимацию без заметных мерцаний. Зарезервированная область памяти называется Memory DC (DC - Device Context - контекст устройства), или просто DC.
Blitting состоит из нескольких шагов:
1. Создать разные DC для фона, спрайтов, их масок и рабочей области для того чтобы собрать все вместе. 2. Загрузить фон, спрайты и маски в свои DC 3. Загрузить фон в DC рабочей области 4. Загрузить маски в DС рабочей области 5. Загрузить спрайты в DC рабочей области 6. Отобразить содержимое DC рабочей области на экран
Этот процесс также называется буфферинг. Если вы будете собирать изображение из нескольких спрайтов прямо на экране, будет заметно мерцание. С помощью буфферинга, вы переводите изображение на экран за одну операцию.
[форма]
Picture1: ScaleMode=Pixel Timer1: Enabled=False Interval=30
[программа]
Для начала, определим несколько функций. Я рекомендую поместить эти объявления в модуль, так чтобы Вы могли поключить его к проекту, где понадобится BitBlt. Сначала, объявления:
Declare Function CreateCompatibleDC Lib "gdi32" (ByVal hdc As Long) As Long Declare Function CreateCompatibleBitmap Lib "gdi32" (ByVal hdc As Long, ByVal nWidth As Long, ByVal nHeight As Long) As Long Declare Function DeleteObject Lib "gdi32" (ByVal hObject As Long) As Long Declare Function DeleteDC Lib "gdi32" (ByVal hdc As Long) As Long Declare Function SelectObject Lib "gdi32" (ByVal hdc As Long, ByVal hObject As Long) As Long Declare Function BitBlt Lib "gdi32" (ByVal hDestDC As Long, ByVal x As Long, ByVal y As Long, ByVal nWidth As Long, ByVal nHeight As Long, ByVal hSrcDC As Long, ByVal xSrc As Long, ByVal ySrc As Long, ByVal dwRop As Long) As Long
Здесь объявляются функции API, которые мы будем использовать. CreateCompatibleDC создает экземпляр DC, CreateCompatibleBitmap - растрового изображения, DeleteObject и DeleteDC... а вот не скажу! BitBlt получается как сердце, соединяющее все это. Синтаксис BitBlt похож на синтаксис PaintPicture.
Теперь, несколько массивов, костант и объявление типа.
Dim MemHdc() As Long Dim BitMapHdc() As Long Dim TrashbmpHdc() As Long Dim NumOfDcs As Integer
Type tArea hdc As Long left As Integer top As Integer Width As Integer Height As Integer End Type
Public Const SRCAND = &H8800C6 Public Const SRCCOPY = &HCC0020 Public Const SRCINVERT = &H660046
С помощью типа мы сможем подступиться к нужной области памяти. Константы определяют, каким образом будет скопировано растровое изображение. SRCAND используется для наложения маски. Маска обрезает ненужные пиксели и меняет цвет других так, чтобы вокруг спрайта на фоне не было черного обрамления. SRCINVERT используется для спрайта, а SRCCOPY для загрузки фона и окончания переноса битов. После этого, нам надо создать DС. Я использовал для этого функцию.
Function CreateMemHdc (ScreenHdc As Long, Width As Integer, Height As Integer) As Long
Redim Preserve MemHdc(NumOfDcs) Redim Preserve BitMapHdc(NumOfDcs) Redim Preserve TrashbmpHdc(NumOfDcs)
MemHdc(NumOfDcs) = CreateCompatibleDC(ScreenHdc) If MemHdc(NumOfDcs) Then BitMapHdc(NumOfDcs) = CreateCompatibleBitmap(ScreenHdc, Width, Height) If BitMapHdc(NumOfDcs) Then TrashbmpHdc(NumOfDcs) = SelectObject(MemHdc(NumOfDcs), BitMapHdc(NumOfDcs)) CreateMemHdc(NumOfDcs) End If End If NumOfDcs = NumOfDcs + 1 End Function
Эта функция вызывается позже в программе. Вот место, где рождаются DC! Конечно, создание одних лишь DC без загрузки в них графики - бесполезная трата времени. Вот подпрограмма, которая занимается этим:
Sub LoadBmpToHdc(MHdc As Long, FileN As String) Dim OrgBmp As Long OrgBmp = SelectObject(MHdc, LoadPicture(FileN)) If OrgBmp Then DeleteObject (OrgBmp) End If End Sub
Сердце этой подпрограммы - вызов функции SelectObject, которая загружает изображение из файла в созданный DC. Отлично, этот модуль почти сделан, но нам еще понадобится подпрограмма, которая уничтожит все созданные DC, чтобы освободить ОЗУ при выходе из программы.
Sub DestroyHdcs() Dim i As Integer For i = 0 To NumOfDcs - 1 BitMapHdc(i) = SelectObject(MemHdc(i), TrashbmpHdc(i)) DeleteObject (BitMapHdc(i)) DeleteDC (MemHdc(i)) Next i End Sub
Итак, это уже целый модуль. То есть, если вы скопируете его себе, все должно работать (если только нет опечаток, но я вроде проверял). Наконец, настало время писать подпрограммы, которые соберут вместе все вышеупомянутые процедуры и заставят их работать. Снова все по порядку. Сначала секция General:
Dim x As Integer Dim PicSprite As tArea Dim PicMask As tArea Dim PicWork As tArea Dim PicOrgBack As tArea
Здесь определяются все объекты, использующиеся для адресации наших DC. PicOrgBack - это оригинальный фон, PicSprite и PicMask для спрайта и для маски, PicWork это для DC, где все собирается. Теперь, процедура Form_Load:
Private Sub Form_Load() Call init Timer1.Enabled = True End Sub
Private Sub Form_Unload(Cancel As Integer) Call DestroyHdcs End Sub Здесь вызывается начальная процедура, которая создаст DC:
Sub init() PicOrgBack.hdc = CreateMemHdc(Picture1.hdc, Picture1.ScaleWidth, Picture1.ScaleHeight) Call LoadBmpToHdc(PicOrgBack.hdc, app.path & "map1.bmp")
PicSprite.hdc = CreateMemHdc (Picture1.hdc, 77, 63) Call LoadBmpToHdc (PicSprite.hdc, app.path & "circle2.bmp")
PicMask.hdc = CreateMemHdc (Picture1.hdc, 77, 63) Call LoadBmpToHdc (PicMask.hdc, app.path & "circle3.bmp")
PicWork.hdc = CreateMemHdc (Picture1.hdc, Picture1.ScaleWidth, Picture1.ScaleHeight) End Sub
Здесь вызывается CreateMemHdc для создания DC, затем растровые изображения загружаются в свежесозданные DC, за исключением PicWork, так как там мы будем собирать сцену. Для PicSprite и PicMask числа - это реальный размер изображения. Теперь код для элемента Timer, с помощью которого будет создана анимация.
Private Sub Timer1_Timer() Call screen_refresh x = x + 25 If x > 300 Then x = 0 End Sub
Таймер вызывает процедуру screen_refresh, которая с помощью переноса битов собирает все вместе и очищает предыдущий экран. Она копирует все в PicWork, а затем перебрасывает его содержимое в PictureBox.
Sub screen_refresh() BitBlt PicWork.hdc, 0, 0, Picture1.ScaleWidth, Picture1.ScaleHeight, PicOrgBack.hdc, 0, 0, SRCCOPY BitBlt PicWork.hdc, x, 50, 73, 63, PicMask.hdc, 0, 0, SRCAND BitBlt PicWork.hdc, x, 50, 73, 63, PicSprite.hdc, 0, 0, SRCINVERT BitBlt Picture1.hdc, 0, 0, Picture1.ScaleWidth, Picture1.ScaleHeight, PicWork.hdc, 0, 0, SRCCOPY End Sub
Сначала фон копируется на рабочую часть. Затем, накладывается маска за ней - спрайт. Наконец, последняя инструкция копирует сцену из памяти на экран.
Попробуйте изменять свойство Inteval у таймера. Такой способ не повлияет на скорость PaintPicture, так как он и так медленный, но при использовании BitBlt вы сразу заметите разницу. Вы можете, а скорее даже нужно заменить объект Таймер циклом. Таймер - очень плохой элемент для анимации из за своих глюков.
Ну вот, это был учебник BitBlt. Если вы запустили программу и удивились - какая разница?! - попробуйте запустить программу, использующую PaintPicture в одном окне, а рядом нашу программу с BitBlt. BitBlt как минимум в три раза быстрее! Я надеюсь, что четко все для вас изложил. Я провел чертовски тяжелое время, добывая хорошую информацию. Примерно 3/4 учебников я написал используя "плохую" технику blitting'а. Я бы никогда не научился blitting'у без Timbo's VB Game Tutorial и программы Tutor2 с Zorro's VB Fun Page.
|