#include "VisualProbeCommon.h" #include #include #include #include #include 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& 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& 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 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(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(gdiText.size()), &gdiLogicalExtent) != FALSE; const bool gdiTextSuccess = TextOutW(hdc, 500, 250, gdiText.data(), static_cast(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 anchorPoints{ POINT{ 0, 0 }, POINT{ 100, 100 }, POINT{ 500, 250 }, POINT{ CanvasWidth - 1, CanvasHeight - 1 } }; std::vector 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; } }