16

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;

}
Sinan Ünür
  • 116,958
  • 15
  • 196
  • 339
wonderer
  • 3,487
  • 11
  • 49
  • 59
  • does it really have to be a JPG? if a PNG is ok you could use libpng.. – sean riley Jun 15 '09 at 17:12
  • Yes, it has to be JPG. PNG is not small enough for what I need. Besides, I can't use external or 3rd party libs. libpng it's big and it adds a lot of extra code that I don't need (i tried this already) Thanks for the answer tho. – wonderer Jun 15 '09 at 17:25
  • JPG is not good choice for screenshots. It's much better for photos and like, but for screenshots PNG provides much better results. You can make it smaller by scaling down or color bit depth reduction. – Andriy Tylychko Feb 11 '11 at 17:39

11 Answers11

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?

lurscher
  • 25,930
  • 29
  • 122
  • 185
wonderer
  • 3,487
  • 11
  • 49
  • 59
6

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.

Chris Ostler
  • 677
  • 4
  • 9
  • Thanks, let me try it. What headers are you including to be able to compile this? – wonderer Jun 16 '09 at 11:26
  • Yeah, I can't compile this since all the GDI-based function cannot be found. for example: GpBitmap. Adding #include will make it worse. What am I missing? – wonderer Jun 16 '09 at 14:40
  • The compilation is rather odd. I had to #include , then #include . 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. – Chris Ostler Jun 16 '09 at 15:53
  • 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. – wonderer Jun 16 '09 at 16:35
  • 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. – Chris Ostler Jun 19 '09 at 00:32
  • OK, can you send the header to my email? wonderer_isr@yahoo.com – wonderer Jun 19 '09 at 12:45
  • 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 Jun 22 '09 at 13:10
  • i guess you are not going post the header. – wonderer Jun 23 '09 at 12:18
  • See the reply to see the solution for this – wonderer Jun 23 '09 at 19:26
4

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.

Aric TenEyck
  • 8,002
  • 1
  • 34
  • 48
3

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

Luis
  • 31
  • 2
0

If you're really concerned about space, you can probably jettison the C runtime libraries as well. If you're only using a few functions, just write your own version (e.g. strcpy). It is possible to write Windows applications that are only a few K bytes. I can send you a small sample app which does this.

If I take my C imaging code and strip it down to just the JPEG encoder, it will probably produce about 20K of code (less if you don't need to support grayscale images).

Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
BitBank
  • 8,500
  • 3
  • 28
  • 46
  • Thanks but as you can see I already answered the question myself. – wonderer Jul 12 '09 at 20:04
  • 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 Jul 19 '09 at 21:33
  • Maybe you misunderstood and thought that I was referring to GPL code. – BitBank Jul 19 '09 at 21:33
0

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.

0

You will probably have to use an external library to create a JPEG in C code--there won't be a standard library function to do it. Of course, you could also study the file format and write a function to generate your own, based on the criteria. You could start at wikipedia. If you really can't just use an external library, you could read into the relevant functions of the library to learn about how to generate your own JPEGs. But that sounds like a lot more work than just using the library.

Also, I don't believe the size of a library really comes into play with C -- I believe you only get the size of the functions you call from that library (I think, anyway). Of course if all the functions are tightly coupled than it'd be huge anyway.

Carson Myers
  • 37,678
  • 39
  • 126
  • 176
  • 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 Jun 15 '09 at 17:53
  • 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 Jun 15 '09 at 18:33
0

Good image compression is hard. There's a reason library code exists for the common formats. It requires a lot of code to do. Your constraints on the problem don't make it sound like there is a practical solution.

There are a couple things to try.

  1. Use an 8-bit per pixel BMP rather than a true color BMP. This assumes that the information loss in color mapping is acceptable, of course. It will buy you a factor of three in file size over a 24-bit BMP.

  2. Try using run length encoding in the BMP you write. RLE is a documented format for BMP, and there should be plenty of sample code out there to make it. It works best on images that have a lot of long runs of identical color. A typical screen without too many gradients and fills fits that description.

  3. If those aren't sufficient, then you might need to resort to a stronger compression. The source code to libjpeg is readily available, and it is possible to statically link it rather than using the DLL. It will require some effort to configure in only the bits you need, but the compression and decompression code are almost completely independent of each other and that splits the library almost in half.

RBerteig
  • 41,948
  • 7
  • 88
  • 128
  • 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 Jun 15 '09 at 18:32
  • The flat gdi+ site is http://msdn.microsoft.com/en-us/library/ms533969(VS.85).aspx – wonderer Jun 15 '09 at 18:32
0

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.

Karl E. Peterson
  • 751
  • 5
  • 10
0

libjpeg is free, open source, coded in C. You can copy their code into yours.

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

Windows programmer
  • 7,871
  • 1
  • 22
  • 23
  • 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 Jun 16 '09 at 11:23
-2

Don't reinvent the wheel.

If what you need is a programme that takes a screen shot and saves it as a jpeg, you can just use ImageMagick's import.

The shell command would simply be:

import screen.jpg

Of course, you could save the above as takeScreenshot.bat and you'd have a programme that matches your requirements.

Vertexwahn
  • 7,709
  • 6
  • 64
  • 90
scvalex
  • 14,931
  • 2
  • 34
  • 43
  • 1
    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 Jun 15 '09 at 17:52
  • ImageMagick's import is slow. – Dominic Cerisano Jan 24 '18 at 15:59