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

时间:2009-06-15 16:53:42

标签: c windows gdi+ jpeg screenshot

我正在尝试找到一种(有点)简单的方法来在窗口上截取屏幕截图并将生成的HBITMAP保存为JPEG。这里棘手的部分是因为代码是在C中我不能使用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;

}

11 个答案:

答案 0 :(得分:17)

好的,经过这么多努力就得到了答案:

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(hModuleThread, "GdipCreateBitmapFromHBITMAP");
    if(lGdipCreateBitmapFromHBITMAP == NULL)
    {
        // error
        return 0;
    }

    // load GdipSaveImageToFile
    lGdipSaveImageToFile = (pGdipSaveImageToFile)GetProcAddress(hModuleThread, "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;
}
  • 什么是hModuleThread?查看here。您可以使用GetModuleHandle()

  • 替换
  • 什么是GetEncoderClsid?看here

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

答案 1 :(得分:5)

转换为扁平的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没有任何作用。此外,没有GdipDeleteBitmap(),就像其他GDI +对象一样。因此,我不清楚需要采取哪些措施来避免泄漏。

修改 此代码未解决Microsoft提供的GDI +头文件在C ++命名空间中声明所有必需功能的事实。一种解决方案是将必要的声明(并根据需要转换为C代码)复制到您自己的标头中。另一种可能性是使用WineMono项目提供的标头。它们似乎在编译时表现得更好,如C代码。

答案 2 :(得分:4)

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

如果你不能这样做,独立Jpeg小组自20世纪后期开始提供纯C Jpeg代码:A very minimal web page is available here.

答案 3 :(得分:1)

在代码开头尝试此操作

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

GdipSaveImageToFile()可以用c ++编译我相信。

在纯C中,最好的方法是尝试制作单个包含。查找“gdiplus.h”中的函数声明,并为不包含名称空间的每个函数添加最小包含,例如#include "Gdiplusflat.h"

GdipSaveImageToFile()

答案 4 :(得分:0)

您可能必须使用外部库在C代码中创建JPEG - 不会有标准库函数来执行此操作。当然,您也可以根据标准研究文件格式并编写一个函数来生成您自己的文件格式。你可以开始at wikipedia。如果您真的不能只使用外部库,您可以阅读库的相关功能,以了解如何生成自己的JPEG。但这听起来比使用图书馆要多得多。

另外,我不相信图书馆的大小真的与C有关 - 我相信你只能得到你从那个图书馆调用的函数的大小(我想,无论如何)。当然,如果所有功能都紧密耦合,那么无论如何它都是巨大的。

答案 5 :(得分:0)

良好的图像压缩很难。对于常见格式存在库代码的原因。它需要很多代码才能完成。你对这个问题的限制并没有让它听起来像是一个实际的解决方案。

有几件事要尝试。

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

  2. 尝试在您编写的BMP中使用行程编码。 RLE是BMP的文档格式,应该有大量的示例代码来实现它。它最适用于具有大量相同颜色的长期运行的图像。没有太多渐变和填充的典型屏幕符合该描述。

  3. 如果这些还不够,那么您可能需要采用更强大的压缩效果。 libjpeg的源代码很容易获得,并且可以静态链接它而不是使用DLL。需要花费一些精力才能配置你需要的位,但是压缩和解压缩代码几乎完全相互独立,并且将库几乎分成两半。

答案 6 :(得分:0)

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

答案 7 :(得分:0)

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

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

答案 8 :(得分:0)

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

如果我将我的C成像代码并将其剥离为仅仅JPEG编码器,它可能会生成大约20K的代码(如果您不需要支持灰度图像,则会减少)。

答案 9 :(得分:0)

我遇到了同样的问题并用这段代码解决了。 您还需要在链接器的输入中包含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文件输出添加质量值来增强此代码,在此线程中执行此操作的代码更高。

答案 10 :(得分:-1)

不要重新发明轮子。

如果你需要的是一个采用屏幕截图并将其保存为jpeg的程序,你可以使用ImageMagickimport

shell命令只是:

import screen.jpg

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