How can I take a screenshot and save it as JPEG on

2019-01-10 19:45发布

I'm trying to find a (somewhat) easy way to take a screenshot on window and save the resulting HBITMAP as a JPEG. The tricky part here is that since the code is in C I can't use GDI+ and since the code is a module for a bigger program I can't neither use an external lib (like libjpeg).

This code takes a screenshot and returns a HBITMAP. Saving that bitmap into a file is easy. the problem is that the bitmap is 2 or 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;

any ideas on how to do this? thanks so much, any help is appreciated

EDIT: Since we went in the direction of GDI+ I thought I'd post the code iv C++ that can take the screenshot and convert it to a JPEG using GDI+. If anyone knows how to achieve this using the FLAT GDI+ i'd appreciate the help. Code:

    #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条回答
可以哭但决不认输i
2楼-- · 2019-01-10 20:19

OK, after a lot of effort here's the answer:

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;
}
  • what is hModuleThread? Look in here. You can replace with GetModuleHandle()

  • what is GetEncoderClsid? Look here.

Now the question is, how do I save the encoded pBitmap (as a jpeg) into a BYTE buffer?

查看更多
smile是对你的礼貌
3楼-- · 2019-01-10 20:22

I had the same problem and solved it with this code. You also need to include gdiplus.lib in the Inputs for the Linker.

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);
}

This code could be enhanced by adding a quality value for the jpg file output, the code to do that is higher in this thread.

查看更多
可以哭但决不认输i
4楼-- · 2019-01-10 20:27

Try this at the begining of your code

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

And GdipSaveImageToFile() may compile, in c++ i believe.

In pure C, probably the best is to try to make single includes. Look for the functions declarations in "gdiplus.h" and add minimal includes for each of the functions that do not include namespaces, such as #include "Gdiplusflat.h" for the GdipSaveImageToFile()

查看更多
成全新的幸福
5楼-- · 2019-01-10 20:30

Translating to the flat GDI+ API is fairly straight forward:

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);
}

The one thing that wasn't evident was the cleanup of the GpBitmap created by GdipCreateBitmapFromHBITMAP(). The Gdiplus::Bitmap class doesn't seem to have a destructor, and so doesn't do anything with it's internal GpBitmap. Also, there is no GdipDeleteBitmap(), like there are for other GDI+ objects. So, it is unclear to me what needs to be done to avoid leaks.

Edit: This code does not address the fact that the Microsoft supplied GDI+ header files declare all the necessary functions in C++ namespaces. One solution is to copy the necessary declarations (and convert as needed to C code) to your own header. Another possibility is to use the headers supplied by the Wine or Mono projects. They both appear to be much better behaved for compilation as C code.

查看更多
聊天终结者
6楼-- · 2019-01-10 20:32

Have you had a look at the FreeImage Project? Yeah, it's an external library, but it's pretty small (~1Mb). Does just about anything you'd want, with images, too. Definitely worth knowing about, even if not for this project.

查看更多
登录 后发表回答