fix: VST GUI embed chup frame trang (Ozone 11 GPU) - luon chup ca BitBlt va PrintWindow PW_RENDERFULLCONTENT, chon frame chi tiet hon; fix loi JPEG chi ghi hang dau tien

This commit is contained in:
2026-08-18 09:56:53 +07:00
parent 4eadf897a4
commit 308857c4a7
+33 -20
View File
@@ -128,9 +128,11 @@ void sendResponse(SOCKET c, int code, const char* contentType,
}
// --- frame capture -----------------------------------------------------------
// BitBlt the window client rect; if the result is all-black (plugin uses a
// layered/DirectX surface GDI cannot see), fall back to PrintWindow with
// PW_RENDERFULLCONTENT. Returns JPEG bytes in `out`.
// Capture the window client rect twice — BitBlt (GDI view) and PrintWindow
// with PW_RENDERFULLCONTENT (DWM composition, includes DirectX/GPU surfaces
// that GDI cannot see) — and keep the frame with more visual detail. Ozone 11
// and other GPU-rendered plugins paint WHITE into the GDI DC, so the old
// all-black fallback never fired and a white frame was served. Returns JPEG.
bool captureHwndToJpeg(HWND hwnd, std::vector<unsigned char>& out, int quality) {
RECT rc;
if (!GetClientRect(hwnd, &rc)) return false;
@@ -143,8 +145,8 @@ bool captureHwndToJpeg(HWND hwnd, std::vector<unsigned char>& out, int quality)
HBITMAP bmp = CreateCompatibleBitmap(win, w, h);
HGDIOBJ oldBmp = SelectObject(mem, bmp);
std::vector<unsigned char> px((size_t)w * h * 4);
auto grab = [&]() -> bool {
std::vector<unsigned char> pxB((size_t)w * h * 4), pxP((size_t)w * h * 4);
auto grabInto = [&](unsigned char* p) -> bool {
BITMAPINFO bi = {};
bi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
bi.bmiHeader.biWidth = w;
@@ -152,29 +154,37 @@ bool captureHwndToJpeg(HWND hwnd, std::vector<unsigned char>& out, int quality)
bi.bmiHeader.biPlanes = 1;
bi.bmiHeader.biBitCount = 32;
bi.bmiHeader.biCompression = BI_RGB;
return GetDIBits(mem, bmp, 0, h, px.data(), &bi, DIB_RGB_COLORS) == h;
return GetDIBits(mem, bmp, 0, h, p, &bi, DIB_RGB_COLORS) == h;
};
auto isAllBlack = [&]() -> bool {
// Sample a coarse grid of pixels; all zero => invisible to GDI.
const int step = 8;
auto detailScore = [&](const unsigned char* p) -> long long {
// Count sampled pixels that are neither near-black nor near-white.
// GPU-rendered plugins (Ozone 11, ...) are invisible to GDI: BitBlt
// yields a white/black-strip frame with ~2 distinct values, while the
// real UI has many mid-grey levels. Pick the frame with more detail.
long long d = 0;
const int step = 4;
for (int y = 0; y < h; y += step)
for (int x = 0; x < w; x += step) {
const size_t i = ((size_t)y * w + x) * 4;
if (px[i] || px[i + 1] || px[i + 2]) return false;
const unsigned lum = ((unsigned)p[i] * 77u + (unsigned)p[i + 1] * 150u + (unsigned)p[i + 2] * 29u) >> 8;
if (lum >= 28 && lum <= 232) ++d; // exclude near-black/white
}
return true;
return d;
};
bool ok = BitBlt(mem, 0, 0, w, h, win, 0, 0, SRCCOPY | CAPTUREBLT) != 0 && grab();
if (ok && isAllBlack()) {
ok = PrintWindow(hwnd, mem, PW_RENDERFULLCONTENT) != 0 && grab();
}
bool okB = BitBlt(mem, 0, 0, w, h, win, 0, 0, SRCCOPY | CAPTUREBLT) != 0 && grabInto(pxB.data());
// PW_RENDERFULLCONTENT asks DWM to compose the window (includes child
// windows + DirectX surfaces) — the only way to see GPU-rendered editors.
bool okP = PrintWindow(hwnd, mem, PW_RENDERFULLCONTENT) != 0 && grabInto(pxP.data());
unsigned char* px = nullptr;
if (okB && okP) px = detailScore(pxP.data()) > detailScore(pxB.data()) ? pxP.data() : pxB.data();
else if (okP) px = pxP.data();
else if (okB) px = pxB.data();
SelectObject(mem, oldBmp);
DeleteObject(bmp);
DeleteDC(mem);
ReleaseDC(hwnd, win);
if (!ok) return false;
if (!px) return false;
// BGRA (GDI) -> RGB, then JPEG.
std::vector<unsigned char> rgb((size_t)w * h * 3);
@@ -198,9 +208,12 @@ bool captureHwndToJpeg(HWND hwnd, std::vector<unsigned char>& out, int quality)
jpeg_set_defaults(&cinfo);
jpeg_set_quality(&cinfo, quality, TRUE);
jpeg_start_compress(&cinfo, TRUE);
JSAMPROW row = rgb.data();
while (cinfo.next_scanline < cinfo.image_height)
jpeg_write_scanlines(&cinfo, &row, 1);
JSAMPROW rows[1];
rows[0] = rgb.data();
while (cinfo.next_scanline < cinfo.image_height) {
jpeg_write_scanlines(&cinfo, rows, 1);
rows[0] += (size_t)w * 3;
}
jpeg_finish_compress(&cinfo);
if (memBuf && memSize > 0) out.assign(memBuf, memBuf + memSize);
std::free(memBuf);