Browse Source

修复摄像头不显示帧的问题

张洋 1 week ago
parent
commit
83f7b91952

+ 86 - 14
zhipuzi_pos_windows/page/CAIxuexiPageUI.cpp

@@ -53,6 +53,13 @@ void CAIxuexiPageUI::InitShow()
 	std::thread(&CAIxuexiPageUI::HandleUpdateVideo, this).detach();
 	std::thread(&CAIxuexiPageUI::HandleUpdateVideo, this).detach();
 	CAppEnv::GetInstance()->AddWorkerNum();
 	CAppEnv::GetInstance()->AddWorkerNum();
 
 
+	//【R50】把本页登记为「后绘制」控件:摄像头帧改由 DoPostPaint() 画进框架本帧
+	//真正提交的那张图。不能省 —— 主窗口是分层窗口,直画窗口 DC 的帧没人看。
+	//RemovePostPaint 先跑一次是为了幂等:InitShow 每次进页面都会调,
+	//而 AddPostPaint 对重复登记只有 ASSERT(Release 下会重复入表)。
+	m_pManager->RemovePostPaint(this);
+	m_pManager->AddPostPaint(this);
+
 	//重新进来的时候,重置当前选中的分类和商品
 	//重新进来的时候,重置当前选中的分类和商品
 	m_cur_click_food_item = NULL;
 	m_cur_click_food_item = NULL;
 
 
@@ -581,36 +588,101 @@ void CAIxuexiPageUI::HandleUpdateVideo()
 	CAppEnv::GetInstance()->DelWorkerNum(L"CAIxuexiPageUI::HandleUpdateVideo");
 	CAppEnv::GetInstance()->DelWorkerNum(L"CAIxuexiPageUI::HandleUpdateVideo");
 }
 }
 
 
+//【R50 · 分层窗口下的摄像头预览】为什么不能再直接 StretchBlt 到窗口 DC:
+//  main.xml 的 <Window roundcorner="10,10"> 让主窗口走「分层渲染 + 内存圆角蒙版」
+//  (SetRoundCorner → SetLayered(true)),WM_PAINT 把整棵树画进 m_hDcOffscreen、
+//  经内容×蒙版调制后用 UpdateLayeredWindow 提交 ⇒ 窗口可见像素 100% 来自那张位图,
+//  往 m_pManager->GetPaintDC()(::GetDC 出来的窗口 DC)画什么都没人看。
+//  唯一在「分层」与「普通」两种渲染下都成立的落点是后绘制:框架两条分支都会调
+//  DoPostPaint()(分层传 m_hDcOffscreen,普通传 m_hDcPaint,见 UIManager.cpp 的
+//  m_aPostPaintControls 段),帧因此进入最终提交的那张图。且分层分支里后绘制跑在
+//  「内容×蒙版」调制之前,而蒙版对内部像素给的 alpha 就是 255 ⇒ 调制在内部恒等,
+//  视频像素原样保留。
+//  所以:UpdateVideoShow() 只负责标脏,真正画帧的活交给 DoPostPaint()。
 void CAIxuexiPageUI::UpdateVideoShow()
 void CAIxuexiPageUI::UpdateVideoShow()
 {
 {
-	HDC hDC = m_pManager->GetPaintDC();
+	if (m_pManager == NULL)
+	{
+		return;
+	}
 
 
-	if (!m_hMemDC)
+	CControlUI* pImage = static_cast<CControlUI*>(this->FindSubControl(_T("image")));
+	if (pImage == NULL)
 	{
 	{
-		m_hMemDC = CreateCompatibleDC(hDC);
+		return;
 	}
 	}
 
 
-	HBITMAP hOldBitmap = (HBITMAP)SelectObject(m_hMemDC, m_hBitmap);
-	BITMAP bm;
-	GetObject(m_hBitmap, sizeof(BITMAP), &bm);
+	//只脏摄像头那一块:分层窗口每次 WM_PAINT 都要重提交整张 ULW 位图,
+	//全窗 Invalidate 会把 400x300 的开销放大到 1366x768。
+	//外扩 1px 是为了让拉伸的边界像素也落在脏区里,避免右下留一条旧帧。
+	RECT rcDirty = pImage->GetPos();
+	::InflateRect(&rcDirty, 1, 1);
+	m_pManager->Invalidate(rcDirty);
+}
+
+void CAIxuexiPageUI::DoPostPaint(HDC hDC, const RECT& rcPaint)
+{
+	if (hDC == NULL || m_hBitmap == NULL)
+	{
+		return;
+	}
+
+	//AddPostPaint 的登记是常驻的(不随切页摘除),所以这里必须自己判断「本页是不是
+	//当前页」—— 否则切到别的页面还会往那个坐标上画摄像头画面。
+	if (m_pMainWnd == NULL || m_pMainWnd->GetCurPageName() != CMainWnd::AIXUEXI)
+	{
+		return;
+	}
+	if (!this->IsVisible())
+	{
+		return;
+	}
 
 
 	CControlUI* pImage = static_cast<CControlUI*>(this->FindSubControl(_T("image")));
 	CControlUI* pImage = static_cast<CControlUI*>(this->FindSubControl(_T("image")));
-	if (!pImage)
+	if (pImage == NULL)
 	{
 	{
 		return;
 		return;
 	}
 	}
 
 
-	RECT imageRect = pImage->GetPos();
+	const RECT rcTarget = pImage->GetPos();
+	RECT rcClip = { 0 };
+	if (!::IntersectRect(&rcClip, &rcPaint, &rcTarget))
+	{
+		return;
+	}
+
+	BITMAP bm = { 0 };
+	if (::GetObject(m_hBitmap, sizeof(BITMAP), &bm) == 0)
+	{
+		return;
+	}
+	if (bm.bmWidth <= 0 || bm.bmHeight <= 0)
+	{
+		return;
+	}
 
 
-	StretchBlt(hDC, imageRect.left, imageRect.top, imageRect.right - imageRect.left,
-		imageRect.bottom - imageRect.top, m_hMemDC, 0, 0, bm.bmWidth, bm.bmHeight, SRCCOPY);
+	if (m_hMemDC == NULL)
+	{
+		m_hMemDC = ::CreateCompatibleDC(hDC);
+		if (m_hMemDC == NULL)
+		{
+			return;
+		}
+	}
 
 
-	SelectObject(m_hMemDC, hOldBitmap);
+	HBITMAP hOldBitmap = (HBITMAP)::SelectObject(m_hMemDC, m_hBitmap);
+	if (hOldBitmap == NULL)
+	{
+		return;
+	}
 
 
-	//(原「StretchBlt 之后补画圆角遮罩」的代码已随摄像头去圆角一并移除:
-	//  摄像头区现在是直角,不再需要 float 遮罩层,也就没有会被视频盖掉的前景层了。)
+	//摄像头区是直角:原「StretchBlt 之后补画圆角遮罩」的代码已随摄像头去圆角一并
+	//移除,没有会被视频盖掉的前景层。
+	::StretchBlt(hDC, rcTarget.left, rcTarget.top,
+		rcTarget.right - rcTarget.left, rcTarget.bottom - rcTarget.top,
+		m_hMemDC, 0, 0, bm.bmWidth, bm.bmHeight, SRCCOPY);
 
 
-	return;
+	::SelectObject(m_hMemDC, hOldBitmap);
 }
 }
 
 
 void CAIxuexiPageUI::StopVideoThread()
 void CAIxuexiPageUI::StopVideoThread()

+ 7 - 0
zhipuzi_pos_windows/page/CAIxuexiPageUI.h

@@ -58,6 +58,13 @@ public:
 
 
 	void UpdateVideoShow();
 	void UpdateVideoShow();
 
 
+	//【R50】摄像头帧的「后绘制」落点。主窗口自 09-24 起是分层窗口(main.xml 的
+	//  <Window roundcorner="10,10">)——可见像素全部来自离屏 ARGB 位图 +
+	//  UpdateLayeredWindow,往窗口 DC 直画一律不可见;帧必须画在框架本帧真正
+	//  提交的那张图上(分层 = m_hDcOffscreen)。两条渲染分支都会调本函数,
+	//  见 cpp 里的详细说明。
+	virtual void DoPostPaint(HDC hDC, const RECT& rcPaint);
+
 	//暂停摄像头线程
 	//暂停摄像头线程
 	void StopVideoThread();
 	void StopVideoThread();
 
 

+ 63 - 25
zhipuzi_pos_windows/page/CSettingPageUI.cpp

@@ -1121,6 +1121,12 @@ void CSettingPageUI::InitShow()
 		std::thread(&CSettingPageUI::HandleUpdateVideo, this).detach();
 		std::thread(&CSettingPageUI::HandleUpdateVideo, this).detach();
 		CAppEnv::GetInstance()->AddWorkerNum();
 		CAppEnv::GetInstance()->AddWorkerNum();
 	}
 	}
+
+	//【R50】登记「后绘制」:摄像头帧与红色裁剪框都改由 DoPostPaint() 画进
+	//框架本帧真正提交的那张图(分层模式 = m_hDcOffscreen)。见 CAIxuexiPageUI.cpp
+	//里 UpdateVideoShow() 上方的说明。
+	m_pManager->RemovePostPaint(this);
+	m_pManager->AddPostPaint(this);
 }
 }
 
 
 //处理按钮点击类事件
 //处理按钮点击类事件
@@ -3681,21 +3687,61 @@ void CSettingPageUI::HandleUpdateVideo()
 	CAppEnv::GetInstance()->DelWorkerNum(L"CSettingPageUI::HandleUpdateVideo");
 	CAppEnv::GetInstance()->DelWorkerNum(L"CSettingPageUI::HandleUpdateVideo");
 }
 }
 
 
+//【R50】原实现直画 m_pManager->GetPaintDC()(窗口 DC),自 09-24 主窗口转为分层窗口后
+//全部不可见(连红色裁剪框一起)。现改为:本函数只标脏,绘制搬进 DoPostPaint()。
 void CSettingPageUI::UpdateVideoShow()
 void CSettingPageUI::UpdateVideoShow()
 {
 {
-	HDC hDC = m_pManager->GetPaintDC();
+	if (m_pManager == NULL)
+	{
+		return;
+	}
+
+	CControlUI* pImage = static_cast<CControlUI*>(this->FindSubControl(_T("image_video")));
+	if (pImage == NULL)
+	{
+		return;
+	}
+
+	//红色裁剪框被 IntersectClipRect 限在 image 里,所以脏区取 image 的矩形就够;
+	//外扩 2px 覆盖 2px 宽的画笔描边。
+	RECT rcDirty = pImage->GetPos();
+	::InflateRect(&rcDirty, 2, 2);
+	m_pManager->Invalidate(rcDirty);
+}
+
+void CSettingPageUI::DoPostPaint(HDC hDC, const RECT& rcPaint)
+{
 	if (!hDC || !m_hBitmap)
 	if (!hDC || !m_hBitmap)
 	{
 	{
 		return;
 		return;
 	}
 	}
 
 
-	if (!m_hMemDC)
+	//登记常驻,必须自己判断「本页是不是当前页、AI 识别子界面是不是开着」。
+	if (m_pMainWnd == NULL || m_pMainWnd->GetCurPageName() != CMainWnd::SETTING)
 	{
 	{
-		m_hMemDC = CreateCompatibleDC(hDC);
+		return;
+	}
+	if (!m_is_show_video_capture)
+	{
+		return;
 	}
 	}
 
 
 	CControlUI* pImage = static_cast<CControlUI*>(this->FindSubControl(_T("image_video")));
 	CControlUI* pImage = static_cast<CControlUI*>(this->FindSubControl(_T("image_video")));
-	if (!pImage)
+	if (!pImage || !pImage->IsVisible())
+	{
+		return;
+	}
+
+	RECT imageRect = pImage->GetPos();
+	RECT rcClip = { 0 };
+	if (!::IntersectRect(&rcClip, &rcPaint, &imageRect))
+	{
+		return;
+	}
+
+	int imageWidth = imageRect.right - imageRect.left;
+	int imageHeight = imageRect.bottom - imageRect.top;
+	if (imageWidth <= 0 || imageHeight <= 0)
 	{
 	{
 		return;
 		return;
 	}
 	}
@@ -3707,19 +3753,23 @@ void CSettingPageUI::UpdateVideoShow()
 		return;
 		return;
 	}
 	}
 
 
-	HBITMAP hOldBitmap = (HBITMAP)SelectObject(m_hMemDC, m_hBitmap);
+	if (!m_hMemDC)
+	{
+		m_hMemDC = CreateCompatibleDC(hDC);
+	}
+	if (!m_hMemDC)
+	{
+		return;
+	}
 
 
-	RECT imageRect = pImage->GetPos();
-	int imageWidth = imageRect.right - imageRect.left;
-	int imageHeight = imageRect.bottom - imageRect.top;
-	if (imageWidth <= 0 || imageHeight <= 0)
+	HBITMAP hOldBitmap = (HBITMAP)SelectObject(m_hMemDC, m_hBitmap);
+	if (hOldBitmap == NULL)
 	{
 	{
-		SelectObject(m_hMemDC, hOldBitmap);
 		return;
 		return;
 	}
 	}
 
 
-	StretchBlt(hDC, imageRect.left, imageRect.top, imageRect.right - imageRect.left,
-		imageRect.bottom - imageRect.top, m_hMemDC, 0, 0, bm.bmWidth, bm.bmHeight, SRCCOPY);
+	StretchBlt(hDC, imageRect.left, imageRect.top, imageWidth, imageHeight,
+		m_hMemDC, 0, 0, bm.bmWidth, bm.bmHeight, SRCCOPY);
 
 
 	RECT borderRect = GetVideoBorderDisplayRect(imageRect);
 	RECT borderRect = GetVideoBorderDisplayRect(imageRect);
 
 
@@ -3731,7 +3781,7 @@ void CSettingPageUI::UpdateVideoShow()
 	HBRUSH hOldBrush = (HBRUSH)SelectObject(hDC, GetStockObject(NULL_BRUSH));
 	HBRUSH hOldBrush = (HBRUSH)SelectObject(hDC, GetStockObject(NULL_BRUSH));
 
 
 	Rectangle(hDC, borderRect.left, borderRect.top, borderRect.right, borderRect.bottom);
 	Rectangle(hDC, borderRect.left, borderRect.top, borderRect.right, borderRect.bottom);
-	
+
 	HBRUSH hHandleBrush = CreateSolidBrush(RGB(255, 0, 0));
 	HBRUSH hHandleBrush = CreateSolidBrush(RGB(255, 0, 0));
 	auto drawHandle = [&](int x, int y)
 	auto drawHandle = [&](int x, int y)
 		{
 		{
@@ -3743,18 +3793,6 @@ void CSettingPageUI::UpdateVideoShow()
 	int cx = (borderRect.left + borderRect.right) / 2;
 	int cx = (borderRect.left + borderRect.right) / 2;
 	int cy = (borderRect.top + borderRect.bottom) / 2;
 	int cy = (borderRect.top + borderRect.bottom) / 2;
 
 
-	/*
-	drawHandle(borderRect.left, borderRect.top);
-	drawHandle(borderRect.right, borderRect.top);
-	drawHandle(borderRect.left, borderRect.bottom);
-	drawHandle(borderRect.right, borderRect.bottom);
-
-	drawHandle(cx, borderRect.top);
-	drawHandle(cx, borderRect.bottom);
-	drawHandle(borderRect.left, cy);
-	drawHandle(borderRect.right, cy);
-	*/
-
 	drawHandle(borderRect.left + 4, borderRect.top + 4);
 	drawHandle(borderRect.left + 4, borderRect.top + 4);
 	drawHandle(borderRect.right - 4, borderRect.top + 4);
 	drawHandle(borderRect.right - 4, borderRect.top + 4);
 	drawHandle(borderRect.left + 4, borderRect.bottom - 4);
 	drawHandle(borderRect.left + 4, borderRect.bottom - 4);

+ 4 - 0
zhipuzi_pos_windows/page/CSettingPageUI.h

@@ -34,6 +34,10 @@ public:
 
 
 	void HandleUpdateVideo();
 	void HandleUpdateVideo();
 	void UpdateVideoShow();
 	void UpdateVideoShow();
+
+	//【R50】见 CAIxuexiPageUI.h 的同名声明:主窗口是分层窗口(roundcorner),
+	//  摄像头帧与红色裁剪框都必须画在后绘制阶段才可见。
+	virtual void DoPostPaint(HDC hDC, const RECT& rcPaint);
 	void StopVideoThread();
 	void StopVideoThread();
 
 
 private:
 private: