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:
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user