7c6b182402
build / build (push) Failing after 10s
Программа накладывает русский перевод прямо на английские надписи — как переводчик по фото, только для монитора. Работает с играми, интерфейсами и документами: распознаёт кадр, собирает текст в связные блоки, переводит и рисует плашки под цвет фона. Распознавание — встроенный Windows.Media.Ocr, офлайн. Перевод — цепочка движков с автопереключением (google, lingva, mymemory), потому что до разных сервисов из разных сетей достучаться получается по-разному. Собирается без .NET SDK: Roslyn из Build Tools плюс сборки Framework 4.8, которые уже есть в системе. На выходе один exe без установщика. Главная сложность оказалась не в переводе, а в том, чтобы понять, где на экране связный текст, а где отдельные надписи. Классические алгоритмы сегментации решают это по межстрочному зазору, но в плотном интерфейсе он одинаков у строк абзаца и у соседних контролов. Поэтому решение принимает ансамбль признаков: фон, граница между строками, заполненность строки, кегль. Надписи на чужом языке отсеиваются тремя уровнями — форма слова, вторая модель распознавания и частоты буквенных пар. Чтобы правки эвристик не оценивались на глаз, в tools лежат эталонные кадры и RenderTest: прогон конвейера без окна с подсчётом попаданий. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
119 lines
4.6 KiB
C#
119 lines
4.6 KiB
C#
using System;
|
|
using System.Drawing;
|
|
using System.Runtime.InteropServices;
|
|
|
|
namespace ScreenTranslator
|
|
{
|
|
/// <summary>P/Invoke. Всё, что здесь возвращает координаты, отдаёт их в ФИЗИЧЕСКИХ пикселях
|
|
/// (процесс помечен PerMonitorV2 в app.manifest), поэтому они напрямую совпадают с пикселями
|
|
/// скриншота и с координатами боксов OCR.</summary>
|
|
internal static class Native
|
|
{
|
|
// ---- горячие клавиши ----
|
|
public const int WM_HOTKEY = 0x0312;
|
|
public const uint MOD_ALT = 0x0001, MOD_CONTROL = 0x0002, MOD_SHIFT = 0x0004, MOD_WIN = 0x0008;
|
|
public const uint MOD_NOREPEAT = 0x4000;
|
|
|
|
[DllImport("user32.dll", SetLastError = true)]
|
|
public static extern bool RegisterHotKey(IntPtr hWnd, int id, uint fsModifiers, uint vk);
|
|
|
|
[DllImport("user32.dll", SetLastError = true)]
|
|
public static extern bool UnregisterHotKey(IntPtr hWnd, int id);
|
|
|
|
// ---- мониторы ----
|
|
[StructLayout(LayoutKind.Sequential)]
|
|
public struct POINT { public int X, Y; }
|
|
|
|
[StructLayout(LayoutKind.Sequential)]
|
|
public struct RECT
|
|
{
|
|
public int Left, Top, Right, Bottom;
|
|
public Rectangle ToRectangle() { return Rectangle.FromLTRB(Left, Top, Right, Bottom); }
|
|
}
|
|
|
|
[StructLayout(LayoutKind.Sequential)]
|
|
public struct MONITORINFO
|
|
{
|
|
public int cbSize;
|
|
public RECT rcMonitor;
|
|
public RECT rcWork;
|
|
public uint dwFlags;
|
|
}
|
|
|
|
public const uint MONITOR_DEFAULTTONEAREST = 2;
|
|
|
|
[DllImport("user32.dll")]
|
|
public static extern bool GetCursorPos(out POINT lpPoint);
|
|
|
|
[DllImport("user32.dll")]
|
|
public static extern IntPtr MonitorFromPoint(POINT pt, uint dwFlags);
|
|
|
|
[DllImport("user32.dll", CharSet = CharSet.Auto)]
|
|
public static extern bool GetMonitorInfo(IntPtr hMonitor, ref MONITORINFO lpmi);
|
|
|
|
/// <summary>Границы монитора под курсором, физические пиксели.</summary>
|
|
public static Rectangle MonitorUnderCursor()
|
|
{
|
|
POINT p;
|
|
if (!GetCursorPos(out p)) return VirtualScreen();
|
|
IntPtr h = MonitorFromPoint(p, MONITOR_DEFAULTTONEAREST);
|
|
var mi = new MONITORINFO();
|
|
mi.cbSize = Marshal.SizeOf(typeof(MONITORINFO));
|
|
if (!GetMonitorInfo(h, ref mi)) return VirtualScreen();
|
|
return mi.rcMonitor.ToRectangle();
|
|
}
|
|
|
|
public const int SM_XVIRTUALSCREEN = 76, SM_YVIRTUALSCREEN = 77,
|
|
SM_CXVIRTUALSCREEN = 78, SM_CYVIRTUALSCREEN = 79;
|
|
|
|
[DllImport("user32.dll")]
|
|
public static extern int GetSystemMetrics(int nIndex);
|
|
|
|
public static Rectangle VirtualScreen()
|
|
{
|
|
return new Rectangle(
|
|
GetSystemMetrics(SM_XVIRTUALSCREEN), GetSystemMetrics(SM_YVIRTUALSCREEN),
|
|
GetSystemMetrics(SM_CXVIRTUALSCREEN), GetSystemMetrics(SM_CYVIRTUALSCREEN));
|
|
}
|
|
|
|
// ---- окна ----
|
|
public const int GWL_EXSTYLE = -20;
|
|
public const int WS_EX_TOOLWINDOW = 0x00000080;
|
|
public const int WS_EX_NOACTIVATE = 0x08000000;
|
|
public const int WS_EX_TRANSPARENT = 0x00000020;
|
|
public const int WS_EX_LAYERED = 0x00080000;
|
|
|
|
[DllImport("user32.dll", SetLastError = true)]
|
|
public static extern int GetWindowLong(IntPtr hWnd, int nIndex);
|
|
|
|
[DllImport("user32.dll", SetLastError = true)]
|
|
public static extern int SetWindowLong(IntPtr hWnd, int nIndex, int dwNewLong);
|
|
|
|
[DllImport("user32.dll")]
|
|
public static extern IntPtr GetForegroundWindow();
|
|
|
|
[DllImport("user32.dll")]
|
|
public static extern bool SetForegroundWindow(IntPtr hWnd);
|
|
|
|
public static readonly IntPtr HWND_TOPMOST = new IntPtr(-1);
|
|
public const uint SWP_NOACTIVATE = 0x0010, SWP_SHOWWINDOW = 0x0040;
|
|
|
|
[DllImport("user32.dll", SetLastError = true)]
|
|
public static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter,
|
|
int X, int Y, int cx, int cy, uint uFlags);
|
|
|
|
// ---- курсор для скриншота ----
|
|
[StructLayout(LayoutKind.Sequential)]
|
|
public struct CURSORINFO
|
|
{
|
|
public int cbSize;
|
|
public int flags;
|
|
public IntPtr hCursor;
|
|
public POINT ptScreenPos;
|
|
}
|
|
|
|
[DllImport("user32.dll")]
|
|
public static extern bool GetCursorInfo(out CURSORINFO pci);
|
|
}
|
|
}
|