内存中截屏并转换为JPG编码的数据 -- VC
最近工作中要用到截屏,都知道使用BitBlt截取到的数据是BMP编码,一个1024x*的桌面就要2M多,转换为jpg编码大小是100K左右,gif根据quality也会很小。
但是网上找到的转码都会生成一个临时文件,比如将BMP保存成文件后,建立编码器然后对文件编码,还有的就是编码器编码完后直接就生成了文件,这个明显不符合我们的需求,我们需要的就是内存中截取,内存中转换。
经过一上午的资料查询,搞定了这个玩意。大致流程是先BitBlt到内存BMP编码的数据,然后转换为数据流(Stream),建立一个编码器直接对流进行编码,最后将流转换为buffer,最后生成的buffer可以直接保存成jpg后缀的文件,直接打开即可查看。
代码如下(参考了网上很多代码,也没有留意是谁的所以没有署名版权,所以对作者说声抱歉,这个代码是加上了很多自己的注释,参考了多个代码按照自己风格重写的):
//内存截屏并转换为JPG编码buffer(by Fenlog)
ScreenCapture::ScreenCapture(void)
{
//装载gdi+
GdiplusStartup(&m_pGdiToken,&m_gdiplusStartupInput,NULL);//
szJpg = NULL;
nSizeJpg = 0;
}
ScreenCapture::~ScreenCapture(void)
{
if(szJpg) free(szJpg); //清理缓冲区
//关闭完所有的设备场景后才能调用shutdown 否则会挂掉
GdiplusShutdown(m_pGdiToken);
}
/*
image/bmp
image/jpeg
image/gif
image/tiff
image/png
*/
int ScreenCapture::GetEncoderClsid(const WCHAR* format, CLSID* pClsid)
{
UINT num= 0;
UINT size= 0;
ImageCodecInfo* pImageCodecInfo= NULL;
GetImageEncodersSize(&num, &size); //获取编码器缓冲区长度
if(size== 0)
{
return -1;
}
pImageCodecInfo= (ImageCodecInfo*)(malloc(size)); //开辟出来控件
if(pImageCodecInfo == NULL)
{
return -1;
}
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;
}
}
free(pImageCodecInfo);
return -1;
}
//流数据转换为buffer malloc出来的 记得销毁
BOOL ScreenCapture::stream_to_mem(IStream *stream, void **outbuf, size_t *size)
{
ULARGE_INTEGER ulnSize;
LARGE_INTEGER lnOffset;
lnOffset.QuadPart = 0;
/* get the stream size */
if( stream->Seek( lnOffset, STREAM_SEEK_END, &ulnSize ) != S_OK )
{
return FALSE;
}
if( stream->Seek( lnOffset, STREAM_SEEK_SET, NULL ) != S_OK )
{
return FALSE;
}
/* read it */
*outbuf = malloc((size_t)ulnSize.QuadPart );
*size = (size_t) ulnSize.QuadPart;
ULONG bytesRead;
if( stream->Read(*outbuf,(ULONG)ulnSize.QuadPart,&bytesRead) != S_OK )
{
free(*outbuf);
return FALSE;
}
return TRUE;
}
BOOL ScreenCapture::GetScreenCapture()
{
BOOL nRet = FALSE;
//截屏 保存在BYTE * pCapture指针中
CDC *pDC,memDC;//屏幕DC,内存DC
pDC = CDC::FromHandle(GetDC(NULL));//获取当前整个屏幕DC
int BitPerPixel = pDC->GetDeviceCaps(BITSPIXEL);//获得颜色模式
int Width = pDC->GetDeviceCaps(HORZRES);
int Height = pDC->GetDeviceCaps(VERTRES);
memDC.CreateCompatibleDC(pDC);
CBitmap memBitmap, *oldmemBitmap;
memBitmap.CreateCompatibleBitmap(pDC, Width, Height);//建立和屏幕兼容的bitmap
oldmemBitmap = memDC.SelectObject(&memBitmap);//将memBitmap选入内存DC
memDC.BitBlt(0, 0, Width, Height, pDC, 0, 0, SRCCOPY);//复制屏幕图像到内存DC
Bitmap bm((HBITMAP)memBitmap, NULL);//定义bitmap
CLSID pngClsid;
IStream *stream = NULL;
ULONG quality = 50;
EncoderParameters enParameters;
enParameters.Count = 1;
enParameters.Parameter[0].Guid = EncoderQuality;
enParameters.Parameter[0].Type = EncoderParameterValueTypeLong;
enParameters.Parameter[0].NumberOfValues = 1;
enParameters.Parameter[0].Value = &quality;
//先准备stream
if(CreateStreamOnHGlobal( NULL, TRUE, &stream ) != S_OK )
goto Ext;
//查找编码器 这里也可以使用
if(-1 == GetEncoderClsid(L"image/jpeg", &pngClsid))
goto Ext;
//用编码器 将bmp保存成jpg的istream流
if(Ok != bm.Save(stream, &pngClsid, &enParameters))
goto Ext;
//将流转换成buffer
if( !stream_to_mem(stream, (void **)&szJpg, &nSizeJpg))
goto Ext;
if(nSizeJpg > 0) nRet = TRUE;
Ext:
//清理资源
if(stream) stream->Release();
//SelectObject(memDC,oldmemBitmap);
SelectObject(memDC,oldmemBitmap);
if(!memDC.DeleteDC())
PrintToDebugView(LOG_INFO,"memDC Delete err:%d",GetLastError());
if(!memBitmap.DeleteObject())
PrintToDebugView(LOG_INFO,"memBitmap delete err:%d",GetLastError());
return nRet;
}
这个类里的定义声明:
#pragma once
#include "GdiPlus.h"
using namespace Gdiplus;
#pragma comment(lib,"gdiplus.lib")
class HintScreenCapture
{
private:
GdiplusStartupInput m_gdiplusStartupInput;//
ULONG_PTR m_pGdiToken;//
TCHAR *szJpg; //指向JPG的指针
size_t nSizeJpg; //缓冲区长度
public:
int GetEncoderClsid(const WCHAR* format, CLSID* pClsid); //获取编码器
BOOL stream_to_mem(IStream *stream, void **outbuf, size_t *size); //istream流数据转换为buffer malloc出来滴
BOOL GetScreenCapture();
HintScreenCapture(void);
~HintScreenCapture(void);
};
有了这些调用就简单了
ScreenCapture *m_pScreenCap = new ScreenCapture(); //建立实例
m_pScreenCap->GetScreenCapture(); //抓
CFile file;
file.Open("c:\\a.jpg",CFile::modeCreate|CFile::modeWrite); //写一个文件
file.Write(m_pScreenCap->szJpg,m_pScreenCap->nSizeJpg); //写
file.Close();
delete m_pScreenCap; //删除实例
欢迎大家参考,随时使用,使用请记得标记版权。
留言列表:
由 blackfeather 于 2013-7-10 11:29:16 最后编辑