Created
September 23, 2019 13:57
-
-
Save adenkiewicz/cf82db944d02c1f3c3bfdc50dcd6cc91 to your computer and use it in GitHub Desktop.
Wrapper for Microsoft OCR and image-creation methods.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Modified code from https://github.com/HumanEquivalentUnit/PowerShell-Misc | |
using namespace System.Drawing | |
using namespace System.Windows.Forms | |
using namespace Windows.Storage | |
using namespace Windows.Graphics.Imaging | |
Add-Type -AssemblyName System.Drawing | |
Add-Type -AssemblyName System.Windows.Forms | |
function Export-StringToPng | |
{ | |
[CmdletBinding()] | |
Param | |
( | |
# Param1 help description | |
[Parameter(Mandatory=$true, | |
ValueFromPipeline=$true, | |
Position=0)] | |
[string[]]$InputObject, | |
# Path where output image should be saved | |
[string]$Path, | |
# Clipboard support | |
[switch]$ToClipboard, | |
# Custom font family | |
[string]$FontFamily = "Calibri", | |
# Custom font size | |
[int]$FontSize = 50 | |
) | |
begin | |
{ | |
# can render multiple lines, so $lines exists to gather | |
# all input from the pipeline into one collection | |
[Collections.Generic.List[String]]$lines = @() | |
} | |
Process | |
{ | |
# each incoming string from the pipeline, works even | |
# if it's a multiline-string. If it's an array of string | |
# this implicitly joins them using $OFS | |
$null = $lines.Add($InputObject) | |
} | |
End | |
{ | |
# join the array of lines into a string, so the | |
# drawing routines can render the multiline string directly | |
# without us looping over them or calculating line offsets, etc. | |
[string]$lines = $lines -join "`n" | |
# placeholder 1x1 pixel bitmap, will be used to measure the line | |
# size, before re-creating it big enough for all the text | |
[Bitmap]$bmpImage = [Bitmap]::new(1, 1) | |
# Create the Font, Calibri works best for simple OCR usage | |
# hardcoded size and style, because it's easy | |
[Font]$font = [Font]::new([FontFamily]::new($FontFamily), $FontSize, [FontStyle]::Regular, [GraphicsUnit]::Pixel) | |
# Create a graphics object and measure the text's width and height, | |
# in the chosen font, with the chosen style. | |
[Graphics]$Graphics = [Graphics]::FromImage($BmpImage) | |
[int]$width = $Graphics.MeasureString($lines, $font).Width + 10 | |
[int]$height = $Graphics.MeasureString($lines, $font).Height + 10 | |
# Recreate the bmpImage big enough for the text. | |
# and recreate the Graphics context from the new bitmap | |
$BmpImage = [Bitmap]::new($width, $height) | |
$Graphics = [Graphics]::FromImage($BmpImage) | |
# Set Background color, and font drawing styles | |
# hard coded because early version, it's easy | |
$Graphics.Clear([Color]::White) | |
$Graphics.SmoothingMode = [Drawing2D.SmoothingMode]::Default | |
$Graphics.TextRenderingHint = [Text.TextRenderingHint]::AntiAlias | |
$brushColour = [SolidBrush]::new([Color]::Black) | |
# Render the text onto the image | |
$Graphics.DrawString($lines, $Font, $brushColour, 0, 0) | |
$Graphics.Flush() | |
if ($Path) | |
{ | |
# Export image to file | |
[System.IO.Directory]::SetCurrentDirectory(((Get-Location -PSProvider FileSystem).ProviderPath)) | |
$Path = [System.IO.Path]::GetFullPath($Path) | |
$bmpImage.Save($Path, [Imaging.ImageFormat]::Png) | |
} | |
if ($ToClipboard) | |
{ | |
[Windows.Forms.Clipboard]::SetImage($bmpImage) | |
} | |
if (-not $ToClipboard -and -not $Path) | |
{ | |
Write-Warning -Message "No output chosen. Use parameter -LiteralPath 'out.png' , or -ToClipboard , or both" | |
} | |
} | |
} | |
function Export-PngToString | |
{ | |
[CmdletBinding()] | |
Param | |
( | |
# Path to an image file | |
[Parameter(Mandatory=$true, | |
ValueFromPipeline=$true, | |
ValueFromPipelineByPropertyName=$true, | |
Position=0, | |
HelpMessage='Path to an image file, to run OCR on')] | |
[ValidateNotNullOrEmpty()] | |
$Path | |
) | |
Begin { | |
# Add the WinRT assembly, and load the appropriate WinRT types | |
Add-Type -AssemblyName System.Runtime.WindowsRuntime | |
$null = [Windows.Storage.StorageFile, Windows.Storage, ContentType = WindowsRuntime] | |
$null = [Windows.Media.Ocr.OcrEngine, Windows.Foundation, ContentType = WindowsRuntime] | |
$null = [Windows.Foundation.IAsyncOperation`1, Windows.Foundation, ContentType = WindowsRuntime] | |
$null = [Windows.Graphics.Imaging.SoftwareBitmap, Windows.Foundation, ContentType = WindowsRuntime] | |
$null = [Windows.Storage.Streams.RandomAccessStream, Windows.Storage.Streams, ContentType = WindowsRuntime] | |
# [Windows.Media.Ocr.OcrEngine]::AvailableRecognizerLanguages | |
$ocrEngine = [Windows.Media.Ocr.OcrEngine]::TryCreateFromUserProfileLanguages() | |
# PowerShell doesn't have built-in support for Async operations, | |
# but all the WinRT methods are Async. | |
# This function wraps a way to call those methods, and wait for their results. | |
$getAwaiterBaseMethod = [WindowsRuntimeSystemExtensions].GetMember('GetAwaiter'). | |
Where({ | |
$PSItem.GetParameters()[0].ParameterType.Name -eq 'IAsyncOperation`1' | |
}, 'First')[0] | |
Function Await { | |
param($AsyncTask, $ResultType) | |
$getAwaiterBaseMethod. | |
MakeGenericMethod($ResultType). | |
Invoke($null, @($AsyncTask)). | |
GetResult() | |
} | |
} | |
Process | |
{ | |
foreach ($p in $Path) | |
{ | |
# From MSDN, the necessary steps to load an image are: | |
# Call the OpenAsync method of the StorageFile object to get a random access stream containing the image data. | |
# Call the static method BitmapDecoder.CreateAsync to get an instance of the BitmapDecoder class for the specified stream. | |
# Call GetSoftwareBitmapAsync to get a SoftwareBitmap object containing the image. | |
# | |
# https://docs.microsoft.com/en-us/windows/uwp/audio-video-camera/imaging#save-a-softwarebitmap-to-a-file-with-bitmapencoder | |
# .Net method needs a full path, or at least might not have the same relative path root as PowerShell | |
$p = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($p) | |
$params = @{ | |
AsyncTask = [StorageFile]::GetFileFromPathAsync($p) | |
ResultType = [StorageFile] | |
} | |
$storageFile = Await @params | |
$params = @{ | |
AsyncTask = $storageFile.OpenAsync([FileAccessMode]::Read) | |
ResultType = [Streams.IRandomAccessStream] | |
} | |
$fileStream = Await @params | |
$params = @{ | |
AsyncTask = [BitmapDecoder]::CreateAsync($fileStream) | |
ResultType = [BitmapDecoder] | |
} | |
$bitmapDecoder = Await @params | |
$params = @{ | |
AsyncTask = $bitmapDecoder.GetSoftwareBitmapAsync() | |
ResultType = [SoftwareBitmap] | |
} | |
$softwareBitmap = Await @params | |
# Run the OCR | |
Await $ocrEngine.RecognizeAsync($softwareBitmap) ([Windows.Media.Ocr.OcrResult]) | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment