Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Taking a JPEG-encoded screenshot to a buffer using GDI+ and C++

I've adapted this code from another article here on SO. It takes a screenshot of the desktop and writes it to a file named "test.jpg."

I'm interested in saving the JPEG data directly to a buffer to be sent over the network. I'm pretty sure GdipSaveImageToStream is what I need, but I can't figure out how it works. The GpImage parameter is particularly confusing.

I appreciate any help you can provide.

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

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 = GetDesktopWindow(); // 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);

        GpImage *bob;
        IStream *ssStr;

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

}

int _tmain(int argc, _TCHAR* argv[])
{
    GetScreeny(L"test.jpg", 75);
    return 0;
}
like image 666
Smurf64 Avatar asked Apr 10 '11 06:04

Smurf64


1 Answers

Short answer: Use the IStream version of Gdiplus::Image::Save. Use CreateHStreamOnGlobal to make a temporary IStream that you can convert back to a buffer;

Long winded version with code sample.

Replace this line:

 iRes = (pScreenShot->Save(lpszFilename, &imageCLSID, &encoderParams) == Ok);

With this block of code:

// Note: For the sake of brevity and readability, I'm deliberately not checking
// the return value of any of these calls. In production code, you should do diligent error 
// checking on each function call. Also, there may be an optimization where you can just
// use the memory of the stream itself (by calling GetHGlobalFromStream and GlobalLock)
// But that's an exercise left to the reader.

{
    IStream *pStream = NULL;
    LARGE_INTEGER liZero = {};
    ULARGE_INTEGER pos = {};
    STATSTG stg = {};
    ULONG bytesRead=0;
    HRESULT hrRet=S_OK;

    BYTE* buffer = NULL;  // this is your buffer that will hold the jpeg bytes
    DWORD dwBufferSize = 0;  // this is the size of that buffer;


    hrRet = CreateStreamOnHGlobal(NULL, TRUE, &pStream))
    hrRet = pScreenShot->Save(pStream, &imageCLSID, &encoderParams) == 0 ? S_OK : E_FAIL;
    hrRet = pStream->Seek(liZero, STREAM_SEEK_SET, &pos);
    hrRet = pStream->Stat(&stg, STATFLAG_NONAME);

    // allocate a byte buffer big enough to hold the jpeg stream in memory
    buffer = new BYTE[stg.cbSize.LowPart];
    hrRet = (buffer == NULL) ? E_OUTOFMEMORY : S_OK;
    dwBufferSize = stg.cbSize.LowPart;

    // copy the stream into memory
    hrRet = pStream->Read(buffer, stg.cbSize.LowPart, &bytesRead);

    // now go save "buffer" and "dwBufferSize" off somewhere.  This is the jpeg buffer
    // don't forget to free it when you are done

    // After success or if any of the above calls fail, don't forget to release the stream
    if (pStream)
    {
        pStream->Release();
    }
}
like image 138
selbie Avatar answered Nov 17 '22 23:11

selbie