diff --git a/server/2015Remote/ScreenSpyDlg.cpp b/server/2015Remote/ScreenSpyDlg.cpp index 99044b4..3366e9e 100644 --- a/server/2015Remote/ScreenSpyDlg.cpp +++ b/server/2015Remote/ScreenSpyDlg.cpp @@ -281,6 +281,13 @@ CScreenSpyDlg::~CScreenSpyDlg() ::DeleteDC(m_hFullMemDC); //Create匹配内存DC ::DeleteObject(m_BitmapHandle); + + if (m_hComposeDC) { + SelectObject(m_hComposeDC, m_hComposeOldBmp); + DeleteObject(m_hComposeBmp); + DeleteDC(m_hComposeDC); + m_hComposeDC = NULL; + } if (m_BitmapData_Full!=NULL) { m_BitmapData_Full = NULL; } @@ -595,6 +602,34 @@ void CScreenSpyDlg::OnLButtonDblClk(UINT nFlags, CPoint point) __super::OnLButtonDblClk(nFlags, point); } +// 计算自适应模式的等比缩放布局。 +// 纯计算,无副作用,供 OnPaint / OnSize / PrepareDrawing 共用。 +static void ComputeAdaptiveLayout( + int srcW, int srcH, int dstW, int dstH, + int& renderW, int& renderH, int& offsetX, int& offsetY, + double& wZoom, double& hZoom) +{ + if (srcW <= 0 || srcH <= 0 || dstW <= 0 || dstH <= 0) { + renderW = dstW; renderH = dstH; + offsetX = offsetY = 0; + wZoom = hZoom = 1.0; + return; + } + if ((long long)srcW * dstH > (long long)srcH * dstW) { + renderW = dstW; + renderH = (int)((long long)dstW * srcH / srcW); + } else { + renderH = dstH; + renderW = (int)((long long)dstH * srcW / srcH); + } + if (renderW < 1) renderW = 1; + if (renderH < 1) renderH = 1; + offsetX = (dstW - renderW) / 2; + offsetY = (dstH - renderH) / 2; + wZoom = (double)srcW / renderW; + hZoom = (double)srcH / renderH; +} + void CScreenSpyDlg::PrepareDrawing(const LPBITMAPINFO bmp) { if (m_hFullDC) ::ReleaseDC(m_hWnd, m_hFullDC); @@ -624,8 +659,17 @@ void CScreenSpyDlg::PrepareDrawing(const LPBITMAPINFO bmp) } GetClientRect(&m_CRect); - m_wZoom = ((double)bmp->bmiHeader.biWidth) / ((double)(m_CRect.Width())); - m_hZoom = ((double)bmp->bmiHeader.biHeight) / ((double)(m_CRect.Height())); + if (m_bAdaptiveSize) { + int renderW, renderH; + ComputeAdaptiveLayout(bmp->bmiHeader.biWidth, bmp->bmiHeader.biHeight, + m_CRect.Width(), m_CRect.Height(), + renderW, renderH, m_offsetX, m_offsetY, + m_wZoom, m_hZoom); + } else { + m_offsetX = m_offsetY = 0; + m_wZoom = m_CRect.Width() > 0 ? (double)bmp->bmiHeader.biWidth / m_CRect.Width() : 1.0; + m_hZoom = m_CRect.Height() > 0 ? (double)bmp->bmiHeader.biHeight / m_CRect.Height() : 1.0; + } } BOOL CScreenSpyDlg::OnInitDialog() @@ -1660,14 +1704,22 @@ bool CScreenSpyDlg::Decode(LPBYTE Buffer, int size) //I420 ---> ARGB. //WaitForSingleObject(m_hMutex,INFINITE); - libyuv::I420ToARGB( - m_AVFrame.data[0], m_AVFrame.linesize[0], - m_AVFrame.data[1], m_AVFrame.linesize[1], - m_AVFrame.data[2], m_AVFrame.linesize[2], - (uint8_t*)m_BitmapData_Full, - m_BitmapInfor_Full->bmiHeader.biWidth*4, - m_BitmapInfor_Full->bmiHeader.biWidth, - m_BitmapInfor_Full->bmiHeader.biHeight); + { + int W = m_BitmapInfor_Full->bmiHeader.biWidth; + int H = m_BitmapInfor_Full->bmiHeader.biHeight; + // biClrImportant==1: top-down H264 (Android MediaCodec standard output). + // 精确匹配 1,避免调色板 DIB(biClrImportant=N 表示重要色数量)被误判。 + // libyuv 负高度只翻转目标写入方向,源指针不变,data[0] 仍指向第一行。 + bool topDown = (m_BitmapInfor_Full->bmiHeader.biClrImportant == 1); + libyuv::I420ToARGB( + m_AVFrame.data[0], m_AVFrame.linesize[0], + m_AVFrame.data[1], m_AVFrame.linesize[1], + m_AVFrame.data[2], m_AVFrame.linesize[2], + (uint8_t*)m_BitmapData_Full, + W * 4, + W, + topDown ? -H : H); + } return true; } Mprintf("avcodec_receive_frame failed with error: %d\n", err); @@ -1715,8 +1767,53 @@ void CScreenSpyDlg::OnPaint() m_rcZoomSrc.Width(), m_rcZoomSrc.Height(), SRCCOPY); } else if (m_bAdaptiveSize) { - // 尺寸相同时用 BitBlt(更快),否则用 StretchBlt - if (srcW == dstW && srcH == dstH) { + // 保持宽高比居中缩放,多余区域填黑(信箱 / 柱状黑边) + // 同步更新 m_offsetX/Y 和 m_wZoom/hZoom,供坐标映射使用 + int renderW, renderH; + ComputeAdaptiveLayout(srcW, srcH, dstW, dstH, + renderW, renderH, m_offsetX, m_offsetY, + m_wZoom, m_hZoom); + + // 图像未填满窗口(含整除截断 renderH=dstH-1 导致 offset=0 的情况)时走合成路径 + if (renderW < dstW || renderH < dstH) { + // 先在内存 DC 合成完整帧再一次性上屏,避免"先黑后图"的闪烁 + // 按窗口尺寸缓存 DC/Bitmap,尺寸不变时直接复用,不重复分配 GDI 资源 + if (!m_hComposeDC || m_nComposeCX != dstW || m_nComposeCY != dstH) { + if (m_hComposeDC) { + SelectObject(m_hComposeDC, m_hComposeOldBmp); + DeleteObject(m_hComposeBmp); + DeleteDC(m_hComposeDC); + m_hComposeDC = NULL; m_hComposeBmp = NULL; m_hComposeOldBmp = NULL; + } + m_hComposeDC = CreateCompatibleDC(m_hFullDC); + if (m_hComposeDC) { + m_hComposeBmp = CreateCompatibleBitmap(m_hFullDC, dstW, dstH); + if (m_hComposeBmp) { + m_hComposeOldBmp = (HBITMAP)SelectObject(m_hComposeDC, m_hComposeBmp); + m_nComposeCX = dstW; + m_nComposeCY = dstH; + } else { + DeleteDC(m_hComposeDC); + m_hComposeDC = NULL; + } + } + } + if (m_hComposeDC) { + SetStretchBltMode(m_hComposeDC, GetFastStretchMode() ? COLORONCOLOR : HALFTONE); + if (!GetFastStretchMode()) SetBrushOrgEx(m_hComposeDC, 0, 0, NULL); + RECT rcFull = { 0, 0, dstW, dstH }; + FillRect(m_hComposeDC, &rcFull, (HBRUSH)GetStockObject(BLACK_BRUSH)); + StretchBlt(m_hComposeDC, m_offsetX, m_offsetY, renderW, renderH, + m_hFullMemDC, 0, 0, srcW, srcH, SRCCOPY); + BitBlt(m_hFullDC, 0, 0, dstW, dstH, m_hComposeDC, 0, 0, SRCCOPY); + } else { + // GDI 资源不足,退化为直接渲染(可能闪一帧黑边,但不崩溃) + RECT rcFull = { 0, 0, dstW, dstH }; + FillRect(m_hFullDC, &rcFull, (HBRUSH)GetStockObject(BLACK_BRUSH)); + StretchBlt(m_hFullDC, m_offsetX, m_offsetY, renderW, renderH, + m_hFullMemDC, 0, 0, srcW, srcH, SRCCOPY); + } + } else if (srcW == dstW && srcH == dstH) { BitBlt(m_hFullDC, 0, 0, srcW, srcH, m_hFullMemDC, 0, 0, SRCCOPY); } else { StretchBlt(m_hFullDC, 0, 0, dstW, dstH, m_hFullMemDC, 0, 0, srcW, srcH, SRCCOPY); @@ -1784,9 +1881,9 @@ void CScreenSpyDlg::OnPaint() // 只有当本地鼠标不在工具栏区域时,才绘制远程位图光标 if (!rcToolbar.PtInRect(ptLocal)) { - // 1. 计算缩放位置 - int drawX = m_bAdaptiveSize ? (int)(m_ClientCursorPos.x / m_wZoom) : (m_ClientCursorPos.x - m_ulHScrollPos); - int drawY = m_bAdaptiveSize ? (int)(m_ClientCursorPos.y / m_hZoom) : (m_ClientCursorPos.y - m_ulVScrollPos); + // 1. 计算缩放位置(自适应模式需加黑边偏移) + int drawX = m_bAdaptiveSize ? (int)(m_ClientCursorPos.x / m_wZoom) + m_offsetX : (m_ClientCursorPos.x - m_ulHScrollPos); + int drawY = m_bAdaptiveSize ? (int)(m_ClientCursorPos.y / m_hZoom) + m_offsetY : (m_ClientCursorPos.y - m_ulVScrollPos); // 2. 获取光标句柄(支持自定义光标) HCURSOR hCursor; @@ -2557,8 +2654,18 @@ void CScreenSpyDlg::SendScaledMouseMessage(MSG* pMsg, bool makeLP) MYMSG msg(*pMsg); LONG x = LOWORD(pMsg->lParam), y = HIWORD(pMsg->lParam); - LONG low = m_bAdaptiveSize ? x * m_wZoom : x + m_ulHScrollPos; - LONG high = m_bAdaptiveSize ? y * m_hZoom : y + m_ulVScrollPos; + LONG low, high; + if (m_bAdaptiveSize) { + // 减去黑边偏移后映射到远端坐标,并 clamp 到合法范围 + // 防止黑边区域的点击转换为负数后经 MAKELPARAM 截断为 WORD 大正数 + int srcW = m_BitmapInfor_Full ? m_BitmapInfor_Full->bmiHeader.biWidth : 0; + int srcH = m_BitmapInfor_Full ? abs(m_BitmapInfor_Full->bmiHeader.biHeight) : 0; + low = max(0L, min((LONG)((x - m_offsetX) * m_wZoom), (LONG)(srcW - 1))); + high = max(0L, min((LONG)((y - m_offsetY) * m_hZoom), (LONG)(srcH - 1))); + } else { + low = x + m_ulHScrollPos; + high = y + m_ulVScrollPos; + } if (makeLP) msg.lParam = MAKELPARAM(low, high); msg.pt.x = low; msg.pt.y = high; @@ -2945,8 +3052,9 @@ CPoint CScreenSpyDlg::ScreenToImage(CPoint pt) (int)(m_rcZoomSrc.top + pt.y * scaleY) ); } else if (m_bAdaptiveSize) { - // 自适应模式:按比例缩放 - return CPoint((int)(pt.x * m_wZoom), (int)(pt.y * m_hZoom)); + // 自适应模式:先减去黑边偏移,再乘缩放比 + return CPoint((int)((pt.x - m_offsetX) * m_wZoom), + (int)((pt.y - m_offsetY) * m_hZoom)); } else { // 滚动模式:加上滚动偏移 return CPoint(pt.x + m_ulHScrollPos, pt.y + m_ulVScrollPos); @@ -2972,8 +3080,10 @@ CPoint CScreenSpyDlg::ImageToScreen(CPoint pt) (int)((pt.y - m_rcZoomSrc.top) * scaleY) ); } else if (m_bAdaptiveSize) { + // 自适应模式:除以缩放比,再加黑边偏移 if (m_wZoom > 0 && m_hZoom > 0) { - return CPoint((int)(pt.x / m_wZoom), (int)(pt.y / m_hZoom)); + return CPoint((int)(pt.x / m_wZoom) + m_offsetX, + (int)(pt.y / m_hZoom) + m_offsetY); } return pt; } else { @@ -3159,10 +3269,11 @@ bool CScreenSpyDlg::ScreenRectToImageRect(const CRect& rcScreen, CRect& rcImage) rcImage.right = (int)(m_rcZoomSrc.left + rcScreen.right * scaleX); rcImage.bottom = (int)(m_rcZoomSrc.top + rcScreen.bottom * scaleY); } else if (m_bAdaptiveSize) { - rcImage.left = (int)(rcScreen.left * m_wZoom); - rcImage.top = (int)(rcScreen.top * m_hZoom); - rcImage.right = (int)(rcScreen.right * m_wZoom); - rcImage.bottom = (int)(rcScreen.bottom * m_hZoom); + // 先减去黑边偏移,再乘缩放比,得到原图坐标 + rcImage.left = (int)((rcScreen.left - m_offsetX) * m_wZoom); + rcImage.top = (int)((rcScreen.top - m_offsetY) * m_hZoom); + rcImage.right = (int)((rcScreen.right - m_offsetX) * m_wZoom); + rcImage.bottom = (int)((rcScreen.bottom - m_offsetY) * m_hZoom); } else { rcImage.left = rcScreen.left + m_ulHScrollPos; rcImage.top = rcScreen.top + m_ulVScrollPos; @@ -3381,8 +3492,18 @@ void CScreenSpyDlg::OnSize(UINT nType, int cx, int cy) return; GetClientRect(&m_CRect); - m_wZoom = ((double)m_BitmapInfor_Full->bmiHeader.biWidth) / ((double)(m_CRect.Width())); - m_hZoom = ((double)m_BitmapInfor_Full->bmiHeader.biHeight) / ((double)(m_CRect.Height())); + if (!m_BitmapInfor_Full) return; + if (m_bAdaptiveSize) { + int renderW, renderH; + ComputeAdaptiveLayout(m_BitmapInfor_Full->bmiHeader.biWidth, m_BitmapInfor_Full->bmiHeader.biHeight, + m_CRect.Width(), m_CRect.Height(), + renderW, renderH, m_offsetX, m_offsetY, + m_wZoom, m_hZoom); + } else { + m_offsetX = m_offsetY = 0; + m_wZoom = m_CRect.Width() > 0 ? (double)m_BitmapInfor_Full->bmiHeader.biWidth / m_CRect.Width() : 1.0; + m_hZoom = m_CRect.Height() > 0 ? (double)m_BitmapInfor_Full->bmiHeader.biHeight / m_CRect.Height() : 1.0; + } } void CScreenSpyDlg::OnActivate(UINT nState, CWnd* pWndOther, BOOL bMinimized) diff --git a/server/2015Remote/ScreenSpyDlg.h b/server/2015Remote/ScreenSpyDlg.h index 96638a1..59ae062 100644 --- a/server/2015Remote/ScreenSpyDlg.h +++ b/server/2015Remote/ScreenSpyDlg.h @@ -280,8 +280,16 @@ public: BOOL m_bUseCustomCursor = TRUE; // 是否使用自定义光标 CRect m_CRect; double m_wZoom=1, m_hZoom=1; + int m_offsetX=0, m_offsetY=0; // 自适应模式黑边偏移(像素) bool m_bMouseTracking = false; + // 自适应黑边合成缓冲(按窗口尺寸缓存,避免每帧分配 GDI 资源) + HDC m_hComposeDC = NULL; + HBITMAP m_hComposeBmp = NULL; + HBITMAP m_hComposeOldBmp = NULL; + int m_nComposeCX = 0; + int m_nComposeCY = 0; + // ========== 局部放大功能 ========== bool m_bZoomedIn = false; // 是否处于放大状态 CRect m_rcZoomSrc; // 放大区域(原图坐标)