feat(diagnostic): 完成阶段 0C 视觉与 IME 坐标探测
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user