Come posso prendere uno screenshot e salvarlo come JPEG su Windows?

Sto cercando di trovare un modo (un po ‘) facile per fare uno screenshot sulla finestra e salvare l’HBITMAP risultante come JPEG. La parte difficile qui è che poiché il codice è in CI non è ansible utilizzare GDI + e poiché il codice è un modulo per un programma più grande non posso né usare una lib esterna (come libjpeg).

Questo codice prende uno screenshot e restituisce un HBITMAP. Salvare quella bitmap in un file è facile. il problema è che la bitmap è 2 o 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; 

Qualche idea su come fare questo? grazie mille, ogni aiuto è apprezzato

EDIT: Da quando siamo andati nella direzione di GDI + ho pensato di pubblicare il codice iv C ++ che può prendere lo screenshot e convertirlo in un JPEG usando GDI +. Se qualcuno sa come ottenere ciò usando il FLAT GDI +, apprezzerei l’aiuto. Codice:

  #include  #include  #include  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 Save(lpszFilename, &imageCLSID, &encoderParams) == Ok); delete pScreenShot; DeleteObject(hbmCapture); GdiplusShutdown(gdiplusToken); return iRes; } 

OK, dopo un grande sforzo ecco la risposta:

 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; } 
  • cos’è hModuleThread? Guarda qui . Puoi sostituire con GetModuleHandle()

  • cosa è GetEncoderClsid? Guarda qui

Ora la domanda è, come faccio a salvare il pBitmap codificato (come jpeg) in un buffer BYTE?

La conversione nell’API GDI + piatta è abbastanza semplice:

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

L’unica cosa che non era evidente era la pulizia del GpBitmap creato da GdipCreateBitmapFromHBITMAP (). La class Gdiplus :: Bitmap non sembra avere un distruttore e quindi non fa nulla con il suo GpBitmap interno. Inoltre, non esiste GdipDeleteBitmap (), come per altri oggetti GDI +. Quindi, non è chiaro per me cosa deve essere fatto per evitare perdite.

Modifica: questo codice non risolve il fatto che i file di intestazione GDI + forniti da Microsoft dichiarano tutte le funzioni necessarie negli spazi dei nomi C ++. Una soluzione è copiare le dichiarazioni necessarie (e convertirle come necessario nel codice C) nella propria intestazione. Un’altra possibilità è quella di utilizzare le intestazioni fornite dai progetti Wine o Mono . Entrambi sembrano essere molto meglio comportati per la compilazione come codice C.

Una possibilità: se non è ansible modificare questo programma per salvare come Jpeg, scrivere un secondo programma, utilizzando C # / GDI + / altre tecnologie di fantasia per monitorare la directory di salvataggio ed elaborare i BMP salvati in jpeg.

Se non riesci a farlo, il gruppo indipendente Jpeg ha reso disponibile il codice Jpeg puro C dalla fine del 20 ° secolo: qui è disponibile una pagina web molto minimale.

Probabilmente dovrai usare una libreria esterna per creare un JPEG in codice C – non ci sarà una funzione di libreria standard per farlo. Naturalmente, puoi anche studiare il formato del file e scrivere una funzione per generare il tuo, in base ai criteri. Potresti iniziare da wikipedia . Se davvero non puoi semplicemente usare una libreria esterna, puoi leggere le funzioni rilevanti della libreria per imparare come generare i tuoi file JPEG. Ma sembra molto più lavoro che usare la libreria.

Inoltre, non credo che le dimensioni di una libreria entrino davvero in gioco con C – credo che tu abbia solo la dimensione delle funzioni che chiami da quella libreria (penso, comunque). Naturalmente se tutte le funzioni sono strettamente accoppiate di quanto sarebbe enorme comunque.

Una buona compressione delle immagini è difficile. Esiste un motivo per cui esiste un codice di libreria per i formati comuni. Richiede molto codice da fare. I tuoi limiti al problema non fanno sembrare che ci sia una soluzione pratica.

Ci sono un paio di cose da provare.

  1. Utilizza un BMP a 8 bit per pixel anziché un BMP a colors reali. Ciò presuppone che la perdita di informazioni nella mapping dei colors sia accettabile, ovviamente. Ti comprerà un fattore tre nella dimensione del file su un BMP a 24 bit.

  2. Prova a utilizzare la codifica della lunghezza di esecuzione nel BMP che scrivi. RLE è un formato documentato per BMP e ci dovrebbe essere un sacco di codice di esempio là fuori per farlo. Funziona meglio su immagini che hanno un sacco di lunghe tirature di colore identico. Uno schermo tipico senza troppi gradienti e riempimenti si adatta a questa descrizione.

  3. Se questi non sono sufficienti, potrebbe essere necessario ricorrere a una compressione più forte. Il codice sorgente di libjpeg è prontamente disponibile ed è ansible collegarlo staticamente piuttosto che utilizzare la DLL. Sarà necessario un po ‘di sforzo per configurare solo i bit necessari, ma il codice di compressione e decompressione è quasi completamente indipendente l’uno dall’altro e questo divide la libreria quasi a metà.

Hai dato un’occhiata al progetto FreeImage ? Sì, è una libreria esterna, ma è piuttosto piccola (~ 1Mb). Fa quasi tutto quello che vuoi, anche con le immagini. Sicuramente ne vale la pena, anche se non per questo progetto.

libjpeg è gratuito, open source, codificato in C. Puoi copiare il loro codice nel tuo.

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

Prova questo all’inizio del tuo codice

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

E GdipSaveImageToFile () può compilare, in c ++ credo.

In puro C, probabilmente il migliore è cercare di rendere singolo include. Cerca le dichiarazioni delle funzioni in “gdiplus.h” e aggiungi minime include per ognuna delle funzioni che non includono namespace, come #include "Gdiplusflat.h" per GdipSaveImageToFile()

Ho avuto lo stesso problema e l’ho risolto con questo codice. È inoltre necessario includere gdiplus.lib negli Input per il 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); } 

Questo codice potrebbe essere migliorato aggiungendo un valore di qualità per l’output del file jpg, il codice per farlo è più alto in questo thread.

Se sei davvero preoccupato per lo spazio, puoi probabilmente scaricare anche le librerie di runtime C. Se stai usando solo poche funzioni, scrivi la tua versione (es. Strcpy). È ansible scrivere applicazioni Windows che hanno solo pochi byte K. Posso inviarti una piccola app di esempio che fa questo.

Se prendo il mio codice di imaging C e lo spoglio solo al codificatore JPEG, probabilmente produrrà circa 20K di codice (meno se non è necessario supportare le immagini in scala di grigi).

Non reinventare la ruota.

Se ciò di cui hai bisogno è un programma che cattura una schermata e la salva come jpeg, puoi semplicemente usare l’ importazione di ImageMagick .

Il comando della shell sarebbe semplicemente:

 import screen.jpg 

Naturalmente, è ansible salvare quanto sopra takeScreenshot.bat e si dovrebbe avere un programma che soddisfi le proprie esigenze.