如何在Windows上截取屏幕截图并将其保存为JPEG?

我试图find一个(有点)简单的方法来在窗口上截图并将生成的HBITMAP保存为JPEG。 这里棘手的部分是因为代码是在CI不能使用GDI +,因为代码是一个更大的程序模块,我不能使用外部库(如libjpeg)。

此代码需要截图并返回一个HBITMAP。 将该位图保存到文件中很容易。 问题是位图是2或3mb。

HDC hDCMem = CreateCompatibleDC(NULL); HBITMAP hBmp; RECT rect; HDC hDC; HGDIOBJ hOld; GetWindowRect(hWnd, & rect); hBmp = NULL; { hDC = GetDC(hWnd); hBmp = CreateCompatibleBitmap(hDC, rect.right - rect.left, rect.bottom - rect.top); ReleaseDC(hWnd, hDC); } hOld = SelectObject(hDCMem, hBmp); SendMessage(hWnd, WM_PRINT, (WPARAM) hDCMem, PRF_CHILDREN | PRF_CLIENT | PRF_ERASEBKGND | PRF_NONCLIENT | PRF_OWNED); SelectObject(hDCMem, hOld); DeleteObject(hDCMem); return hBmp; 

任何想法如何做到这一点? 非常感谢,任何帮助表示赞赏

编辑:因为我们走在GDI +的方向,我想我会张贴代码iv C ++,可以采取截图,并使用GDI +将其转换为JPEG。 如果有人知道如何使用FLAT GDI +实现这一点,我会很感激这个帮助。 码:

  #include <windows.h> #include <stdio.h> #include <gdiplus.h> using namespace Gdiplus; int GetEncoderClsid(WCHAR *format, CLSID *pClsid) { unsigned int num = 0, size = 0; GetImageEncodersSize(&num, &size); if(size == 0) return -1; ImageCodecInfo *pImageCodecInfo = (ImageCodecInfo *)(malloc(size)); if(pImageCodecInfo == NULL) return -1; GetImageEncoders(num, size, pImageCodecInfo); for(unsigned int j = 0; j < num; ++j) { if(wcscmp(pImageCodecInfo[j].MimeType, format) == 0){ *pClsid = pImageCodecInfo[j].Clsid; free(pImageCodecInfo); return j; } } free(pImageCodecInfo); return -1; } int GetScreeny(LPWSTR lpszFilename, ULONG uQuality) // by Napalm { ULONG_PTR gdiplusToken; GdiplusStartupInput gdiplusStartupInput; GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL); HWND hMyWnd = GetForegroundWindow(); // get my own window RECT r; // the area we are going to capture int w, h; // the width and height of the area HDC dc; // the container for the area int nBPP; HDC hdcCapture; LPBYTE lpCapture; int nCapture; int iRes; CLSID imageCLSID; Bitmap *pScreenShot; HGLOBAL hMem; int result; // get the area of my application's window //GetClientRect(hMyWnd, &r); GetWindowRect(hMyWnd, &r); dc = GetWindowDC(hMyWnd);// GetDC(hMyWnd) ; w = r.right - r.left; h = r.bottom - r.top; nBPP = GetDeviceCaps(dc, BITSPIXEL); hdcCapture = CreateCompatibleDC(dc); // create the buffer for the screenshot BITMAPINFO bmiCapture = { sizeof(BITMAPINFOHEADER), w, -h, 1, nBPP, BI_RGB, 0, 0, 0, 0, 0, }; // create a container and take the screenshot HBITMAP hbmCapture = CreateDIBSection(dc, &bmiCapture, DIB_PAL_COLORS, (LPVOID *)&lpCapture, NULL, 0); // failed to take it if(!hbmCapture) { DeleteDC(hdcCapture); DeleteDC(dc); GdiplusShutdown(gdiplusToken); printf("failed to take the screenshot. err: %d\n", GetLastError()); return 0; } // copy the screenshot buffer nCapture = SaveDC(hdcCapture); SelectObject(hdcCapture, hbmCapture); BitBlt(hdcCapture, 0, 0, w, h, dc, 0, 0, SRCCOPY); RestoreDC(hdcCapture, nCapture); DeleteDC(hdcCapture); DeleteDC(dc); // save the buffer to a file pScreenShot = new Bitmap(hbmCapture, (HPALETTE)NULL); EncoderParameters encoderParams; encoderParams.Count = 1; encoderParams.Parameter[0].NumberOfValues = 1; encoderParams.Parameter[0].Guid = EncoderQuality; encoderParams.Parameter[0].Type = EncoderParameterValueTypeLong; encoderParams.Parameter[0].Value = &uQuality; GetEncoderClsid(L"image/jpeg", &imageCLSID); iRes = (pScreenShot->Save(lpszFilename, &imageCLSID, &encoderParams) == Ok); delete pScreenShot; DeleteObject(hbmCapture); GdiplusShutdown(gdiplusToken); return iRes; } 

好的,经过这么多的努力,答案是:

 int SaveJpeg(HBITMAP hBmp, LPCWSTR lpszFilename, ULONG uQuality) { ULONG *pBitmap = NULL; CLSID imageCLSID; EncoderParameters encoderParams; int iRes = 0; typedef Status (WINAPI *pGdipCreateBitmapFromHBITMAP)(HBITMAP, HPALETTE, ULONG**); pGdipCreateBitmapFromHBITMAP lGdipCreateBitmapFromHBITMAP; typedef Status (WINAPI *pGdipSaveImageToFile)(ULONG *, const WCHAR*, const CLSID*, const EncoderParameters*); pGdipSaveImageToFile lGdipSaveImageToFile; // load GdipCreateBitmapFromHBITMAP lGdipCreateBitmapFromHBITMAP = (pGdipCreateBitmapFromHBITMAP)GetProcAddress(hmoduleeThread, "GdipCreateBitmapFromHBITMAP"); if(lGdipCreateBitmapFromHBITMAP == NULL) { // error return 0; } // load GdipSaveImageToFile lGdipSaveImageToFile = (pGdipSaveImageToFile)GetProcAddress(hmoduleeThread, "GdipSaveImageToFile"); if(lGdipSaveImageToFile == NULL) { // error return 0; } lGdipCreateBitmapFromHBITMAP(hBmp, NULL, &pBitmap); iRes = GetEncoderClsid(L"image/jpeg", &imageCLSID); if(iRes == -1) { // error return 0; } encoderParams.Count = 1; encoderParams.Parameter[0].NumberOfValues = 1; encoderParams.Parameter[0].Guid = EncoderQuality; encoderParams.Parameter[0].Type = EncoderParameterValueTypeLong; encoderParams.Parameter[0].Value = &uQuality; lGdipSaveImageToFile(pBitmap, lpszFilename, &imageCLSID, &encoderParams); return 1; } 
  • 什么是hmoduleeThread? 看这里 你可以用GetmoduleeHandle()来替换

  • 什么是GetEncoderClsid? 看这里

现在的问题是,如何将编码的pBitmap(作为jpeg)保存到BYTE缓冲区中?

一种可能性:如果你不能修改这个程序保存为Jpeg,使用C#/ GDI + /其他花哨的技术来编写第二个程序来监视保存目录,并将保存的BMP处理成jpeg。

如果你不能做到这一点,独立的Jpeg小组自20世纪后期就已经制作出纯C的Jpeg代码:这里有一个非常小的网页。

翻译成平坦的GDI + API非常简单:

 void SaveJpeg(HBITMAP hBmp, LPCWSTR lpszFilename, ULONG uQuality) { GpBitmap* pBitmap; GdipCreateBitmapFromHBITMAP(hBmp, NULL, &pBitmap); CLSID imageCLSID; GetEncoderClsid(L"image/jpeg", &imageCLSID); EncoderParameters encoderParams; encoderParams.Count = 1; encoderParams.Parameter[0].NumberOfValues = 1; encoderParams.Parameter[0].Guid = EncoderQuality; encoderParams.Parameter[0].Type = EncoderParameterValueTypeLong; encoderParams.Parameter[0].Value = &uQuality; GdipSaveImageToFile(pBitmap, lpszFilename, &imageCLSID, &encoderParams); } 

唯一不明显的是GdipCreateBitmapFromHBITMAP()创建的GpBitmap的清理。 Gdiplus :: Bitmap类似乎没有析构函数,所以不会对它的内部GpBitmap做任何事情。 另外,没有像其他GDI +对象一样的GdipDeleteBitmap()。 所以,我不清楚需要做什么来避免泄漏。

编辑:此代码不解决Microsoft提供的GDI +头文件在C ++名称空间中声明所有必需的功能的事实。 一种解决方案是将必要的声明(并根据需要转换为C代码)复制到您自己的头文件中。 另一种可能是使用Wine或Mono项目提供的头文件。 它们似乎比C代码更好地编译。

您可能必须使用外部库来创建C代码中的JPEG – 不会有标准库函数来执行此操作。 当然,你也可以学习文件格式,并根据标准编写一个自己生成的函数。 你可以从维基百科开始。 如果你真的不能只使用外部库,你可以阅读库的相关功能,了解如何生成自己的JPEG。 但是,这听起来像比使用图书馆更多的工作。

另外,我不相信图书馆的规模真的和C相提并论 – 我相信你只能从图书馆那里调用函数的大小(无论如何,我想)。 当然,如果所有的功能都紧密结合在一起,那么它将会是巨大的。

良好的图像压缩很难。 对于通用格式,存在一个库代码存在的原因。 它需要很多代码才能完成。 你对这个问题的限制并不能让人觉得有一个实际的解决方案。

有几件事情可以尝试。

  1. 使用每像素8位的BMP而不是真正的颜色BMP。 这当然假定颜色映射中的信息损失是可以接受的。 它将在24位BMP上为您购买文件大小的三倍。

  2. 尝试在您编写的BMP中使用运行长度编码。 RLE是BMP文档格式,应该有大量的示例代码。 它对具有相同颜色的很多长时间运行的图像效果最佳。 典型的屏幕没有太多的渐变和填充符合这个描述。

  3. 如果这些还不够,那么你可能需要采取更强大的压缩。 libjpeg的源代码是可用的,并且可以静态链接它而不是使用DLL。 这将需要一些努力,只配置你需要的位,但压缩和解压缩代码几乎是完全独立的,并将图书馆几乎分成两半。

你有没有看过FreeImage项目 ? 是的,这是一个外部图书馆,但它很小(〜1Mb)。 几乎任何你想要的东西,也有图像。 绝对值得了解,即使不是这个项目。

libjpeg是免费的,开源的,用C编码。你可以将他们的代码复制到你的代码中。

http://sourceforge.net/project/showfiles.php?group_id=159521

在你的代码开始时试试这个

 #include "windows.h" #include "gdiplus.h" using namespace Gdiplus; using namespace Gdiplus::DllExports; 

和GdipSaveImageToFile()可以编译,在c + +我相信。

在纯粹的C中,最好的办法是尝试使单个包含。 在“gdiplus.h”中查找函数声明,并为每个不包含名称空间的函数(如GdipSaveImageToFile() #include "Gdiplusflat.h"

我有同样的问题,并解决这个代码。 您还需要在链接器的输入中包含gdiplus.lib。

 struct GdiplusStartupInput { UINT32 GdiplusVersion; // Must be 1 (or 2 for the Ex version) UINT_PTR DebugEventCallback; // Ignored on free builds BOOL SuppressBackgroundThread; // FALSE unless you're prepared to call the hook/unhook functions properly BOOL SuppressExternalCodecs; // FALSE unless you want GDI+ only to use its internal image codecs. }; int __stdcall GdiplusStartup(ULONG_PTR *token, struct GdiplusStartupInput *gstart, struct GdiplusStartupInput *gstartout); int __stdcall GdiplusShutdown(ULONG_PTR token); int __stdcall GdipCreateBitmapFromFile(WCHAR *filename, void **GpBitmap); int __stdcall GdipSaveImageToFile(void *GpBitmap, WCHAR *filename, CLSID *encoder, void *params); int __stdcall GdipCreateBitmapFromStream(IStream *pstm, void **GpBitmap); int __stdcall GdipCreateHBITMAPFromBitmap(void *GpBitmap, HBITMAP *bm, COLORREF col); int __stdcall GdipCreateBitmapFromHBITMAP(HBITMAP bm, HPALETTE hpal, void *GpBitmap); int __stdcall GdipDisposeImage(void *GpBitmap); int __stdcall GdipImageGetFrameDimensionsCount(void *GpBitmap, int *count); int __stdcall GdipImageGetFrameDimensionsList(void *GpBitmap, GUID *guid, int count); int __stdcall GdipImageGetFrameCount(void *GpBitmap, GUID *guid, int *count); int __stdcall GdipImageSelectActiveFrame(void *GpBitmap, GUID *guid, int num); int __stdcall GdipImageRotateFlip(void *GpBitmap, int num); /* Save the image memory bitmap to a file (.bmp, .jpg, .png or .tif) */ int save_bitmap_to_file(char *filename, HBITMAP hbitmap) { wchar_t wstr[MAX_FILENAME]; int sts; size_t len; void *imageptr; /* actually an 'image' object pointer */ CLSID encoder; sts = FAILURE; _strlwr(filename); if(strstr(filename, ".png")) sts = CLSIDFromString(L"{557cf406-1a04-11d3-9a73-0000f81ef32e}", &encoder); /* png encoder classid */ else if(strstr(filename, ".jpg")) sts = CLSIDFromString(L"{557cf401-1a04-11d3-9a73-0000f81ef32e}", &encoder); /* jpg encoder classid */ else if(strstr(filename, ".jpeg")) sts = CLSIDFromString(L"{557cf401-1a04-11d3-9a73-0000f81ef32e}", &encoder); /* jpg encoder classid */ else if(strstr(filename, ".tif")) sts = CLSIDFromString(L"{557cf405-1a04-11d3-9a73-0000f81ef32e}", &encoder); /* tif encoder classid */ else if(strstr(filename, ".bmp")) sts = CLSIDFromString(L"{557cf400-1a04-11d3-9a73-0000f81ef32e}", &encoder); /* bmp encoder classid */ else if(strstr(filename, ".gif")) sts = CLSIDFromString(L"{557cf402-1a04-11d3-9a73-0000f81ef32e}", &encoder); /* gif encoder classid */ if(sts != 0) return(FAILURE); mbstowcs_s(&len, wstr, MAX_FILENAME, filename, MAX_FILENAME-1); /* get filename in multi-byte format */ sts = GdipCreateBitmapFromHBITMAP(hbitmap, NULL, &imageptr); if(sts != 0) return(FAILURE); sts = GdipSaveImageToFile(imageptr, wstr, &encoder, NULL); GdipDisposeImage(imageptr); /* destroy the 'Image' object */ return(sts); } 

可以通过为jpg文件输出添加质量值来增强此代码,在此线程中执行此操作的代码更高。

如果你真的关心空间,你也可以抛弃C运行库。 如果你只使用一些函数,只需编写你自己的版本(例如strcpy)。 可以编写只有几K字节的Windows应用程序。 我可以给你一个小样本应用程序这样做。

如果我把我的C图像代码放在JPEG编码器上,它可能会产生大约20K的代码(如果你不需要支持灰度图像的话,可能会少一些)。

不要重新发明轮子。

如果你需要的是一个程序,需要一个屏幕截图,并保存为JPEG格式,你可以使用ImageMagick的导入 。

shell命令可以简单地为:

 import screen.jpg 

当然,你可以将上面的内容保存为takeScreenshot.bat,并且你将有一个符合你的要求的程序。