Подтвердить что ты не робот

Как сделать снимок экрана и сохранить его как JPEG в Windows?

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

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

EDIT: Поскольку мы пошли в направлении GDI +, я подумал, что отправлю код iv С++, который может сделать снимок экрана и преобразовать его в 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 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;

}
4b9b3361

Ответ 1

Хорошо, после долгих усилий здесь ответ:

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?

Ответ 2

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

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

Ответ 3

Перевод на плоский API GDI + довольно прямолинейный:

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) в собственный заголовок. Другая возможность - использовать заголовки, предоставленные Wine или Mono. Оба они кажутся намного лучше, если они компилируются как код C.

Ответ 4

Вам, вероятно, придется использовать внешнюю библиотеку для создания JPEG в коде C - для этой функции не будет стандартной библиотечной функции. Конечно, вы также можете изучить формат файла и написать функцию для генерации своих собственных, основанных на критериях. Вы можете запустить в wikipedia. Если вы действительно не можете просто использовать внешнюю библиотеку, вы можете прочитать соответствующие функции библиотеки, чтобы узнать, как создавать собственные JPEG файлы. Но это звучит намного больше, чем просто использование библиотеки.

Кроме того, я не верю, что размер библиотеки действительно вступает в игру с C - я считаю, что вы получаете только размер функций, которые вы вызываете из этой библиотеки (я думаю, в любом случае). Конечно, если все функции плотно связаны, но в любом случае это будет огромным.

Ответ 5

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

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

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

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

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

Ответ 6

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

Ответ 8

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

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

Ответ 9

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

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

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

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

Ответ 10

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

Ответ 11

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

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

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

import screen.jpg

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