feat(diagnostic): 完成阶段 0C 视觉与 IME 坐标探测

This commit is contained in:
Codex
2026-10-06 01:59:41 +08:00
parent 225bc35219
commit 43cf4ac5f7
10 changed files with 2624 additions and 0 deletions
+270
View File
@@ -0,0 +1,270 @@
#include "VisualProbeCommon.h"
#include <algorithm>
#include <array>
#include <cstdlib>
#include <iostream>
#include <sstream>
namespace VisualProbe
{
namespace
{
constexpr COLORREF Background = RGB(238, 241, 246);
constexpr COLORREF EasyXColor = RGB(220, 45, 70);
constexpr COLORREF GdiColor = RGB(20, 150, 70);
struct RoundTripResult
{
POINT client{};
POINT screen{};
POINT clientRoundTrip{};
int errorX = 0;
int errorY = 0;
bool converted = false;
};
std::string normalizedRelative(const std::filesystem::path& path)
{
std::string value = path.generic_string();
std::replace(value.begin(), value.end(), '\\', '/');
return value;
}
bool addSurfaceEvidence(
const Options& options,
HDC hdc,
const std::string& fileName,
const std::string& purpose,
std::vector<EvidenceFile>& evidence)
{
const auto relative = std::filesystem::path("evidence") / fileName;
const bool saved = saveHdcImage(hdc, CanvasWidth, CanvasHeight,
options.outputDirectory / relative);
if (saved) evidence.push_back({ normalizedRelative(relative), purpose, "surface_bitblt" });
return saved;
}
bool addClientEvidence(
const Options& options,
HWND hwnd,
const std::string& fileName,
const std::string& purpose,
std::vector<EvidenceFile>& evidence)
{
const auto relative = std::filesystem::path("evidence") / fileName;
Sleep(30);
std::string backend;
const bool saved = captureClientImage(hwnd, options.outputDirectory / relative, &backend);
if (saved) evidence.push_back({ normalizedRelative(relative), purpose, backend });
return saved;
}
RoundTripResult roundTrip(HWND hwnd, POINT client)
{
RoundTripResult result;
result.client = client;
result.screen = client;
if (!ClientToScreen(hwnd, &result.screen)) return result;
result.clientRoundTrip = result.screen;
if (!ScreenToClient(hwnd, &result.clientRoundTrip)) return result;
result.converted = true;
result.errorX = result.clientRoundTrip.x - result.client.x;
result.errorY = result.clientRoundTrip.y - result.client.y;
return result;
}
int boundsWidth(const PixelBounds& bounds)
{
return bounds.present ? bounds.right - bounds.left + 1 : 0;
}
int boundsHeight(const PixelBounds& bounds)
{
return bounds.present ? bounds.bottom - bounds.top + 1 : 0;
}
void writeRoundTrip(std::ostream& output, const RoundTripResult& result)
{
output << "{\"converted\":";
writeBoolean(output, result.converted);
output << ",\"client\":";
writePointJson(output, result.client);
output << ",\"screen\":";
writePointJson(output, result.screen);
output << ",\"clientRoundTrip\":";
writePointJson(output, result.clientRoundTrip);
output << ",\"error\":{\"x\":" << result.errorX
<< ",\"y\":" << result.errorY << "}}";
}
}
int runDpiCoordinateProbe(const Options& options, const DpiConfiguration& dpiConfiguration)
{
HWND hwnd = initgraph(CanvasWidth, CanvasHeight, EX_SHOWCONSOLE);
if (!hwnd)
{
std::cerr << "initgraph failed\n";
return 3;
}
SetWindowTextA(hwnd, "StellarX 0C DPI Coordinate Probe");
ShowWindow(hwnd, SW_SHOWNORMAL);
SetForegroundWindow(hwnd);
Sleep(80);
const EnvironmentSnapshot environment = captureEnvironment(hwnd);
SetWorkingImage(nullptr);
HDC hdc = GetImageHDC(nullptr);
std::vector<EvidenceFile> evidence;
bool evidenceComplete = true;
BeginBatchDraw();
setbkcolor(Background);
cleardevice();
setlinestyle(PS_SOLID, 1);
setlinecolor(EasyXColor);
rectangle(100, 100, 240, 180);
line(100, 140, 240, 140);
line(170, 100, 170, 180);
settextcolor(EasyXColor);
setbkmode(TRANSPARENT);
settextstyle(28, 0, "Microsoft YaHei UI");
const char* easyXText = "StellarX DPI";
outtextxy(100, 250, easyXText);
const SIZE easyXLogicalExtent{ textwidth(easyXText), textheight(easyXText) };
const GdiState stateBefore = captureGdiState(hdc);
const int saved = SaveDC(hdc);
HPEN pen = CreatePen(PS_SOLID, 1, GdiColor);
HGDIOBJ previousPen = pen ? SelectObject(hdc, pen) : nullptr;
HBRUSH previousBrush = static_cast<HBRUSH>(SelectObject(hdc, GetStockObject(NULL_BRUSH)));
SetBkMode(hdc, TRANSPARENT);
SetTextColor(hdc, GdiColor);
Rectangle(hdc, 500, 100, 641, 181);
MoveToEx(hdc, 500, 140, nullptr);
LineTo(hdc, 641, 140);
MoveToEx(hdc, 570, 100, nullptr);
LineTo(hdc, 570, 181);
const std::wstring gdiText = L"StellarX DPI";
SIZE gdiLogicalExtent{};
const bool gdiExtentSuccess = GetTextExtentPoint32W(hdc, gdiText.data(),
static_cast<int>(gdiText.size()), &gdiLogicalExtent) != FALSE;
const bool gdiTextSuccess = TextOutW(hdc, 500, 250, gdiText.data(),
static_cast<int>(gdiText.size())) != FALSE;
if (previousBrush) SelectObject(hdc, previousBrush);
if (previousPen) SelectObject(hdc, previousPen);
if (saved) RestoreDC(hdc, saved);
if (pen) DeleteObject(pen);
const GdiState stateAfter = captureGdiState(hdc);
const bool gdiStateRestored = saved != 0 && sameGdiState(stateBefore, stateAfter);
const SurfaceFingerprint afterDraw = captureSurfaceFingerprint(hdc, CanvasWidth, CanvasHeight);
const RECT easyXMarkerRegion{ 90, 90, 250, 190 };
const RECT gdiMarkerRegion{ 490, 90, 650, 190 };
const RECT easyXTextRegion{ 90, 240, 400, 310 };
const RECT gdiTextRegion{ 490, 240, 800, 310 };
const PixelBounds easyXMarkerSurface = findColorBounds(hdc, easyXMarkerRegion, EasyXColor);
const PixelBounds gdiMarkerSurface = findColorBounds(hdc, gdiMarkerRegion, GdiColor);
const PixelBounds easyXTextSurface = findChangedBounds(hdc, easyXTextRegion, Background);
const PixelBounds gdiTextSurface = findChangedBounds(hdc, gdiTextRegion, Background);
evidenceComplete &= addSurfaceEvidence(options, hdc, "dpi_after_draw_surface.png",
"DPI surface after EasyX and raw GDI drawing", evidence);
evidenceComplete &= addClientEvidence(options, hwnd, "dpi_before_flush_client.png",
"DPI client before FlushBatchDraw", evidence);
FlushBatchDraw();
evidenceComplete &= addClientEvidence(options, hwnd, "dpi_after_flush_client.png",
"DPI client after FlushBatchDraw", evidence);
EndBatchDraw();
evidenceComplete &= addClientEvidence(options, hwnd, "dpi_after_end_client.png",
"DPI client after EndBatchDraw", evidence);
PixelBounds easyXMarkerClient;
PixelBounds gdiMarkerClient;
HDC clientDc = GetDC(hwnd);
if (clientDc)
{
easyXMarkerClient = findColorBounds(clientDc, easyXMarkerRegion, EasyXColor);
gdiMarkerClient = findColorBounds(clientDc, gdiMarkerRegion, GdiColor);
ReleaseDC(hwnd, clientDc);
}
const std::array<POINT, 4> anchorPoints{
POINT{ 0, 0 }, POINT{ 100, 100 }, POINT{ 500, 250 },
POINT{ CanvasWidth - 1, CanvasHeight - 1 }
};
std::vector<RoundTripResult> roundTrips;
bool roundTripsExact = true;
for (const POINT point : anchorPoints)
{
roundTrips.push_back(roundTrip(hwnd, point));
const auto& result = roundTrips.back();
roundTripsExact = roundTripsExact && result.converted &&
result.errorX == 0 && result.errorY == 0;
}
const bool surfaceMarkerSizesAgree = easyXMarkerSurface.present && gdiMarkerSurface.present &&
std::abs(boundsWidth(easyXMarkerSurface) - boundsWidth(gdiMarkerSurface)) <= 2 &&
std::abs(boundsHeight(easyXMarkerSurface) - boundsHeight(gdiMarkerSurface)) <= 2;
const bool clientMatchesSurface = easyXMarkerClient.present && gdiMarkerClient.present &&
std::abs(boundsWidth(easyXMarkerClient) - boundsWidth(easyXMarkerSurface)) <= 1 &&
std::abs(boundsHeight(easyXMarkerClient) - boundsHeight(easyXMarkerSurface)) <= 1 &&
std::abs(boundsWidth(gdiMarkerClient) - boundsWidth(gdiMarkerSurface)) <= 1 &&
std::abs(boundsHeight(gdiMarkerClient) - boundsHeight(gdiMarkerSurface)) <= 1;
const bool implicitScalingObserved = !surfaceMarkerSizesAgree || !clientMatchesSurface;
const bool completed = evidenceComplete && gdiStateRestored && gdiExtentSuccess &&
gdiTextSuccess && easyXMarkerSurface.present && gdiMarkerSurface.present &&
easyXTextSurface.present && gdiTextSurface.present && roundTripsExact;
std::ostringstream result;
result << "{\"batchState\":\"inside\",\"surfaceFingerprintAfterDraw\":";
writeSurfaceFingerprintJson(result, afterDraw);
result << ",\"gdiStateRestored\":";
writeBoolean(result, gdiStateRestored);
result << ",\"easyXLogicalExtent\":";
writeSizeJson(result, easyXLogicalExtent);
result << ",\"gdiLogicalExtent\":";
writeSizeJson(result, gdiLogicalExtent);
result << ",\"gdiExtentSuccess\":";
writeBoolean(result, gdiExtentSuccess);
result << ",\"gdiTextSuccess\":";
writeBoolean(result, gdiTextSuccess);
result << ",\"surfaceBounds\":{\"easyXMarker\":";
writeBoundsJson(result, easyXMarkerSurface);
result << ",\"gdiMarker\":";
writeBoundsJson(result, gdiMarkerSurface);
result << ",\"easyXText\":";
writeBoundsJson(result, easyXTextSurface);
result << ",\"gdiText\":";
writeBoundsJson(result, gdiTextSurface);
result << "},\"clientBounds\":{\"easyXMarker\":";
writeBoundsJson(result, easyXMarkerClient);
result << ",\"gdiMarker\":";
writeBoundsJson(result, gdiMarkerClient);
result << "},\"surfaceMarkerSizesAgree\":";
writeBoolean(result, surfaceMarkerSizesAgree);
result << ",\"clientMatchesSurface\":";
writeBoolean(result, clientMatchesSurface);
result << ",\"implicitScalingObserved\":";
writeBoolean(result, implicitScalingObserved);
result << ",\"roundTripsExact\":";
writeBoolean(result, roundTripsExact);
result << ",\"coordinateRoundTrips\":[";
for (std::size_t index = 0; index < roundTrips.size(); ++index)
{
if (index) result << ',';
writeRoundTrip(result, roundTrips[index]);
}
result << "]}";
SetWorkingImage(nullptr);
closegraph();
std::string error;
if (!writeRawResult(options, dpiConfiguration, environment, completed,
evidence, result.str(), error))
{
std::cerr << error << '\n';
return 4;
}
return completed ? 0 : 5;
}
}