Компьютерный форум OSzone.net  

Компьютерный форум OSzone.net (http://forum.oszone.net/index.php)
-   AutoIt (http://forum.oszone.net/forumdisplay.php?f=103)
-   -   [решено] WinApi, Как сохранить GDI регион на жесткий диск? (http://forum.oszone.net/showthread.php?t=150123)

Diamond 09-09-2009 18:23 1215007

WinApi, Как сохранить GDI регион на жесткий диск?
 
Возник интерес создать сложный 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 1215294

Цитата:

Цитата Diamond
Как сохранить созданный регион в файл, но самое главное как его потом загружать? »

думаю где-то так (извиняюсь - на комменты нет времени):
Код:

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

Цитата:

Цитата Diamond
Сканировать изображение при каждом запуске скрипта, думаю, плохая идея »

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

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

Diamond 10-09-2009 07:20 1215313

amel27, Супер! Просто нет слов. :up Спасибо!
Цитата:

Цитата amel27
буферизовать всё изображение в память (например через _GDIPlus_BitmapLockBits) и работать уже с массивом пикселей без обращений к GDI »

Я даже и не представлял что такое возможно, спасибо за идею, буду разбираться в структуре которую возвращает эта функция.
Цитата:

Цитата amel27
P.S. по ходу полученный регион отличается от исходной картинки, т.к. каждая строка развертки заменяется на две строки»

Нет вроде бы... всё один к одному. Нарисовал несколько черно-белых картинок , протестировал, и у меня все совпало.

amel27 10-09-2009 08:07 1215331

Цитата:

Цитата Diamond
Нет вроде бы... всё один к одному »

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

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

Diamond 10-09-2009 10:05 1215414

Цитата:

Цитата amel27
у тебя при вызове SetRectRgn() вертикаль $iY и $iY+1, что соответствует прямоугольнику высотой 2 пикселя, а кодируется линия (высотой 1 пиксель)... другое дело, что для одинаковых Y API ф-ция не сработает... »

Я думаю что это особенность GDI, область формируется по внутренней стороне переданных координат, а если так - то всё верно, это как раз и будет прямоугольник высотой в один пиксель.(имхо)
Цитата:

Цитата amel27
почему ты используешь SetRectRgn вместо CreateRectRgn? »

У меня изначально так и было :), мне не понравилось то что приходиться постоянно удалять временный регион после объединения с основным, т.е. получается лишний вызов функции DeleteObject.

amel27 10-09-2009 10:25 1215428

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

Diamond 10-09-2009 12:16 1215516

Цитата:

Цитата amel27
понятно... а то подумал может так быстрее работает »

:)
Цитата:

Цитата amel27
сделал профилирование твоего скрипта - 95% отбирает GetPixel »

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

amel27 10-09-2009 12:37 1215542

Diamond, да, там все крутится вокруг GetDIBits()


Время: 01:01.

Время: 01:01.
© OSzone.net 2001-