在公司打報告,希望用 OCR 判讀螢幕數字並自動抄錄以減少作業時間。這功能需要每個螢幕的長寬大小(pixel)才能讓程式知道 OCR 該看螢幕截圖的哪個位置。
原本是用 Autohotkey 的 SysGet Monitor 解決。但 SysGet 傳回的資料是錯的,也沒體力除錯了。為了省工不想寫額外程式,希望能用簡單的腳本解決。後來發現 powershell 可以做這件事情
Get-WmiObject -Class Win32_VideoController | Out-File screen.out
輸出結果導向到檔案儲存,然後回 Autohotkey 從檔案中用 regex 從 VideoModeDescription 讀出解析度數值,然後做些加減乘除就有各螢幕大小和座標了。
值得一提的是其他的失敗方案:
Add-Type -AssemblyName System.Windows.Forms
System.Windows.Forms.Screen]::AllScreens
這個 powershell 方法能得到全螢幕解析度,但會受到 scaling factor 干擾,得到的 width、height 必須乘以該螢幕的對應 scaling factor 才能還原為真實的螢幕解析度,但找了一陣子沒發現快速得到 scaling factor 的方法,因此就放棄此法。
[System.Windows.Forms.SystemInformation]::PrimaryMonitorSize
這個方法則是只能得到主螢幕解析度,看不到第二第三顆的。
年紀真的大了,花了一個半小時研究城市又開始眩暈 QQ
--------------------------------------------
$pinvokeCode = @"
using System;
using System.Runtime.InteropServices;
using System.Collections.Generic;
namespace Resolution
{
[StructLayout(LayoutKind.Sequential)]
public struct DEVMODE1
{
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
public string dmDeviceName;
public short dmSpecVersion;
public short dmDriverVersion;
public short dmSize;
public short dmDriverExtra;
public int dmFields;
public short dmOrientation;
public short dmPaperSize;
public short dmPaperLength;
public short dmPaperWidth;
public short dmScale;
public short dmCopies;
public short dmDefaultSource;
public short dmPrintQuality;
public short dmColor;
public short dmDuplex;
public short dmYResolution;
public short dmTTOption;
public short dmCollate;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
public string dmFormName;
public short dmLogPixels;
public short dmBitsPerPel;
public int dmPelsWidth;
public int dmPelsHeight;
public int dmDisplayFlags;
public int dmDisplayFrequency;
public int dmICMMethod;
public int dmICMIntent;
public int dmMediaType;
public int dmDitherType;
public int dmReserved1;
public int dmReserved2;
public int dmPanningWidth;
public int dmPanningHeight;
};
[Flags()]
public enum DisplayDeviceStateFlags : int
{
/// <summary>The device is part of the desktop.</summary>
AttachedToDesktop = 0x1,
MultiDriver = 0x2,
/// <summary>The device is part of the desktop.</summary>
PrimaryDevice = 0x4,
/// <summary>Represents a pseudo device used to mirror application drawing for remoting or other purposes.</summary>
MirroringDriver = 0x8,
/// <summary>The device is VGA compatible.</summary>
VGACompatible = 0x10,
/// <summary>The device is removable; it cannot be the primary display.</summary>
Removable = 0x20,
/// <summary>The device has more display modes than its output devices support.</summary>
ModesPruned = 0x8000000,
Remote = 0x4000000,
Disconnect = 0x2000000
}
[StructLayout(LayoutKind.Sequential, CharSet=CharSet.Ansi)]
public struct DISPLAY_DEVICE
{
[MarshalAs(UnmanagedType.U4)]
public int cb;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst=32)]
public string DeviceName;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst=128)]
public string DeviceString;
[MarshalAs(UnmanagedType.U4)]
public DisplayDeviceStateFlags StateFlags;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst=128)]
public string DeviceID;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst=128)]
public string DeviceKey;
}
class User_32
{
[DllImport("user32.dll")]
public static extern int EnumDisplaySettings(string deviceName, int modeNum, ref DEVMODE1 devMode);
[DllImport("user32.dll")]
public static extern int ChangeDisplaySettings(ref DEVMODE1 devMode, int flags);
[DllImport("user32.dll")]
public static extern bool EnumDisplayDevices(string lpDevice, uint iDevNum, ref DISPLAY_DEVICE lpDisplayDevice, uint dwFlags);
public const int ENUM_CURRENT_SETTINGS = -1;
public const int CDS_UPDATEREGISTRY = 0x01;
public const int CDS_TEST = 0x02;
public const int DISP_CHANGE_SUCCESSFUL = 0;
public const int DISP_CHANGE_RESTART = 1;
public const int DISP_CHANGE_FAILED = -1;
}
public class Displays
{
public static IList<string> GetDisplayNames()
{
var returnVals = new List<string>();
for(var x=0U; x<1024; ++x)
{
DISPLAY_DEVICE outVar = new DISPLAY_DEVICE();
outVar.cb = (short)Marshal.SizeOf(outVar);
if(User_32.EnumDisplayDevices(null, x, ref outVar, 1U))
{
returnVals.Add(outVar.DeviceName);
}
}
return returnVals;
}
public static string GetCurrentResolution(string deviceName)
{
string returnValue = null;
DEVMODE1 dm = GetDevMode1();
if (0 != User_32.EnumDisplaySettings(deviceName, User_32.ENUM_CURRENT_SETTINGS, ref dm))
{
returnValue = dm.dmPelsWidth + "," + dm.dmPelsHeight;
}
return returnValue;
}
public static IList<string> GetResolutions()
{
var displays = GetDisplayNames();
var returnValue = new List<string>();
foreach(var display in displays)
{
returnValue.Add(GetCurrentResolution(display));
}
return returnValue;
}
private static DEVMODE1 GetDevMode1()
{
DEVMODE1 dm = new DEVMODE1();
dm.dmDeviceName = new String(new char[32]);
dm.dmFormName = new String(new char[32]);
dm.dmSize = (short)Marshal.SizeOf(dm);
return dm;
}
}
}
"@
Add-Type $pinvokeCode
[Resolution.Displays]::GetResolutions();
方法出處
結果本以為能用的方法失敗,最後換了這個方法,在 Autohotkey 中呼叫 powershell.exe 執行這段腳本,用 Out-File 導出成文字檔用 regex parse。
話說 powershell 可以直接執行 C# 代碼耶,作為「腳本語言」這會不會太強了點?XDDDDD
全文連結