J'écris actuellement un petit programme qui scanne l'écran et recherche des pixels. Mon problème est que la fonction GetDIBits ne semble pas renvoyer une capture d'écran correcte de l'écran.
La copie du bitmap dans le presse-papiers place la bonne image d'écran dans le presse-papiers. J'ai décidé d'imprimer la sortie de la fonction dans un fichier BMP pour avoir une idée de ce qui se passe et ce n'est clairement pas ce à quoi je m'attendais.
Je mentionnerai également que j'ai 3 moniteurs, au cas où cela pourrait expliquer pourquoi il ne se comporte pas comme prévu.
class Test { int screenWidth; int screenHeight; HWND targetWindow; HDC targetDC; HDC captureDC; RGBQUAD *pixels; HBITMAP captureBitmap; bool TakeScreenshot() { ZeroMemory(pixels, screenHeight*screenWidth); screenWidth = GetSystemMetrics(SM_CXSCREEN); screenHeight = GetSystemMetrics(SM_CYSCREEN); targetWindow = GetDesktopWindow(); targetDC = GetDC(NULL); captureDC = CreateCompatibleDC(targetDC); captureBitmap = CreateCompatibleBitmap(targetDC, screenWidth, screenHeight); HGDIOBJ old = SelectObject(captureDC, captureBitmap); if (!old) printf("Error selecting object\n"); OpenClipboard(NULL); EmptyClipboard(); SetClipboardData(CF_BITMAP, captureBitmap); CloseClipboard(); if (BitBlt(captureDC, 0, 0, screenWidth, screenHeight, targetDC, 0, 0, SRCCOPY)) { BITMAPINFO bmi = { 0 }; bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); bmi.bmiHeader.biWidth = screenWidth; bmi.bmiHeader.biHeight = -screenHeight; bmi.bmiHeader.biPlanes = 1; bmi.bmiHeader.biBitCount = 32; bmi.bmiHeader.biCompression = BI_RGB; bmi.bmiHeader.biSizeImage = 0; if (!SelectObject(captureDC, old)) printf("Error unselecting object\n"); if (!GetDIBits(captureDC, captureBitmap, 0, screenHeight, pixels, &bmi, DIB_RGB_COLORS )) { printf("%s: GetDIBits failed\n", __FUNCTION__); return false; } } else { printf("%s: BitBlt failed\n", __FUNCTION__); return false; } return true; } // This is from somewhere on stackoverflow - can't find where. void MakePicture() { typedef struct /**** BMP file header structure ****/ { unsigned int bfSize; /* Size of file */ unsigned short bfReserved1; /* Reserved */ unsigned short bfReserved2; /* ... */ unsigned int bfOffBits; /* Offset to bitmap data */ } BITMAPFILEHEADER; BITMAPFILEHEADER bfh; BITMAPINFOHEADER bih; unsigned short bfType = 0x4d42; bfh.bfReserved1 = 0; bfh.bfReserved2 = 0; bfh.bfSize = 2 + sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) + 2560 * 1440 * 3; bfh.bfOffBits = 0x36; bih.biSize = sizeof(BITMAPINFOHEADER); bih.biWidth = screenWidth; bih.biHeight = screenHeight; bih.biPlanes = 1; bih.biBitCount = 24; bih.biCompression = 0; bih.biSizeImage = 0; bih.biXPelsPerMeter = 5000; bih.biYPelsPerMeter = 5000; bih.biClrUsed = 0; bih.biClrImportant = 0; FILE *file; fopen_s(&file, "test.bmp", "wb"); if (!file) { printf("Could not write file\n"); return; } /*Write headers*/ fwrite(&bfType, 1, sizeof(bfType), file); fwrite(&bfh, 1, sizeof(bfh), file); fwrite(&bih, 1, sizeof(bih), file); /*Write bitmap*/ for (int y = 0; y < screenHeight; y++) { for (int x = 0; x < screenWidth; x++) { unsigned char r = pixels[x + y].rgbRed; unsigned char g = pixels[x + y].rgbGreen; unsigned char b = pixels[x + y].rgbBlue; fwrite(&b, 1, 1, file); fwrite(&g, 1, 1, file); fwrite(&r, 1, 1, file); } } fclose(file); } Test() { screenWidth = GetSystemMetrics(SM_CXSCREEN); screenHeight = GetSystemMetrics(SM_CYSCREEN); pixels = new RGBQUAD[screenWidth * screenHeight]; } ~Test() { //cleanup } };
Voici le résultat que le le code donne (au lieu d'une capture d'écran):
Il semble que cela prenne quelques pixels du haut de mon écran et les étire en une image. La capture d'écran provient de l'ouverture de Visual Studio (la partie orange étant les notifications).
Si je mets un carré rouge géant (255, 0, 0) dans mon écran, si sa hauteur n'est pas de 0, le Le tableau de pixels ne contiendra pas un seul pixel rouge.
3 Réponses :
Fonction GetDIBits référence , section remarques:
Le bitmap identifié par le paramètre hbmp ne doit pas être sélectionné dans un contexte d'appareil lorsque l'application appelle cette fonction.
Désélectionnez le bitmap avant d'appeler GetBIBits
.
HBITMAP oldBitmap = SelectObject(captureDC, captureBitmap); ... // Deselect captureBitmap by selecting oldBitmap. SelectObject(captureDC, oldBitmap);
N'oubliez pas d'ajouter du code de nettoyage (restaurer le bitmap, détruire le bitmap, détruire ou libérer contextes de l'appareil).
Cela me donne toujours de mauvais résultats. Il semble prendre quelques pixels du haut de mon écran et les mettre d'une manière étrange. (L'image que j'ai fournie est celle résultant de l'ouverture de Visual Studio, le bit orange est l'indicateur de notification qui apparaît dans la barre supérieure)
BitBlt
effectue la copie proprement dite. Les fonctions du presse-papiers doivent être appelées après BitBlt
Notez également, dans les paramètres multi-moniteurs, SM_CXSCREEN / Y ...
donne la taille du moniteur principal. Utilisez SM_XVIRTUALSCREEN / XV ...
pour tout l'écran. SM_XVIRTUALSCREEN / Y
donnera la coordonnée X / Y (elle est généralement zéro)
Assurez-vous de libérer toutes les poignées et de supprimer les objets utilisés lorsque vous avez terminé. En fait, il n'est pas nécessaire de déclarer targetDC
etc. comme membres de la classe.
Si l'application n'est pas consciente de DPI, le bitmap peut paraître plus petit en fonction des paramètres DPI. Appelez SetProcessDPIAware ()
au début du programme pour une solution rapide ou définissez le manifeste.
Comme indiqué dans le commentaire, avec SetClipboardData (CF_BITMAP, captureBitmap);
le système prend en charge captureBitmap
. Évitez d'appeler cette fonction ou de faire une copie du bitmap à passer dans le presse-papiers.
int main() { int screenWidth = GetSystemMetrics(SM_CXVIRTUALSCREEN); int screenHeight = GetSystemMetrics(SM_CYVIRTUALSCREEN); int screen_x = GetSystemMetrics(SM_XVIRTUALSCREEN); int screen_y = GetSystemMetrics(SM_YVIRTUALSCREEN); screenWidth = GetSystemMetrics(SM_CXSCREEN); screenHeight = GetSystemMetrics(SM_CYSCREEN); screen_x = 0; screen_y = 0; RGBQUAD* pixels = new RGBQUAD[screenWidth * screenHeight]; DWORD size = screenWidth * screenHeight * 4; ZeroMemory(pixels, size); HDC targetDC = GetDC(NULL); HDC captureDC = CreateCompatibleDC(targetDC); HBITMAP captureBitmap = CreateCompatibleBitmap(targetDC, screenWidth, screenHeight); HGDIOBJ old = SelectObject(captureDC, captureBitmap); if(!BitBlt(captureDC, 0, 0, screenWidth, screenHeight, targetDC, screen_x, screen_y, SRCCOPY)) printf("BitBlt error\n"); SelectObject(captureDC, old); BITMAPINFO bmi = { 0 }; bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); bmi.bmiHeader.biWidth = screenWidth; bmi.bmiHeader.biHeight = -screenHeight; bmi.bmiHeader.biPlanes = 1; bmi.bmiHeader.biBitCount = 32; bmi.bmiHeader.biCompression = BI_RGB; bmi.bmiHeader.biSizeImage = 0; if(OpenClipboard(NULL)) { EmptyClipboard(); SetClipboardData(CF_BITMAP, CopyImage(captureBitmap, IMAGE_BITMAP, 0, 0, LR_DEFAULTSIZE)); CloseClipboard(); } if(!GetDIBits(targetDC, captureBitmap, 0, screenHeight, pixels, &bmi, DIB_RGB_COLORS )) printf("%s: GetDIBits failed\n", __FUNCTION__); BITMAPFILEHEADER filehdr = { 'MB', 54 + size, 0, 0, 54 }; std::ofstream f("test.bmp", std::ios::binary); f.write((char*)&filehdr, sizeof(filehdr)); f.write((char*)&bmi, sizeof(bmi)); f.write((char*)pixels, size); //cleanup: SelectObject(captureDC, old); DeleteObject(captureBitmap); DeleteDC(captureDC); ReleaseDC(0, targetDC); }
Le tableau «pixels» semble toujours donner l'image ci-dessus.
Je l'ai ajouté au message. J'ai également essayé d'imprimer la couleur à une coordonnée spécifique (x, y) dans la console et cela donne la même couleur que celle de l'image. Les seules couleurs de ce tableau semblent provenir de la première ligne de pixel de mon écran.
Vous lisez le fichier au format 32 bits, les pixels sont 32 bits, puis vous écrivez en 24 bits. Voir la mise à jour sur l'enregistrement du bitmap au format 32 bits. Ou changez les pixels en 24 bits et enregistrez-les en 24 bits.
Beaucoup de bonnes informations ici, mais cela pose toujours le problème qu'une fois que vous avez donné le bitmap au presse-papiers, ce n'est plus le vôtre. Vous ne pouvez pas en lire de manière fiable avec GetDIBits. Si vous supprimez les appels au presse-papiers dans ce code (ou dans l'OP), vous obtenez les données attendues en pixels
.
Bon point @AdrianMcCarthy. Mais cela fait-il une différence dans ce cas? car GetDIBits
ne tente pas de modifier le bitmap. J'ai ajouté CopyImage
pour éviter le problème.
@BarmakShemirani: De la documentation de SetClipboardData: "Si SetClipboardData réussit, le système possède l'objet identifié par le paramètre hMem. L'application peut ne pas écrire ou libérer les données une fois que la propriété a été transférée au système, mais elle peut se verrouiller et lire à partir de les données jusqu'à ce que la fonction CloseClipboard soit appelée. " Depuis que CloseClipboard a été appelé, vous ne pouvez même pas être sûr que les données sont toujours là. En outre, le code fonctionne pour moi si vous omettez les opérations du presse-papiers.
Bogue supplémentaire:
bfh.bfSize = 2 + sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) + ((screenWidth*3 + 3) & ~3) * screenHeight;
Je pense que cela devrait être
// Important: each row has to be padded to multiple of DWORD. // Valid only for 24 bits per pixel bitmaps. // Remark: 32 bits per pixel have rows always aligned (padding==0) int padding = 3 - (screenWidth*3 + 3)%4; // or // int padding = 3 - ((screenWidth*3 + 3) & 3); for (int y = 0; y < screenHeight; y++) { for (int x = 0; x < screenWidth; x++) { unsigned char r = pixels[x + y*screenWidth].rgbRed; unsigned char g = pixels[x + y*screenWidth].rgbGreen; unsigned char b = pixels[x + y*screenWidth].rgbBlue; fwrite(&b, 1, 1, file); fwrite(&g, 1, 1, file); fwrite(&r, 1, 1, file); } // Important: each row has to be padded to multiple of DWORD. fwrite("\0\0\0\0", 1, padding, file); }
Mais les lignes nécessitent un remplissage multiplié par 4 octets: p >
for (int y = 0; y < screenHeight; y++) { for (int x = 0; x < screenWidth; x++) { unsigned char r = pixels[x + y*screenWidth].rgbRed; unsigned char g = pixels[x + y*screenWidth].rgbGreen; unsigned char b = pixels[x + y*screenWidth].rgbBlue; fwrite(&b, 1, 1, file); fwrite(&g, 1, 1, file); fwrite(&r, 1, 1, file); } }
Ajuster la taille du fichier (valable pour 24 bits par pixel):
for (int y = 0; y < screenHeight; y++) { for (int x = 0; x < screenWidth; x++) { unsigned char r = pixels[x + y].rgbRed; unsigned char g = pixels[x + y].rgbGreen; unsigned char b = pixels[x + y].rgbBlue; fwrite(&b, 1, 1, file); fwrite(&g, 1, 1, file); fwrite(&r, 1, 1, file); } }
Vous n'initialisez pas
screenWidth
et d'autres globaux. Modifiez votre question pour inclure un exemple reproductible minimal .Au moment où vous mettez
captureBitmap
dans le presse-papiers (1), vous n'avez pas encore capturé le bitmap et (2) vous ne possédez plus cette poignée bitmap - c'est le système.