内存中截屏并转换为JPG编码的数据 -- VC

其他代码 blackfeather

 

最近工作中要用到截屏,都知道使用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;                                                           //删除实例

 

 

 

 

欢迎大家参考,随时使用,使用请记得标记版权。

 

 

 

评论列表:

轩辕钧
楼主 ,我想尝试在system权限下截取活动桌面的屏幕,但用opendesktop在win7下失败。。。请问有方法没?。
由 blackfeather 于 2013-7-10 11:29:16 最后编辑
admin
印象中要先搞搞windowstation
admin2013/7/10 11:21:25 回复
轩辕钧2013/7/10 8:55:08 回复

发表评论: