【GDI+】C++截长图

一、准备

1、屏幕截图

【GDI+】C++屏幕截图

使用BitBlt来进行截图

2、强制刷新

c++窗口立即刷新重绘

截图的时候,有可能由于界面没有及时刷新,导致截到的内容,这时就需要强制立刻刷新重绘

cpp 复制代码
InvalidateRect(m_hWnd, NULL, TRUE);
UpdateWindow(m_hWnd);

二、 截长图

1、截全屏【不考虑表头、顶部、底部等因素】

需求:界面只有一个List控件m_pList,放着数据m_vecData,需要将整个List的所有内容都截图拼成一个长图。先说简单粗暴的,只是截图,然后拼接。(不考虑拼接的长图表头只截一次的问题)

思路

  • 先计算出长图的总高度,然后创建该高度的兼容位图并写入内存DC
  • 拷贝屏幕内容到设备上下文(内存DC),翻页,继续拷贝当前页码的屏幕内容到设备上下文(挨着上次内存DC拷贝后位置的底部放),直到翻页到最后。
  • 保存为.png或者.bmp格式
cpp 复制代码
#include<windows.h>
#include <gdiplus.h>
using namespace Gdiplus;
#pragma comment(lib, "gdiplus.lib")

#define LIST_HEADER_HEIGHT 36      //表头高度
#define LIST_ITEM_ROW_HEIGHT 35    //列表每行高度


void ScreenShotLongImage()
{
    //列表
    CListUI* m_pList = nullptr;
    //列表内容
    std::vector<Data> m_vecData;
    ...
	
    //一页列表高度
	int nListHeight = m_pList->GetHeight();
    //列表内容总高度
	int nContentTotalHeight = m_vecData.size() * LIST_ITEM_ROW_HEIGHT;
    //总页数
	int nTotalPage = nContentTotalHeight / (nListHeight - LIST_HEADER_HEIGHT);
    //最后一页剩余高度
	int nLeftHeight = nContentTotalHeight % (nListHeight - LIST_HEADER_HEIGHT);
	if (nLeftHeight != 0) nTotalPage++;

	//计算长图总高度	
	int nDstTotalHeight = 0;
	for (int nPage = 0; nPage < nTotalPage; nPage++)
	{
		RECT rcSrcClient;
		GetClientRect(m_hWnd, &rcSrcClient);
		int nHeight = rcSrcClient.bottom - rcSrcClient.top;
		nDstTotalHeight += nHeight;
	}

	//截长图
	//1、确定长图区域
	RECT rcDstClient;
	GetClientRect(m_hWnd, &rcDstClient);
	rcDstClient.bottom = nDstTotalHeight;
	int nDstWidth = rcDstClient.right - rcDstClient.left;
	//2、获得指定窗口的dc(源dc)
	//注意GetWindowDC会把窗口的标题栏也同时截图,如果不需要窗口的标题就使用GetDC(hwnd)
	HDC sourceDC = GetDC(m_hWnd);
	//3、根据源dc创建兼容内存DC
	HDC memDC = CreateCompatibleDC(sourceDC);
	//3、根据源dc创建兼容位图
	HBITMAP memBitmap = CreateCompatibleBitmap(sourceDC, rcDstClient.right - rcDstClient.left, rcDstClient.bottom - rcDstClient.top);
	//4、将兼容位图写入内存dc
	SelectObject(memDC, memBitmap);

	//5、翻页截图拼接成长图
	int nDstTop = 0;
	for (int nPage = 0; nPage < nTotalPage; nPage++)
	{
		//强制重绘
		UpdateWindow(m_hWnd);
		// 拷贝屏幕内容到设备上下文		
		RECT rcSrcClient;
		GetClientRect(m_hWnd, &rcSrcClient);
		if (nPage == nTotalPage)
		{
			//最后一页
			int nTopPadding = LIST_HEADER_HEIGHT + (nListHeight - LIST_HEADER_HEIGHT - nLeftHeight);
			rcSrcClient.top += nTopPadding;
		}
		int nDstHeight = rcSrcClient.bottom - rcSrcClient.top;
		BitBlt(memDC, 0, nDstTop, nDstWidth, nDstHeight, GetDC(m_hWnd), rcSrcClient.left, rcSrcClient.top, SRCCOPY);
		nDstTop += rcSrcClient.bottom - rcSrcClient.top;
		//翻页		
		m_pList->PageDown();		
	}

	//6、保存为.png格式
	GdiplusStartupInput gdiplusStartupInput;
	ULONG_PTR gdiplusToken;
	GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);
	Gdiplus::Bitmap bmp(memBitmap, NULL);
	CLSID pngClsid;
	int i = GetEncoderClsid(L"image/png", &pngClsid);
	bmp.Save(L"test.png", &pngClsid, NULL);
	GdiplusShutdown(gdiplusToken);

    //保存位.bmp格式
	{
	    //6、以下代码保存截图信息到文件中保存为bmp格式
		//6.1获得位图信息
		BITMAP bmp;
		GetObject(memBitmap, sizeof(BITMAP), &bmp);
		//6.2图片保存路径和方式
		FILE* fp;
		fopen_s(&fp, L"test.bmp", "w+b");
		//6.3创建位图文件头
		//位图文件头设置默认值为0
		BITMAPFILEHEADER bfh = { 0 };
		//到位图数据的偏移量(此步骤固定,位图编译量即为位图文件头 + 位图信息头 + 调色板的大小,调色板设置为0)
		bfh.bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER);
		//文件总的大小
		bfh.bfSize = bfh.bfOffBits + bmp.bmWidthBytes * bmp.bmHeight;
		//bfType固定为BM,(WORD)0x4d42表示为BM
		bfh.bfType = (WORD)0x4d42;
		//6.4创建文件信息头
		//位图信息头,默认设置为0
		BITMAPINFOHEADER bih = { 0 };
		//每个像素字节大小
		bih.biBitCount = bmp.bmBitsPixel;
		bih.biCompression = BI_RGB;
		//高度
		bih.biHeight = bmp.bmHeight;
		bih.biPlanes = 1;
		bih.biSize = sizeof(BITMAPINFOHEADER);
		//图像数据大小
		bih.biSizeImage = bmp.bmWidthBytes * bmp.bmHeight;
		//宽度
		bih.biWidth = bmp.bmWidth;
		//6.5写入位图文件头
		fwrite(&bfh, 1, sizeof(BITMAPFILEHEADER), fp);
		//6.6写入位图信息头
		fwrite(&bih, 1, sizeof(BITMAPINFOHEADER), fp);
		//6.7申请内存保存位图数据
		byte* p = new byte[bmp.bmWidthBytes * bmp.bmHeight];
		//6.8获取位图数据
		GetDIBits(memDC, (HBITMAP)memBitmap, 0, rcDstClient.bottom - rcDstClient.top, p,
			(LPBITMAPINFO)&bih, DIB_RGB_COLORS);
		//6.9写入位图数据        
		fwrite(p, 1, bmp.bmWidthBytes * bmp.bmHeight, fp);
		//6.10先删除开辟的内存,然后关闭文件
		delete[] p;
		fclose(fp);
	}
	// 释放资源
	DeleteObject(memBitmap);
	DeleteDC(memDC);
	ReleaseDC(m_hWnd, sourceDC);
}

int GetEncoderClsid(const WCHAR *format, CLSID *pClsid)
{
	UINT  num = 0;          // number of image encoders      
	UINT  size = 0;         // size of the image encoder array in bytes      
 
	ImageCodecInfo* pImageCodecInfo = NULL;
 
	GetImageEncodersSize(&num, &size);
	if (size == 0)
		return -1;  // Failure      
 
	pImageCodecInfo = (ImageCodecInfo*)(malloc(size));
	if (pImageCodecInfo == NULL)
		return -1;  // Failure      
 
	GetImageEncoders(num, size, pImageCodecInfo);
 
	for (UINT j = 0; j < num; ++j)
	{
		if (wcscmp(pImageCodecInfo[j].MimeType, format) == 0)
		{
			*pClsid = pImageCodecInfo[j].Clsid;
			free(pImageCodecInfo);
			return j;  // Success      
		}
	}
 
	free(pImageCodecInfo);
	return -1;  // Failure 
}
 

2、截长图【考虑表头、头部、底部内容等】

cpp 复制代码
#define LIST_HEADER_HEIGHT 36 //表头高度
#define LIST_TOP_HEIGHT 42 //列表顶部高度
#define LIST_BOTTOM_HEIGHT 105 //列表顶部高度
#define LIST_ITEM_ROW_HEIGHT 35//列表每行高度
#define LIST_YEAR_Q_WITH 705 //全端年榜列表宽度
#define LIST_YEAR_D_WITH 615 //点播年榜列表宽度
#define FRAME_MAX_WITH 1920//最大化宽度

void CMainFrame::ScreenShotLongImage(int &nSplitLineSize, bool bQ)
{
	int nRightPadding = bQ ? FRAME_MAX_WITH - LIST_YEAR_Q_WITH : FRAME_MAX_WITH - LIST_YEAR_D_WITH;
	int nListHeight = bQ ? m_pKyTVPlayYearQList->GetHeight(): m_pKyTVPlayYearDList->GetHeight();
	int nTotalHeight = m_vecKyTVPlayYear.size() * LIST_ITEM_ROW_HEIGHT + nSplitLineSize;
	int nTotalPage = nTotalHeight / (nListHeight - LIST_HEADER_HEIGHT);
	int nLeftHeight = nTotalHeight % (nListHeight - LIST_HEADER_HEIGHT);
	if (nLeftHeight != 0) nTotalPage++;

	//计算长图总高度	
	int nDstTotalHeight = 0;
	for (int nPage = 0; nPage < nTotalPage; nPage++)
	{		
		RECT rcSrcClient;
		int nHeight = GetSrcRect(rcSrcClient, nRightPadding, nListHeight, nLeftHeight, nPage, nTotalPage);
		nDstTotalHeight += nHeight;
	}

	//截长图
	//1、确定长图区域
	RECT rcDstClient;
	GetClientRect(m_hWnd, &rcDstClient);
	rcDstClient.bottom = nDstTotalHeight;
	rcDstClient.right -= nRightPadding;
	int nDstWidth = rcDstClient.right - rcDstClient.left;
	//2、获得指定窗口的dc(源dc)
	//注意GetWindowDC会把窗口的标题栏也同时截图,如果不需要窗口的标题就使用GetDC(hwnd)
	HDC sourceDC = GetDC(m_hWnd);
	//3、根据源dc创建兼容内存DC
	HDC memDC = CreateCompatibleDC(sourceDC);
	//3、根据源dc创建兼容位图
	HBITMAP memBitmap = CreateCompatibleBitmap(sourceDC, rcDstClient.right - rcDstClient.left, rcDstClient.bottom - rcDstClient.top);
	//4、将兼容位图写入内存dc
	SelectObject(memDC, memBitmap);

	//5、翻页截图拼接成长图
	int nDstTop = 0;
	for (int nPage = 0; nPage < nTotalPage; nPage++)
	{
		//强制重绘
		UpdateWindow(m_hWnd);	
		// 拷贝屏幕内容到设备上下文		
		RECT rcSrcClient;
		int nDstHeight =  GetSrcRect(rcSrcClient, nRightPadding, nListHeight, nLeftHeight, nPage, nTotalPage);
		BitBlt(memDC, 0, nDstTop, nDstWidth, nDstHeight, GetDC(m_hWnd), rcSrcClient.left, rcSrcClient.top, SRCCOPY);
		nDstTop += rcSrcClient.bottom - rcSrcClient.top;
		//翻页
		if (bQ)
		{
			m_pKyTVPlayYearQList->PageDown();
		}
		else
		{
			m_pKyTVPlayYearDList->PageDown();
		}	
	}

	//6、保存为.png格式
	GdiplusStartupInput gdiplusStartupInput;
	ULONG_PTR gdiplusToken;
	GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);
	Gdiplus::Bitmap bmp(memBitmap, NULL);
	CLSID pngClsid;
	int i = miscutil::system::GetEncoderClsid(L"image/png", &pngClsid);
	string strImageName = "q.png";
	bmp.Save(miscutil::convert::ANSI2Unicode(strImageName.c_str()).c_str(), &pngClsid, NULL);
	GdiplusShutdown(gdiplusToken);

	//保存位.bmp格式
	{
		//6、以下代码保存截图信息到文件中保存为bmp格式
		//6.1获得位图信息
		BITMAP bmp;
		GetObject(memBitmap, sizeof(BITMAP), &bmp);
		//6.2图片保存路径和方式
		strImageName.append(".bmp");
		FILE* fp;
		fopen_s(&fp, strImageName.c_str(), "w+b");
		//6.3创建位图文件头
		//位图文件头设置默认值为0
		BITMAPFILEHEADER bfh = { 0 };
		//到位图数据的偏移量(此步骤固定,位图编译量即为位图文件头 + 位图信息头 + 调色板的大小,调色板设置为0)
		bfh.bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER);
		//文件总的大小
		bfh.bfSize = bfh.bfOffBits + bmp.bmWidthBytes * bmp.bmHeight;
		//bfType固定为BM,(WORD)0x4d42表示为BM
		bfh.bfType = (WORD)0x4d42;
		//6.4创建文件信息头
		//位图信息头,默认设置为0
		BITMAPINFOHEADER bih = { 0 };
		//每个像素字节大小
		bih.biBitCount = bmp.bmBitsPixel;
		bih.biCompression = BI_RGB;
		//高度
		bih.biHeight = bmp.bmHeight;
		bih.biPlanes = 1;
		bih.biSize = sizeof(BITMAPINFOHEADER);
		//图像数据大小
		bih.biSizeImage = bmp.bmWidthBytes * bmp.bmHeight;
		//宽度
		bih.biWidth = bmp.bmWidth;
		//6.5写入位图文件头
		fwrite(&bfh, 1, sizeof(BITMAPFILEHEADER), fp);
		//6.6写入位图信息头
		fwrite(&bih, 1, sizeof(BITMAPINFOHEADER), fp);
		//6.7申请内存保存位图数据
		byte* p = new byte[bmp.bmWidthBytes * bmp.bmHeight];
		//6.8获取位图数据
		GetDIBits(memDC, (HBITMAP)memBitmap, 0, rcDstClient.bottom - rcDstClient.top, p,
			(LPBITMAPINFO)&bih, DIB_RGB_COLORS);
		//6.9写入位图数据        
		fwrite(p, 1, bmp.bmWidthBytes * bmp.bmHeight, fp);
		//6.10先删除开辟的内存,然后关闭文件
		delete[] p;
		fclose(fp);
	}

	// 释放资源
	DeleteObject(memBitmap);
	DeleteDC(memDC);
	ReleaseDC(m_hWnd, sourceDC);
}

int CMainFrame::GetSrcRect(RECT &rcClient, int nRightPadding, int nListHeight, int nLeftHeight, int nPageNO, int nTotalPage)
{
	GetClientRect(m_hWnd, &rcClient);
	int nTopPadding = 0, nBottomPadding = 0, nLeftPadding = 0;
	if (nPageNO == 0)
	{
		//第一页
		nBottomPadding = LIST_BOTTOM_HEIGHT;
	}
	else if (nPageNO == (nTotalPage - 1))
	{
		//最后一页
		nTopPadding = LIST_TOP_HEIGHT + LIST_HEADER_HEIGHT + (nListHeight - LIST_HEADER_HEIGHT - nLeftHeight);
	}
	else
	{
		nTopPadding = LIST_TOP_HEIGHT + LIST_HEADER_HEIGHT;
		nBottomPadding = LIST_BOTTOM_HEIGHT;
	}

	rcClient.top += nTopPadding;
	rcClient.bottom -= nBottomPadding;
	rcClient.left += nLeftPadding;
	rcClient.right -= nRightPadding;

	return (rcClient.bottom - rcClient.top);
}
相关推荐
戎梓漩11 分钟前
windows下安装curl,并集成到visual studio
ide·windows·visual studio
傻啦嘿哟13 分钟前
如何使用 Python 开发一个简单的文本数据转换为 Excel 工具
开发语言·python·excel
大数据编程之光17 分钟前
Flink Standalone集群模式安装部署全攻略
java·大数据·开发语言·面试·flink
初九之潜龙勿用17 分钟前
C#校验画布签名图片是否为空白
开发语言·ui·c#·.net
爱摸鱼的孔乙己32 分钟前
【数据结构】链表(leetcode)
c语言·数据结构·c++·链表·csdn
Dola_Pan34 分钟前
C语言:数组转换指针的时机
c语言·开发语言·算法
ExiFengs35 分钟前
实际项目Java1.8流处理, Optional常见用法
java·开发语言·spring
paj12345678936 分钟前
JDK1.8新增特性
java·开发语言
IT古董43 分钟前
【人工智能】Python在机器学习与人工智能中的应用
开发语言·人工智能·python·机器学习
繁依Fanyi1 小时前
简易安卓句分器实现
java·服务器·开发语言·算法·eclipse