views:

1464

answers:

12

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;

}
A: 

Here's example code.

It's in C#, but it's basically all pinvoke so it would be roughly the same code in C++.

Assaf Lavie
it looks promising. if I understood correctly they are importing the functions directly from the GDI lib and taking the screenshot like that?hmmm... how about the saving to JPEG? I mean, I can take the screenshot using pure API and messages. The only part that I have missing is the compression to jpeg
wonderer
JPG is tricky. The GDI+ solution only works for images whose width is divisible by 4. See this question I asked once about PNG (same deal): http://stackoverflow.com/questions/366768/convert-bitmap-to-png-in-memory-in-c-win32
Assaf Lavie
+3  A: 

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
Thank you. The problem with that lib is that it's HUGE. In any case I can't use an external lib.
wonderer
A: 

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
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
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
A: 

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.

Cheers.

scvalex
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
A: 

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
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
The flat gdi+ site is http://msdn.microsoft.com/en-us/library/ms533969(VS.85).aspx
wonderer
A: 

Here is some documentation on GDI+: http://msdn.microsoft.com/en-us/library/ms533814(VS.85).aspx. It looks like you might be able to convert that bitmap to JPEG using this API without relying on third party libraries (which I IIRC was your original question).

EDIT: OK, looking at http://msdn.microsoft.com/en-us/library/ms534038(VS.85).aspx, there are functions for creating a GDI+ object from various types of handles. I have never worked with these interfaces before, but if I were trying to do this, I would use the appropriate function from that list to create the object and then modify the code for saving a PNG file given above to save in JPEG format using the functions discussed at http://msdn.microsoft.com/en-us/library/ms534041(VS.85).aspx.

Specifically, when you have the bitmap, use GdipCreateBitmapFromHBITMAP to create the GDI+ object. Then, make the appropriate changes in http://msdn.microsoft.com/en-us/library/ms533837(VS.85).aspx. Since Bitmap is a subclass of Image, this should work.

I would try to do this but I don't regularly do Windows programming and it would take me a while to read the relevant API documents to tie all the pieces together.

Sinan Ünür
Thanks. If you read my original question you can see that I am aware of the GDI+ but i can't use it because it's a collection of C++ classes and I need to use pure C. There is a flat GDI+ that is pure C but 1) no one knows how to use it, 2) MS advice against using it and 3) no one knows how to use it.I do have code in C++ that does exactly what I asked: it takes a screenshot and converts it to JPEG. but I can't use it.
wonderer
@wonderer Sorry, can't write the code right now, but I updated my post to explain how I would go about this.
Sinan Ünür
Thanks. I'm trying too without success since I don't know what header to include and the MSDN is not clear about this.
wonderer
@wonderer http://msdn.microsoft.com/en-us/library/ms533969(VS.85).aspx says "Microsoft Windows GDI+ exposes a flat API that consists of about 600 functions, which are implemented in Gdiplus.dll and declared in Gdiplusflat.h." and then cautions not to use the flat API.
Sinan Ünür
Thanks Sinan, if you read my comments you can see that i am aware of that: "There is a flat GDI+ that is pure C but 1) no one knows how to use it, 2) MS advice against using it and 3) no one knows how to use it"However, the way things are going i think it's the only solution for me.
wonderer
@wonderer I am going to have to try then because, for this particular case, it seems really straightforward to me (famous last words ;-)
Sinan Ünür
Well, you let me know how it went. I'm really running out of options here and my code is way overdue.Thanks for the help!
wonderer
Sinan, any luck?
wonderer
@wonderer I haven't had time to look at it yet.
Sinan Ünür
@wonderer I give up. I cannot get Gdiplusflat.h to be included without running into the same problems Chris Ostler ran into. Apologies.
Sinan Ünür
Sinan, thanks for the effort. I really appreciate it.
wonderer
@ wonderer You are welcome. Wish things could have been as easy as I thought they were going to be.
Sinan Ünür
+1  A: 

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
Thanks, let me try it.What headers are you including to be able to compile this?
wonderer
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?
wonderer
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.
Chris Ostler
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
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
OK, can you send the header to my email? [email protected]
wonderer
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
i guess you are not going post the header.
wonderer
See the reply to see the solution for this
wonderer
A: 

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
Sorry, as I said I can't use external libs. My module is 200k, adding that lib will make huge.thanks
wonderer
Understood. I saw that comment, but thought I also saw a bit of openness to it later. Ah well, maybe another project...
Karl E. Peterson
A: 

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
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
+2  A: 

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

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

wonderer
+1 Thanks for reporting back.
Sinan Ünür
A: 

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

Let me know if I can lend a hand.

Larry B. [email protected]

BitBank
Thanks but as you can see I already answered the question myself.
wonderer
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
Maybe you misunderstood and thought that I was referring to GPL code.
BitBank
A: 

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