Skip to content

Instantly share code, notes, and snippets.

@adenkiewicz
Created September 23, 2019 13:57
Show Gist options
  • Save adenkiewicz/cf82db944d02c1f3c3bfdc50dcd6cc91 to your computer and use it in GitHub Desktop.
Save adenkiewicz/cf82db944d02c1f3c3bfdc50dcd6cc91 to your computer and use it in GitHub Desktop.
Wrapper for Microsoft OCR and image-creation methods.
# 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