[2024-02-12] WinAPI로 스크린샷이 되지 않을 때
개인 Toy프로젝트인 UmaKey를 작업하면서 도통 알 수 없는 오류가 생겼다. UmaKey는 스크린 캡쳐를 필요로 하는 프로그램이다. 하지만 파이썬으로는 생각보다 부담이 강해서, 상대적으로 빠르고 성능이 좋은 c++로 이미지 처리 부분을 교체하기로 했다.
근데 이게 웬걸, 창의 핸들을 가져와서 캡쳐하려 하니, 검은 화면밖에 안보인다. 어떻게든 해결해 보려고 노력했지만 안되는 건 안되는 거다. 찾아보니, 그냥 전체화면 스크린샷 찍고 잘라내라고 한다. 어쩔수 있나, 안되면 돌아가야지.
그런데 어째서인지 오류가 난다. 아무리 인터넷을 뒤져보고 수정해봐도 오류가 해결이 되지 않는다. 오류 현상은 다음과 같았다.
- 스크린샷시 검은 이미지만 출력됨.
- 스크린샷시 기묘하게 뒤틀린 이미지가 출력됨. 혹은 강제 종료됨.
후… 뭐 언제는 한 번에 된 적이 있던가. 어쨌든 난 이걸 해결해야 했고, 하루종일 매달린 결과 간신히 원인을 파악할 수 있었다.
일단 코드 전문은 이렇다.
void CaptureAndCropScreen(unsigned char** imageData, int x, int y, int cropWidth, int cropHeight) {
SetProcessDPIAware();
HDC hdcScreen = GetDC(NULL);
HDC hdcMem = CreateCompatibleDC(hdcScreen);
HBITMAP hBitmap = CreateCompatibleBitmap(hdcScreen, cropWidth, cropHeight);
HBITMAP hOldBitmap = (HBITMAP)SelectObject(hdcMem, hBitmap);
BitBlt(hdcMem, 0, 0, cropWidth, cropHeight, hdcScreen, x, y, SRCCOPY);
int size = cropWidth * cropHeight * 3;
*imageData = (unsigned char*)malloc(size);
BITMAPINFO bmi;
ZeroMemory(&bmi, sizeof(BITMAPINFO));
bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
bmi.bmiHeader.biWidth = cropWidth;
bmi.bmiHeader.biHeight = -cropHeight;
bmi.bmiHeader.biPlanes = 1;
bmi.bmiHeader.biBitCount = 24;
bmi.bmiHeader.biCompression = BI_RGB;
GetDIBits(hdcMem, hBitmap, 0, cropHeight, *imageData, &bmi, DIB_RGB_COLORS);
SelectObject(hdcMem, hOldBitmap);
DeleteObject(hBitmap);
DeleteDC(hdcMem);
ReleaseDC(NULL, hdcScreen);
}
void FreeImageData(unsigned char* imageData) {
free(imageData);
}
아주 무난한 화면 캡쳐 동작이다. 그런데 어째서인지 이 간단한 작업도 오류가 난다. 그래서 정말 여러 시도를 해 봤고, 그 결과 캡쳐하는 화면의 크기가 달라지면 오류가 난다는 사실을 알았다. 당최 이해는 할 수 없지만.
아무튼 그래서 원인을 알았으니 검색하면 해결되겠지…는 개뿔, 어째 아무도 이런 증상이 없다. 아무래도 다른 사람은 c++만을 사용하지만, 나는 이 코드를 dll로 빌드하고 파이썬으로 불러오기에 생긴 오작동인거 같기도 하다. 자세한 건 잘 모르겠지만.
그래서 또 한참을 해맸다. 대체 왜 안되는 건지, 수십번 화면 크기를 바꿔 봤고, 또 비트맵의 크기를 조정해 봤다. 그래서 내린 결론은,
int size = cropWidth * cropHeight * 3;
설마 이거 오버플로우 된건가? 였다. 사실 그정도로 화면이 클 리는 없지만, 그래도 거슬리는 부분이니 바꿔보았다.
unsigned long long size = cropWidth * cropHeight * 3;
그러니 해결…되지 않았다. 혹시나 했지만 역시나였다. 그렇게 또다시 원인을 찾다가, 자세히 보니 오류가 나는 크기들의 공통점이 있지 않은가.
가로 길이, 세로 길이 둘 중 하나가 홀수면 오류가 난다!
맙소사, 설마 이거일 줄이야. 화면 크기는 무조건 짝수여야 했다. 왜인지는 모른다. 아마 자료형 문제, 혹은 버퍼 문제이지 않을까. 그래서 화면 크기는 무조건 짝수로 설정하니, 해결되…지 않았다.
아 이런, 또 왜이런거야. 또 한 시간동안 코드를 뒤적거리면서 수정했고, 나는 설마설마하는 마음으로 다음과 같이 설정했다.
가로, 세로는 모두 4의 배수여야 한다.
그러니까 귀신같이 된다. 이걸 해결해서 좋아야 할지, 아니면 이 간단한 거에 꼬박 하루를 갖다 바친 거에 화내야 할지, ChatGPT와 하루종일 싸운 걸 생각하면 후자에 좀 더 가깝다.
아무튼 뇌피셜로는 이렇다.
unsigned char*의 크기는 8byte, 그리고 RGB 한 픽셀의 크기는 3byte… 딱 봐도 메모리 크기 문제다. 3과 8의 최소공배수는 4이니, 화면 크기도 4의 배수여야 하는 것 아닐까? 그걸 생각해 보면, 결론은 다음과 같다.
화면의 가로 크기는 반드시 4의 배수
아님말고~
해결한 게 중요한 거다. 난 해결했고, 승리했다. 뭐가 문제인가. 지금을 즐기자. 한잔해~