Sign in to follow this  
Followers 0
DrGamut

PixelDumpBitmap

10 posts in this topic

I've written a function to scan a pixel region into an array and dump it into a 24 bit Windows Bitmap image. I'm a novice scripter at best so please don't tear apart my style and methods or lack thereof. This function is very slow and I would recommend its use be limited to debugging or non-critical output. I welcome all input and suggested optimizations.

#include <String.au3>
;Example: PixelDumpBitmap('Untitled - Notepad',0,0,200,200,"notepad.bmp")
Func PixelDumpBitmap($hWnd, $left, $top, $right, $bottom, $file, $PCM = 0)
    $OldCM = Opt("ColorMode", 1)
    $OldPCM = Opt('PixelCoordMode', $PCM)
    If IsString($hWnd) Then $hWnd = WinGetHandle($hWnd)
    Local $width = $right - $left + 1,$height = $bottom - $top + 1,$arrayX = 0,$arrayY = 0,$bitmap[$width][$height]
    If Not WinActive($hWnd) Then WinWaitActive($hWnd)
    For $y = $top To $bottom Step 1
        For $x = $left To $right Step 1
            $bitmap[$arrayX][$arrayY] = Hex(PixelGetColor($x,$y),6)
            $arrayX += 1
        Next
        $arrayY += 1
        $arrayX = 0
    Next
    $headerType = "424D"
    $headerFileSize = _BigToLittle(Hex($width * $height * 3 + Mod($width, 4) * $height + 54,8))
    $headerReserved = "00000000"
    $headerOffset = "36000000"
    $headerInfoSize = "28000000"
    $headerWidth = _BigToLittle(Hex($width,8))
    $headerHeight = _BigToLittle(Hex($height,8))
    $headerPlanes = "0100"
    $headerBits = "1800"
    $headerCompression = "00000000"
    $headerImageSize = _BigToLittle(Hex($width * $height * 3 + Mod($width, 4) * $height,8))
    $headerXPelsPerMeter = "00000000"
    $headerYPelsPerMeter = "00000000"
    $headerClrUsed = "00000000"
    $headerClrImportant = "00000000"
    $bmp = FileOpen($file, 2)
    FileWrite($bmp, _HexToString($headerType & $headerFileSize & $headerReserved & $headerOffset & $headerInfoSize & $headerWidth & $headerHeight & $headerPlanes & $headerBits & $headerCompression & $headerImageSize & $headerXPelsPerMeter & $headerYPelsPerMeter & $headerClrUsed & $headerClrImportant))
    For $y = $height - 1 To 0 Step -1
        For $x = 0 To $width - 1 Step 1
            FileWrite($bmp, _HexToString($bitmap[$x][$y]))
        Next
        FileWrite($bmp, _HexToString(_StringRepeat("00",Mod($width, 4))))
    Next
    FileClose($bmp)
    Opt('ColorMode', $OldCM)  
    Opt('PixelCoordMode', $OldPCM)  
    Return 1
EndFunc
Func _BigToLittle($Motorola)
    Local $Intel = ""
    For $i = 1 To StringLen($Motorola) Step 2
        $Intel &= StringMid($Motorola,StringLen($Motorola) - $i,2)
    Next
    Return $Intel
EndFunc

Share this post


Link to post
Share on other sites



Anyone know what could make this script faster?

I believe the pixel scanning itself is fairly speedy, but writing the file byte by byte consumes a lot of time.

Share this post


Link to post
Share on other sites

im not sure but maby if you compute ur whole string befor writing it ( $out &= $currPixie ) something like that, but I really dont know if it would help speed.

I also noticed that dealing with big arrays seems very slow, for an example, on my p3 it takes ~20 sec to declare $array[160][120][7] and set all values to 0, im still looking for a way to speed up things.

Another thing that I noticed is that you have those 2 loops:

For $y = $top To $bottom Step 1
        For $x = $left To $right Step 1
            $bitmap[$arrayX][$arrayY] = Hex(PixelGetColor($x,$y),6)
            $arrayX += 1
        Next
        $arrayY += 1
        $arrayX = 0
    Next

For $y = $height - 1 To 0 Step -1
        For $x = 0 To $width - 1 Step 1
            FileWrite($bmp, _HexToString($bitmap[$x][$y]))
        Next
        FileWrite($bmp, _HexToString(_StringRepeat("00",Mod($width, 4))))
    Next

I think you could dump your bitmap array and use something like that (only a fast example)

For $y = $top To $bottom Step 1
        For $x = $left To $right Step 1
            $outString &= _HexToString( Hex(PixelGetColor($x,$y),6) )
        Next
        $outString &= _HexToString(_StringRepeat("00",Mod($width, 4)))
    Next

this would probably speed up the process a little, but you still need to implement some stuff so the string is the correct format.

Hope this can help btw that look very nice

Share this post


Link to post
Share on other sites

#4 ·  Posted (edited)

Thank you for the response.

I tried your suggestions, and quite to my own surprise it actually slowed the script further. I can't imagine why, as it's eliminating a loop and an array. With your suggestions in mind I modified the script to this:

#include <String.au3>
;Example: PixelDumpBitmap('Untitled - Notepad',0,0,200,200,"notepad.bmp")
Func PixelDumpBitmap($hWnd, $left, $top, $right, $bottom, $file, $PCM = 0)
    $OldCM = Opt("ColorMode", 1)
    $OldPCM = Opt('PixelCoordMode', $PCM)
    If IsString($hWnd) Then $hWnd = WinGetHandle($hWnd)
    Local $width = $right - $left + 1,$height = $bottom - $top + 1,$bitmap
    If Not WinActive($hWnd) Then WinWaitActive($hWnd)
    $headerType = "424D"
    $headerFileSize = _BigToLittle(Hex($width * $height * 3 + Mod($width, 4) * $height + 54,8))
    $headerReserved = "00000000"
    $headerOffset = "36000000"
    $headerInfoSize = "28000000"
    $headerWidth = _BigToLittle(Hex($width,8))
    $headerHeight = _BigToLittle(Hex($height,8))
    $headerPlanes = "0100"
    $headerBits = "1800"
    $headerCompression = "00000000"
    $headerImageSize = _BigToLittle(Hex($width * $height * 3 + Mod($width, 4) * $height,8))
    $headerXPelsPerMeter = "00000000"
    $headerYPelsPerMeter = "00000000"
    $headerClrUsed = "00000000"
    $headerClrImportant = "00000000"
    For $y = $bottom To $top Step -1
        For $x = $left To $right Step 1
            $bitmap &= Hex(PixelGetColor($x,$y),6)
        Next
        $bitmap &= _StringRepeat("00",Mod($width, 4))
    Next
    $bmp = FileOpen($file, 2)
    FileWrite($bmp, _HexToString($headerType & $headerFileSize & $headerReserved & $headerOffset & $headerInfoSize & $headerWidth & $headerHeight & $headerPlanes & $headerBits & $headerCompression & $headerImageSize & $headerXPelsPerMeter & $headerYPelsPerMeter & $headerClrUsed & $headerClrImportant & $bitmap))
    FileClose($bmp)
    Opt('ColorMode', $OldCM)  
    Opt('PixelCoordMode', $OldPCM)  
    Return 1
EndFunc
Func _BigToLittle($Motorola)
    Local $Intel = ""
    For $i = 1 To StringLen($Motorola) Step 2
        $Intel &= StringMid($Motorola,StringLen($Motorola) - $i,2)
    Next
    Return $Intel
EndFunc

I ran both the old script and the new script with my example command a few times each. The old script completed in ~14.3 seconds, and the new script completed in ~15.5 seconds.

I wonder why.

EDIT: More findings, I suspected running _HexToString on a very long string such as in the above example was eating up CPU time, and I was correct. By limiting the use of _HexToString to the results of each pixel read instead of using it on the completed output string I was able to reduce the completion time of the modified script to 13.3 seconds, beating the original. The function now looks like this:

#include <String.au3>
;Example: PixelDumpBitmap('Untitled - Notepad',0,0,200,200,"notepad.bmp")
Func PixelDumpBitmap($hWnd, $left, $top, $right, $bottom, $file, $PCM = 0)
    $OldCM = Opt("ColorMode", 1)
    $OldPCM = Opt('PixelCoordMode', $PCM)
    If IsString($hWnd) Then $hWnd = WinGetHandle($hWnd)
    Local $width = $right - $left + 1,$height = $bottom - $top + 1,$bitmap
    If Not WinActive($hWnd) Then WinWaitActive($hWnd)
    $headerType = _HexToString("424D")
    $headerFileSize = _HexToString(_BigToLittle(Hex($width * $height * 3 + Mod($width, 4) * $height + 54,8)))
    $headerReserved = _HexToString("00000000")
    $headerOffset = _HexToString("36000000")
    $headerInfoSize = _HexToString("28000000")
    $headerWidth = _HexToString(_BigToLittle(Hex($width,8)))
    $headerHeight = _HexToString(_BigToLittle(Hex($height,8)))
    $headerPlanes = _HexToString("0100")
    $headerBits = _HexToString("1800")
    $headerCompression = _HexToString("00000000")
    $headerImageSize = _HexToString(_BigToLittle(Hex($width * $height * 3 + Mod($width, 4) * $height,8)))
    $headerXPelsPerMeter = _HexToString("00000000")
    $headerYPelsPerMeter = _HexToString("00000000")
    $headerClrUsed = _HexToString("00000000")
    $headerClrImportant = _HexToString("00000000")
    For $y = $bottom To $top Step -1
        For $x = $left To $right Step 1
            $bitmap &= _HexToString(Hex(PixelGetColor($x,$y),6))
        Next
        $bitmap &= _StringRepeat("00",Mod($width, 4))
    Next
    $bmp = FileOpen($file, 2)
    FileWrite($bmp, $headerType & $headerFileSize & $headerReserved & $headerOffset & $headerInfoSize & $headerWidth & $headerHeight & $headerPlanes & $headerBits & $headerCompression & $headerImageSize & $headerXPelsPerMeter & $headerYPelsPerMeter & $headerClrUsed & $headerClrImportant & $bitmap)
    FileClose($bmp)
    Opt('ColorMode', $OldCM)  
    Opt('PixelCoordMode', $OldPCM)  
    Return 1
EndFunc
Func _BigToLittle($Motorola)
    Local $Intel = ""
    For $i = 1 To StringLen($Motorola) Step 2
        $Intel &= StringMid($Motorola,StringLen($Motorola) - $i,2)
    Next
    Return $Intel
EndFunc
Edited by DrGamut

Share this post


Link to post
Share on other sites

#5 ·  Posted (edited)

hmm weird indeed maby someone more experienced could tell us why it is slower (I would personally really like to know too)

btw im gonna go do some research i will edit this post later, im sure there is a way to deal directly with file streams so all this convertion wont be needed (maby hex())

and for the variables at the top, do you really need to convert them to string? like could u, instead of:

-$headerOffset = _HexToString("36000000")

do this?

-$headerOffset = 0x36000000

because autoit does have hex syntax :)

edit:

have you looked at this?

CreateBitmap

The CreateBitmap function creates a bitmap with the specified width, height, and color format (color planes and bits-per-pixel).

HBITMAP CreateBitmap(
  int nWidth,        // bitmap width, in pixels
  int nHeight,      // bitmap height, in pixels
  UINT cPlanes,    // number of color planes
  UINT cBitsPerPel,   // number of bits to identify color
  CONST VOID *lpvBits // color data array
);

Parameters

nWidth
    [in] Specifies the bitmap width, in pixels. 
nHeight
    [in] Specifies the bitmap height, in pixels. 
cPlanes
    [in] Specifies the number of color planes used by the device. 
cBitsPerPel
    [in] Specifies the number of bits required to identify the color of a single pixel. 
lpvBits
    [in] Pointer to an array of color data used to set the colors in a rectangle of pixels. Each scan line in the rectangle must be word aligned (scan lines that are not word aligned must be padded with zeros). If this parameter is NULL, the contents of the new bitmap is undefined. 

Return Values

If the function succeeds, the return value is a handle to a bitmap.

If the function fails, the return value is NULL.

while dealing with arrays might be slow, it will be alot faster than dealing with strings (strings are probably the slower variables to process on every languages) so it might worth a try.

Edited by ramadash

Share this post


Link to post
Share on other sites

hmm weird indeed maby someone more experienced could tell us why it is slower (I would personally really like to know too)

btw im gonna go do some research i will edit this post later, im sure there is a way to deal directly with file streams so all this convertion wont be needed (maby hex())

and for the variables at the top, do you really need to convert them to string? like could u, instead of:

-$headerOffset = _HexToString("36000000")

do this?

-$headerOffset = 0x36000000

because autoit does have hex syntax :)

edit:

have you looked at this?

CreateBitmap

The CreateBitmap function creates a bitmap with the specified width, height, and color format (color planes and bits-per-pixel).

HBITMAP CreateBitmap(
  int nWidth,        // bitmap width, in pixels
  int nHeight,      // bitmap height, in pixels
  UINT cPlanes,    // number of color planes
  UINT cBitsPerPel,   // number of bits to identify color
  CONST VOID *lpvBits // color data array
);

Parameters

nWidth
    [in] Specifies the bitmap width, in pixels. 
nHeight
    [in] Specifies the bitmap height, in pixels. 
cPlanes
    [in] Specifies the number of color planes used by the device. 
cBitsPerPel
    [in] Specifies the number of bits required to identify the color of a single pixel. 
lpvBits
    [in] Pointer to an array of color data used to set the colors in a rectangle of pixels. Each scan line in the rectangle must be word aligned (scan lines that are not word aligned must be padded with zeros). If this parameter is NULL, the contents of the new bitmap is undefined. 

Return Values

If the function succeeds, the return value is a handle to a bitmap.

If the function fails, the return value is NULL.

while dealing with arrays might be slow, it will be alot faster than dealing with strings (strings are probably the slower variables to process on every languages) so it might worth a try.

What is that bitmap function from and how do I use it?

I interpreted your first post as suggesting I use a string instead of an array, I suppose you only meant to use it to avoid accessing the output file multiple times. So I should keep my array creation loop then?

Share this post


Link to post
Share on other sites

the reason it is slow is because of the loops you have to go through to get every pixel in the area. There is'nt really much you can do about that. You could try to write it in C++ and that will increase the speed dramatically!

Share this post


Link to post
Share on other sites

#8 ·  Posted (edited)

the reason it is slow is because of the loops you have to go through to get every pixel in the area. There is'nt really much you can do about that. You could try to write it in C++ and that will increase the speed dramatically!

In a test I did with my original function script and a 40,000 pixel area on an AMD 2500+, it took 0.9 seconds to scan the pixels into an array and ~13.4 seconds to write them into a file.

Edited by DrGamut

Share this post


Link to post
Share on other sites

In a test I did with my original function script and a 40,000 pixel area on an AMD 2500+, it took 0.9 seconds to scan the pixels into an array and ~13.4 seconds to write them into a file.

Take a look for BitBlt function - this did the things alot faster. Here the UDF you can start from.

Share this post


Link to post
Share on other sites

What is that bitmap function from and how do I use it?

I interpreted your first post as suggesting I use a string instead of an array, I suppose you only meant to use it to avoid accessing the output file multiple times. So I should keep my array creation loop then?

this bitmap function is from (I believe) Kernel32.dll (not sure 100%?). The win32 API functions are documented on msdn.com (search win32 api documentation). Btw it would be better to use MakeCompatibleBitmap Instaed of MAkeBitamp (if you want to make color bmps atleast).

This function can be used with dllcall (maby COM also but i dontt hink so).

My first suggestion was this, but not to get rid of the array. Because you had 2 loops: 1 that put the values in an array, and another that parse that array and use filewrite, what I was saying is that all this could be done with 1 loop, so I dont think the array is needed since instead of holding the data, you can deal with it immediatly, but it should not make a very big difference anyway, but I realyl think that calling multiple filewriteline is slower than appending to a string and using filewrite.

well all those are supposition we would need to do some concrete tests to see what is fastest :)

but that create bitmap function seem very insteresting!

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!


Register a new account

Sign in

Already have an account? Sign in here.


Sign In Now
Sign in to follow this  
Followers 0