🔍
Windows - F# と標準機能で選択領域の OCR
概要
スクリーンショット撮影アプリ「切り取り&スケッチ[1]」と「Windows.Media.Ocr[2]」を組み合わせて、選択した領域のテキストを読み取るプログラムを F# で作る。
形式としては、領域の選択をグローバルホットキーで開始し、読み取ったテキストをクリップボードに保存する、タスクトレイ常駐型のアプリとしました。
作成環境
対象 | バージョン |
---|---|
Windows | 20H2 (19042.928) |
.NET SDK | 5.0.202 |
実装例
※「切り取り&スケッチ」の設定「クリップボードに自動コピー」をオンにする。
.fsproj
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net5.0-windows10.0.19041.0</TargetFramework>
<RootNamespace>プロジェクト名</RootNamespace>
<UseWindowsForms>true</UseWindowsForms>
<UseWPF>true</UseWPF>
</PropertyGroup>
<ItemGroup>
<Compile Include="Program.fs" />
</ItemGroup>
</Project>
Program.fs
open System
let await (i: Windows.Foundation.IAsyncOperation<'TResult>) =
i.AsTask() |> Async.AwaitTask
let startScreenclip () =
let cmd = new System.Diagnostics.ProcessStartInfo "cmd"
cmd.Arguments <- @"/c start ms-screenclip:"
cmd.UseShellExecute <- true
cmd.WindowStyle <- System.Diagnostics.ProcessWindowStyle.Hidden
let cmdProcess = System.Diagnostics.Process.Start cmd
cmdProcess.WaitForExit()
let processArray = System.Diagnostics.Process.GetProcessesByName "ScreenClippingHost"
processArray.[0].WaitForExit()
let getClipboardBitmap () =
let bitmapSource = System.Windows.Clipboard.GetImage()
if bitmapSource <> null then
let bitmapFrame = System.Windows.Media.Imaging.BitmapFrame.Create bitmapSource
let encoder = new System.Windows.Media.Imaging.BmpBitmapEncoder()
encoder.Frames.Add bitmapFrame
use memoryStream = new System.IO.MemoryStream()
encoder.Save memoryStream
let byteArray = memoryStream.ToArray()
use randomAccessStream = new Windows.Storage.Streams.InMemoryRandomAccessStream()
use outputStream = randomAccessStream.GetOutputStreamAt(uint64 0)
use dataWriter = new Windows.Storage.Streams.DataWriter(outputStream)
dataWriter.WriteBytes byteArray
async {
let! _ = await <| dataWriter.StoreAsync()
let! _ = await <| outputStream.FlushAsync()
let! decoder = await <| Windows.Graphics.Imaging.BitmapDecoder.CreateAsync randomAccessStream
let! softwareBitmap = await <| decoder.GetSoftwareBitmapAsync(Windows.Graphics.Imaging.BitmapPixelFormat.Bgra8, Windows.Graphics.Imaging.BitmapAlphaMode.Premultiplied)
return softwareBitmap
}
|> Async.RunSynchronously
else
null
let startOCR bitmap =
let ocrEngine = Windows.Media.Ocr.OcrEngine.TryCreateFromUserProfileLanguages()
if bitmap <> null then
async {
let! ocrResult = await <| ocrEngine.RecognizeAsync bitmap
return ocrResult.Text
}
|> Async.RunSynchronously
else
""
let createIcon () =
let toolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(Text = "&終了")
toolStripMenuItem.Click.Add(fun e -> System.Windows.Forms.Application.Exit())
let contextMenuStrip = new System.Windows.Forms.ContextMenuStrip()
contextMenuStrip.Items.Add toolStripMenuItem |> ignore
new System.Windows.Forms.NotifyIcon(
Icon = new System.Drawing.Icon @".\icon.ico",
Visible = true,
Text = "TestApp",
ContextMenuStrip = contextMenuStrip
)
[<System.Runtime.InteropServices.DllImport("user32.dll")>]
extern int RegisterHotKey(IntPtr HWnd, int ID, int MOD_KEY, int KEY)
[<System.Runtime.InteropServices.DllImport("user32.dll")>]
extern int UnregisterHotKey(IntPtr HWnd, int ID)
type HotkeyForm () as this =
inherit System.Windows.Forms.Form()
let icon = createIcon()
do
RegisterHotKey(this.Handle, 0x0000, 0x0001 ||| 0x0008, int System.Windows.Forms.Keys.C) |> ignore
System.Windows.Forms.Application.ApplicationExit.Add(fun e ->
UnregisterHotKey(this.Handle, 0x0000) |> ignore
)
override this.WndProc m =
base.WndProc(&m)
if m.Msg = 0x0312 && int m.WParam = 0x0000 then
startScreenclip()
System.Windows.Clipboard.SetText(startOCR <| getClipboardBitmap())
interface IDisposable with
member this.Dispose() =
icon.Dispose()
[<STAThread>]
do
use form = new HotkeyForm()
System.Windows.Forms.Application.Run()
解説
切り取り&スケッチ
「切り取り&スケッチ」のクリップボードに選択領域の画像を保存する機能を利用する。
let startScreenclip () =
// コマンドプロンプト
let cmd = new System.Diagnostics.ProcessStartInfo "cmd"
// 「ms-screenclip:」新規切り取りを開始する URI スキーム
cmd.Arguments <- @"/c start ms-screenclip:"
cmd.UseShellExecute <- true
cmd.WindowStyle <- System.Diagnostics.ProcessWindowStyle.Hidden
// 「切り取り&スケッチ」の起動
let cmdProcess = System.Diagnostics.Process.Start cmd
cmdProcess.WaitForExit()
// 「切り取り&スケッチ」の終了待機
let processArray = System.Diagnostics.Process.GetProcessesByName "ScreenClippingHost"
processArray.[0].WaitForExit()
クリップボードから画像を取得する
let getClipboardBitmap () =
// WPF の機能を利用
let bitmapSource = System.Windows.Clipboard.GetImage()
if bitmapSource <> null then
// 以下「Windows.Media.Ocr」が認識できる SoftwareBitmap に変換する処理
let bitmapFrame = System.Windows.Media.Imaging.BitmapFrame.Create bitmapSource
let encoder = new System.Windows.Media.Imaging.BmpBitmapEncoder()
encoder.Frames.Add bitmapFrame
use memoryStream = new System.IO.MemoryStream()
encoder.Save memoryStream
let byteArray = memoryStream.ToArray()
use randomAccessStream = new Windows.Storage.Streams.InMemoryRandomAccessStream()
use outputStream = randomAccessStream.GetOutputStreamAt(uint64 0)
use dataWriter = new Windows.Storage.Streams.DataWriter(outputStream)
dataWriter.WriteBytes byteArray
async {
let! _ = await <| dataWriter.StoreAsync()
let! _ = await <| outputStream.FlushAsync()
let! decoder = await <| Windows.Graphics.Imaging.BitmapDecoder.CreateAsync randomAccessStream
let! softwareBitmap = await <| decoder.GetSoftwareBitmapAsync(Windows.Graphics.Imaging.BitmapPixelFormat.Bgra8, Windows.Graphics.Imaging.BitmapAlphaMode.Premultiplied)
return softwareBitmap
}
|> Async.RunSynchronously
else
null
Windows.Media.Ocr
グローバルホットキー
更新履歴
日付 | 内容 |
---|---|
2021/04/26 | 「切り取り&スケッチ」に関する内容を追加。 |
Discussion