Статьи

Опыт создания "вырезанных" форм.

Часть 2

Часть вторая посвящена работе с картинками, по контуру которых происходит "вырезание". Здесь вынужден с прискорбием заметить, что не все графические форматы удовлетворяют для этой работы. Например, наиболее популярный формат JPG, обладающий хорошим сжатием абсолютно не подходит из-за невозможности создать ровный однотонный фон для исключаемого цвета. Формат GIF, так же ограничен в применении, но по другой причине - он поддерживает только 256 цветов, что не позволяет в полной мере использовать фотографические изображения. В итоге нам остается стандартный BMP, поддерживающий возможность фотографического изображения и исключения однотонного фонового цвета, но, к сожалению, достаточно большой по объему.

Принципы работы с  вырезанием графического изображения по контуру остаются теми же, что и описанные в 1-ой части. А именно:

И если последние два пункта остаются неизменными, то с первым придется немного повозиться. Общий принцип создания региона - это считывание информации о каждом пикселе и включение в регион тех, которые не содержат исключаемый цвет. Давайте посмотрим как это выглядит в программе.

Примечание: Основа кодов предоставлена Иваном Шатрыкиным

Напишем отдельную функцию, создающую регион, с исключением заказанного, исключаемого цвета. Вначале нам понадобятся истинные размеры картинки в пикселах. Для этого используем объектную переменную sPic, приравняем ее к свойству Picture текущей формы. Правда размеры мы получим в единицах HIMETRIC, но это легко преобразуется в пикселы с помощью стандартных VB-функций  - ScaleX и ScaleY. Изначально создадим пустой регион. А затем, для каждого пиксела картинки, получая их с помощью API-функции GetPixel, устроим проверку. Если пиксел подходит нам (т.е. не содержит исключаемого цвета), мы включаем его в регион.
Объявление необходимых API-функций см. в кодах примера.

Public Function lGetRegion(frm As Form, lBackColor As Long) As Long
Dim lRgn As Long
Dim lSkinRgn As Long
Dim lStart As Long
Dim lX As Long
Dim lY As Long
Dim lHeight As Long
Dim lWidth As Long
Dim sPic As StdPicture

'создаем пустой регион, с которого начнем работу
lSkinRgn = CreateRectRgn(0, 0, 0, 0)

With frm
    Set sPic = .Picture
    'подсчитаем размеры рисунка в Pixel
    lHeight = .ScaleY(sPic.Height)
    lWidth = .ScaleX(sPic.Width)
    
    For lX = 0 To lHeight - 1
        lY = 0
        Do While lY < lWidth
        'ищем нужный Pixel
            Do While lY < lWidth And GetPixel(.hdc, lY, lX) = lBackColor
                lY = lY + 1
            Loop
        
            If lY < lWidth Then
            lStart = lY
            Do While lY < lWidth And GetPixel(.hdc, lY, lX) <> lBackColor
                lY = lY + 1
            Loop
            If lY > lWidth Then lY = lWidth
                'нужный Pixel найден, добавим его в регион
                lRgn = CreateRectRgn(lStart, lX, lY, lX + 1)
                CombineRgn lSkinRgn, lSkinRgn, lRgn, RGN_OR
                'удалим ненужный объект
                DeleteObject lRgn
            End If
        Loop
    Next
End With


Set sPic = Nothing

lGetRegion = lSkinRgn
End Function

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

Private Sub cmdPreview_Click()
Dim hRgn As Long

With frmDemonstration

    Select Case FindOptionButton
    ...
    Case 6
        Screen.MousePointer = vbHourglass
        .Picture = LoadPicture(App.Path & "\Sample.bmp")
        hRgn = lGetRegion(frmDemonstration, vbWhite)
        Screen.MousePointer = vbDefault
        ...
    End Select
    
    SetWindowRgn .hwnd, hRgn, True
    DeleteObject hRgn
    
    .Show vbModal
End With
End Sub

Чем прельщает этот пример, так это легкостью восприятия кода: считываем пикселы и ненужные выбрасываем. Однако здесь скрывается "маленький" недостаток - это скорость. Если картинка маленькая, то особых проблем не возникает. Но как только размеры картинки начинают увеличиваться, так катастрофически падает скорость появления вырезанного изображения на экране.
Как же ускорить этот процесс? Ответ напрашивается сам собой: перенести все расчеты региона в память. Поэтому следующий представленный здесь вариант несколько сложнее для восприятия, но зато и быстрее своего предшественника минимум в 4 раза.

Почему минимум? Потому что данная функция вынуждена учитывать цветовую палитру Вашего экрана. Давайте последовательно, шаг за шагом, разберем эту функцию. Для начала нам потребуется еще один тип, отвечающий за свойства картинки:
Type BITMAP
    bmType As Long
    bmWidth As Long
    bmHeight As Long
    bmWidthBytes As Long
    bmPlanes As Integer
    bmBitsPixel As Integer
    bmBits As Long
End Type

Так же, как и в предыдущем примере, будем использовать объектную переменную sPic
Public Function FitToPicture(frm As Form) As Long
...
Set sPic = frm.Picture

На следующем этапе получим информацию о картинке в формате типа BITMAP.
GetObject sPic, Len(bm), bm
lWidth = bm.bmWidth
lHeight = bm.bmHeight

А вот теперь, исходя из полученной цветовой палитры напишем обработку каждого пиксела:
Select Case bm.bmBitsPixel
Case равный 8 соответствует 256 цветам, остальные - соответствующему количеству бит. Обработка 8, 16 и 32 бит почти идентична, за исключением типа используемых массивов и вывода значения исключаемого цвета. Поэтому рассмотрим подробно только один из них на примере 16 бит.

Вначале переобъявим используемый массив, учитывая размеры картинки и получим это значение в битах
ReDim ints(0 To bm.bmWidthBytes \ colourDepth - 1, 0 To lHeight - 1)
GetBitmapBits sPic, lHeight * bm.bmWidthBytes, ints(0, 0)

А затем в цикле For ... Next произведем обработку каждого пиксела. Если пиксел удовлетворяет условиям (не является исключаемым цветом), то он добавляется в регион. Для ускорения обработки используется маленький трюк: считывается и записывается не каждый пиксел по отдельности, а часть строки, находящаяся между двумя пикселами исключаемого цвета. Далее этот регион суммируется с предыдущими.
If start_c < lWidth Then
    If stop_c >= lWidth Then stop_c = lWidth - 1
    
    new_rgn = CreateRectRgn(start_c, R, stop_c, R + 1)
    
    If combined_rgn = 0 Then
        combined_rgn = new_rgn
    Else
        CombineRgn combined_rgn, combined_rgn, new_rgn, RGN_OR
        DeleteObject new_rgn
    End If
End If

При работе с 24 битами необходимо позаботиться о смещении, для корректной обработки цвета. Но в остальном, структура функции остается та же.

 Использование данной функции в нашем примере ничем не отличается от предыдущего варианта:
Private Sub cmdPreview_Click()
Dim hRgn As Long

With frmDemonstration
    
    Select Case FindOptionButton
    ...
    Case 7
        Screen.MousePointer = vbHourglass
        .Picture = LoadPicture(App.Path & "\Sample.bmp")
        hRgn = FitToPicture(frmDemonstration)
        Screen.MousePointer = vbDefault
    End Select
    
    SetWindowRgn .hwnd, hRgn, True
    DeleteObject hRgn
    
    .Show vbModal
End With
End Sub

На этом, пожалуй, можно поставить точку. Удачи Вам!


Назад

К 1 части статьи

Скачать пример

Hosted by uCoz