c - Hvordan kan jeg tage et screenshot og gemme det som JPEG på Windows?

Indlæg af Hanne Mølgaard Plasc

Problem



Jeg forsøger at finde en (lidt) nem måde at tage et screenshot på vindue og gem den resulterende HBITMAP som en JPEG. Den vanskelige del her er, at siden koden er i, kan jeg ikke bruge GDI +, og da koden er en modul til et større program Jeg kan ikke bruge en ekstern lib (som libjpeg).


Denne kode tager et skærmbillede og returnerer en HBITMAP. Det er nemt at gemme den bitmap i en fil. Problemet er, at bitmapet er 2 eller 3mb.


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;


nogen ideer om hvordan man gør dette?
mange tak, enhver hjælp er værdsat


REDIGERE:
Da vi gik i retning af GDI +, tænkte jeg, at jeg postede koden iv C ++, der kan tage screenshotet og konvertere det til en JPEG ved hjælp af GDI +. Hvis nogen ved, hvordan man opnår dette ved hjælp af FLAT GDI +, vil jeg sætte pris på hjælpen.
Kode:


    #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
", 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;

}

Bedste reference


OK, efter en masse indsats her er svaret:


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



  • Hvad er hModuleThread? Kig ind her. Du kan erstatte med GetModuleHandle() [11]

  • Hvad er GetEncoderClsid? Se her. [12]



Nu er spørgsmålet, hvordan gemmer jeg den kodede pBitmap (som en jpeg) i en BYTE buffer?

Andre referencer 1


Oversættelse til den flade GDI + API er ret lige fremad:


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);
}


Den eneste ting, der ikke var oplagt, var oprydning af GpBitmap oprettet af GdipCreateBitmapFromHBITMAP (). Gdiplus :: Bitmap-klassen synes ikke at have en destructor, og det gør ikke noget med det interne GpBitmap. Der er også ingen GdipDeleteBitmap (), ligesom der findes andre GDI + objekter. Så det er uklart for mig, hvad der skal gøres for at undgå lækager.



Denne kode angiver ikke, at Microsoft leverede GDI + header-filer, erklærer alle de nødvendige funktioner i C ++ navneområder. En løsning er at kopiere de nødvendige erklæringer (og konverter efter behov til C-kode) til dit eget overskrift. En anden mulighed er at bruge de overskrifter, der leveres af Wine eller Mono-projekterne. De ser begge ud til at være meget bedre optrådt i kompilering som C-kode. [13] [14]

Andre referencer 2


En mulighed: Hvis du ikke kan ændre dette program for at gemme som Jpeg, skal du skrive et andet program ved hjælp af C #/GDI +/andre fancy teknologier til at overvåge lagringsmappen og behandle gemte BMP'er i jpeg'er.


Hvis du ikke kan, har Independent Jpeg-gruppen lavet ren C-Jpeg-kode siden slutningen af ​​det 20. århundrede: En meget minimal webside er tilgængelig her. [15]

Andre referencer 3


Du skal sandsynligvis bruge et eksternt bibliotek til at oprette en JPEG i C-kode - der vil ikke være en standard biblioteksfunktion for at gøre det. Selvfølgelig kan du også studere filformatet og skrive en funktion til at generere din egen, baseret på kriterierne. Du kan starte på wikipedia. Hvis du virkelig ikke kan bruge et eksternt bibliotek, kan du læse i bibliotekets relevante funktioner for at lære om, hvordan du genererer dine egne JPEG'er. Men det lyder som meget mere arbejde end blot at bruge biblioteket. [16]


Jeg tror heller ikke, at bibliotekets størrelse virkelig kommer i spil med C - Jeg tror, ​​du kun får størrelsen på de funktioner, du ringer fra biblioteket (jeg tror alligevel). Selvfølgelig, hvis alle funktioner er tætte koblet end det 'd være alligevel alligevel.

Andre referencer 4


God billedkomprimering er hård. Der findes en grund bibliotekskode for de fælles formater. Det kræver meget kode at gøre. Dine begrænsninger på problemet gør det ikke lyde, som om der er en praktisk løsning.


Der er et par ting at prøve.



  1. Brug en 8-bit pr. pixel BMP i stedet for en ægte farve BMP. Dette forudsætter, at informationsfaldet i farvekortlægning er acceptabelt, selvfølgelig. Det vil købe dig en faktor på tre i filstørrelse over en 24-bit BMP.

  2. Prøv at bruge kørelængdekodning i BMP du skriver. RLE er et dokumenteret format til BMP, og der skal være rigeligt med prøvekode derude for at gøre det. Det fungerer bedst på billeder, der har mange lange kørsler af samme farve. En typisk skærm uden for mange gradienter og fyldninger passer til denne beskrivelse.

  3. Hvis det ikke er tilstrækkeligt, så skal du måske ty til en stærkere komprimering. Kildekoden til libjpeg er let tilgængelig, og det er muligt at statisk forbinde det i stedet for at bruge DLL'en. Det vil kræve en vis indsats for at konfigurere i kun de bits, du har brug for, men komprimerings- og dekomprimeringskoden er næsten fuldstændig uafhængig af hinanden, og det spalter biblioteket næsten halvdelen.


Andre referencer 5


Har du kigget på FreeImage-projektet? Ja, det er et eksternt bibliotek, men det er ret lille (~ 1 MB). Gør næsten alt hvad du vil, med billeder også. Absolut værd at vide om, selvom ikke til dette projekt. [17]

Andre referencer 6


libjpeg er gratis, open source, kodet i C. Du kan kopiere deres kode til din.


http://sourceforge.net/project/showfiles.php?group\_id=159521[18]

Andre referencer 7


Prøv dette ved begyndelsen af ​​din kode


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


Og GdipSaveImageToFile () kan kompilere, i c ++ tror jeg.


I ren C, sandsynligvis det bedste er at forsøge at gøre single indeholder. Kig efter funktionserklæringerne i 'gdiplus.h' og tilføj minimal indeholder for hver af de funktioner, der ikke indeholder navneområder, f.eks. #include "Gdiplusflat.h" for GdipSaveImageToFile()

Andre referencer 8


Jeg havde det samme problem og løst det med denne kode.
Du skal også inkludere gdiplus.lib i Inputs for Linker.


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);
}


Denne kode kan forbedres ved at tilføje en kvalitetsværdi til jpg-filproduktionen, koden til at gøre det er højere i denne tråd.

Andre referencer 9


Hvis du virkelig er bekymret for plads, kan du nok også bruge C runtime-biblioteker. Hvis du kun bruger nogle få funktioner, skal du bare skrive din egen version (fx strcpy). Det er muligt at skrive Windows-programmer, der kun er et par K bytes. Jeg kan sende dig en lille prøve app, der gør dette.


Hvis jeg tager min C-billedkode og striper den ned til bare JPEG-encoderen, vil den nok producere ca. 20K kode (mindre hvis du ikke behøver at understøtte gråtonebilleder).

Andre referencer 10


Forvent ikke hjulet igen.


Hvis du har brug for et program, der tager et skærmbillede og gemmer det som en jpeg, kan du bare bruge ImageMagicks import. [19] [20]


Shell kommandoen ville simpelthen være:


import screen.jpg


Selvfølgelig kan du gemme ovenstående som takeScreenshot.bat og du har et program, der matcher dine krav.