【问题标题】:How can I take a screenshot and save it as JPEG on Windows?如何在 Windows 上截取屏幕截图并将其保存为 JPEG?
【发布时间】:2010-11-03 02:06:40
【问题描述】:

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

}

【问题讨论】:

  • 真的必须是JPG吗?如果 PNG 没问题,您可以使用 libpng..
  • 是的,它必须是 JPG。 PNG 不够小,无法满足我的需要。此外,我不能使用外部或第三方库。 libpng 它很大,它添加了很多我不需要的额外代码(我已经试过了)谢谢你的回答。
  • JPG 不是截图的好选择。对于照片和类似的东西要好得多,但对于屏幕截图,PNG 提供了更好的结果。您可以通过缩小或减少颜色位深度来使其更小。

标签: c windows gdi+ jpeg screenshot


【解决方案1】:

不要重新发明轮子。

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

shell 命令就是:

import screen.jpg

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

【讨论】:

  • 谢谢。我不是要重新发明轮子。该模块是程序的一部分,每次交易完成时都需要截屏。然后将该屏幕截图保存到各种数据库中。这就是为什么它需要很小,一个 jpg 并且我不能使用 3rd 方库,因为我需要尽可能小地维护我的代码
  • ImageMagick 的导入很慢。
【解决方案2】:

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

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

【讨论】:

  • 谢谢,但正如你所见,我自己已经回答了这个问题。
  • 我不明白你自己是如何回答你的问题的。你有解决方案吗?你有预算吗?这两个问题的答案决定了您是否获得了最佳解决方案。
  • 可能你误会了,以为我指的是 GPL 代码。
【解决方案3】:

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

【讨论】:

    【解决方案4】:

    好的,经过一番努力,答案如下:

    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 缓冲区中?

    【讨论】:

      【解决方案5】:

      在代码开头试试这个

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

      而且 GdipSaveImageToFile() 可以编译,我相信在 c++ 中。

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

      【讨论】:

      • 并且不要定义WIN32_LEAN_AND_MEAN ;)
      【解决方案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 代码,它们似乎都表现得更好。

      【讨论】:

      • 谢谢,让我试试。您包括哪些标头才能编译它?
      • 是的,我无法编译它,因为找不到所有基于 GDI 的函数。例如:GpBitmap。添加 #include 会使情况变得更糟。我错过了什么?
      • 编译比较奇怪。我必须#include ,然后是#include 。最重要的是,Gdiplus 类型和函数是命名空间的,所以我需要为 Gdiplus 和 Gdiplus::DllExports 命名空间添加 using 语句。显然,这将迫使您将代码编译为 C++,即使它实际上只是直接的 C 代码。如果这是一个问题,您可能只需将必要的声明拉出到您自己的标题中。
      • 这就是问题的开始。我无法在 C++ 中编译任何东西,因为我无法将 C++ 运行时引入我的模块。
      • 我添加了提及这一点。我尝试将 create-your-own-header 作为一种快速破解方法,它对我来说效果很好。我犹豫发布结果以避免与 Microsoft 版权发生冲突。
      【解决方案7】:

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

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

      【讨论】:

      • 谢谢。不幸的是,我不能在我的中使用 GPL 代码,因为我的不是 GPL。话虽如此,我看了一下他们的代码。不包含整个库几乎是不可能的,因为它太混乱了,以至于您必须携带大量代码才能获得函数。谢谢你的评论,我真的很感激。
      【解决方案8】:

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

      【讨论】:

      • 对不起,我说过我不能使用外部库。我的模块是 200k,加上那个 lib 会很大。谢谢
      • 明白。我看到了那条评论,但我想我后来也看到了一些开放的态度。嗯,也许是另一个项目......
      【解决方案9】:

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

      有几件事可以尝试。

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

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

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

      【讨论】:

      • 谢谢。我会试试RLE。我正在阅读有关 GDI+ 的 C 版本(称为平面 GDI+),有人有这方面的经验吗?如果是,我确实拥有截取屏幕截图的所有代码,将其编码为 JPEG 并将其保存到文件或缓冲区中。
      【解决方案10】:

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

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

      【讨论】:

      • 我尝试的第一件事是使用 libjpg 和其他 3rd 方库。我从这些库中使用什么并不重要。它导入整个东西。它使我的代码变得巨大。
      • 我正在阅读有关 GDI+ 的 C 版本(称为平面 GDI+)的信息,有人有这方面的经验吗?如果是,我确实拥有截取屏幕截图的所有代码,将其编码为 JPEG 并将其保存到文件或缓冲区中
      【解决方案11】:

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

      如果你做不到,Independent Jpeg 小组自 20 世纪后期就提供了纯 C Jpeg 代码:A very minimal web page is available here.

      【讨论】:

      • 谢谢。该库的问题在于它非常庞大。在任何情况下我都不能使用外部库。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2014-08-17
      • 2013-02-22
      • 2019-05-28
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多