Optical character recognition (OCR) with UWP API

Post your working scripts, libraries and tools for AHK v1.1 and older
malcev
Posts: 1769
Joined: 12 Aug 2014, 12:37

Re: Optical character recognition (OCR) with UWP API

Post by malcev » 10 Apr 2022, 15:57

Code: Select all

   BitmapFrameWithSoftwareBitmap := ComObjQuery(BitmapDecoder, IBitmapFrameWithSoftwareBitmap := "{FE287C9A-420C-4963-87AD-691436E08383}")
   DllCall(NumGet(NumGet(BitmapFrameWithSoftwareBitmap+0)+6*A_PtrSize), "ptr", BitmapFrameWithSoftwareBitmap, "ptr*", SoftwareBitmap)   ; GetSoftwareBitmapAsync
   WaitForAsync(SoftwareBitmap)
   DllCall(NumGet(NumGet(OcrEngine+0)+6*A_PtrSize), "ptr", OcrEngine, ptr, SoftwareBitmap, "ptr*", OcrResult)   ; RecognizeAsync
   WaitForAsync(OcrResult)
   DllCall(NumGet(NumGet(OcrResult+0)+6*A_PtrSize), "ptr", OcrResult, "ptr*", LinesList)   ; get_Lines
   DllCall(NumGet(NumGet(LinesList+0)+7*A_PtrSize), "ptr", LinesList, "int*", count)   ; count
   loop % count
   {
      DllCall(NumGet(NumGet(LinesList+0)+6*A_PtrSize), "ptr", LinesList, "int", A_Index-1, "ptr*", OcrLine)
      DllCall(NumGet(NumGet(OcrLine+0)+6*A_PtrSize), "ptr", OcrLine, "ptr*", WordsList)   ; get_Words
      DllCall(NumGet(NumGet(WordsList+0)+7*A_PtrSize), "ptr", WordsList, "int*", WordsCount)   ; Words count
      loop % WordsCount
      {
         DllCall(NumGet(NumGet(WordsList+0)+6*A_PtrSize), "ptr", WordsList, "int", A_Index-1, "ptr*", OcrWord)
         VarSetCapacity(RECT, 16, 0)
         DllCall(NumGet(NumGet(OcrWord+0)+6*A_PtrSize), "ptr", OcrWord, "ptr", &RECT)   ; get_BoundingRect
         DllCall(NumGet(NumGet(OcrWord+0)+7*A_PtrSize), "ptr", OcrWord, "ptr*", hText)   ; get_Text
         buffer := DllCall("Combase.dll\WindowsGetStringRawBuffer", "ptr", hText, "uint*", length, "ptr")
         x := NumGet(&RECT, 0, "float")
         y := NumGet(&RECT, 4, "float")
         w := NumGet(&RECT, 8, "float")
         h := NumGet(&RECT, 12, "float")
         text := StrGet(buffer, "UTF-16")
         msgbox %text%`nx: %x%, y: %y%, w: %w%, h: %h%
         ObjRelease(OcrWord)
      }
      ObjRelease(WordsList)
      ObjRelease(OcrLine)
   }
viewtopic.php?p=318651#p318651

OUmSKILLS
Posts: 1
Joined: 26 Jun 2022, 07:02

Re: Optical character recognition (OCR) with UWP API

Post by OUmSKILLS » 26 Jun 2022, 07:07

I'm not sure what I am doing wrong. I have a png of text in the same directory as the script but when the msgbox for the english language pops up its just empty without any sort of error provided. What steps can I take to troubleshoot this?

Spitzi
Posts: 313
Joined: 24 Feb 2022, 03:45

Re: Optical character recognition (OCR) with UWP API

Post by Spitzi » 24 Aug 2022, 02:47

Thanks @malcev for this nice OCR tool.

I use it to recognize names and birthdates of patients. I noticed, that when the names are foreign, like from Thailand or Indonesia, the OCR does not perform too well, leaving some characters out etc.

For example, the name 'Harilaj Aredin' on screen whould be recognized as 'Haril Aredin', ignoring the 'aj' at the end of the first name. Can I do something to improve the OCR in such a situation?

Thanks and Greets Spitzi

User avatar
rommmcek
Posts: 1478
Joined: 15 Aug 2014, 15:18

Re: Optical character recognition (OCR) with UWP API

Post by rommmcek » 24 Aug 2022, 23:50

To increase precision, enlarging the snippet helps.

Spitzi
Posts: 313
Joined: 24 Feb 2022, 03:45

Re: Optical character recognition (OCR) with UWP API

Post by Spitzi » 28 Aug 2022, 17:40

Hi @rommmcek.

What do you mean by "enlarging the snippet"? I could not find a reference to it anywhere in the thread you linked.

User avatar
rommmcek
Posts: 1478
Joined: 15 Aug 2014, 15:18

Re: Optical character recognition (OCR) with UWP API

Post by rommmcek » 29 Aug 2022, 17:41

In most cases applies, the bigger the text image the better result (accuracy). So if the text image is small, enlarging it usually helps. Note that precision also depends of the font (some edgy fonts yield bad results).
Show your code producing bad result.
Scanning a paragraph of your post works well, I guess:
Attachments
Harilaj Aredin.png
Harilaj Aredin.png (21.43 KiB) Viewed 3918 times

Spitzi
Posts: 313
Joined: 24 Feb 2022, 03:45

Re: Optical character recognition (OCR) with UWP API

Post by Spitzi » 31 Aug 2022, 02:47

Ok, now I know what you mean.

Unfortunately I cannot control how big the written text is, nor the font that is used... no luck for me, than

Thanks.

User avatar
rommmcek
Posts: 1478
Joined: 15 Aug 2014, 15:18

Re: Optical character recognition (OCR) with UWP API

Post by rommmcek » 31 Aug 2022, 06:03

Sure you cannot control the font, but size of the text image you can.
You still did not explain how do you perform OCR so nobody can help you, I guess.

neogna2
Posts: 595
Joined: 15 Sep 2016, 15:44

Re: Optical character recognition (OCR) with UWP API

Post by neogna2 » 09 Mar 2023, 18:27

@malcev @teadrinker Do you perhaps have plans to convert the scripts in the first post to v2? I began trying to convert teadrinker's but quickly got stuck.

malcev
Posts: 1769
Joined: 12 Aug 2014, 12:37

Re: Optical character recognition (OCR) with UWP API

Post by malcev » 10 Mar 2023, 05:20

I currently do not have plans to start using ahk v2.

teadrinker
Posts: 4350
Joined: 29 Mar 2015, 09:41
Contact:

Re: Optical character recognition (OCR) with UWP API

Post by teadrinker » 10 Mar 2023, 08:03

Maybe I'll translate it later, HBitmapFromScreen() is already translated:

Code: Select all

#Requires AutoHotkey v2

MsgBox hBitmap := HBitmapFromScreen(GetArea()*)

GetArea() {
    area := {w: 0}
    StartSelection(area)
    while !area.w
       Sleep 100
    return [area.x, area.y, area.w, area.h]
}

StartSelection(area) {
    Hotkey 'LButton', Select.Bind(area), 'On'
    ReplaceSystemCursors('IDC_CROSS')
}

Select(area, *) {
    static SelGui := CreateSelectionGui(0x82FFC800)
    Hook := WindowsHook(WH_MOUSE_LL := 14, LowLevelMouseProc.Bind(SelGui))
    Loop {
        KeyWait 'LButton'
        WinGetPos &x, &y, &w, &h, SelGui.hwnd
    } until w > 0
    ReplaceSystemCursors('')
    Hotkey 'LButton', 'Off'
    Hook := ''
    SelGui.Show('Hide')
    for v in ['x', 'y', 'w', 'h'] {
        area.%v% := %v%
    }
}

ReplaceSystemCursors(IDC := '', *)
{
    static IMAGE_CURSOR := 2, SPI_SETCURSORS := 0x57
         , exitFunc := ReplaceSystemCursors.Bind('')
         , SysCursors := { IDC_APPSTARTING : 32650, IDC_NO   : 32648, IDC_ARROW : 32512, IDC_SIZEWE  : 32644,
                           IDC_SIZENWSE    : 32642, IDC_HAND : 32649, IDC_IBEAM : 32513, IDC_SIZENS  : 32645,
                           IDC_SIZENESW    : 32643, IDC_HELP : 32651, IDC_CROSS : 32515, IDC_SIZEALL : 32646,
                           IDC_UPARROW     : 32516, IDC_WAIT : 32514 }
    if !IDC {
        DllCall('SystemParametersInfo', 'UInt', SPI_SETCURSORS, 'UInt', 0, 'UInt', 0, 'UInt', 0)
        OnExit exitFunc, 0
    } else {
        hCursor := DllCall('LoadCursor', 'Ptr', 0, 'UInt', SysCursors.%IDC%, 'Ptr')
        for k, v in SysCursors.OwnProps() {
            hCopy := DllCall('CopyImage', 'Ptr', hCursor, 'UInt', IMAGE_CURSOR, 'Int', 0, 'Int', 0, 'UInt', 0, 'Ptr')
            DllCall('SetSystemCursor', 'Ptr', hCopy, 'UInt', v)
        }
        OnExit exitFunc
    }
}

CreateSelectionGui(colorARGB) {
    Wnd := Gui('+AlwaysOnTop -Caption +ToolWindow -DPIScale +E' . WS_EX_TRANSPARENT := 0x20)
    WinSetTransparent colorARGB >> 24, Wnd
    Wnd.BackColor := colorARGB & 0xFFFFFF
    return Wnd
}

HBitmapFromScreen(X, Y, W, H) {
    hDC := DllCall('GetDC', 'Ptr', 0, 'Ptr')
    hBM := DllCall('CreateCompatibleBitmap', 'Ptr', hDC, 'Int', W, 'Int', H, 'Ptr')
    hMDC := DllCall('CreateCompatibleDC', 'Ptr', hDC, 'Ptr')
    hObj := DllCall('SelectObject', 'Ptr', hMDC, 'Ptr', hBM, 'Ptr')
    DllCall('BitBlt', 'Ptr', hMDC, 'Int', 0, 'Int', 0, 'Int', W, 'Int', H
                    , 'Ptr', hDC, 'Int', X, 'Int', Y, 'UInt', SRCCOPY := 0x00CC0020)
    DllCall('SelectObject', 'Ptr', hMDC, 'Ptr', hObj, 'Ptr')
    DllCall('DeleteDC', 'Ptr', hMDC)
    DllCall('ReleaseDC', 'Ptr', 0, 'Ptr', hDC)
    return hBM
}

LowLevelMouseProc(SelGui, nCode, wParam, lParam) {
    static WM_MOUSEMOVE := 0x200, WM_LBUTTONUP := 0x202
         , startMouseX := '', startMouseY := ''

    if (wParam = WM_LBUTTONUP)
        startMouseX := startMouseY := ''
    if (wParam = WM_MOUSEMOVE) {
        mouseX := NumGet(lParam + 0, 'Int')
        mouseY := NumGet(lParam + 4, 'Int')
        if (startMouseX = '') {
            startMouseX := mouseX
            startMouseY := mouseY
        }
        x := startMouseX < mouseX ? startMouseX : mouseX
        y := startMouseY < mouseY ? startMouseY : mouseY
        w := Abs(mouseX - startMouseX)
        h := Abs(mouseY - startMouseY)
        try SelGui.Show('NA x' . x . ' y' . y . ' w' . w . ' h' . h)
    }
    return DllCall('CallNextHookEx', 'Ptr', 0, 'Int', nCode, 'UInt', wParam, 'Ptr', lParam, 'Ptr')
}

class WindowsHook {
    __New(type, callback, isGlobal := true) {
        this.pCallback := CallbackCreate(callback, 'Fast', 3)
        this.hHook := DllCall('SetWindowsHookEx', 'Int', type, 'Ptr', this.pCallback
            , 'Ptr', !isGlobal ? 0 : DllCall('GetModuleHandle', 'UInt', 0, 'Ptr')
            , 'UInt', isGlobal ? 0 : DllCall('GetCurrentThreadId'), 'Ptr')
    }
    __Delete() {
        DllCall('UnhookWindowsHookEx', 'Ptr', this.hHook)
        CallbackFree(this.pCallback)
    }
}

Descolada
Posts: 1154
Joined: 23 Dec 2021, 02:30

Re: Optical character recognition (OCR) with UWP API

Post by Descolada » 22 Apr 2023, 02:24

@neogna2, this might help you along:

Code: Select all

#Requires AutoHotkey v2

hBitmap := HBitmapFromScreen(0, 0, A_ScreenWidth, A_ScreenHeight)
pIRandomAccessStream := HBitmapToRandomAccessStream(hBitmap)
ocr(pIRandomAccessStream, "en")
MsgBox "done"
Return

HBitmapFromScreen(X, Y, W, H) {
   HDC := DllCall("GetDC", "Ptr", 0, "UPtr")
   HBM := DllCall("CreateCompatibleBitmap", "Ptr", HDC, "Int", W, "Int", H, "UPtr")
   PDC := DllCall("CreateCompatibleDC", "Ptr", HDC, "UPtr")
   DllCall("SelectObject", "Ptr", PDC, "Ptr", HBM)
   DllCall("BitBlt", "Ptr", PDC, "Int", 0, "Int", 0, "Int", W, "Int", H
                   , "Ptr", HDC, "Int", X, "Int", Y, "UInt", 0x00CC0020) ; SRCCOPY
   DllCall("DeleteDC", "Ptr", PDC)
   DllCall("ReleaseDC", "Ptr", 0, "Ptr", HDC)
   Return HBM
}

HBitmapToRandomAccessStream(hBitmap) {
   static IID_IRandomAccessStream := "{905A0FE1-BC53-11DF-8C49-001E4FC686DA}"
        , IID_IPicture            := "{7BF80980-BF32-101A-8BBB-00AA00300CAB}"
        , PICTYPE_BITMAP := 1
        , BSOS_DEFAULT   := 0
        
   DllCall("Ole32\CreateStreamOnHGlobal", "Ptr", 0, "UInt", true, "Ptr*", &pIStream:=0, "UInt")
   
   PICTDESC := Buffer(sz := 8 + A_PtrSize*2, 0)
   NumPut("uint", sz, "uint", PICTYPE_BITMAP, "ptr", hBitmap, PICTDESC)
   riid := CLSIDFromString(IID_IPicture)
   DllCall("OleAut32\OleCreatePictureIndirect", "Ptr", PICTDESC, "Ptr", riid, "UInt", 0, "Ptr*", &pIPicture:=0, "UInt")
   ; IPicture::SaveAsFile
   ComCall(15, pIPicture, "Ptr", pIStream, "UInt", true, "uint*", &size:=0, "UInt")
   riid := CLSIDFromString(IID_IRandomAccessStream)
   DllCall("ShCore\CreateRandomAccessStreamOverStream", "Ptr", pIStream, "UInt", BSOS_DEFAULT, "Ptr", riid, "Ptr*", &pIRandomAccessStream:=0, "UInt")
   ObjRelease(pIPicture)
   ObjRelease(pIStream)
   Return pIRandomAccessStream
}

CLSIDFromString(IID) {
   CLSID := Buffer(16)
   if res := DllCall("ole32\CLSIDFromString", "WStr", IID, "Ptr", CLSID, "UInt")
      throw Error("CLSIDFromString failed. Error: " . Format("{:#x}", res))
   Return CLSID
}


ocr(f, lang := "FirstFromAvailableLanguages")
{
   static OcrEngineStatics := "", OcrEngine := "", MaxDimension, LanguageFactory, Language, CurrentLanguage := "", BitmapDecoderStatics, GlobalizationPreferencesStatics
   if (OcrEngineStatics = "")
   {
      CreateClass("Windows.Globalization.Language", ILanguageFactory := "{9B0252AC-0C27-44F8-B792-9793FB66C63E}", &LanguageFactory:=0)
      CreateClass("Windows.Graphics.Imaging.BitmapDecoder", IBitmapDecoderStatics := "{438CCB26-BCEF-4E95-BAD6-23A822E58D01}", &BitmapDecoderStatics:=0)
      CreateClass("Windows.Media.Ocr.OcrEngine", IOcrEngineStatics := "{5BFFA85A-3384-3540-9940-699120D428A8}", &OcrEngineStatics:=0)
      ComCall(6, OcrEngineStatics, "uint*", &MaxDimension:=0)   ; MaxImageDimension
   }
   if (f = "ShowAvailableLanguages")
   {
      if (GlobalizationPreferencesStatics = "")
         CreateClass("Windows.System.UserProfile.GlobalizationPreferences", IGlobalizationPreferencesStatics := "{01BF4326-ED37-4E96-B0E9-C1340D1EA158}", &GlobalizationPreferencesStatics:=0)
      ComCall(9, GlobalizationPreferencesStatics, "ptr*", &LanguageList:=0)   ; get_Languages
      ComCall(7, LanguageList, "int*", &count:=0)   ; count
      loop count
      {
         ComCall(6, LanguageList, "int", A_Index-1, "ptr*", &hString:=0)   ; get_Item
         ComCall(6, LanguageFactory, "ptr", hString, "ptr*", &LanguageTest:=0)   ; CreateLanguage
         ComCall(8, OcrEngineStatics, "ptr", LanguageTest, "int*", &bool:=0)   ; IsLanguageSupported
         if (bool = 1)
         {
            ComCall(6, LanguageTest, "ptr*", &hText:=0)
            buf := DllCall("Combase.dll\WindowsGetStringRawBuffer", "ptr", hText, "uint*", &length:=0, "ptr")
            text .= StrGet(buf, "UTF-16") "`n"
         }
         ObjRelease(LanguageTest)
      }
      ObjRelease(LanguageList)
      return text
   }
   if (lang != CurrentLanguage) or (lang = "FirstFromAvailableLanguages")
   {
      if (OcrEngine != "")
      {
         ObjRelease(OcrEngine)
         if (CurrentLanguage != "FirstFromAvailableLanguages")
            ObjRelease(Language)
      }
      if (lang = "FirstFromAvailableLanguages")
         ComCall(10, OcrEngineStatics, "ptr*", &OcrEngine:=0)   ; TryCreateFromUserProfileLanguages
      else
      {
         CreateHString(lang, &hString)
         ComCall(6, LanguageFactory, "ptr", hString, "ptr*", &Language:=0)   ; CreateLanguage
         DeleteHString(hString)
         ComCall(9, OcrEngineStatics, "ptr", Language, "ptr*", &OcrEngine:=0)   ; TryCreateFromLanguage
      }
      if (OcrEngine = 0)
      {
         msgbox "Can not use language `"" lang "`" for OCR, please install language pack."
         ExitApp
      }
      CurrentLanguage := lang
   }

   if f is String {
    if (SubStr(f, 2, 1) != ":")
        f := A_ScriptDir "\" file
     if !FileExist(f) or InStr(FileExist(f), "D")
     {
        msgbox "File `"" f "`" does not exist"
        ExitApp
     }
     GUID := CLSIDFromString(IID_RandomAccessStream := "{905A0FE1-BC53-11DF-8C49-001E4FC686DA}")
     DllCall("ShCore\CreateRandomAccessStreamOnFile", "wstr", f, "uint", Read := 0, "ptr", GUID, "ptr*", &IRandomAccessStream:=0)
   } else
        IRandomAccessStream := f
   ComCall(14, BitmapDecoderStatics, "ptr", IRandomAccessStream, "ptr*", &BitmapDecoder:=0)   ; CreateAsync
   WaitForAsync(&BitmapDecoder)
   BitmapFrame := ComObjQuery(BitmapDecoder, IBitmapFrame := "{72A49A1C-8081-438D-91BC-94ECFC8185C6}")
   ComCall(12, BitmapFrame, "uint*", &width:=0)   ; get_PixelWidth
   ComCall(13, BitmapFrame, "uint*", &height:=0)   ; get_PixelHeight
   if (width > MaxDimension) or (height > MaxDimension)
   {
      msgbox "Image is to big - " width "x" height ".`nIt should be maximum - " MaxDimension " pixels"
      ExitApp
   }
   BitmapFrameWithSoftwareBitmap := ComObjQuery(BitmapDecoder, IBitmapFrameWithSoftwareBitmap := "{FE287C9A-420C-4963-87AD-691436E08383}")
   ComCall(6, BitmapFrameWithSoftwareBitmap, "ptr*", &SoftwareBitmap:=0)   ; GetSoftwareBitmapAsync
   WaitForAsync(&SoftwareBitmap)
   ComCall(6, OcrEngine, "ptr", SoftwareBitmap, "ptr*", &OcrResult:=0)   ; RecognizeAsync
   WaitForAsync(&OcrResult)
   ComCall(6, OcrResult, "ptr*", &LinesList:=0)   ; get_Lines
   ComCall(7, LinesList, "int*", &count:=0)   ; count
   loop count
   {
      ComCall(6, LinesList, "int", A_Index-1, "ptr*", &OcrLine:=0)
      ComCall(6, OcrLine, "ptr*", &WordsList:=0)   ; get_Words
      ComCall(7, WordsList, "int*", &WordsCount:=0)   ; Words count
      loop WordsCount
      {
         ComCall(6, WordsList, "int", A_Index-1, "ptr*", &OcrWord:=0)
         RECT := Buffer(16, 0)
         ComCall(6, OcrWord, "ptr", RECT)   ; get_BoundingRect
         ComCall(7, OcrWord, "ptr*", &hText:=0)   ; get_Text
         buf := DllCall("Combase.dll\WindowsGetStringRawBuffer", "ptr", hText, "uint*", &length:=0, "ptr")
         x := NumGet(RECT, 0, "float")
         y := NumGet(RECT, 4, "float")
         w := NumGet(RECT, 8, "float")
         h := NumGet(RECT, 12, "float")
         text := StrGet(buf, "UTF-16")
         msgbox text "`nx: " x ", y: " y ", w: " w ", h: " h
         ObjRelease(OcrWord)
      }
      ObjRelease(WordsList)
      ObjRelease(OcrLine)
   }
   Close := ComObjQuery(IRandomAccessStream, IClosable := "{30D5A829-7FA4-4026-83BB-D75BAE4EA99E}")
   ComCall(6, Close)   ; Close
   Close := ComObjQuery(SoftwareBitmap, IClosable := "{30D5A829-7FA4-4026-83BB-D75BAE4EA99E}")
   ComCall(6, Close)   ; Close
   ObjRelease(IRandomAccessStream)
   ObjRelease(BitmapDecoder)
   ObjRelease(BitmapFrame)
   ObjRelease(BitmapFrameWithSoftwareBitmap)
   ObjRelease(SoftwareBitmap)
   ObjRelease(OcrResult)
   ObjRelease(LinesList)
   return text
}

CreateClass(str, interface, &cls)
{
   CreateHString(str, &hString)
   GUID := CLSIDFromString(interface)
   result := DllCall("Combase.dll\RoGetActivationFactory", "ptr", hString, "ptr", GUID, "ptr*", &cls:=0, "uint")
   if (result != 0)
   {
      if (result = 0x80004002)
         msgbox "No such interface supported"
      else if (result = 0x80040154)
         msgbox "Class not registered"
      else
         msgbox "error: " result
      ExitApp
   }
   DeleteHString(hString)
}

CreateHString(str, &hString)
{
    DllCall("Combase.dll\WindowsCreateString", "wstr", str, "uint", StrLen(str), "ptr*", &hString:=0)
}

DeleteHString(hString)
{
   DllCall("Combase.dll\WindowsDeleteString", "ptr", hString)
}

WaitForAsync(&obj)
{
   AsyncInfo := ComObjQuery(obj, IAsyncInfo := "{00000036-0000-0000-C000-000000000046}")
   loop
   {
      ComCall(7, AsyncInfo, "uint*", &status:=0)   ; IAsyncInfo.Status
      if (status != 0)
      {
         if (status != 1)
         {
            ComCall(8, ASyncInfo, "uint*", &ErrorCode:=0)   ; IAsyncInfo.ErrorCode
            msgbox "AsyncInfo status error:" ErrorCode
            ExitApp
         }
         break
      }
      sleep 10
   }
   ComCall(8, obj, "ptr*", &ObjectResult:=0)   ; GetResults
   ObjRelease(obj)
   obj := ObjectResult
}

jdpmdphd
Posts: 13
Joined: 03 Jul 2015, 13:33

Re: Optical character recognition (OCR) with UWP API

Post by jdpmdphd » 26 May 2023, 14:22

I notice it fails if the box encloses just 1 letter in width - why? This issue seems unrelated to size:
1
12
1
12

I capture 12 at either size, but come up blank for 1 at either size.

Code: Select all

#SingleInstance, force
#NoEnv
SetBatchLines, -1
;#include lib/gdip_all.ahk
;     1    A
;     2    B
;     3    C
;
;
/*
area := GetArea()
pBitmap := HBitmapFromScreen(area.x "|" area.y "|" area.w "|" area.h)
pBitmap := Gdip_ResizeBitmap(pBitmap, 500, 500, true)
;Gdip_SaveBitmapToFile(pBitmap, "output_test.png")
hBitmap := Gdip_CreateHBITMAPFromBitmap(pBitmap)
pIRandomAccessStream := HBitmapToRandomAccessStream(hBitmap)
DllCall("DeleteObject", "Ptr", hBitmap)
Gdip_DisposeImage(pBitmap)
text := ocr(pIRandomAccessStream, "en-US")
MsgBox, % text
*/
Return
;msgbox % mergeColumns("1`n2`n3`n4","A`nB`nC`nD`n`n"," = ")
gosub,makeROI
GuiControl, ROI:,ROI_EDIT, % ocr("ShowAvailableLanguages")


Return

Esc:: ExitApp

#t::
makeCapture:
   hBitmap := HBitmapFromScreen(GetArea()*)
   pIRandomAccessStream := HBitmapToRandomAccessStream(hBitmap)
   DllCall("DeleteObject", "Ptr", hBitmap)
   newtext := ocr(pIRandomAccessStream, "en")  ; available es, en

   GuiControlGet,ROI_CAT,ROI:
   GuiControlGet,ROI_COL,ROI:
   ;msgbox RC=%ROI_CAT%
   if ROI_COL
   {
      tooltip text=%text%`nnewText=%newText%
      text:=mergeColumns(text,newtext)
   }
   else if ROI_CAT
      text.=newtext
   else
      text:=newtext
   Clipboard:=text
   tooltip %text%
   SetTimer,tooltipoff,1000
   gosub,makeROI
Return

tooltipoff:
   ToolTip
return

#r::
makeROI:
   if ROI_exists
   {
      Gui, ROI: Show
      GuiControl, ROI:,ROI_EDIT, %text%
      return
   }
   else {
      Gui, ROI: +toolwindow +Caption +Resize +LastFound
      Gui, ROI: Add, checkbox, xm vROI_R, -Returns
      Gui, ROI: Add, checkbox, x+10 vROI_Cat, Cat
      Gui, ROI: Add, checkbox, x+10 vROI_COL, COL
      Gui, ROI: Add, button, x+10 vROI_RB gROI_RB, -Returns
      Gui, ROI: Add, button, x+10 vROI_CB gROI_CB, comma
      Gui, ROI: font, s20
      Gui, ROI: Add, EDIT, xm r10 vROI_EDIT gEDIT_ROI -wrap, %text%
      Gui, ROI: Show ;, w640 h480, Capture
      ;WinSet, TransColor, 000000 176
      ROI_exists := true
    }
return
ROI_RB:
   GuiControlGet,ROI_EDIT,ROI:
   StringReplace,newText,ROI_EDIT,`r,`n,all
   StringReplace,newText,newText,% chr(10),`n,all
   Loop{
      StringReplace,newText,newText,`n`n,`n,UseErrorLevel
      if ErrorLevel=0
         break
   }
   StringReplace,newText,newText,`n,` ,all
   Loop{
      StringReplace,newText,newText,` ` ,` ,UseErrorLevel
      if ErrorLevel=0
         break
   }
   GuiControl, ROI:,ROI_EDIT, %newtext%
return
ROI_CB:
   GuiControlGet,ROI_EDIT,ROI:
   StringReplace,newText,ROI_EDIT,`r,`n,all
   StringReplace,newText,newText,% chr(10),`n,all
   Loop{
      StringReplace,newText,newText,`n`n,`n,UseErrorLevel
      if ErrorLevel=0
         break
   }
   StringReplace,newText,newText,`n,`,` ,all
   Loop{
      StringReplace,newText,newText,` ` ,` ,UseErrorLevel
      if ErrorLevel=0
         break
   }
   newtext:=RegExReplace(newtext,", $", ". ")
   GuiControl, ROI:,ROI_EDIT, %newtext%
return
EDIT_ROI:
   guicontrolget,ROI_EDIT,ROI:
   ;msgbox,% ROI_EDIT
   text:=ROI_EDIT
   StringReplace,clipboard,ROI_EDIT,`n, `r`n,all
   ;clipboard:=text
return

ROIGuiSize:
   h:=A_GuiHeight-40
   w:=A_GuiWidth-20
   GuiControl,move,ROI_EDIT, w%w% h%h%
return

ROIguiClose:
    ;WinGetPos, X, Y, Width, Height, ahk_id %MyPicHwnd%
    Gui, ROI: submit
    gosub, makeCapture
return

mergeColumns(text1,text2,sep=" ")
{
   merged=
   ;count rows in text now
      StringReplace, junk, text1, `n,  , UseErrorLevel
      textRows:=ErrorLevel
   ;fold in newText
   array1:=[]
   ln1=0
   StringReplace,text1,text1,`r`n,`n,all
   loop,parse,text1, `n
   {
      if (A_Loopfield~="[\w\d]+")
      {
        array1.push(trim(A_LoopField))
        ln1++
      }
   }
   array2:=[]
   ln2=0
   StringReplace,text2,text2,`r`n,`n,all
   loop,parse,text2, `n
   {
      if (A_Loopfield~="[\w\d]+")
      {
        array2.push(trim(A_LoopField))
        ln2++
      }
   }
   for ii in array1
   {
      merged .= array1[A_Index] . sep . array2[A_Index] . "`n"
   }
   return merged
}

GetArea() {
   area := []
   StartSelection(area)
   while !area.w
      Sleep, 100
   Return area
}

StartSelection(area) {
   handler := Func("Select").Bind(area)
   Hotkey, LButton, % handler, On
   ReplaceSystemCursors("IDC_CROSS")
}

Select(area) {
   static hGui := CreateSelectionGui()
   Hook := new WindowsHook(WH_MOUSE_LL := 14, "LowLevelMouseProc", hGui)
   Loop {
      KeyWait, LButton
      ;WinGetPos, , , WW, , ahk_id %hGui%
      ;if !WW
      ;    KeyWait, LButton, D
      WinGetPos, X, Y, W, H, ahk_id %hGui%
   } until w > 0
   ReplaceSystemCursors("")
   Hotkey, LButton, Off
   Hook := ""
   Gui, %hGui%:Show, Hide
   for k, v in ["x", "y", "w", "h"]
      area[v] := %v%
}

ReplaceSystemCursors(IDC = "")
{
   static IMAGE_CURSOR := 2, SPI_SETCURSORS := 0x57
        , exitFunc := Func("ReplaceSystemCursors").Bind("")
        , SysCursors := { IDC_APPSTARTING: 32650
                        , IDC_ARROW      : 32512
                        , IDC_CROSS      : 32515
                        , IDC_HAND       : 32649
                        , IDC_HELP       : 32651
                        , IDC_IBEAM      : 32513
                        , IDC_NO         : 32648
                        , IDC_SIZEALL    : 32646
                        , IDC_SIZENESW   : 32643
                        , IDC_SIZENWSE   : 32642
                        , IDC_SIZEWE     : 32644
                        , IDC_SIZENS     : 32645
                        , IDC_UPARROW    : 32516
                        , IDC_WAIT       : 32514 }
   if !IDC {
      DllCall("SystemParametersInfo", UInt, SPI_SETCURSORS, UInt, 0, UInt, 0, UInt, 0)
      OnExit(exitFunc, 0)
   }
   else  {
      hCursor := DllCall("LoadCursor", Ptr, 0, UInt, SysCursors[IDC], Ptr)
      for k, v in SysCursors  {
         hCopy := DllCall("CopyImage", Ptr, hCursor, UInt, IMAGE_CURSOR, Int, 0, Int, 0, UInt, 0, Ptr)
         DllCall("SetSystemCursor", Ptr, hCopy, UInt, v)
      }
      OnExit(exitFunc)
   }
}

CreateSelectionGui() {
   Gui, New, +hwndhGui +Alwaysontop -Caption +LastFound +ToolWindow +E0x20 -DPIScale
   WinSet, Transparent, 130
   Gui, Color, FFC800
   Return hGui
}

LowLevelMouseProc(nCode, wParam, lParam) {
   static WM_MOUSEMOVE := 0x200, WM_LBUTTONUP := 0x202
        , coords := [], startMouseX, startMouseY, hGui
        , timer := Func("LowLevelMouseProc").Bind("timer", "", "")

   if (nCode = "timer") {
      while coords[1] {
         point := coords.RemoveAt(1)
         mouseX := point[1], mouseY := point[2]
         x := startMouseX < mouseX ? startMouseX : mouseX
         y := startMouseY < mouseY ? startMouseY : mouseY
         w := Abs(mouseX - startMouseX)
         h := Abs(mouseY - startMouseY)
         try Gui, %hGUi%: Show, x%x% y%y% w%w% h%h% NA
      }
   }
   else {
      (!hGui && hGui := A_EventInfo)
      if (wParam = WM_LBUTTONUP)
         startMouseX := startMouseY := ""
      if (wParam = WM_MOUSEMOVE)  {
         mouseX := NumGet(lParam + 0, "Int")
         mouseY := NumGet(lParam + 4, "Int")
         if (startMouseX = "") {
            startMouseX := mouseX
            startMouseY := mouseY
         }
         coords.Push([mouseX, mouseY])
         SetTimer, % timer, -10
      }
      Return DllCall("CallNextHookEx", Ptr, 0, Int, nCode, UInt, wParam, Ptr, lParam)
   }
}

class WindowsHook {
   __New(type, callback, eventInfo := "", isGlobal := true) {
      this.callbackPtr := RegisterCallback(callback, "Fast", 3, eventInfo)
      this.hHook := DllCall("SetWindowsHookEx", "Int", type, "Ptr", this.callbackPtr
                                              , "Ptr", !isGlobal ? 0 : DllCall("GetModuleHandle", "UInt", 0, "Ptr")
                                              , "UInt", isGlobal ? 0 : DllCall("GetCurrentThreadId"), "Ptr")
   }
   __Delete() {
      DllCall("UnhookWindowsHookEx", "Ptr", this.hHook)
      DllCall("GlobalFree", "Ptr", this.callBackPtr, "Ptr")
   }
}

HBitmapFromScreen(X, Y, W, H) {
   HDC := DllCall("user32.dll\GetDC", "Ptr", 0, "UPtr")
   HBM := DllCall("gdi32.dll\CreateCompatibleBitmap", "Ptr", HDC, "Int", W, "Int", H, "UPtr")
   PDC := DllCall("gdi32.dll\CreateCompatibleDC", "Ptr", HDC, "UPtr")
   DllCall("gdi32.dll\SelectObject", "Ptr", PDC, "Ptr", HBM)
   DllCall("gdi32.dll\BitBlt", "Ptr", PDC, "Int", 0, "Int", 0, "Int", W, "Int", H, "Ptr", HDC, "Int", X, "Int", Y, "UInt", 0x00CC0020)
   DllCall("gdi32.dll\DeleteDC", "Ptr", PDC)
   DllCall("user32.dll\ReleaseDC", "Ptr", 0, "Ptr", HDC)
   Return HBM
}

HBitmapToRandomAccessStream(hBitmap) {
   static IID_IRandomAccessStream := "{905A0FE1-BC53-11DF-8C49-001E4FC686DA}"
        , IID_IPicture            := "{7BF80980-BF32-101A-8BBB-00AA00300CAB}"
        , PICTYPE_BITMAP := 1
        , BSOS_DEFAULT   := 0

   DllCall("Ole32\CreateStreamOnHGlobal", "Ptr", 0, "UInt", true, "PtrP", pIStream, "UInt")

   VarSetCapacity(PICTDESC, sz := 8 + A_PtrSize*2, 0)
   NumPut(sz, PICTDESC)
   NumPut(PICTYPE_BITMAP, PICTDESC, 4)
   NumPut(hBitmap, PICTDESC, 8)
   riid := CLSIDFromString(IID_IPicture, GUID1)
   DllCall("OleAut32\OleCreatePictureIndirect", "Ptr", &PICTDESC, "Ptr", riid, "UInt", false, "PtrP", pIPicture, "UInt")
   ; IPicture::SaveAsFile
   DllCall(NumGet(NumGet(pIPicture+0) + A_PtrSize*15), "Ptr", pIPicture, "Ptr", pIStream, "UInt", true, "UIntP", size, "UInt")
   riid := CLSIDFromString(IID_IRandomAccessStream, GUID2)
   DllCall("ShCore\CreateRandomAccessStreamOverStream", "Ptr", pIStream, "UInt", BSOS_DEFAULT, "Ptr", riid, "PtrP", pIRandomAccessStream, "UInt")
   ObjRelease(pIPicture)
   ObjRelease(pIStream)
   Return pIRandomAccessStream
}

CLSIDFromString(IID, ByRef CLSID) {
   VarSetCapacity(CLSID, 16, 0)
   if res := DllCall("ole32\CLSIDFromString", "WStr", IID, "Ptr", &CLSID, "UInt")
      throw Exception("CLSIDFromString failed. Error: " . Format("{:#x}", res))
   Return &CLSID
}


ocr(file, lang := "FirstFromAvailableLanguages")
{
   static OcrEngineStatics, OcrEngine, MaxDimension, LanguageFactory, Language, CurrentLanguage, BitmapDecoderStatics, GlobalizationPreferencesStatics
   if (OcrEngineStatics = "")
   {
      CreateClass("Windows.Globalization.Language", ILanguageFactory := "{9B0252AC-0C27-44F8-B792-9793FB66C63E}", LanguageFactory)
      CreateClass("Windows.Graphics.Imaging.BitmapDecoder", IBitmapDecoderStatics := "{438CCB26-BCEF-4E95-BAD6-23A822E58D01}", BitmapDecoderStatics)
      CreateClass("Windows.Media.Ocr.OcrEngine", IOcrEngineStatics := "{5BFFA85A-3384-3540-9940-699120D428A8}", OcrEngineStatics)
      DllCall(NumGet(NumGet(OcrEngineStatics+0)+6*A_PtrSize), "ptr", OcrEngineStatics, "uint*", MaxDimension)   ; MaxImageDimension
   }
   if (file = "ShowAvailableLanguages")
   {
      if (GlobalizationPreferencesStatics = "")
         CreateClass("Windows.System.UserProfile.GlobalizationPreferences", IGlobalizationPreferencesStatics := "{01BF4326-ED37-4E96-B0E9-C1340D1EA158}", GlobalizationPreferencesStatics)
      DllCall(NumGet(NumGet(GlobalizationPreferencesStatics+0)+9*A_PtrSize), "ptr", GlobalizationPreferencesStatics, "ptr*", LanguageList)   ; get_Languages
      DllCall(NumGet(NumGet(LanguageList+0)+7*A_PtrSize), "ptr", LanguageList, "int*", count)   ; count
      loop % count
      {
         DllCall(NumGet(NumGet(LanguageList+0)+6*A_PtrSize), "ptr", LanguageList, "int", A_Index-1, "ptr*", hString)   ; get_Item
         DllCall(NumGet(NumGet(LanguageFactory+0)+6*A_PtrSize), "ptr", LanguageFactory, "ptr", hString, "ptr*", LanguageTest)   ; CreateLanguage
         DllCall(NumGet(NumGet(OcrEngineStatics+0)+8*A_PtrSize), "ptr", OcrEngineStatics, "ptr", LanguageTest, "int*", bool)   ; IsLanguageSupported
         if (bool = 1)
         {
            DllCall(NumGet(NumGet(LanguageTest+0)+6*A_PtrSize), "ptr", LanguageTest, "ptr*", hText)
            buffer := DllCall("Combase.dll\WindowsGetStringRawBuffer", "ptr", hText, "uint*", length, "ptr")
            text .= StrGet(buffer, "UTF-16") "`n"
         }
         ObjRelease(LanguageTest)
      }
      ObjRelease(LanguageList)
      return text
   }
   if (lang != CurrentLanguage) or (lang = "FirstFromAvailableLanguages")
   {
      if (OcrEngine != "")
      {
         ObjRelease(OcrEngine)
         if (CurrentLanguage != "FirstFromAvailableLanguages")
            ObjRelease(Language)
      }
      if (lang = "FirstFromAvailableLanguages")
         DllCall(NumGet(NumGet(OcrEngineStatics+0)+10*A_PtrSize), "ptr", OcrEngineStatics, "ptr*", OcrEngine)   ; TryCreateFromUserProfileLanguages
      else
      {
         CreateHString(lang, hString)
         DllCall(NumGet(NumGet(LanguageFactory+0)+6*A_PtrSize), "ptr", LanguageFactory, "ptr", hString, "ptr*", Language)   ; CreateLanguage
         DeleteHString(hString)
         DllCall(NumGet(NumGet(OcrEngineStatics+0)+9*A_PtrSize), "ptr", OcrEngineStatics, ptr, Language, "ptr*", OcrEngine)   ; TryCreateFromLanguage
      }
      if (OcrEngine = 0)
      {
         msgbox Can not use language "%lang%" for OCR, please install language pack.
         ExitApp
      }
      CurrentLanguage := lang
   }
   IRandomAccessStream := file
   DllCall(NumGet(NumGet(BitmapDecoderStatics+0)+14*A_PtrSize), "ptr", BitmapDecoderStatics, "ptr", IRandomAccessStream, "ptr*", BitmapDecoder)   ; CreateAsync
   WaitForAsync(BitmapDecoder)
   BitmapFrame := ComObjQuery(BitmapDecoder, IBitmapFrame := "{72A49A1C-8081-438D-91BC-94ECFC8185C6}")
   DllCall(NumGet(NumGet(BitmapFrame+0)+12*A_PtrSize), "ptr", BitmapFrame, "uint*", width)   ; get_PixelWidth
   DllCall(NumGet(NumGet(BitmapFrame+0)+13*A_PtrSize), "ptr", BitmapFrame, "uint*", height)   ; get_PixelHeight
;msgbox, % "w=" width " h=" height " max=" MaxDimension
/*
   if (width > MaxDimension) or (height > MaxDimension)
   {
      msgbox Image is too big - %width%x%height%.`nIt should be maximum - %MaxDimension% pixels
      return
      ;ExitApp
   }
*/
   BitmapFrameWithSoftwareBitmap := ComObjQuery(BitmapDecoder, IBitmapFrameWithSoftwareBitmap := "{FE287C9A-420C-4963-87AD-691436E08383}")
   DllCall(NumGet(NumGet(BitmapFrameWithSoftwareBitmap+0)+6*A_PtrSize), "ptr", BitmapFrameWithSoftwareBitmap, "ptr*", SoftwareBitmap)   ; GetSoftwareBitmapAsync
   WaitForAsync(SoftwareBitmap)
   DllCall(NumGet(NumGet(OcrEngine+0)+6*A_PtrSize), "ptr", OcrEngine, ptr, SoftwareBitmap, "ptr*", OcrResult)   ; RecognizeAsync
   WaitForAsync(OcrResult)
   DllCall(NumGet(NumGet(OcrResult+0)+6*A_PtrSize), "ptr", OcrResult, "ptr*", LinesList)   ; get_Lines
   DllCall(NumGet(NumGet(LinesList+0)+7*A_PtrSize), "ptr", LinesList, "int*", count)   ; count
;msgbox, % "OCR=" StrGet(OcrResult, "UTF-16") " L=" StrGet(LinesList, "UTF-16")  " #=" count
   loop % count
   {
      DllCall(NumGet(NumGet(LinesList+0)+6*A_PtrSize), "ptr", LinesList, "int", A_Index-1, "ptr*", OcrLine)
      DllCall(NumGet(NumGet(OcrLine+0)+7*A_PtrSize), "ptr", OcrLine, "ptr*", hText)
      buffer := DllCall("Combase.dll\WindowsGetStringRawBuffer", "ptr", hText, "uint*", length, "ptr")
      ;;tooltip % "count=" count " buffer=" buffer " -> " StrGet(buffer, "UTF-16")
      ;;sleep 5000
      text .= StrGet(buffer, "UTF-16") "`n"
      ObjRelease(OcrLine)
   }
   Close := ComObjQuery(IRandomAccessStream, IClosable := "{30D5A829-7FA4-4026-83BB-D75BAE4EA99E}")
   DllCall(NumGet(NumGet(Close+0)+6*A_PtrSize), "ptr", Close)   ; Close
   ObjRelease(Close)
   Close := ComObjQuery(SoftwareBitmap, IClosable := "{30D5A829-7FA4-4026-83BB-D75BAE4EA99E}")
   DllCall(NumGet(NumGet(Close+0)+6*A_PtrSize), "ptr", Close)   ; Close
   ObjRelease(Close)
   ObjRelease(IRandomAccessStream)
   ObjRelease(BitmapDecoder)
   ObjRelease(BitmapFrame)
   ObjRelease(BitmapFrameWithSoftwareBitmap)
   ObjRelease(SoftwareBitmap)
   ObjRelease(OcrResult)
   ObjRelease(LinesList)
   return text
}



CreateClass(string, interface, ByRef Class)
{
   CreateHString(string, hString)
   VarSetCapacity(GUID, 16)
   DllCall("ole32\CLSIDFromString", "wstr", interface, "ptr", &GUID)
   result := DllCall("Combase.dll\RoGetActivationFactory", "ptr", hString, "ptr", &GUID, "ptr*", Class)
   if (result != 0)
   {
      if (result = 0x80004002)
         msgbox No such interface supported
      else if (result = 0x80040154)
         msgbox Class not registered
      else
         msgbox error: %result%
      ExitApp
   }
   DeleteHString(hString)
}

CreateHString(string, ByRef hString)
{
    DllCall("Combase.dll\WindowsCreateString", "wstr", string, "uint", StrLen(string), "ptr*", hString)
}

DeleteHString(hString)
{
   DllCall("Combase.dll\WindowsDeleteString", "ptr", hString)
}

WaitForAsync(ByRef Object)
{
   AsyncInfo := ComObjQuery(Object, IAsyncInfo := "{00000036-0000-0000-C000-000000000046}")
   loop
   {
      DllCall(NumGet(NumGet(AsyncInfo+0)+7*A_PtrSize), "ptr", AsyncInfo, "uint*", status)   ; IAsyncInfo.Status
      if (status != 0)
      {
         if (status != 1)
         {
            DllCall(NumGet(NumGet(AsyncInfo+0)+8*A_PtrSize), "ptr", AsyncInfo, "uint*", ErrorCode)   ; IAsyncInfo.ErrorCode
            msgbox AsyncInfo status error: %ErrorCode%
            ExitApp
         }
         ObjRelease(AsyncInfo)
         break
      }
      sleep 10
   }
   DllCall(NumGet(NumGet(Object+0)+8*A_PtrSize), "ptr", Object, "ptr*", ObjectResult)   ; GetResults
   ObjRelease(Object)
   Object := ObjectResult
}

RumCAJZ
Posts: 1
Joined: 23 Jun 2023, 16:33

Re: Optical character recognition (OCR) with UWP API

Post by RumCAJZ » 23 Jun 2023, 16:44

Hi all, Is it possible for it to recognise only numbers? ( Or add some kind of whitelist )

User avatar
rommmcek
Posts: 1478
Joined: 15 Aug 2014, 15:18

Re: Optical character recognition (OCR) with UWP API

Post by rommmcek » 25 Jun 2023, 00:14

Try something like: msgbox % RegExReplace(ocr("C:\Users\RRR\My Ahk-s\test.jpg", "en"), "[^0-9]").

MoUse_G
Posts: 10
Joined: 07 Oct 2021, 00:52

Re: Optical character recognition (OCR) with UWP API

Post by MoUse_G » 21 Jul 2023, 08:06

Can someone help me?
I use this OCR can't get the number.

Thank you.
Attachments
Screenshot_3944.png
Screenshot_3944.png (9.11 KiB) Viewed 2263 times

_Doug_
Posts: 1
Joined: 20 Dec 2023, 15:08

Re: Optical character recognition (OCR) with UWP API

Post by _Doug_ » 20 Dec 2023, 15:28

First off, Malcev, amazing work. I've used some other things around here from you as well, so thank you.

I noticed an interesting issue this morning and I'm hoping someone has an idea.

I'm using the OCR on the screen, rather than a file. When I call it the first time, you can tell it uses the coordinates I passed based on the text it returns. When I call it again, regardless of what coordinates I pass the second time, it returns the text belonging to the system clock in the bottom right. It ignores any coordinates after the first call.

I even tried duplicating the dllcalls/object deletions from your functions into the main code body, setting all the variables (such as coordinates) in your function back to := "" after the first call, but before the next. Made sure I don't use coordinates variables for any other function in script, or have globals declared after the first call that would interfere. I'd only expect any of this to work if subsequent calls continued using the SAME coordinates as the first call, regardless of subsequent coordinates, but I did it anyways.

I didn't even think the default search direction was bottom of screen then to the left, which makes it picking up system clock first even stranger. Any ideas would be appreciated.

User avatar
rommmcek
Posts: 1478
Joined: 15 Aug 2014, 15:18

Re: Optical character recognition (OCR) with UWP API

Post by rommmcek » 21 Jan 2024, 03:19

I made an attempt to sort the words.
To test: Run the script, activate the desired window (if you want Ocr only active window, maximize it) and press Enter.

P.s.: First you have to manually (in the script) set the diff i.e. tolerance of the lines. For auto setting and minimal formatting see here (v2).
Attachments
Ocr Uwp Aligned_02.ahk
(11.62 KiB) Downloaded 132 times

Post Reply

Return to “Scripts and Functions (v1)”