可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
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;
}
回答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;
}
Now the question is, how do I save the encoded pBitmap (as a jpeg) into a BYTE buffer?
回答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.
回答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.
回答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()
回答5:
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.
回答6:
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.
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.
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.
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.
回答7:
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.
回答8:
libjpeg is free, open source, coded in C. You can copy their code into yours.
http://sourceforge.net/project/showfiles.php?group_id=159521
回答9:
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.
回答10:
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).
回答11:
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.