Войти

Показать полную графическую версию : [решено] WinApi, Как сохранить GDI регион на жесткий диск?


Diamond
09-09-2009, 18:23
Возник интерес создать сложный GDI регион на основе изображения(bitmap), регион создал, но не смотря на все ухищрения, скорость создания региона оставляет желать лучшего.
Пример сканирования:
#include <GuiConstants.au3>
#include <WindowsConstants.au3>
#include <WinApi.au3>
#include <Constants.au3>
Global $hGui, $gdi32dll, $hBmp, $hMemDC, $hOldBmp, $hRgn, $TagBitmap, $tBitmap, $iW, $iH, $Color
$hGui = GUICreate("Test_BitmapInRegion", 200, 200, -1, -1, $WS_POPUPWINDOW)
$gdi32dll = DllOpen("gdi32.dll")
;~ Загружаю bitmap из файла:
$hBmp = _WinAPI_LoadImage(0, "Form.bmp", $IMAGE_BITMAP, 0, 0, $LR_LOADFROMFILE)
;~ Определяю его ширину и высоту:
$TagBitmap = "int bmType;int bmWidth;int bmHeight;int bmWidthBytes;byte bmPlanes;byte bmBitsPixel;ptr bmBits"
$tBitmap = DllStructCreate($TagBitmap)
_WinAPI_GetObject($hBmp, DllStructGetSize($tBitmap), DllStructGetPtr($tBitmap))
$iW = DllStructGetData($tBitmap, 2)
$iH = DllStructGetData($tBitmap, 3)
$tObject = ""
;~ создаю контекст устpойства памяти:
$hMemDC = _WinAPI_CreateCompatibleDC(0)
;~ заменяю bitmap по умолчанию, bitmap'ом загруженным из файла:
$hOldBmp = _WinAPI_SelectObject($hMemDC, $hBmp)
;~ Создаю пустой регион:
$hRgn = _WinAPI_CreateRectRgn(0, 0, 0, 0)
$Color = 0xFFFFFF ; <- цвет на основе которого будут добавляться (или исключаться) области.

;~ Есть два варианта создания региона на основе bitmap, путём добавления к региону($RGN_OR), и путём исключения из него($RGN_DIFF).
;~ В первом варианте создаётся пустой регион к которому впоследствии (в цикле) добавляются нужные нам области.
;~ Во втором варианте создаётся прямоугольный регион идентичный размеру bitmap, из которого будут исключаться лишние области.
;~ Какой из способов предпочтительнее, зависит от размеров региона по отношению к размеру изображения,
;~ если площадь нужного нам региона (визуально) превышает 50% площади изображения, то второй вариант будет работать чуть быстрее.

;~ Формирую регион путём добавления(сканирование "горизонтальными блоками" высотой в 1 пиксель):
Dim $fState, $iXStart, $iX, $iY, $hRgnBlock, $iColor
$hRgnBlock = _WinAPI_CreateRectRgn(0, 0, 0, 0)
For $iY = 0 To $iH
For $iX = 0 To $iW
$iColor = GetPixel($hMemDC, $iX, $iY, $gdi32dll) ; определяю цвет
If $iColor = $Color Then ; если цвет совпадает
If Not $fState Then ; Если цвет на текущей линии (для этого блока) совпал впервые
$iXStart = $iX ;<- запоминаю стартовую позицию.
$fState = True ; <- Отмечаю что сканирование блока было начато
ElseIf $iX = $iW Then ; если достигнут правый край изображения и цвет совпал
SetRectRgn($hRgnBlock, $iXStart, $iY, $iX, $iY+1, $gdi32dll)
_WinAPI_CombineRgn($hRgn, $hRgn, $hRgnBlock, $RGN_OR)
$fState = False ; <- Отмечаю что сканирование текущего блока было завершено
EndIf
ElseIf $fState Then ; если цвет не совпал, но уже совпадал раньше для этого блока(там где мы запоминали стартовую позицию)
SetRectRgn($hRgnBlock, $iXStart, $iY, $iX, $iY+1, $gdi32dll)
_WinAPI_CombineRgn($hRgn, $hRgn, $hRgnBlock, $RGN_OR)
$fState = False ; <- Отмечаю что сканирование текущего блока было завершено
EndIf
Next
Next
_WinAPI_DeleteObject($hRgnBlock)
_WinAPI_SetWindowRgn($hGui, $hRgn, False)

;~ возвращаю старый bitmap:
_WinAPI_SelectObject($hMemDC, $hOldBmp)
;~ освобождаю контекст:
_WinAPI_DeleteDC($hMemDC)
;~ удаляю загруженный bitmap:
_WinAPI_DeleteObject($hBmp)
DllClose($gdi32dll)

GUISetState(@SW_SHOW)

While 1
$msg = GUIGetMsg()
Switch $msg
Case $GUI_EVENT_CLOSE
Exit
EndSwitch
WEnd

Func GetPixel($hDC, $iX, $iY, $gdi32dll="gdi32.dll")
Local $aResult
$aResult = DllCall($gdi32dll, "long", "GetPixel", "hwnd", $hDC, "int", $iX, "int", $iY)
If @error Then Return SetError(@error, 0, 0)
Return SetError(0, 0, $aResult[0])
EndFunc ;==>GetPixel

;~ Преобразует область, в прямоугольную область с указанным pазмеpом.
;~ $hRgn - Идентификатор области.
;~ $X1, $Y1 - Веpхний левый угол пpямоугольной области.
;~ $X2, $Y2 - Нижний пpавый угол пpямоугольной области.
Func SetRectRgn($hRgn, $iX1, $iY1, $iX2, $iY2, $gdi32dll="gdi32.dll")
Local $aResult
$aResult = DllCall($gdi32dll, "int", "SetRectRgn", "hwnd", $hRgn, "int", $iX1, "int", $iY1, "int", $iX2, "int", $iY2)
If @error Then Return SetError(@error, 0, 0)
Return SetError(0, 0, $aResult[0])
EndFunc ;==>SetRectRgnСканировать изображение при каждом запуске скрипта, думаю, плохая идея.
Вопрос: Как сохранить созданный регион в файл, но самое главное как его потом загружать?
P.S. Я знаю что при загрузке и сохранении региона, используются функции GetRegionData и ExtCreateRegion, синтаксис которых мне тоже непонятен.

amel27
10-09-2009, 05:41
Как сохранить созданный регион в файл, но самое главное как его потом загружать? »думаю где-то так (извиняюсь - на комменты нет времени):
Func _RegionSave($hRgn, $sFile)
Local $aRet, $tBuf, $hFile, $iErr

$aRet = DllCall($gdi32dll, "uint", "GetRegionData", "hwnd", $hRgn, "dword", 0, "ptr", 0)
If $aRet[0]=0 Then Return SetError(2, 1, False)
$tBuf = DllStructCreate("byte["& $aRet[0] &"]")
$aRet = DllCall($gdi32dll, "uint", "GetRegionData", "hwnd", $hRgn, "dword", $aRet[0], "ptr", DllStructGetPtr($tBuf))
If $aRet[0]=0 Then Return SetError(2, 2, False)

$hFile = FileOpen($sFile, 18)
If $hFile=-1 Then Return SetError(1, 1, False)
$iErr = FileWrite($hFile, DllStructGetData($tBuf,1))

FileClose($hFile)
Return SetError($iErr, 2, False)
EndFunc

Func _RegionLoad($sFile)
Local $aRet, $tBuf, $hFile, $bFile, $iErr

$hFile = FileOpen($sFile, 16)
If $hFile=-1 Then Return SetError(1, 0, 0)
$bFile = FileRead ($hFile)
$zFile = BinaryLen($bFile)
FileClose($hFile)

$tBuf = DllStructCreate("byte["& $zFile &"]")
DllStructSetData($tBuf, 1, $bFile)
$aRet = DllCall($gdi32dll, "hwnd", "ExtCreateRegion", "ptr", 0, "uint", $zFile, "ptr", DllStructGetPtr($tBuf))
If $aRet=0 Then Return SetError(2, 0, 0)
Return $aRet[0]
EndFunc

Сканировать изображение при каждом запуске скрипта, думаю, плохая идея »думаю не очень - только придется отказаться от GetPixel, буферизовать всё изображение в память (например через _GDIPlus_BitmapLockBits) и работать уже с массивом пикселей без обращений к GDI

P.S. по ходу полученный регион отличается от исходной картинки, т.к. каждая строка развертки заменяется на две строки (прямоугольник), это так?

Diamond
10-09-2009, 07:20
amel27, Супер! Просто нет слов. :up Спасибо!
буферизовать всё изображение в память (например через _GDIPlus_BitmapLockBits) и работать уже с массивом пикселей без обращений к GDI »
Я даже и не представлял что такое возможно, спасибо за идею, буду разбираться в структуре которую возвращает эта функция.
P.S. по ходу полученный регион отличается от исходной картинки, т.к. каждая строка развертки заменяется на две строки»
Нет вроде бы... всё один к одному. Нарисовал несколько черно-белых картинок , протестировал, и у меня все совпало.

amel27
10-09-2009, 08:07
Нет вроде бы... всё один к одному »
гм... у тебя при вызове SetRectRgn() вертикаль $iY и $iY+1, что соответствует прямоугольнику высотой 2 пикселя, а кодируется линия (высотой 1 пиксель)... другое дело, что для одинаковых Y API ф-ция не сработает... В идеале (как я себе это представляю) нужно сначала на основе файла сваять массив полигонов, представляющих трафарет региона (отдельным UDF), а потом уже применять этот массив к региону... задача нетривиальная, но ИМХО вполне решаема

и еще вопрос - почему ты используешь SetRectRgn вместо CreateRectRgn?

Diamond
10-09-2009, 10:05
у тебя при вызове SetRectRgn() вертикаль $iY и $iY+1, что соответствует прямоугольнику высотой 2 пикселя, а кодируется линия (высотой 1 пиксель)... другое дело, что для одинаковых Y API ф-ция не сработает... »
Я думаю что это особенность GDI, область формируется по внутренней стороне переданных координат, а если так - то всё верно, это как раз и будет прямоугольник высотой в один пиксель.(имхо)почему ты используешь SetRectRgn вместо CreateRectRgn? »
У меня изначально так и было :), мне не понравилось то что приходиться постоянно удалять временный регион после объединения с основным, т.е. получается лишний вызов функции DeleteObject.

amel27
10-09-2009, 10:25
Diamond, понятно... а то подумал может так быстрее работает :)
сделал профилирование твоего скрипта - 95% отбирает GetPixel

Diamond
10-09-2009, 12:16
понятно... а то подумал может так быстрее работает »
:)
сделал профилирование твоего скрипта - 95% отбирает GetPixel »
В общем-то не могу сказать что я сильно удивлён - точнее этого и стоило ожидать, ведь функция вызывается для каждого пикселя картинки, ну и конечно чем проще фигура на самой картинке тем выше этот процент. Ещё я где-то вычитал что GetPixel довольно медлительна сама по себе.
Нашел статью где автор применяет (на c++ вроде бы) схожий с моим скриптом принцип создания региона, но вот упоминания о GetPixel я в его коде не нашёл, также там прилагается ссылка на утилиту RegionCreator(почти в самом низу страницы), да и работает она гораздо быстрее чем мой скрипт.
http://sources.ru/cpp/bitmap/regions.shtml

amel27
10-09-2009, 12:37
Diamond, да, там все крутится вокруг GetDIBits() (http://msdn.microsoft.com/en-us/library/dd144879(VS.85).aspx)




© OSzone.net 2001-2012