271 lines
9.7 KiB
C++
271 lines
9.7 KiB
C++
#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;
|
|
}
|
|
}
|