Search code examples
cwindowsgdi+jpegscreenshot

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


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;

}

Solution

  • 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?