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>
102 lines
4.3 KiB
PowerShell
102 lines
4.3 KiB
PowerShell
<#
|
|
Рисует эталонный кадр с англоязычным интерфейсом: мелкое меню, заголовки, абзац,
|
|
кнопки, светлая и тёмная зоны. Нужен, чтобы прогонять конвейер на предсказуемом входе.
|
|
#>
|
|
param([string]$Out = "$PSScriptRoot\..\dist\sample.png")
|
|
|
|
Add-Type -AssemblyName System.Drawing
|
|
|
|
$W, $H = 1600, 900
|
|
$bmp = New-Object Drawing.Bitmap($W, $H, [Drawing.Imaging.PixelFormat]::Format32bppArgb)
|
|
$g = [Drawing.Graphics]::FromImage($bmp)
|
|
$g.SmoothingMode = 'AntiAlias'
|
|
$g.TextRenderingHint = 'ClearTypeGridFit'
|
|
|
|
function Brush($r, $gg, $b) { New-Object Drawing.SolidBrush ([Drawing.Color]::FromArgb($r, $gg, $b)) }
|
|
function Fnt($size, $style = 'Regular') { New-Object Drawing.Font('Segoe UI', $size, [Drawing.FontStyle]::$style, [Drawing.GraphicsUnit]::Pixel) }
|
|
function Text($s, $font, $brush, $x, $y) { $g.DrawString($s, $font, $brush, [single]$x, [single]$y) }
|
|
|
|
# фон приложения
|
|
$g.FillRectangle((Brush 250 250 252), 0, 0, $W, $H)
|
|
# сайдбар
|
|
$g.FillRectangle((Brush 32 34 42), 0, 0, 240, $H)
|
|
# верхняя панель
|
|
$g.FillRectangle((Brush 255 255 255), 240, 0, $W - 240, 56)
|
|
$g.DrawLine((New-Object Drawing.Pen(([Drawing.Color]::FromArgb(226, 228, 233)))), 240, 56, $W, 56)
|
|
|
|
$white = Brush 236 238 243
|
|
$grey = Brush 150 155 168
|
|
$dark = Brush 28 30 36
|
|
$body = Brush 70 74 84
|
|
$blue = Brush 46 108 220
|
|
|
|
# --- сайдбар: мелкий текст на тёмном ---
|
|
Text 'Workspace' (Fnt 12) $grey 20 18
|
|
Text 'Dashboard' (Fnt 14) $white 20 46
|
|
Text 'Recent files' (Fnt 14) $white 20 76
|
|
Text 'Shared with me' (Fnt 14) $white 20 106
|
|
Text 'Notifications' (Fnt 14) $white 20 136
|
|
Text 'Trash' (Fnt 14) $white 20 166
|
|
Text 'Storage almost full' (Fnt 12) $grey 20 210
|
|
Text 'Upgrade your plan to continue' (Fnt 12) $grey 20 230
|
|
|
|
# --- верхняя панель ---
|
|
Text 'Project settings' (Fnt 15 'Bold') $dark 264 18
|
|
Text 'Save changes' (Fnt 13) $blue 1360 20
|
|
Text 'Cancel' (Fnt 13) $body 1480 20
|
|
|
|
# --- заголовок и абзац ---
|
|
Text 'Network configuration' (Fnt 30 'Bold') $dark 264 100
|
|
Text 'Choose how this device connects to the internet.' (Fnt 16) $body 264 148
|
|
|
|
$para = @(
|
|
'When automatic configuration is enabled, the application selects the fastest available',
|
|
'connection and switches between them without interrupting downloads. Manual mode gives',
|
|
'you full control over the proxy address, port and authentication method, but requires',
|
|
'the settings to be updated whenever the server changes.'
|
|
)
|
|
$y = 200
|
|
foreach ($line in $para) { Text $line (Fnt 15) $body 264 $y; $y += 26 }
|
|
|
|
# --- карточка со списком ---
|
|
$g.FillRectangle((Brush 255 255 255), 264, 330, 700, 250)
|
|
$g.DrawRectangle((New-Object Drawing.Pen(([Drawing.Color]::FromArgb(224, 226, 232)))), 264, 330, 700, 250)
|
|
Text 'Connection status' (Fnt 17 'Bold') $dark 288 350
|
|
Text 'Interface' (Fnt 13) $grey 288 392
|
|
Text 'Wireless adapter' (Fnt 14) $dark 288 412
|
|
Text 'IP address' (Fnt 13) $grey 288 448
|
|
Text 'Assigned automatically' (Fnt 14) $dark 288 468
|
|
Text 'Last error' (Fnt 13) $grey 288 504
|
|
Text 'The remote server did not respond in time' (Fnt 14) $dark 288 524
|
|
|
|
# --- кнопки ---
|
|
$g.FillRectangle((Brush 46 108 220), 264, 620, 170, 40)
|
|
Text 'Apply and restart' (Fnt 14) (Brush 255 255 255) 282 631
|
|
$g.FillRectangle((Brush 238 240 244), 450, 620, 130, 40)
|
|
Text 'Restore defaults' (Fnt 14) $body 464 631
|
|
|
|
# --- тёмная плашка с предупреждением ---
|
|
$g.FillRectangle((Brush 58 30 30), 264, 700, 700, 90)
|
|
Text 'Warning' (Fnt 15 'Bold') (Brush 255 180 170) 288 716
|
|
Text 'Changing these values may disconnect active sessions.' (Fnt 14) (Brush 240 200 195) 288 742
|
|
Text 'Make sure no downloads are running before you continue.' (Fnt 14) (Brush 240 200 195) 288 764
|
|
|
|
# --- правая колонка, мелкий текст ---
|
|
Text 'Activity' (Fnt 15 'Bold') $dark 1020 100
|
|
$log = @(
|
|
'Connection established',
|
|
'Certificate verified',
|
|
'Profile synchronised',
|
|
'Background sync paused',
|
|
'Waiting for network'
|
|
)
|
|
$y = 140
|
|
foreach ($line in $log) { Text $line (Fnt 12) $body 1020 $y; $y += 22 }
|
|
|
|
$g.Dispose()
|
|
$dir = Split-Path $Out -Parent
|
|
New-Item -ItemType Directory -Force -Path $dir | Out-Null
|
|
$bmp.Save($Out, [Drawing.Imaging.ImageFormat]::Png)
|
|
$bmp.Dispose()
|
|
Write-Host "Эталонный кадр: $Out"
|