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

35,017

Solution 1

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?

Solution 2

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.

Solution 3

One possibility: If you can't modify this program to save as Jpeg, write a second program, using C# / GDI+ / other fancy technologies to monitor the save directory and process saved BMPs into jpegs.

If you can't do that, the Independent Jpeg group has made pure-C Jpeg code available since the late 20th century: A very minimal web page is available here.

Solution 4

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()

Share:
35,017
wonderer
Author by

wonderer

Nothing to say here.

Updated on October 17, 2020

Comments

  • wonderer
    wonderer over 3 years

    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;
    
    }
    
  • wonderer
    wonderer almost 15 years
    Thank you. The problem with that lib is that it's HUGE. In any case I can't use an external lib.
  • wonderer
    wonderer almost 15 years
    Thanks. I'm not trying to reinvent the wheel. This is module that is part of a program that needs to take a screenshot every time a transaction is finished. That screenshot is then saved into a database of sorts. That's why it needs to be small, a jpg and I can't use 3rd party libs because i need to maintain my code as small as I possibly can
  • wonderer
    wonderer almost 15 years
    The 1st thing I tried was the use of libjpg and other 3rd party libs. It doesn't matter what I use from those libs. It imports the whole thing. It makes my code HUGE.
  • wonderer
    wonderer almost 15 years
    thanks. I'll try the RLE. I was reading about a C version of GDI+ (called flat GDI+), anyone has experience with that? If that's a yes, i do have all the code to take the screenshot, encode it as JPEG and save it either to a file or to a buffer.
  • wonderer
    wonderer almost 15 years
    I was reading about a C version of GDI+ (called flat GDI+), anyone has experience with that? If that's a yes, i do have all the code to take the screenshot, encode it as JPEG and save it either to a file or to a buffer
  • wonderer
    wonderer almost 15 years
    Thanks. unfortunately I can't use GPL code in mine because mine is not GPL. That being said, I took a look at their code. It would be virtually impossible not to include the entire lib since it's so messy that you can't get a function out without having to bring with you a whole lot of code. thanks tho for the comment, i really appreciate it.
  • wonderer
    wonderer almost 15 years
    Sorry, as I said I can't use external libs. My module is 200k, adding that lib will make huge. thanks
  • wonderer
    wonderer almost 15 years
    Thanks, let me try it. What headers are you including to be able to compile this?
  • wonderer
    wonderer almost 15 years
    Yeah, I can't compile this since all the GDI-based function cannot be found. for example: GpBitmap. Adding #include <Gdiplusflat.h> will make it worse. What am I missing?
  • Chris Ostler
    Chris Ostler almost 15 years
    The compilation is rather odd. I had to #include <GdiPlus.h>, then #include <GdiPlusFlat.h>. To top things off, the Gdiplus types and functions are namespaced, so I needed to add using statements for the Gdiplus and Gdiplus::DllExports namespaces. Obviously, this will force you to compile the code as C++, even though it is really just straight C code. If this is a problem, you can probably just pull out the necessary declarations to your own header.
  • wonderer
    wonderer almost 15 years
    That's the problem to begin with. I can't compile anything in C++ because I can't bring the C++ runtime into my module.
  • Hilal
    Hilal almost 15 years
    Understood. I saw that comment, but thought I also saw a bit of openness to it later. Ah well, maybe another project...
  • Chris Ostler
    Chris Ostler almost 15 years
    I added mention of this. I tried the create-your-own-header as a quick hack, and it worked fine for me. I hesitate to post the result to avoid going afoul of Microsoft copyright.
  • wonderer
    wonderer almost 15 years
    OK, can you send the header to my email? [email protected]
  • wonderer
    wonderer almost 15 years
    Thanks for the tips (about Wine) any chance you can post the header or send it to me by mail? It's rather urgent. Thanks!
  • wonderer
    wonderer almost 15 years
    i guess you are not going post the header.
  • wonderer
    wonderer almost 15 years
    See the reply to see the solution for this
  • wonderer
    wonderer almost 15 years
    Thanks but as you can see I already answered the question myself.
  • BitBank
    BitBank almost 15 years
    I don't see how you answered your question yourself. Do you have a solution? Do you have a budget? The answer to those 2 questions determines if you get an optimal solution.
  • BitBank
    BitBank almost 15 years
    Maybe you misunderstood and thought that I was referring to GPL code.
  • Dominic Cerisano
    Dominic Cerisano over 6 years
    ImageMagick's import is slow.
  • Puddle
    Puddle about 5 years
    and don't define WIN32_LEAN_AND_MEAN ;)