16

我正在尝试找到一种(有点)简单的方法来在窗口上截取屏幕截图并将生成的 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;

}
4

11 回答 11

20

好的,经过大量努力,这是答案:

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?看这里。您可以替换为GetModuleHandle()

  • 什么是 GetEncoderClsid?看这里

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

于 2009-06-23T19:25:03.923 回答
6

转换为平面 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 代码,它们似乎都表现得更好。

于 2009-06-16T00:18:16.103 回答
4

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

如果您无法做到这一点,Independent Jpeg 小组自 20 世纪后期就提供了纯 C Jpeg 代码:此处提供了一个非常小的网页。

于 2009-06-15T16:58:59.680 回答
3

在代码的开头试试这个

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

我相信在 C++ 中 GdipSaveImageToFile() 可以编译。

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

于 2009-11-26T14:22:20.817 回答
0

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

另外,我不相信库的大小真的与 C 有关系——我相信你只能得到你从那个库调用的函数的大小(我认为,无论如何)。当然,如果所有功能都紧密耦合,那么无论如何它都会很大。

于 2009-06-15T17:47:30.700 回答
0

良好的图像压缩很难。通用格式存在库代码是有原因的。它需要大量的代码来完成。您对问题的限制听起来不像有一个实际的解决方案。

有几件事可以尝试。

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

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

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

于 2009-06-15T18:11:06.850 回答
0

你看过FreeImage 项目吗?是的,它是一个外部库,但它非常小(~1Mb)。几乎可以做任何你想做的事情,包括图像。绝对值得了解,即使不是为了这个项目。

于 2009-06-16T00:22:26.170 回答
0

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

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

于 2009-06-16T01:12:55.753 回答
0

如果你真的很关心空间,你也可以抛弃 C 运行时库。如果您只使用几个函数,只需编写您自己的版本(例如 strcpy)。可以编写只有几 K 字节的 Windows 应用程序。我可以向您发送一个小型示例应用程序来执行此操作。

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

于 2009-07-03T20:11:33.567 回答
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 文件输出添加质量值来增强此代码,执行此操作的代码在此线程中更高。

于 2014-01-22T03:34:23.077 回答
-2

不要重新发明轮子。

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

shell 命令只是:

import screen.jpg

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

于 2009-06-15T17:47:39.420 回答