Gdip: image binary data to hex string for OCR

Get help with using AutoHotkey (v1.1 and older) and its commands and hotkeys
User avatar
jeeswg
Posts: 6902
Joined: 19 Dec 2016, 01:58
Location: UK

Gdip: image binary data to hex string for OCR

02 Aug 2017, 05:20

I was looking for some code in Gdip to retrieve binary data from a pBitmap, to convert it to a hex string. After that I would do some text replacements to obtain 0s and 1s for use with OCR.

I have some working code. Please post any links to similar scripts below.

I use a technique below to convert a bitmap to monochrome, but when I retrieve the data it appears to return 'BGRA' bytes, rather than 0/1 bits. I'm mentioning this in case there is a way to get raw monochrome bitmap data directly, where 2 hex characters (1 byte) correspond to 8 pixels. Actually, perhaps 1-byte per pixel would be preferable, that would give 2 hex characters (1 byte) per 1 pixel, (e.g. replace 00 (black) with 1, and FF (white) with 0), if there is a way to that also. Thanks.

Btw since each pixel needs 4 bytes, there are no padding issues. I.e. padding is normally used to ensure that each row of pixels starts at a multiple of 4 bytes.

Code: Select all

;GDI+ standard library 1.45 by tic - AutoHotkey Community
;https://autohotkey.com/boards/viewtopic.php?f=6&t=6517

q:: ;ARGB colour values to 1s/0s for use with OCR
;colours from:
;Progress/SplashImage
;https://autohotkey.com/docs/commands/Progress.htm
;vListRGB := "000000,C0C0C0,808080,FFFFFF,800000,FF0000,800080,FF00FF,008000,00FF00,808000,FFFF00,000080,0000FF,008080,00FFFF"
vListRGB := "000000,000000,000000,FFFFFF"
pToken := Gdip_Startup()
StrReplace(vListRGB, ",", "", vImgW), vImgW += 1
vImgH := 4
pBitmap := Gdip_CreateBitmap(vImgW, vImgH)
Loop, Parse, vListRGB, % ","
{
	vX := A_Index-1
	Loop, 4
		Gdip_SetPixel(pBitmap, vX, A_Index-1, 0xFF000000 | SubStr("0x" A_LoopField, 1))
}

;[MS-EMFPLUS]: PixelFormat Enumeration
;https://msdn.microsoft.com/en-us/library/cc230858.aspx
;PixelFormat32bppARGB := 0x26200A
;PixelFormat24bppRGB := 0x21808
;PixelFormat8bppIndexed := 0x30803
;PixelFormat4bppIndexed := 0x30402
;PixelFormat1bppIndexed := 0x30101
vFormat := 0x26200A
vFormat := 0x21808
vFormat := 0x30803
vFormat := 0x30402
vFormat := 0x30101
pBitmap2 := Gdip_CloneBitmapArea(pBitmap, 0, 0, vImgW, vImgH, vFormat)

hBitmap := Gdip_CreateHBITMAPFromBitmap(pBitmap2)
VarSetCapacity(DIBSECTION, vSizeDS:=A_PtrSize=8?104:84, 0)
DllCall("gdi32\GetObject", Ptr,hBitmap, Int,vSizeDS, Ptr,&DIBSECTION)
vAddr := NumGet(DIBSECTION, A_PtrSize=8?24:20, "Ptr") ;bmBits
vSize := NumGet(DIBSECTION, A_PtrSize=8?52:44, "UInt") ;biSizeImage
vHex := JEE_BinDataToHex(vAddr, vSize)

DeleteObject(hBitmap)
Gdip_DisposeImage(pBitmap)
Gdip_DisposeImage(pBitmap2)
Gdip_Shutdown(pToken)
MsgBox, % vHex
vOutput := vHex
vOutput := StrReplace(vOutput, "000000ff", "1")
vOutput := StrReplace(vOutput, "ffffffff", "0")
vOutput := RegExReplace(vOutput, ".{" vImgW "}", "$0`r`n")
MsgBox, % vOutput
return

;==================================================

JEE_BinDataToHex(vAddr, vSize)
{
	;CRYPT_STRING_HEX := 0x4 ;to return space/CRLF-separated text
	;CRYPT_STRING_HEXRAW := 0xC ;to return raw hex (not supported by Windows XP)
	DllCall("crypt32\CryptBinaryToString", Ptr,vAddr, UInt,vSize, UInt,0x4, Ptr,0, UIntP,vChars)
	VarSetCapacity(vHex, vChars*2, 0)
	DllCall("crypt32\CryptBinaryToString", Ptr,vAddr, UInt,vSize, UInt,0x4, Str,vHex, UIntP,vChars)
	vHex := StrReplace(vHex, "`r`n", "")
	vHex := StrReplace(vHex, " ", "")
	return vHex
}
homepage | tutorials | wish list | fun threads | donate
WARNING: copy your posts/messages before hitting Submit as you may lose them due to CAPTCHA
User avatar
SKAN
Posts: 1551
Joined: 29 Sep 2013, 16:58

Re: Gdip: image binary data to hex string for OCR

13 Aug 2017, 12:22

jeeswg wrote:Actually, perhaps 1-byte per pixel would be preferable, that would give 2 hex characters (1 byte) per 1 pixel, (e.g. replace 00 (black) with 1, and FF (white) with 0), if there is a way to that also.
Converting GDI bitmap to mono-color ( or inverted mono-color ) is best done with GDI.
Will a conversion function be helpful to you?
My Scripts and Functions: V1  V2
User avatar
jeeswg
Posts: 6902
Joined: 19 Dec 2016, 01:58
Location: UK

Re: Gdip: image binary data to hex string for OCR

13 Aug 2017, 12:30

Hello SKAN. That does sound most helpful.

Yes, I've noticed occasionally that GDI is very good, and that you don't always need GDI+.

In general I'm interested in converting between these 4 formats (list from MS Paint (Windows XP)). Or formats similar to them.

I.e. to take a 16777216 colour image, and reduce the number of colours.

Monochrome Bitmap (*.bmp;*.dib)
16 Color Bitmap (*.bmp;*.dib)
256 Color Bitmap (*.bmp;*.dib)
24-bit Bitmap (*.bmp;*.dib) [256^3=16777216 colours]

Another interesting conversion is to greyscale, which you did a nice script for. Thanks.
GDI_GrayscaleBitmap() - Converts GDI bitmap to Greyscale - Scripts and Functions - AutoHotkey Community
https://autohotkey.com/board/topic/8279 ... greyscale/

And in this particular case I'm interested in getting binary data from an image and converting it to a text string.

I think there are two fundamental questions, one is how to convert, the other is: can you get the raw binary data from a pBitmap/hBitmap, the same data that you would find in the image file. Cheers.

[EDIT:] To convert, I've used Gdip_CloneBitmapArea, although another idea, as used in the greyscale script, is to use BitBlt.
homepage | tutorials | wish list | fun threads | donate
WARNING: copy your posts/messages before hitting Submit as you may lose them due to CAPTCHA
User avatar
SKAN
Posts: 1551
Joined: 29 Sep 2013, 16:58

Re: Gdip: image binary data to hex string for OCR

13 Aug 2017, 13:19

jeeswg wrote:In general I'm interested in converting between these 4 formats (list from MS Paint (Windows XP)). Or formats similar to them.

I.e. to take a 16777216 colour image, and reduce the number of colours.

Monochrome Bitmap (*.bmp;*.dib)
16 Color Bitmap (*.bmp;*.dib)
256 Color Bitmap (*.bmp;*.dib)
24-bit Bitmap (*.bmp;*.dib) [256^3=16777216 colours]
I'm not familiar with 16 color bitmap. Never tried!.
Converting TO 256 Color Bitmap is hard or almost impossible with GDI + AHK
Because we have to parse thru every pixel and resolve them to use only 256 colors.
I actually tried it - got irriated - and decided to fill the color table with 256 colors like 0x000000, 0x010101 to 0xFFFFFF
and.. BAM!, I got a grayscale image!
jeeswg wrote:And in this particular case I'm interested in getting binary data from an image and converting it to a text string.
You are doing it correctly in OP with GetObject() and DIBSECTION.
jeeswg wrote:I think there are two fundamental questions, one is how to convert
I can help you with mono-color conversion is such a way that bitmap bytes are either 0x00 for black andr 0xFF for white or inversed.
It shouldl be done as follows
Blit Source BMP to 1bit bitmap
Blit from 1bit bitmap to 256 color bitmap ( with only two colors defined in color table i.e. black and white ).
jeeswg wrote:the other is: can you get the raw binary data from a pBitmap/hBitmap, the same data that you would find in the image file
.

We can get the image file itself from pBitmap.
This might interest you
GdiPlus_SaveImageToBuffer()
https://autohotkey.com/board/topic/8552 ... etobuffer/

I find pBitmap very tricky and a bit annoying that it would let go of the data.

:)
My Scripts and Functions: V1  V2
User avatar
jeeswg
Posts: 6902
Joined: 19 Dec 2016, 01:58
Location: UK

Re: Gdip: image binary data to hex string for OCR

13 Aug 2017, 14:39

Thanks, I will take a look.

One concern I have is 16.8M colours straight to 2 colours, v. 16.8M to 256 to 16 to 2 colours, that the latter might give better results.

Another potentially useful thing would be, e.g. for blue text, to keep all blue pixels with a specific RGB value, and replace all the other pixels with white.

Re. best format for reading binary:
With a 16 colour lookup table, where W=0, B=1, with 4 bits = 0.5 bytes per pixel:
'BWBW' has binary: '0001 0000 0001 0000',
and goes to hex string '1010', ready for use.
One issue would be handling/avoiding padding bytes.
homepage | tutorials | wish list | fun threads | donate
WARNING: copy your posts/messages before hitting Submit as you may lose them due to CAPTCHA
User avatar
SKAN
Posts: 1551
Joined: 29 Sep 2013, 16:58

Re: Gdip: image binary data to hex string for OCR

13 Aug 2017, 14:48

I finished the function and extracted the hex with your JEE_BixDataToHex()

Code: Select all

hBM := LoadPicture( A_AhkPath, "GDI+ Icon2 w16 h16" )
The icon is upside down :(
I don't know anything about OCR..
But I can do a stretchblt to flip the image upside down to counter the effect..
Any suggestions?

Code: Select all

Original

00000000000000000000000000000000
0000FFFFFFFFFFFFFFFFFFFFFFFF0000
0000FFFFFFFFFFFFFFFFFFFFFFFF0000
0000FFFFFFFFFFFFFFFFFFFFFFFF0000
0000FFFFFF00FFFFFFFF00FFFFFF0000
0000FFFFFF00FFFFFFFF00FFFFFF0000
0000FFFFFF00FFFFFFFF00FFFFFF0000
0000FFFFFF00000000FF00FFFFFF0000
0000FFFFFF00FFFFFFFF00FFFFFF0000
0000FFFFFF00FFFFFFFF00FFFFFF0000
0000FFFFFF00FFFFFFFF00FFFFFF0000
0000FFFFFFFFFFFFFFFFFFFFFFFF0000
0000FFFFFFFFFFFFFFFFFFFFFFFF0000
0000FFFFFFFFFFFFFFFFFFFFFFFF0000
0000FFFFFFFFFFFFFFFFFFFFFFFF0000
0000FFFFFFFFFFFFFFFFFFFF00000000


Inverted

FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
FFFF000000000000000000000000FFFF
FFFF000000000000000000000000FFFF
FFFF000000000000000000000000FFFF
FFFF000000FF00000000FF000000FFFF
FFFF000000FF00000000FF000000FFFF
FFFF000000FF00000000FF000000FFFF
FFFF000000FFFFFFFF00FF000000FFFF
FFFF000000FF00000000FF000000FFFF
FFFF000000FF00000000FF000000FFFF
FFFF000000FF00000000FF000000FFFF
FFFF000000000000000000000000FFFF
FFFF000000000000000000000000FFFF
FFFF000000000000000000000000FFFF
FFFF000000000000000000000000FFFF
FFFF00000000000000000000FFFFFFFF
My Scripts and Functions: V1  V2
User avatar
jeeswg
Posts: 6902
Joined: 19 Dec 2016, 01:58
Location: UK

Re: Gdip: image binary data to hex string for OCR

13 Aug 2017, 15:08

Don't know really. There are some structs where if you specify a negative value it turns it upside down, if you're able to specify a negative value in some DllCall parameter/struct somewhere, or can get hold of the icon data, and edit that value in the data. Otherwise you could just sort the lines of text at the end.

Info on icon file binary data:
graphics: create icons from scratch - AutoHotkey Community
https://autohotkey.com/boards/viewtopic.php?f=6&t=35758

To reverse items in a list:

Code: Select all

JEE_SortReverseList(vTextA, vTextB, vOffset) ;for use with AHK's Sort command
{
	return vOffset
}
From:
graphics: create bmp files from scratch - AutoHotkey Community
https://autohotkey.com/boards/viewtopic.php?f=6&t=34952

Btw you haven't posted any code yet, although I think you meant not to.

Btw also, those icons look pretty cool. Cheers.

[EDIT:] Re. OCR, I don't need any help with that, you just have to write algorithms to handle the 'image as text' string, and work out letters/spaces/lines etc. I will share some OCR code soon. But I am interested in methods to convert images to a text string of 0s and 1s.

[EDIT:] Btw where did you get that purple i icon image from, did you draw it? The one I use in my 'create icons from scratch' script.
Last edited by jeeswg on 13 Aug 2017, 15:37, edited 1 time in total.
homepage | tutorials | wish list | fun threads | donate
WARNING: copy your posts/messages before hitting Submit as you may lose them due to CAPTCHA
User avatar
SKAN
Posts: 1551
Joined: 29 Sep 2013, 16:58

Re: Gdip: image binary data to hex string for OCR

13 Aug 2017, 15:26

Thanks for the useful info and links. :)
jeeswg wrote:Btw you haven't posted any code yet, although I think you meant not to.
I searching my archives for image flip code. Will update the function and post it.

:)
My Scripts and Functions: V1  V2
User avatar
SKAN
Posts: 1551
Joined: 29 Sep 2013, 16:58

GDI_MonocolorBitmap()

13 Aug 2017, 16:29

The function:

Code: Select all

#NoEnv
#Warn
#SingleInstance, Force

oBM := LoadPicture( A_AhkPath, "GDI+ Icon2 w64 h64" )
hBM := GDI_MonocolorBitmap( oBM )
DllCall( "DeleteObject", "Ptr",oBM ) 

Gui, Margin, 100, 100
Gui, Add, Picture, w64 h64 vPicture, HBITMAP:%hBM% 
Gui, Show, x100 y100

Return ; // end of auto-execute section //

;-------------------------------------------------------------------------------------------------------------

GDI_MonocolorBitmap( hBM, Flip := False, Inverse := False, Monocolor := 0xFFFFFF  )  { 
; Topic : https://autohotkey.com/boards/viewtopic.php?t=35339                        By SKAN 14-Aug-2017
Static OBJ_BITMAP = 7, SRCCOPY := 0x00CC0020, CIFlags := 0x2008 ; LR_CREATEDIBSECTION | LR_COPYDELETEORG
Local  V, W, H, mDC, tDC, tBM, RGBQUAD256, RECT

  If ( DllCall( "GetObjectType", "Ptr",hBM, "UInt" ) <> OBJ_BITMAP )
    Return 0

  VarSetCapacity( BITMAP, A_PtrSize = 8 ? 32 : 24 )
  DllCall( "GetObject", "Ptr",hBM, "Int",A_PtrSize = 8 ? 32 : 24, "Ptr",&BITMAP )

  W := Numget( BITMAP, 4, "UInt"  )  
  H := Numget( BITMAP, 8, "UInt"  )

  mDC := DllCall( "CreateCompatibleDC", "Ptr",0, "Ptr" )       
  DllCall( "SaveDC", "Ptr",mDC )
  DllCall( "SelectObject", "Ptr",mDC, "Ptr",hBM )

  tBM := DllCall( "CreateBitmap", "Int",W, "Int",H, "UInt",1, "UInt",1, "Ptr",0, "Ptr" ) ; 1 BPP
  tBM := DllCall( "CopyImage", "Ptr",tBM, "Int",0, "Int",0, "Int",0, "UInt",CIFlags, "Ptr" )
  tDC := DllCall( "CreateCompatibleDC", "Ptr",0, "Ptr" )       
  DllCall( "SaveDC", "Ptr",tDC )
  DllCall( "SelectObject", "Ptr",tDC, "Ptr",tBM )

  V := ( Flip ) ? [ 0, H - 1, W, 0 - H ] : [ 0, 0, W, H ]
  DllCall( "StretchBlt", "Ptr",tDC, "Int",0,   "Int",0,   "Int",W,   "Int",H
                       , "Ptr",mDC, "Int",V.1, "Int",V.2, "Int",V.3, "Int",V.4
                       , "UInt",SRCCOPY)

  DllCall( "RestoreDC", "Ptr",mDC, "Int",-1 )

  hBM := DllCall( "CreateBitmap", "Int",W, "Int",H, "UInt",1, "UInt",8, "Ptr",0, "Ptr" ) ; 8 BPP
  hBM := DllCall( "CopyImage", "Ptr",hBM, "Int",0, "Int",0, "Int",0, "UInt",CIFlags, "Ptr" )
  DllCall( "SelectObject", "Ptr",mDC, "Ptr",hBM )

  VarSetCapacity( RGBQUAD256, 256*4, 0 ), NumPut( MonoColor, RGBQUAD256, 1020, "UInt" ) 
  DllCall( "SetDIBColorTable", "Ptr",mDC, "UInt",0, "UInt",256, "Ptr",&RGBQUAD256 )
  
  DllCall( "BitBlt", "Ptr",mDC, "Int",0, "Int",0, "Int",W, "Int",H
                   , "Ptr",tDC, "Int",0, "Int",0, "UInt",SRCCOPY )
  
  If ( Inverse )
     VarSetCapacity( RECT,16,0 ),  NumPut( W,RECT,8,"UInt" ),  NumPut( H,RECT,12,"UInt" )
  ,  DllCall( "InvertRect", "Ptr",mDC, "Ptr",&RECT )


  DllCall( "RestoreDC", "Ptr",tDC, "Int",-1 )
  DllCall( "RestoreDC", "Ptr",mDC, "Int",-1 )
  DllCall( "DeleteDC", "Ptr",tDC )
  DllCall( "DeleteDC", "Ptr",mDC )
  DllCall( "DeleteObject", "Ptr",tBM )
Return hBM
}  
One concern I have is 16.8M colours straight to 2 colours, v. 16.8M to 256 to 16 to 2 colours, that the latter might give better results.
Will definitely try that and compare the results.
As is, this function takes 2.9s to convert a 24MP image ( 6000x4000 )
Not bad, I would say!
My Scripts and Functions: V1  V2
User avatar
jeeswg
Posts: 6902
Joined: 19 Dec 2016, 01:58
Location: UK

Re: Gdip: image binary data to hex string for OCR

13 Aug 2017, 16:44

This is very nice. You get a funny effect for the H/S icons, if you specify Icon1 or Icon3. (Which is what happens when you force every pixel to be one of 2 colours.)

Another thing of interest is if there is a basic way to brighten/darken pixels en masse.

I will look over GdiPlus_SaveImageToBuffer soon, and report back.

Btw did you see my comment about the purple i icon used in Crazy Scripting? Cheers.
homepage | tutorials | wish list | fun threads | donate
WARNING: copy your posts/messages before hitting Submit as you may lose them due to CAPTCHA
User avatar
SKAN
Posts: 1551
Joined: 29 Sep 2013, 16:58

Re: Gdip: image binary data to hex string for OCR

13 Aug 2017, 17:29

jeeswg wrote:did you see my comment about the purple i icon used in Crazy Scripting?
What purple icon? Can you provide a link, please. :roll:
My Scripts and Functions: V1  V2
User avatar
jeeswg
Posts: 6902
Joined: 19 Dec 2016, 01:58
Location: UK

Re: Gdip: image binary data to hex string for OCR

13 Aug 2017, 17:31

Crazy Scripting : Include an Icon in your script - Scripts and Functions - AutoHotkey Community
https://autohotkey.com/board/topic/3104 ... ur-script/
homepage | tutorials | wish list | fun threads | donate
WARNING: copy your posts/messages before hitting Submit as you may lose them due to CAPTCHA
User avatar
SKAN
Posts: 1551
Joined: 29 Sep 2013, 16:58

Re: Gdip: image binary data to hex string for OCR

13 Aug 2017, 17:43

Thanks for the link! :)
Ah! that one.. was done with in JASC Paintshop pro.
My Scripts and Functions: V1  V2
User avatar
SKAN
Posts: 1551
Joined: 29 Sep 2013, 16:58

Re: Gdip: image binary data to hex string for OCR

13 Aug 2017, 18:00

jeeswg wrote:I use a technique below to convert a bitmap to monochrome, but when I retrieve the data it appears to return 'BGRA' bytes, rather than 0/1 bits.
GdipCreateHBITMAPFromBitmap() will always create pARGB bitmap ( Also LoadPicture( , "GDI+" ) )
This is annoying as STM_SETIMAGE doesn't prefer pARGB and will convert it to 32bpp RGB.
So we have to remember to delete two bitmaps with STM_SETIMAGE.

You could try: GdipBitmapLockBits() to access bitmap data
http://www.jose.it-berater.org/gdiplus/ ... ckbits.htm

I've never tried.. just noticed.
My Scripts and Functions: V1  V2
User avatar
jeeswg
Posts: 6902
Joined: 19 Dec 2016, 01:58
Location: UK

Re: Gdip: image binary data to hex string for OCR

22 Aug 2017, 12:56

Many thanks for the GdipBitmapLockBits recommendation, it works very nicely, although you have to remember that there will be padding at the end of each row of pixels depending on the image width and the pixel format.

I'm not sure if GdiPlus_SaveImageToBuffer can or can't be made to work for this.

Some examples with GdipBitmapLockBits. I create an image, and the binary data associated with that image is displayed for various pixel formats.

Code: Select all

;GDI+ standard library 1.45 by tic - AutoHotkey Community
;https://autohotkey.com/boards/viewtopic.php?f=6&t=6517

q:: ;ARGB colour values to 1s/0s for use with OCR
;colours from:
;Progress/SplashImage
;https://autohotkey.com/docs/commands/Progress.htm
;vListRGB := "000000,C0C0C0,808080,FFFFFF,800000,FF0000,800080,FF00FF,008000,00FF00,808000,FFFF00,000080,0000FF,008080,00FFFF"

;4 wide (an example with padding in some pixel formats)
vListRGB := "000000,000000,000000,FFFFFF"

;32 wide (an example with no padding)
vListRGB := "", vTemp := "000000,000000,000000,FFFFFF"
Loop, 8
	vListRGB .= (A_Index=1?"":",") vTemp

pToken := Gdip_Startup()
StrReplace(vListRGB, ",", "", vImgW), vImgW += 1
vImgH := 4
pBitmap := Gdip_CreateBitmap(vImgW, vImgH)
Loop, Parse, vListRGB, % ","
{
	vX := A_Index-1
	Loop, % vImgH
		Gdip_SetPixel(pBitmap, vX, A_Index-1, 0xFF000000 | SubStr("0x" A_LoopField, 1))
}

;[MS-EMFPLUS]: PixelFormat Enumeration
;https://msdn.microsoft.com/en-us/library/cc230858.aspx
;PixelFormat32bppARGB := 0x26200A
;PixelFormat24bppRGB := 0x21808
;PixelFormat16bppRGB565 := 0x21006
;PixelFormat8bppIndexed := 0x30803
;PixelFormat4bppIndexed := 0x30402
;PixelFormat1bppIndexed := 0x30101

oArray := {}
oArray.1 := 0x26200A ;4 bytes per pixel
oArray.2 := 0x21808 ;3 bytes per pixel
oArray.3 := 0x21006 ;2 bytes per pixel
oArray.4 := 0x30803 ;1 byte per pixel
oArray.5 := 0x30402 ;1/2 byte per pixel (1 byte = 2 pixels)
oArray.6 := 0x30101 ;1/8 byte per pixel (1 byte = 8 pixels)

oArray2 := [4,3,2,1,0.5,0.125]
(oArray3 := StrSplit("0000,0001,0010,0011,0100,0101,0110,0111,1000,1001,1010,1011,1100,1101,1110,1111", ",")).RemoveAt(0)

Loop, 6
{
	vFormat := oArray[A_Index]
	Gdip_LockBits(pBitmap, 0, 0, vImgW, vImgH, vStride, vScan0, vBitmapData, 3, vFormat)
	vOutputOrig := JEE_BinDataToHex(vScan0, vStride*vImgH)
	Gdip_UnlockBits(pBitmap, vBitmapData)
	vOutput := RegExReplace(vOutputOrig, ".{" vStride*2 "}", "$0`r`n")
	if (oArray2[A_Index] = 0.125)
	{
		vOutputOrig2 := ""
		Loop, Parse, vOutputOrig
			vOutputOrig2 .= oArray3[Format("{:i}", "0x" A_LoopField)]
		vOutput2 := RegExReplace(vOutputOrig2, ".{" vStride*2*4 "}", "$0`r`n")
		MsgBox, % "hex:`r`n" vOutputOrig "`r`n`r`n" vOutput "`r`n" "bin:`r`n" vOutputOrig2 "`r`n`r`n" vOutput2
		continue
	}
	MsgBox, % vOutputOrig "`r`n`r`n" vOutput
}
oArray := ""

DeleteObject(hBitmap)
Gdip_DisposeImage(pBitmap)
Gdip_Shutdown(pToken)
return

;==================================================

JEE_BinDataToHex(vAddr, vSize)
{
	;CRYPT_STRING_HEX := 0x4 ;to return space/CRLF-separated text
	;CRYPT_STRING_HEXRAW := 0xC ;to return raw hex (not supported by Windows XP)
	DllCall("crypt32\CryptBinaryToString", Ptr,vAddr, UInt,vSize, UInt,0x4, Ptr,0, UIntP,vChars)
	VarSetCapacity(vHex, vChars*2, 0)
	DllCall("crypt32\CryptBinaryToString", Ptr,vAddr, UInt,vSize, UInt,0x4, Str,vHex, UIntP,vChars)
	vHex := StrReplace(vHex, "`r`n", "")
	vHex := StrReplace(vHex, " ", "")
	return vHex
}
homepage | tutorials | wish list | fun threads | donate
WARNING: copy your posts/messages before hitting Submit as you may lose them due to CAPTCHA
MaxAstro
Posts: 557
Joined: 05 Oct 2016, 13:00

Re: Gdip: image binary data to hex string for OCR

22 Aug 2017, 15:01

Interesting. I just finished writing a hackjob OCR that uses GDIP to do almost exactly what you are talking about, although mine is super simple and just uses true/false to identify text pixels. I don't know if anything in here is useful to you - I'm at a much lower skill level than you for sure - but here is my script:

Code: Select all

; Read text from the screen

global OCRFile := A_WorkingDir . "\Utils\OCR.ini"			; Location of the OCR database file
global NeedlePath := A_WorkingDir . "\Utils\PromoArrow.png"	; Location of the promo select graphic

; Text object class that holds targetting information for the OCR
class TextObj
{
	X := ""
	Y := ""
	Length := ""
	Height := ""
	WhiteText := ""
	cWhite := ""
	cBlack := ""
	cBlue := ""
	
	Initialize()
	{
		this.X := 0
		this.Y := 0
		this.Length := 0
		this.Height := 0
		this.WhiteText := false
		this.cWhite := 4294967295
		this.cBlack := 4278190080
		this.cBlue := 4278190335
	}
	
	ColorMatch(PixelColor)	; Simple method that determines if the pixel color matches the font color
	{
		if ((PixelColor == this.cWhite && this.WhiteText) || (PixelColor == this.cBlack && !this.WhiteText))
			return true
		else
			return false
	}
}

OCR_FindTextStart(aX, aY)	; Finds the nearest text to the lower right of a given pixel; checks a 10-pixel area
{							; Works relative to active window, and requires GDI+ to be started!
	pY := 1, PixelFound := false
	OCRText := new TextObj
	OCRText.Initialize()
	pHwnd := WinExist("A")
	pHaystack := Gdip_BitmapFromHWND(pHwnd)		; Create the haystack from the active window
	
	PixelColor := Gdip_GetPixel(pHaystack, aX -1, aY -1)	; Test to see if we expect white text or black
	if (PixelColor == OCRText.cBlue)	; Background is blue, therefore text is white
		OCRText.WhiteText := true
	else
		OCRText.WhiteText := false
	
	while (pY < 11)		; This block searches a 10-by-10 area left -> right -> down for the first text pixel
	{
		pX := 1
		while (pX < 11)
		{
			PixelColor := Gdip_GetPixel(pHaystack, aX + pX, aY + pY)
			if (OCRText.ColorMatch(PixelColor))
			{
				OCRText.X := aX + pX
				OCRText.Y := aY + pY
				PixelFound := true
				break
			}
			pX++
		}
		pY++
		if (PixelFound)
			break
	}
	
	if (!PixelFound)
	{
		ErrorMsg := "No text start point found in search area."
		ShowErrorMessage(ErrorMsg)
		return
	}
	
	BreakCount := 0		; This block determines the length of the text string
	PixelFound := false
	Loop, 400
	{
		PixelColor := Gdip_GetPixel(pHaystack, OCRText.X + A_Index, OCRText.Y + 5)
		if (OCRText.ColorMatch(PixelColor))
			BreakCount := 0
		else
			BreakCount++
		if (BreakCount > 12)
		{
			OCRText.Length := A_Index - 4
			PixelFound := true
			break
		}
	}
	
	if (!PixelFound)
	{
		ErrorMsg := "Unable to determine text length (string too long?)."
		ShowErrorMessage(ErrorMsg)
		return
	}
		
	TopFound := false		; This block determines the top and height of the text string
	BottomFound := false
	TempY := 0
	Loop, 20
	{
		TempY := aY - 1 + A_Index
		Loop, % OCRText.Length
		{
			PixelColor := Gdip_GetPixel(pHaystack, OCRText.X + A_Index, TempY)
			if (OCRText.ColorMatch(PixelColor))
			{
				OCRText.Y := TempY
				TopFound := true
				break
			}
		}
	} until TopFound
	
	Loop, 20
	{
		TempY := OCRText.Y + A_Index
		BottomFound := true, LineCheck := 0
		Loop, % OCRText.Length
		{
			PixelColor := Gdip_GetPixel(pHaystack, OCRText.X + A_Index, TempY)
			if (OCRText.ColorMatch(PixelColor))
			{
				BottomFound := false
				LineCheck++
			}
			else
				LineCheck := 0
			if (LineCheck > 20)		; A solid row of 20 pixels is almost certainly not text
			{
				TempY -= 1
				BottomFound := true
				break
			}
		}
	} until BottomFound
	
	OCRText.Height := TempY - OCRText.Y
	
	if (!TopFound || !BottomFound)
	{
		ErrorMsg := "Unable to locate string or string too tall."
		ShowErrorMessage(ErrorMsg)
		return
	}
	
	Gdip_DisposeImage(pHaystack)
	return OCRText
}

OCR_ReadActiveWindow(OCRText)		; Capture black or white text at the given X and Y; length is in pixels
{									; Requires GDI+ to be started and a TextObj passed as the parameter!
	;Convert the pixels in the selected area into an array
	TextString := ""
	pX := 0
	pHwnd := WinExist("A")
	pHaystack := Gdip_BitmapFromHWND(pHwnd)		; Create the haystack from the active window
	
	if (!OCRText.X || !OCRText.Y || !OCRText.Length || !OCRText.Height)
	{
		ErrorMsg := "Text object not ready for OCR."
		ShowErrorMessage(ErrorMsg)
		return
	}
	
	while (pX <= OCRText.Length)
	{
		pY := 0
		while (pY < OCRText.Height)
		{
			PixelColor := Gdip_GetPixel(pHaystack, OCRText.X -1 + pX, OCRText.Y + pY)
			if (OCRText.ColorMatch(PixelColor))
				TextString .= "1"
			else
				TextString .= "0"
			pY++
		}
		if (pX < OCRText.Length)	; Skip the line return the last time to keep the string clean
			TextString .= "`n"
		pX++
	}
	Gdip_DisposeImage(pHaystack)
	
	;Parse the string into an array of characters
	CharacterArray := []
	LastStart := 1
	Loop, Parse, TextString, `n
	{
		if (InStr(A_LoopField, "1"))
		{
			if (!LastStart)
			{
				LastStart := A_Index
				CharacterArray[LastStart] := ""
			}
			CharacterArray[LastStart] .= A_LoopField . "`n"
		}
		else
			LastStart := 0
	}
	
	for key, value in CharacterArray
	{
		value := JEE_StrTranspose(value)
		Loop, 2			; Doing this twice removes empty rows first from the start and then the end of the string
		{
			Start := false
			Cycle := A_Index
			Loop, Parse, value, `n
			{
				if (InStr(A_LoopField, "1"))
					Start := true
				if (Start)
					Temp := A_LoopField . "`n" . Temp
			}
			value := Temp, Temp := ""
			StringTrimRight, value, value, 1
		}
		value := StrReplace(value, "`r`n", ".")
		CharacterArray[key] := value
		str.="Array " key ":`n" value "`n"
	}
	
	TextString := ""
	for key, value in CharacterArray
	{
		value := StrReplace(value, "`n")
		value := StrReplace(value, "`r")
		IniRead, Char, % OCRFile, Characters, %value%
		if (Char == "ERROR")
		{
			Pos := InStr(value, ".")
			NumLines := StrLen(SubStr(value, 1, Pos))
			Char := OCR_AddToLibrary(OCRText.X + key - 3, OCRText.Y - 1, NumLines + 1, OCRText.Height + 2, value)
		}
		TextString .= Char
	}
	
	return TextString
}

OCR_AddToLibrary(aX, aY, Width, Height, Char)	; Prompt the user to translate the character at the given screen location
{													; Requires GDI+ to be started!
	WinGetPos, wX, wY, , , A	; Get the X and Y of the active window in order to find the relative coordinates
	vScreen := (wX + aX) . "|" . (wY + aY) . "|" . (Width) . "|" . (Height)
	pHaystack := Gdip_BitmapFromScreen(vScreen)
	Gdip_SaveBitmapToFile(pHaystack, LocalDataDir . "\tempbmp.jpg")	; Save the bitmap as a file to display in the GUI
	Sleep, 500
	Gdip_DisposeImage(pHaystack)
	
	Gui, AddOCRLib: New, -SysMenu +HwndAddOCRLib, % "Identify Text"
	static TextString
	Gui, Add, Picture, , % LocalDataDir . "\tempbmp.jpg"
	Gui, Add, Text,, % "Unknown text detected.  Please enter the character`n"
					 . "or characters in the above image exactly as they`n"
					 . "appear. Capitalization and punctuation are important,`n"
					 . "and only input the character(s) centered in the image!"
	Gui, Add, Edit, r1 vTextString w50, % ReportDescription
	Gui, Add, Button, x8 y+4 gHandleButton default, % "OK"
	Gui, Show
	while (!ButtonHandler.Button["AddOCRLib"])
		Sleep, -1
	Gui, Submit
	KeyWait, Enter
	
	TextString := StrReplace(TextString, A_Space)	; Remove spaces and line breaks because there shouldn't ever be any
	TextString := StrReplace(TextString, "`n")
	TextString := StrReplace(TextString, "`r")
	IniWrite, % TextString, % OCRFile, Characters, %Char%
	return TextString
}

FindActivePromo(ReturnPosition := false)	; Returns the y-value of the active promo; requires GDI+ to be started!
{											; If ReturnPosition := true, instead returns the position of the active promo
	pNeedle := Gdip_CreateBitmapFromFile(NeedlePath)
	pHwnd := WinExist("A")
	pHaystack := Gdip_BitmapFromHWND(pHwnd)		; Create the haystack from the active window
	Gdip_ImageSearch(pHaystack, pNeedle, ImageCoords)
	Pos := InStr(ImageCoords, ",")				; Split the coordinates into X and Y
	aY := SubStr(ImageCoords, Pos+1)
	
	Gdip_DisposeImage(pNeedle)
	Gdip_DisposeImage(pHaystack)
	if (ReturnPosition)
		return (aY - 246) / 16
	return aY
}

ReadPromoData(PromoNum := -1, FieldNum := 1)	; Reads field data from a chosen promo
{												; Defaults to the active promo if PromoNum is not specified
	ErrorCheckReadiness(6, true, "Any")
	if (ErrorState)
		return
	
	if !pToken := Gdip_Startup()	; Start GDI+ for advanced image handling
	{
		MsgBox, 48, % "GDI+ error!", % "GDI+ failed to start. Please ensure you have gdiplus on your system!"
		return
	}
	
	if (PromoNum == -1)
		aY := FindActivePromo()
	else
		aY := PromoNum * 16 + 246
	
	pHwnd := WinExist("A")
	pHaystack := Gdip_BitmapFromHWND(pHwnd)		; Create the haystack from the active window
	PixelColor := Gdip_GetPixel(pHaystack, 38, aY)
	if PixelColor == TextObj.cWhite
	{
		ErrorMsg := "The selected promo number doesn't exist."
		ShowErrorMessage(ErrorMsg)
		return
	}
	
	if (FieldNum == 1)
		aX := 42
	else if (FieldNum == 2)
		aX := 179
	else if (FieldNum == 3)
		aX := 294
	else if (FieldNum == 4)
		aX := 387
	else if (FieldNum == 5)
		aX := 557
	else if (FieldNum == 6)
		aX := 727
	else if (FieldNum == 7)
		aX := 820
	else if (FieldNum == 8)
		aX := 913
	else
	{
		ErrorMsg := "Field number out of bounds."
		ShowErrorMessage(ErrorMsg)
		return
	}
	
	OCRText := OCR_FindTextStart(aX, aY)
	TextString := OCR_ReadActiveWindow(OCRText)
	Gdip_DisposeImage(pHaystack)
	Gdip_Shutdown(pToken)
	return TextString
}
A couple of the functions (FindActivePromo and ReadPromoData) are specific to what I am using the script for - reading text from a specific place in a specific program. It also has some obvious limitations - it expects black text on a non-black background, and I imagine anti-aliasing would mess it up something fierce. It also can't do spaces.

Anyway, as I said, no idea if any of that is useful to you, I just found it interesting that you seem to be doing something very similar to what I just finished. :)
User avatar
jeeswg
Posts: 6902
Joined: 19 Dec 2016, 01:58
Location: UK

Re: Gdip: image binary data to hex string for OCR

02 Sep 2017, 23:28

Thank you MaxAstro, I remember talking about a few of these issues in the past, it's nice to have a record of discussing some of the principles. I've done one-off jobs a reasonably long time ago, and now I'm trying to release a nice script for public consumption. Certain types of scripts, especially this one, are hard to understand, so it's usually best to try and summarise one's approach in simple terms, possibly by adding comments at the top of and throughout a script.

My main interest is, once you have the image as a text string of 1s and 0s, processing that, e.g. identifying blank rows of pixels between lines, and spaces between characters, and removing whitespace. Or perhaps doing something that I don't do, which is to search for characters like ImageSearch. So I'm generally interested in how people approach this.

For a small number of things, including OCR, I have been previously annoyed by the lack of progress on the forum. It's such a classic thing to do, and so obviously related to the types of things AutoHotkey tries to achieve, also, anyone could have made an attempt at it, I had no useful prior experience when I began. But there have been some good efforts recently, regarding pixel and general OCR, so I feel more positive about sharing an OCR script here.

Btw have you tried Capture2Text, does it work well for you?

Some issues I've wanted to address before finishing:
- Speeding up image to text string, which I now feel pretty happy with, although any further optimisations are always welcome. (The next two posts will cover this.)
- Teaching the script characters, i.e. 'pixel maps' as I call them, in a specific font. When I did tests, I had the same font on the screen and in memory but there were slight pixel differences, which is of course no good. (I'll ask here on the forum about that in the next few days.)
- [EDIT:] One thing that I've not personally wanted or needed, but that might be useful, is a script to allow the user to select a rectangle on the screen (and then OCR that area). So I would welcome any recommendations for this, I will probably start a new thread on it.
Last edited by jeeswg on 03 Sep 2017, 00:59, edited 1 time in total.
homepage | tutorials | wish list | fun threads | donate
WARNING: copy your posts/messages before hitting Submit as you may lose them due to CAPTCHA
User avatar
jeeswg
Posts: 6902
Joined: 19 Dec 2016, 01:58
Location: UK

Re: Gdip: image binary data to hex string for OCR

02 Sep 2017, 23:33

Here are some benchmark tests regarding 5 methods for image to hex string:
method 1 - Gdip_GetPixel - Loop [very slow]
method 2 - GetObject - Loop and NumGet [quite quick]
method 3 - GetObject - Loop and InBuf function (x32 only) (on binary data) [ranges from slow to super fast depending on the number of pixels found that we want to keep]
method 4 - GetObject - binary to hex string, Loop and SubStr [quite quick]
method 5 - GetObject - binary to hex string, RegExReplace 4 times [fast, best overall]

Included are some tests where all the pixels *were* and *were not* the desired font needle colour.

Note the machine code function, InBuf (x32 only) is used to search for pixels, RegExMatch cannot handle needle lengths or offsets that are an odd number of bytes. I talk about trying to create an x64 version of InBuf here, in case anyone can convert it.
InBuf function currently 32-bit only (machine code binary buffer searching) - AutoHotkey Community
https://autohotkey.com/boards/viewtopic.php?f=5&t=28393

Code: Select all

;[Gdip functions]
;GDI+ standard library 1.45 by tic - AutoHotkey Community
;https://autohotkey.com/boards/viewtopic.php?f=6&t=6517

;#Include Gdip_All.ahk
;WinMove, ahk_class Notepad,,,, 200, 200
;WinMove, ahk_class Notepad,,,, 100, 200
;Sleep 1000

;create a text string with 1s/0s based on a certain pixel colour
;method 1 - Gdip_GetPixel - Loop
;method 2 - GetObject - Loop and NumGet
;method 3 - GetObject - Loop and InBuf function (x32 only) (on binary data)
;method 4 - GetObject - binary to hex string, Loop and SubStr
;method 5 - GetObject - binary to hex string, RegExReplace 4 times

;q:: ;ARGB colour values to 1s/0s for use with OCR
;needle colour, check for pixels with this colour
vColRGB := 0x000000 ;black
;vColRGB := 0xabcdef

pToken := Gdip_Startup()
ControlGet, hCtl, Hwnd,, Edit1, A
WinGetPos, vWinX, vWinY, vWinW, vWinH, % "ahk_id " hCtl
vWinW := 500, vWinH := 500
vScreen := vWinX "|"  vWinY "|" vWinW "|" vWinH
vScreen := 0 "|"  0 "|" A_ScreenWidth "|" A_ScreenHeight
pBitmap := Gdip_BitmapFromScreen(vScreen)
Gdip_GetImageDimensions(pBitmap, vImgW, vImgH)

if 0
{
	;fill with one colour
	pBrush := Gdip_BrushCreateSolid(0xff000000 | vColRGB)
	;pBrush := Gdip_BrushCreateSolid(0xff000000 | ~vColRGB)
	G := Gdip_GraphicsFromImage(pBitmap)
	Gdip_FillRectangle(G, pBrush, 0, 0, vImgW, vImgH)
	Gdip_DeleteBrush(pBrush)
	Gdip_DeleteGraphics(G)
}

hBitmap := Gdip_CreateHBITMAPFromBitmap(pBitmap)
VarSetCapacity(DIBSECTION, vSizeDS:=A_PtrSize=8?104:84, 0)
DllCall("gdi32\GetObject", Ptr,hBitmap, Int,vSizeDS, Ptr,&DIBSECTION)
vAddr := NumGet(DIBSECTION, A_PtrSize=8?24:20, "Ptr") ;bmBits
vSize := NumGet(DIBSECTION, A_PtrSize=8?52:44, "UInt") ;biSizeImage
vHex := JEE_BinDataToHex(vAddr, vSize)
oTickStart := [], oTickEnd := [], oPixelMap := []
;MsgBox, % vSize " " (vImgW*4*vImgH)
;MsgBox, % ((vImgW+1)*vImgH)-1
;MsgBox, % vHex

;==================================================

;method 1 - Gdip_GetPixel - Loop
oTickStart.Push(A_TickCount)
vOutput := ""
VarSetCapacity(vOutput, (vImgW+1)*vImgH*2)
Loop, % vImgH
{
	vY := A_Index-1
	Loop, % vImgW
	{
		vX := A_Index-1
		if (vColRGB = (Gdip_GetPixel(pBitmap, vX, vY) & 0xFFFFFF))
			vOutput .= "1"
		else
			vOutput .= "0"
	}
	vOutput .= "`n"
}
vOutput := SubStr(vOutput, 1, -1)
oTickEnd.Push(A_TickCount)
oPixelMap.Push(vOutput)

;==================================================

;method 2 - GetObject - Loop and NumGet
oTickStart.Push(A_TickCount)
vOutput := ""
VarSetCapacity(vOutput, (vImgW+1)*vImgH*2)
vAddr2 := vAddr
Loop, % vImgH
{
	Loop, % vImgW
	{
		if (vColRGB = (NumGet(vAddr2+0, "UInt") & 0xFFFFFF))
			vOutput .= "1"
		else
			vOutput .= "0"
		vAddr2 += 4
	}
	vOutput .= "`n"
}
vOutput := SubStr(vOutput, 1, -1)
vOutput := JEE_StrReverseLines(vOutput, "`n")
oTickEnd.Push(A_TickCount)
oPixelMap.Push(vOutput)

;==================================================

;method 3 - GetObject - Loop and InBuf function (x32 only) (on binary data)
oTickStart.Push(A_TickCount)
vOutput := ""
VarSetCapacity(vOutput, (vImgW+1)*vImgH*2)
Loop, % vImgH
	vOutput .= Format("{:0" vImgW "}", 0) "`n"
vOutput := SubStr(vOutput, 1, -1)
VarSetCapacity(vDataNeedle, 3)
NumPut(vColRGB & 0xFF, &vDataNeedle, "UChar") ;(blue)
NumPut((vColRGB & 0xFF00) >> 8, &vDataNeedle+1, "UChar") ;(green)
NumPut((vColRGB & 0xFF0000) >> 16, &vDataNeedle+2, "UChar") ;(red)
vType := A_IsUnicode?"UShort":"UChar"

vPos := 0
Loop
{
	vPos := InBuf(vAddr, &vDataNeedle, vSize, 3, vPos)
	if (vPos = -1)
		break
	if !Mod(vPos, 4)
	{
		vOffset := vPos >> 2 ;divide by 4
		vOffset := (vOffset + Floor(vOffset/vImgW)) * (!!A_IsUnicode+1)
		;StrPut("1", &vOutput+vOffset, 1)
		;StrPut("1", &vOutput+vOffset, 1, A_IsUnicode?"UTF-16":"CP0")
		NumPut(49, &vOutput+vOffset, vType)
	}
	vPos++
}
vOutput := JEE_StrReverseLines(vOutput, "`n")
oTickEnd.Push(A_TickCount)
oPixelMap.Push(vOutput)

;==================================================

;method 4 - GetObject - binary to hex string, Loop and SubStr
oTickStart.Push(A_TickCount)
vOutput := ""
VarSetCapacity(vOutput, (vImgW+1)*vImgH*2)
vColBGR := Format("{:02X}{:02X}{:02X}", vColRGB & 0xFF, (vColRGB & 0xFF00) >> 8, (vColRGB & 0xFF0000) >> 16)
vPos := 1
Loop, % vImgH
{
	Loop, % vImgW
	{
		if (vColBGR = SubStr(vHex, vPos, 6))
			vOutput .= "1"
		else
			vOutput .= "0"
		vPos += 8
	}
	vOutput .= "`n"
}
vOutput := SubStr(vOutput, 1, -1)
vOutput := JEE_StrReverseLines(vOutput, "`n")
oTickEnd.Push(A_TickCount)
oPixelMap.Push(vOutput)

;==================================================

;method 5 - GetObject - binary to hex string, RegExReplace 4 times
oTickStart.Push(A_TickCount)
;note: if hex is lowercase, hex needle must be lowercase, if we use RegEx in case-sensitive mode
vColBGR := Format("{:02x}{:02x}{:02x}", vColRGB & 0xFF, (vColRGB & 0xFF00) >> 8, (vColRGB & 0xFF0000) >> 16)
vOutput := RegExReplace(vHex, ".{8}", "_$0")
vOutput := RegExReplace(vOutput, "_" vColBGR "..", "1")
vOutput := RegExReplace(vOutput, "_.{8}", "0")
vOutput := RegExReplace(vOutput, ".{" vImgW "}", "$0`n")
vOutput := SubStr(vOutput, 1, -1)
vOutput := JEE_StrReverseLines(vOutput, "`n")
oTickEnd.Push(A_TickCount)
oPixelMap.Push(vOutput)

;==================================================

vOutput2 := "if the pixel maps match, then all the numbers below should equal 1:`r`n"
Loop, % oPixelMap.Length()
{
	vIndex := A_Index
	Loop, % oPixelMap.Length()
		vOutput2 .= (oPixelMap[vIndex] = oPixelMap[A_Index])
	vOutput2 .= "`r`n"
}
MsgBox, % vOutput2

;Loop, % oPixelMap.Length()
;	MsgBox, % oPixelMap[A_Index]

vOutput3 := ((vImgW+1)*vImgH)-1 "`r`n"
Loop, % oPixelMap.Length()
	vOutput3 .= (A_Index=1?"":" ") StrLen(oPixelMap[A_Index])
;MsgBox, % vOutput3

;vOutput1 := oPixelMap.1
;vOutput2 := oPixelMap.2
;JEE_WinMergeCompareStrings(vOutput1, vOutput2)
vOutput := StrReplace(vOutput, "`n", "`r`n")
vOutput := StrReplace(vOutput, "0", " ")
;vOutput := StrReplace(vOutput, "0", "..")
;vOutput := StrReplace(vOutput, "0", "_")
vOutput := StrReplace(vOutput, "1", "#")
;vOutput := StrReplace(vOutput, "1", Chr(9608)) ;FULL BLOCK
Clipboard := vOutput
MsgBox, % vOutput

;vOutput := StrReplace(vOutput, "0", " ")
;vOutput := StrReplace(vOutput, "1", Chr(9608)) ;FULL BLOCK
;Progress, zh0 b c0 fs1 w1000, % vOutput,,, Courier New
;Progress, zh0 b c0 fs1 w1000, % SubStr(vOutput, 1, 1000),,, Courier New
;Sleep 1000
;Progress, Off
;Notepad2 is useful for displaying the output
;Clipboard := vOutput
;MsgBox, % vOutput

vOutput4 := vImgW "x" vImgH "`r`n"
Loop, % oTickStart.Length()
	vOutput4 .= A_Index "`t" (oTickEnd[A_Index] - oTickStart[A_Index]) "`r`n"
MsgBox, % Clipboard := vOutput4

DeleteObject(hBitmap)
Gdip_DisposeImage(pBitmap)
Gdip_Shutdown(pToken)
;MsgBox, % "done"
return

;==================================================

JEE_BinDataToHex(vAddr, vSize)
{
	;CRYPT_STRING_HEX := 0x4 ;to return space/CRLF-separated text
	;CRYPT_STRING_HEXRAW := 0xC ;to return raw hex (not supported by Windows XP)
	DllCall("crypt32\CryptBinaryToString", Ptr,vAddr, UInt,vSize, UInt,0x4, Ptr,0, UIntP,vChars)
	VarSetCapacity(vHex, vChars*2, 0)
	DllCall("crypt32\CryptBinaryToString", Ptr,vAddr, UInt,vSize, UInt,0x4, Str,vHex, UIntP,vChars)
	vHex := StrReplace(vHex, "`r`n")
	vHex := StrReplace(vHex, " ")
	return vHex
}

;==================================================

JEE_StrReverseLines(ByRef vText, vDelim:="`r`n")
{
	if (vText = "")
		return vText
	vOutput := ""
	VarSetCapacity(vOutput, StrLen(vText)*2)
	oArray := StrSplit(vText, vDelim)
	Loop, % oArray.Length() - 1
		vOutput .= oArray[oArray.Length()+1-A_Index] vDelim
	vOutput .= oArray.1
	return vOutput
}

;==================================================

;Machine code binary buffer searching regardless of NULL - Scripts and Functions - AutoHotkey Community
;https://autohotkey.com/board/topic/23627-machine-code-binary-buffer-searching-regardless-of-null/

InBuf(haystackAddr, needleAddr, haystackSize, needleSize, StartOffset=0)
{   Static fun
   IfEqual,fun,
   {
      h=
      ( LTrim join
         5589E583EC0C53515256579C8B5D1483FB000F8EC20000008B4D108B451829C129D9410F8E
         B10000008B7D0801C78B750C31C0FCAC4B742A4B742D4B74364B74144B753F93AD93F2AE0F
         858B000000391F75F4EB754EADF2AE757F3947FF75F7EB68F2AE7574EB628A26F2AE756C38
         2775F8EB569366AD93F2AE755E66391F75F7EB474E43AD8975FC89DAC1EB02895DF483E203
         8955F887DF87D187FB87CAF2AE75373947FF75F789FB89CA83C7038B75FC8B4DF485C97404
         F3A775DE8B4DF885C97404F3A675D389DF4F89F82B45089D5F5E5A595BC9C2140031C0F7D0EBF0
      )
      VarSetCapacity(fun,StrLen(h)//2)
      Loop % StrLen(h)//2
         NumPut("0x" . SubStr(h,2*A_Index-1,2), fun, A_Index-1, "Char")
   }
   Return DllCall(&fun
      , "uint",haystackAddr, "uint",needleAddr
      , "uint",haystackSize, "uint",needleSize
      , "uint",StartOffset)
}

;==================================================

;tests done using AHK U32 v1.1.26.01

;some results

;500x500
;1	2683
;2	343
;3	2216
;4	374
;5	125

;500x500
;1	2715
;2	343
;3	2246
;4	343
;5	110

;500x500
;1	3027
;2	343
;3	2215
;4	375
;5	78

;1280x720
;1	10031
;2	1248
;3	5991
;4	1248
;5	280

;1280x720
;1	11013
;2	1248
;3	5991
;4	1248
;5	296

;1280x720
;1	10078
;2	1279
;3	5990
;4	1248
;5	328

;some results: all pixels the needle colour

;1280x720
;1	10452
;2	936
;3	6490
;4	1154
;5	266

;1280x720
;1	10390
;2	936
;3	6489
;4	1186
;5	249

;1280x720
;1	10701
;2	905
;3	6646
;4	1123
;5	296

;some results: all pixels the needle colour (using StrPut instead of NumPut in method 3)

;1280x720
;1	10452
;2	936
;3	6708
;4	1123
;5	281

;1280x720
;1	10405
;2	998
;3	6833
;4	1186
;5	343

;1280x720
;1	10546
;2	905
;3	6833
;4	1123
;5	297

;some results: no pixels the needle colour

;1280x720
;1	10483
;2	936
;3	32
;4	904
;5	312

;1280x720
;1	10515
;2	998
;3	31
;4	967
;5	328

;1280x720
;1	10640
;2	936
;3	31
;4	967
;5	296

;==================================================
Last edited by jeeswg on 03 Sep 2017, 03:15, edited 4 times in total.
homepage | tutorials | wish list | fun threads | donate
WARNING: copy your posts/messages before hitting Submit as you may lose them due to CAPTCHA
User avatar
jeeswg
Posts: 6902
Joined: 19 Dec 2016, 01:58
Location: UK

Re: Gdip: image binary data to hex string for OCR

02 Sep 2017, 23:34

This is my best script so far for image to hex string:

Btw at present I am having to reverse the lines of text, in case this can be avoided by some means.

Code: Select all

;[Gdip functions]
;GDI+ standard library 1.45 by tic - AutoHotkey Community
;https://autohotkey.com/boards/viewtopic.php?f=6&t=6517

;create a text string with 1s/0s based on a certain pixel colour

q:: ;ARGB colour values to 1s/0s for use with OCR
;needle colour, check for pixels with this colour
vColRGB := 0x000000 ;black

pToken := Gdip_Startup()
ControlGet, hCtl, Hwnd,, Edit1, A
WinGetPos, vWinX, vWinY, vWinW, vWinH, % "ahk_id " hCtl
;vWinW := 500, vWinH := 500
vScreen := vWinX "|"  vWinY "|" vWinW "|" vWinH
;vScreen := 0 "|"  0 "|" A_ScreenWidth "|" A_ScreenHeight
pBitmap := Gdip_BitmapFromScreen(vScreen)
Gdip_GetImageDimensions(pBitmap, vImgW, vImgH)

hBitmap := Gdip_CreateHBITMAPFromBitmap(pBitmap)
VarSetCapacity(DIBSECTION, vSizeDS:=A_PtrSize=8?104:84, 0)
DllCall("gdi32\GetObject", Ptr,hBitmap, Int,vSizeDS, Ptr,&DIBSECTION)
vAddr := NumGet(DIBSECTION, A_PtrSize=8?24:20, "Ptr") ;bmBits
vSize := NumGet(DIBSECTION, A_PtrSize=8?52:44, "UInt") ;biSizeImage
vHex := JEE_BinDataToHex(vAddr, vSize)

;note: if hex is lowercase, hex needle must be lowercase, if we use RegEx in case-sensitive mode
vColBGR := Format("{:02x}{:02x}{:02x}", vColRGB & 0xFF, (vColRGB & 0xFF00) >> 8, (vColRGB & 0xFF0000) >> 16)
vOutput := RegExReplace(vHex, ".{8}", "_$0")
vOutput := RegExReplace(vOutput, "_" vColBGR "..", "1")
vOutput := RegExReplace(vOutput, "_.{8}", "0")
vOutput := RegExReplace(vOutput, ".{" vImgW "}", "$0`n")
vOutput := SubStr(vOutput, 1, -1)
vOutput := JEE_StrReverseLines(vOutput, "`n")

vOutput := StrReplace(vOutput, "0", " ")
vOutput := StrReplace(vOutput, "1", Chr(9608)) ;FULL BLOCK
;Progress, zh0 b c0 fs1 w1000, % vOutput,,, Courier New
;Progress, zh0 b c0 fs1 w1000, % SubStr(vOutput, 1, 1000),,, Courier New
;Sleep 1000
;Progress, Off
;Notepad2 is useful for displaying the output
Clipboard := vOutput

DeleteObject(hBitmap)
Gdip_DisposeImage(pBitmap)
Gdip_Shutdown(pToken)
MsgBox, % "done"
return

;==================================================

JEE_BinDataToHex(vAddr, vSize)
{
	;CRYPT_STRING_HEX := 0x4 ;to return space/CRLF-separated text
	;CRYPT_STRING_HEXRAW := 0xC ;to return raw hex (not supported by Windows XP)
	DllCall("crypt32\CryptBinaryToString", Ptr,vAddr, UInt,vSize, UInt,0x4, Ptr,0, UIntP,vChars)
	VarSetCapacity(vHex, vChars*2, 0)
	DllCall("crypt32\CryptBinaryToString", Ptr,vAddr, UInt,vSize, UInt,0x4, Str,vHex, UIntP,vChars)
	vHex := StrReplace(vHex, "`r`n")
	vHex := StrReplace(vHex, " ")
	return vHex
}

;==================================================

JEE_StrReverseLines(ByRef vText, vDelim:="`r`n")
{
	if (vText = "")
		return vText
	vOutput := ""
	VarSetCapacity(vOutput, StrLen(vText)*2)
	oArray := StrSplit(vText, vDelim)
	Loop, % oArray.Length() - 1
		vOutput .= oArray[oArray.Length()+1-A_Index] vDelim
	vOutput .= oArray.1
	return vOutput
}

;==================================================
homepage | tutorials | wish list | fun threads | donate
WARNING: copy your posts/messages before hitting Submit as you may lose them due to CAPTCHA

Return to “Ask for Help (v1)”

Who is online

Users browsing this forum: No registered users and 139 guests