Question

I've implemented the following code in a C++ project on Mac OS X, for capturing the desktop screen:

int ScreenCaptureRoutines::CaptureImage(int width, int height)
{
  CGRect captureRect;

  captureRect.origin.x = 0;
  captureRect.origin.y = 0;
  captureRect.size.width = width;
  captureRect.size.height = height;

  CGImageRef img = CGWindowListCreateImage(captureRect, kCGWindowListOptionOnScreenOnly, kCGNullWindowID, kCGWindowImageDefault);

  if(img == NULL)
  {
      fprintf(stderr, "CGWindowListCreateImage failed\n!");
      return -1;
  }

  /* get pixels */
  CGDataProviderRef provider = CGImageGetDataProvider(img);
  CFDataRef dataRef = CGDataProviderCopyData(provider);
  uint8_t* pixels = (uint8_t*)CFDataGetBytePtr(dataRef);

  BitmapUtility::SaveBitmapToFile(pixels, width, height, 32, "/Users/Main/test32.bmp");
}

The code works well for resolutions such as 1024x768, 1280x1024, 1280x960, 1280x768 etc., but it completely scrambles the image for 1366x768.

This is the SaveBitmapFile method that is used for saving the pixels:

void BitmapUtility::SaveBitmapToFile(u8* pBitmapBits, long lWidth, long lHeight, unsigned short wBitsPerPixel, char* lpszFileName)
{
     BITMAPINFOHEADER bmpInfoHeader = {0};
     bmpInfoHeader.biSize = sizeBITMAPINFOHEADER;
     bmpInfoHeader.biBitCount = wBitsPerPixel;
     bmpInfoHeader.biClrImportant = 0;
     bmpInfoHeader.biClrUsed = 0;
     bmpInfoHeader.biCompression = BI_RGB;
     bmpInfoHeader.biHeight = lHeight;
     bmpInfoHeader.biWidth = lWidth;
     bmpInfoHeader.biPlanes = 1;
     bmpInfoHeader.biSizeImage = lWidth* lHeight * (wBitsPerPixel / 8);

     BITMAPFILEHEADER bfh = {0};
     bfh.bfType=0x4D42;
     bfh.bfOffBits = sizeBITMAPINFOHEADER + sizeBITMAPFILEHEADER;
     bfh.bfSize = bfh.bfOffBits + bmpInfoHeader.biSizeImage;

     //Creating bitmap file
     int fileDescriptor = open(lpszFileName, O_CREAT | O_WRONLY);
     if (fileDescriptor == -1)
     {
        //returning if error encountered when opening file
        return;
     }

     ByteBufferOutputStream bufferInfoHeader;

     bufferInfoHeader.write(&bmpInfoHeader.biSize, 4);
     bufferInfoHeader.write(&bmpInfoHeader.biWidth, 4);
     bufferInfoHeader.write(&bmpInfoHeader.biHeight, 4);
     bufferInfoHeader.write(&bmpInfoHeader.biPlanes, 2);
     bufferInfoHeader.write(&bmpInfoHeader.biBitCount, 2);
     bufferInfoHeader.write(&bmpInfoHeader.biCompression, 4);
     bufferInfoHeader.write(&bmpInfoHeader.biSizeImage, 4);
     bufferInfoHeader.write(&bmpInfoHeader.biXPelsPerMeter, 4);
     bufferInfoHeader.write(&bmpInfoHeader.biYPelsPerMeter, 4);
     bufferInfoHeader.write(&bmpInfoHeader.biClrUsed, 4);
     bufferInfoHeader.write(&bmpInfoHeader.biClrImportant, 4);

     ByteBufferOutputStream bufferFileHeader;

     bufferFileHeader.write(&bfh.bfType, 2);
     bufferFileHeader.write(&bfh.bfSize, 4);
     bufferFileHeader.write(&bfh.bfReserved1, 2);
     bufferFileHeader.write(&bfh.bfReserved2, 2);
     bufferFileHeader.write(&bfh.bfOffBits, 4);

     //Writing bitmap to file
     ssize_t bytesWritten = write(fileDescriptor, bufferFileHeader.get()->getData(), sizeBITMAPFILEHEADER);
     bytesWritten = write(fileDescriptor, bufferInfoHeader.get()->getData(), sizeBITMAPINFOHEADER);
     bytesWritten = write(fileDescriptor, pBitmapBits, bmpInfoHeader.biSizeImage);

     //Closing bitmap file
     close(fileDescriptor);
 }

The SaveBitmapToFile method is only used for testing, In a real-life scenario I'm actually passing the captured pixels through libavcodec to convert and save the frames to an FLV format, but the end result is the same, the image gets scrambled.

Does anyone have any idea why the code doesn't work for 1366x768 (or other non-standard 4:3 or 16:9 resolutions for that matter)?

Was it helpful?

Solution

First you should try to render the CGImageRef to screen right after capture to make sure that the data is valid.

Take a look at the captured bmp file. If the image looks like pseudo-random noise, your pixel format is wrong. You need to call CGImageGetWidth, CGImageGetHeight, and CGImageGetBitsPerPixel (and other functions in CGImage.h) to make sure that the pixel format is correct.

If you can still make out some of the pixels but the rows are misaligned, your byte stride is wrong. 1024 and 1280 are multiples 32, 64, and 128. 1366 on the other hand, the largest power of 2 divisor is 2. Drivers typically use pixel stride that is 64-byte aligned because that is the common cache line size. Stride equals width + zero padding. Call CGImageGetBytesPerRow to get the byte offset from the start address of previous row to get to the start address of the next row.

Pseudocode to write image with stride:

ptr = pixels
bytesPerRow = CGImageGetBytesPerRow()
bytesPerPixel = (CGImageGetBitsPerPixel() + 7) / 8

for row = 0 to height:
    write(ptr, width*bytesPerPixel)
    ptr += bytesPerRow
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top