Как сделать снимок экрана и сохранить его в формате JPEG в Windows?
Я пытаюсь найти (несколько) простой способ сделать снимок экрана в окне и сохранить полученный HBITMAP в формате JPEG. Сложность заключается в том, что, поскольку код находится в C I, я не могу использовать GDI+, и поскольку код является модулем для более крупной программы, я также не могу использовать внешний lib (например, libjpeg).
Этот код делает снимок экрана и возвращает HBITMAP. Сохранение этого растрового изображения в файл очень просто. проблема в том, что растровое изображение составляет 2 или 3 МБ.
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;
Есть идеи, как это сделать? Большое спасибо, любая помощь приветствуется
РЕДАКТИРОВАТЬ: Так как мы пошли в направлении GDI+, я подумал, что я опубликую код iv C++, который может сделать снимок экрана и преобразовать его в JPEG с использованием GDI+. Если кто-нибудь знает, как добиться этого с помощью FLAT GDI+, я был бы признателен за помощь. Код:
#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;
}
11 ответов
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? Посмотри здесь. Вы можете заменить на
GetModuleHandle()
what is GetEncoderClsid? Посмотри здесь
Now the question is, how do I save the encoded pBitmap (as a jpeg) into a BYTE buffer?
Перевод на плоский GDI+ API довольно прост:
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);
}
Единственной вещью, которая не была очевидна, была очистка GpBitmap, созданного GdipCreateBitmapFromHBITMAP(). Класс Gdiplus::Bitmap, похоже, не имеет деструктора и поэтому ничего не делает со своим внутренним GpBitmap. Также нет GdipDeleteBitmap(), как и для других объектов GDI +. Поэтому мне неясно, что нужно сделать, чтобы избежать утечек.
Редактировать: Этот код не учитывает тот факт, что предоставленные Microsoft заголовочные файлы GDI + объявляют все необходимые функции в пространствах имен C++. Одним из решений является копирование необходимых объявлений (и преобразование по мере необходимости в код C) в ваш собственный заголовок. Другая возможность - использовать заголовки, предоставляемые проектами Wine или Mono. Они оба выглядят гораздо лучше при компиляции как C-код.
Одна возможность: если вы не можете изменить эту программу для сохранения в формате Jpeg, напишите вторую программу, используя C# / GDI+ / другие необычные технологии, чтобы отслеживать каталог сохранения и обрабатывать сохраненные BMP в jpegs.
Если вы не можете этого сделать, группа Independent Jpeg сделала код Jpeg на чистом C доступным с конца 20-го века: здесь доступна очень минималистическая веб-страница.
Попробуйте это в начале вашего кода
#include "windows.h"
#include "gdiplus.h"
using namespace Gdiplus;
using namespace Gdiplus::DllExports;
И GdipSaveImageToFile() может компилироваться, в C++, я верю.
В чистом C, вероятно, лучше всего попробовать сделать одиночные включения. Найдите объявления функций в "gdiplus.h" и добавьте минимальные включения для каждой из функций, которые не включают в себя пространства имен, такие как #include "Gdiplusflat.h"
для GdipSaveImageToFile()
Хорошее сжатие изображений сложно. Существует причина, по которой код библиотеки существует для распространенных форматов. Это требует много кода, чтобы сделать. Ваши ограничения по проблеме не делают это звучит так, как будто есть практическое решение.
Есть пара вещей, чтобы попробовать.
Используйте 8-битный на пиксель BMP, а не BMP с истинным цветом. Это предполагает, что потеря информации в цветовом отображении, конечно, приемлема. Он купит вам в три раза больше размера файла по сравнению с 24-битным BMP.
Попробуйте использовать кодирование длины пробега в BMP, который вы пишете. RLE - это документированный формат для BMP, и должно быть множество примеров кода для его создания. Это лучше всего работает на изображениях, которые имеют много длинных серий одинакового цвета. Типичный экран без слишком большого количества градиентов и заливок соответствует этому описанию.
Если этого недостаточно, вам, возможно, придется прибегнуть к более сильному сжатию. Исходный код libjpeg легко доступен, и его можно статически связать, а не использовать DLL. Это потребует определенных усилий для настройки только тех битов, которые вам нужны, но код сжатия и распаковки практически полностью независим друг от друга, что разделяет библиотеку почти пополам.
libjpeg является бесплатным, с открытым исходным кодом, кодируется на C. Вы можете скопировать их код в свой.
http://sourceforge.net/project/showfiles.php?group_id=159521
У меня была такая же проблема, и я решил ее с помощью этого кода. Вам также необходимо включить gdiplus.lib во входы для компоновщика.
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);
}
Этот код можно улучшить, добавив значение качества для вывода файла jpg, код для выполнения которого выше в этом потоке.
Вы смотрели на проект FreeImage? Да, это внешняя библиотека, но она довольно маленькая (~1Mb). Делает все, что вы хотите, с изображениями тоже. Об этом стоит знать, даже если бы не этот проект.
Вам, вероятно, придется использовать внешнюю библиотеку для создания кода JPEG в C - для этого не будет стандартной библиотечной функции. Конечно, вы также можете изучить формат файла и написать функцию для генерации собственного, основываясь на критериях. Вы можете начать с Википедии. Если вы действительно не можете просто использовать внешнюю библиотеку, вы можете прочитать соответствующие функции библиотеки, чтобы узнать о том, как создавать свои собственные файлы JPEG. Но это звучит как гораздо больше работы, чем просто использование библиотеки.
Кроме того, я не верю, что размер библиотеки действительно вступает в игру с C - я верю, что вы получаете только размер функций, которые вы вызываете из этой библиотеки (я думаю, в любом случае). Конечно, если бы все функции были тесно связаны, это было бы в любом случае огромно.
Если вы действительно беспокоитесь о пространстве, вы, вероятно, можете отказаться от библиотек времени выполнения C. Если вы используете только несколько функций, просто напишите свою собственную версию (например, strcpy). Можно писать приложения для Windows, которые имеют размер всего несколько килобайт. Я могу отправить вам небольшой пример приложения, которое делает это.
Если я возьму код C-изображения и урежу его до кодировщика JPEG, он, вероятно, даст около 20 КБ кода (меньше, если вам не нужно поддерживать изображения в градациях серого).
Не изобретай велосипед.
Если вам нужна программа, которая делает снимок экрана и сохраняет его в формате JPEG, вы можете просто использовать импорт ImageMagick.
Команда оболочки будет просто:
import screen.jpg
Конечно, вы можете сохранить вышеупомянутое как takeScreenshot.bat, и у вас будет программа, соответствующая вашим требованиям.