C++ BYTE* array to JPEG image (libjpeg)

118 views Asked by At

I'm trying to write an application for storing screenshots on an external server, but due to limited resources, I'd like to store them in text form, specifically as BYTE* array. As a result, I've encountered some issues related to reading the screenshots uploaded to the server. I can read and save any screenshot I took during a session at any time on my computer, but screenshots taken by my friend are created as 0kb files without content and cause the application to crash during the execution of the function responsible for scanning rows...

LPBYTE SaveScreenshotNew()
{
    int nScreenWidth = GetSystemMetrics(SM_CXSCREEN);
    int nScreenHeight = GetSystemMetrics(SM_CYSCREEN);

    HDC hdcScreen = GetDC(NULL);
    HDC hdcMem = CreateCompatibleDC(hdcScreen);
    HBITMAP hBitmap = CreateCompatibleBitmap(hdcScreen, nScreenWidth, nScreenHeight);
    SelectObject(hdcMem, hBitmap);
    BitBlt(hdcMem, 0, 0, nScreenWidth, nScreenHeight, hdcScreen, 0, 0, SRCCOPY);

    int bufferSize = nScreenWidth * nScreenHeight * 3;
    LPBYTE buffer = new BYTE[bufferSize];

    BITMAPINFOHEADER bi;
    memset(&bi, 0, sizeof(BITMAPINFOHEADER));
    bi.biSize = sizeof(BITMAPINFOHEADER);
    bi.biWidth = nScreenWidth;
    bi.biHeight = -nScreenHeight;
    bi.biPlanes = 1;
    bi.biBitCount = 24;
    bi.biCompression = BI_RGB;
    bi.biSizeImage = 0;

    if (GetDIBits(hdcScreen, hBitmap, 0, nScreenHeight, buffer, (BITMAPINFO*)&bi, DIB_RGB_COLORS) != 0) {
        for (int i = 0; i < nScreenHeight; i++) {
            for (int j = 0; j < nScreenWidth; j++) {
                int index = i * nScreenWidth * 3 + j * 3;
                BYTE blue = buffer[index];
                buffer[index] = buffer[index + 2];
                buffer[index + 2] = blue;
            }
        }
    }

    DeleteObject(hBitmap);
    DeleteDC(hdcMem);
    ReleaseDC(NULL, hdcScreen);

    return buffer;
}

jpeglib code:

    FILE* outfile = fopen(szPath, "wb");

    struct jpeg_compress_struct cinfo;
    struct jpeg_error_mgr jerr;
    cinfo.err = jpeg_std_error(&jerr);
    jpeg_create_compress(&cinfo);

    jpeg_stdio_dest(&cinfo, outfile);

    cinfo.image_width = sWidth;
    cinfo.image_height = sHeight;
    cinfo.input_components = 3;
    cinfo.in_color_space = JCS_RGB;

    jpeg_set_defaults(&cinfo);
    jpeg_start_compress(&cinfo, TRUE);

    while (cinfo.next_scanline < cinfo.image_height) {
        JSAMPROW row_pointer = &gScreenPacket.lpData[cinfo.next_scanline * cinfo.image_width * cinfo.input_components];
        jpeg_write_scanlines(&cinfo, &row_pointer, 1);
    }

    jpeg_finish_compress(&cinfo);

    jpeg_destroy_compress(&cinfo);
    fclose(outfile);

I've tried:

increasing/decreasing the buffer size, changing the screenshot saving function, using a different available function in jpeglib.

Tyvm for your help.

1

There are 1 answers

0
Cem Polat On

Not related with your crash, but there are easier ways of creating jpeg image from screenshot in Windows. Using GDI+ is one of them. I prepared a working example from this for VS2019 as below:

#include <windows.h>
#include <gdiplus.h>
#include <atlimage.h>

using namespace Gdiplus;
#pragma comment (lib,"Gdiplus.lib")


BITMAPINFOHEADER createBitmapHeader(int width, int height)
{
    BITMAPINFOHEADER  bi;

    // create a bitmap
    bi.biSize = sizeof(BITMAPINFOHEADER);
    bi.biWidth = width;
    bi.biHeight = -height;  //this is the line that makes it draw upside down or not
    bi.biPlanes = 1;
    bi.biBitCount = 24;
    bi.biCompression = BI_RGB;
    bi.biSizeImage = 0;
    bi.biXPelsPerMeter = 0;
    bi.biYPelsPerMeter = 0;
    bi.biClrUsed = 0;
    bi.biClrImportant = 0;

    return bi;
}

HBITMAP GdiPlusScreenCapture(HWND hWnd)
{
    // get handles to a device context (DC)
    HDC hwindowDC = GetDC(hWnd);
    HDC hwindowCompatibleDC = CreateCompatibleDC(hwindowDC);
    SetStretchBltMode(hwindowCompatibleDC, COLORONCOLOR);

    // define scale, height and width
    int scale = 1;
    int screenx = GetSystemMetrics(SM_XVIRTUALSCREEN);
    int screeny = GetSystemMetrics(SM_YVIRTUALSCREEN);
    int width = GetSystemMetrics(SM_CXVIRTUALSCREEN);
    int height = GetSystemMetrics(SM_CYVIRTUALSCREEN);

    // create a bitmap
    HBITMAP hbwindow = CreateCompatibleBitmap(hwindowDC, width, height);
    BITMAPINFOHEADER bi = createBitmapHeader(width, height);

    // use the previously created device context with the bitmap
    SelectObject(hwindowCompatibleDC, hbwindow);

    // Starting with 32-bit Windows, GlobalAlloc and LocalAlloc are implemented as wrapper functions that call HeapAlloc using a handle to the process's default heap.
    // Therefore, GlobalAlloc and LocalAlloc have greater overhead than HeapAlloc.
    DWORD dwBmpSize = ((width * bi.biBitCount + 31) / 32) * 4 * height;
    HANDLE hDIB = GlobalAlloc(GHND, dwBmpSize);
    char* lpbitmap = (char*)GlobalLock(hDIB);

    // copy from the window device context to the bitmap device context
    StretchBlt(hwindowCompatibleDC, 0, 0, width, height, hwindowDC, screenx, screeny, width, height, SRCCOPY);   //change SRCCOPY to NOTSRCCOPY for wacky colors !
    GetDIBits(hwindowCompatibleDC, hbwindow, 0, height, lpbitmap, (BITMAPINFO*)&bi, DIB_RGB_COLORS);

    // avoid memory leak
    DeleteDC(hwindowCompatibleDC);
    ReleaseDC(hWnd, hwindowDC);

    return hbwindow;
}

int main()
{
    // Initialize GDI+.
    GdiplusStartupInput gdiplusStartupInput;
    ULONG_PTR gdiplusToken;
    GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);

    // get the bitmap handle to the bitmap screenshot
    HWND hWnd = GetDesktopWindow();
    HBITMAP hBmp = GdiPlusScreenCapture(hWnd);

    CImage image;
    image.Attach(hBmp);
    image.Save(L"d://Screenshot.jpg");

    GdiplusShutdown(gdiplusToken);
    return 0;
}