Как сделать снимок экрана и сохранить его в формате JPEG в Windows? - PullRequest
13 голосов
/ 15 июня 2009

Я пытаюсь найти (несколько) простой способ сделать снимок экрана в окне и сохранить полученный HBITMAP в формате JPEG. Сложность заключается в том, что, поскольку код находится на C, я не могу использовать GDI +, и поскольку код является модулем для более крупной программы, я не могу использовать внешнюю библиотеку (например, 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;

есть идеи как это сделать? Большое спасибо, любая помощь приветствуется

EDIT: Так как мы пошли в направлении 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 ]

17 голосов
/ 23 июня 2009

ОК, после долгих усилий вот ответ:

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;
}
  • что такое hModuleThread? Смотрите в здесь . Вы можете заменить на GetModuleHandle()

  • Что такое GetEncoderClsid? Смотрите здесь .

Теперь вопрос в том, как сохранить закодированную карту pBitmap (в формате jpeg) в буфере BYTE?

5 голосов
/ 16 июня 2009

Перевод на плоский 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 +. Поэтому мне неясно, что нужно сделать, чтобы избежать утечек.

Edit: Этот код не учитывает тот факт, что предоставленные Microsoft заголовочные файлы GDI + объявляют все необходимые функции в пространствах имен C ++. Одним из решений является копирование необходимых объявлений (и преобразование по мере необходимости в код C) в ваш собственный заголовок. Другая возможность - использовать заголовки, поставляемые проектами Wine или Mono . Они оба выглядят гораздо лучше при компиляции как код на языке C.

4 голосов
/ 15 июня 2009

Одна возможность: если вы не можете изменить эту программу для сохранения в формате Jpeg, напишите вторую программу, используя C # / GDI + / другие необычные технологии, чтобы отслеживать каталог сохранения и обрабатывать сохраненные BMP в jpegs.

Если вы не можете сделать это, группа Independent Jpeg с конца 20-го века сделала доступным Jpeg-код на чистом C: Здесь доступна очень минимальная веб-страница.

1 голос
/ 26 ноября 2009

Попробуйте это в начале вашего кода

#include "windows.h"
#include "gdiplus.h"
using namespace Gdiplus;
using namespace Gdiplus::DllExports;

И GdipSaveImageToFile () может скомпилироваться, в C ++, я верю.

В чистом C, вероятно, лучше всего попытаться сделать одиночные включения. Найдите объявления функций в «gdiplus.h» и добавьте минимальные включения для каждой из функций, которые не включают пространства имен, например #include "Gdiplusflat.h" для GdipSaveImageToFile()

0 голосов
/ 22 января 2014

У меня была такая же проблема, и я решил ее с помощью этого кода. Вам также необходимо включить 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, код для выполнения которого выше в этой теме.

0 голосов
/ 04 июля 2009

Если вы действительно обеспокоены пространством, вы, вероятно, также можете отказаться от библиотек времени выполнения C. Если вы используете только несколько функций, просто напишите свою собственную версию (например, strcpy). Можно писать приложения для Windows, которые имеют размер всего несколько килобайт. Я могу отправить вам небольшой пример приложения, которое делает это.

Если я возьму код изображения C и урежу его до только кодировщика JPEG, он, вероятно, даст около 20 КБ кода (меньше, если вам не нужно поддерживать изображения в оттенках серого).

0 голосов
/ 16 июня 2009

libjpeg является бесплатным, с открытым исходным кодом, кодируется на C. Вы можете скопировать их код в свой.

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

0 голосов
/ 16 июня 2009

Вы смотрели на FreeImage Project ? Да, это внешняя библиотека, но она довольно маленькая (~ 1Mb). Делает все, что вы хотите, с изображениями тоже. Об этом стоит знать, даже если бы не этот проект.

0 голосов
/ 15 июня 2009

Хорошее сжатие изображений сложно. Существует причина, по которой код библиотеки существует для распространенных форматов. Для этого требуется много кода. Ваши ограничения в отношении проблемы не делают ее похожей на практическое решение.

Есть пара вещей, которые нужно попробовать.

  1. Используйте 8-битный на пиксель BMP, а не BMP с истинным цветом. Это предполагает, что потеря информации в цветовом отображении, конечно, приемлема. Размер файла будет в три раза больше, чем у 24-битного BMP.

  2. Попробуйте использовать кодировку длины прогона в BMP, который вы пишете. RLE - это документированный формат для BMP, и должно быть множество примеров кода для его создания. Это лучше всего работает на изображениях, которые имеют много длинных серий одинакового цвета. Типичный экран без слишком большого количества градиентов и заливок соответствует этому описанию.

  3. Если этого недостаточно, возможно, вам придется прибегнуть к более сильному сжатию. Исходный код libjpeg легко доступен, и его можно статически связать, а не использовать DLL. Потребуется некоторое усилие для настройки только тех битов, которые вам нужны, но код сжатия и распаковки практически полностью независим друг от друга, и это разделяет библиотеку почти пополам.

0 голосов
/ 15 июня 2009

Не изобретай велосипед.

Если вам нужна программа, которая делает снимок экрана и сохраняет его в формате JPEG, вы можете просто использовать ImageMagick import .

Командой оболочки будет просто:

import screen.jpg

Конечно, вы можете сохранить вышеприведенное как takeScreenshot.bat, и у вас будет программа, соответствующая вашим требованиям.

...